Class: RstFilter::Exec

Inherits:
Object
  • Object
show all
Defined in:
lib/rstfilter/exec.rb

Defined Under Namespace

Classes: Command, ConfigOption

Constant Summary collapse

DEFAULT_SETTING =
{
  # rewrite options
  show_all_results: true,
  show_exceptions: true,
  show_output: false,
  show_decl: false,
  show_specific_line: nil,

  use_pp: false,
  comment_nextline: false,
  comment_indent: 50,
  comment_pattern: '#=>',
  comment_label: nil,

  # execute options
  exec_command: false, # false: simply load file
                       # String value: launch given string as a command
  exec_with_filename: true,

  # dump
  dump: nil, # :json

  # general
  verbose: false,
  ignore_pragma: false,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opt = {}) ⇒ Exec

Returns a new instance of Exec.



13
14
15
16
17
# File 'lib/rstfilter/exec.rb', line 13

def initialize opt = {}
  @output = ''
  @opt = ConfigOption.new(**DEFAULT_SETTING)
  update_opt opt
end

Instance Attribute Details

#outputObject (readonly)

Returns the value of attribute output.



11
12
13
# File 'lib/rstfilter/exec.rb', line 11

def output
  @output
end

Instance Method Details

#comment_labelObject



115
116
117
118
119
# File 'lib/rstfilter/exec.rb', line 115

def comment_label
  if l = @opt.comment_label
    "#{l}: "
  end
end

#err(msg) ⇒ Object



109
110
111
112
113
# File 'lib/rstfilter/exec.rb', line 109

def err msg
  msg.each_line{|line|
    STDERR.puts "[RstFilter] #{line}"
  }
end

#exec_mod_src(mod_src) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/rstfilter/exec.rb', line 167

def exec_mod_src mod_src
  # execute modified src
  ENV['RSTFILTER_SHOW_OUTPUT'] = @opt.show_output ? '1' : nil
  ENV['RSTFILTER_SHOW_EXCEPTIONS'] = @opt.show_exceptions ? '1' : nil
  ENV['RSTFILTER_FILENAME'] = @filename
  ENV['RSTFILTER_PP'] = @opt.use_pp ? '1' : nil

  case cs = @opt.exec_command
  when Array
    @output = String.new

    cs.map do |c|
      require 'tempfile'
      recf = Tempfile.new('rstfilter-rec')
      ENV['RSTFILTER_RECORD_PATH'] = recf.path
      recf.close

      modf = Tempfile.new('rstfilter-modsrc')
      modf.write mod_src
      modf.close
      ENV['RSTFILTER_MOD_SRC_PATH'] = modf.path

      ENV['RUBYOPT'] = "-r#{File.join(__dir__, 'exec_setup')} #{ENV['RUBYOPT']}"

      cmd = c.command
      cmd << ' ' + @filename if @opt.exec_with_filename
      p exec:cmd if @opt.verbose

      io = IO.popen(cmd, err: [:child, :out])
      begin
        Process.waitpid(io.pid)
      ensure
        begin
          Process.kill(:KILL, io.pid)
        rescue Errno::ESRCH, Errno::ECHILD
        else
          Process.waitpid(io.pid)
        end
      end

      @output << io.read
      open(recf.path){|f| Marshal.load f}
    end
  else
    begin
      begin
        require_relative 'exec_setup'
        ::RSTFILTER__.clear
        ::TOPLEVEL_BINDING.eval(mod_src, @filename)
        [::RSTFILTER__.records]
      ensure
        $stdout = $__rst_filter_prev_out if $__rst_filter_prev_out
        $stderr = $__rst_filter_prev_err if $__rst_filter_prev_err
      end
    rescue Exception => e
      if @opt.verbose
        err e.inspect
        err e.backtrace.join("\n")
      else
        err "exit with #{e.inspect}"
      end
      [::RSTFILTER__.records]
    end
  end
end

#make_line_records(rs) ⇒ Object



253
254
255
256
257
258
259
# File 'lib/rstfilter/exec.rb', line 253

def make_line_records rs
  lrs = {}
  rs.each{|(_bl, _bc, el, _ec), result|
    lrs[el] = result
  }
  lrs
end

#modified_src(src, filename = nil) ⇒ Object



233
234
235
236
# File 'lib/rstfilter/exec.rb', line 233

def modified_src src, filename = nil
  rewriter = Rewriter.new @opt
  rewriter.rewrite(src, filename)
end

#optparse!(argv) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/rstfilter/exec.rb', line 49

def optparse! argv
  opt = {}
  o = OptionParser.new
  o.on('-c', '--comment', 'Show result only on comment'){
    opt[:show_all_results] = false
  }
  o.on('-o', '--output', 'Show output results'){
    opt[:show_output] = true
  }
  o.on('-d', '--decl', 'Show results on declaration'){
    opt[:show_decl] = true
  }
  o.on('--no-exception', 'Do not show exception'){
    opt[:show_exception] = false
  }
  o.on('--pp', 'Use pp to represent objects'){
    opt[:use_pp] = true
  }
  o.on('-n', '--nextline', 'Put comments on next line'){
    opt[:comment_nextline] = true
  }
  o.on('--comment-indent=NUM', "Specify comment indent size (default: #{DEFAULT_SETTING[:comment_indent]})"){|n|
    opt[:comment_indent] = n.to_i
  }
  o.on('--comment-pattern=PAT', "Specify comment pattern of -c (default: '#=>')"){|pat|
    opt[:comment_pattern] = pat
  }
  o.on('--coment-label=LABEL', 'Specify comment label (default: "")'){|label|
    opt[:comment_label] = label
  }
  o.on('--verbose', 'Verbose mode'){
    opt[:verbose] = true
  }
  o.on('-e', '--command=COMMAND', 'Execute Ruby script with given command'){|cmdstr|
    opt[:exec_command] ||= []

    if /\A(.+):(.+)\z/ =~ cmdstr
      cmd = Command.new($1, $2)
    else
      cmd = Command.new("e#{(opt[:exec_command]&.size || 0) + 1}", cmdstr)
    end

    opt[:exec_command] << cmd
  }
  o.on('--no-filename', 'Execute -e command without filename'){
    opt[:exec_with_filename] = false
  }
  o.on('-j', '--json', 'Print records in JSON format'){
    opt[:dump] = :json
  }
  o.on('--ignore-pragma', 'Ignore pragma specifiers'){
    opt[:ignore_pragma] = true
  }
  o.on('--verbose', 'Verbose mode'){
    opt[:verbose] = true
  }
  o.parse!(argv)
  update_opt opt
end

#process(filename) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/rstfilter/exec.rb', line 261

def process filename
  records, src, comments = record_records filename
  pp records: records if @opt.verbose
  line_records = records.map{|r|
    make_line_records r
  }

  case @opt.dump
  when :json
    require 'json'
    puts JSON.dump(records)
  else
    replace_comments = comments.filter_map{|c|
      next unless c.text.start_with? @opt.comment_pattern
      e = c.loc.expression
      [e.begin.line, true]
    }.to_h

    src.each_line.with_index{|line, i|
      lineno = i+1
      line_results = line_records.map{|r| r[lineno]&.first}.compact

      if line_results.empty?
        puts line
      else
        if replace_comments[lineno]
          line.match(/(.+)#{@opt.comment_pattern}.*$/) || raise("unreachable")
          puts_result $1, line_results, line
        elsif @opt.show_all_results
          puts_result line, line_results
        end
      end

      if @opt.show_output && !line_results.empty?
        if m = line.match(/^\s+/)
          indent = ' ' * m[0].size
        else
          indent = ''
        end

        line_outputs = line_records.map{|r| r[lineno]}.compact
        line_outputs.each.with_index{|r, i|
          out, err = *r[1..2]
          label = @opt.exec_command && @opt.exec_command[i].label
          label += ':' if label

          {out: out, err: err}.each{|k, o|
            o.strip!
            o.each_line{|ol|
              puts "#{indent}\##{label ? label : nil}#{k}: #{ol}"
            } unless o.empty?
          }
        }
      end
    }

    if !@opt.show_output && !@output.empty?
      puts "# output"
      puts output
    end
  end
end

#puts_result(prefix, results, line = nil) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/rstfilter/exec.rb', line 121

def puts_result prefix, results, line = nil
  prefix = prefix.chomp

  if results.size == 1
    r = results.first
    result_lines = r.lines
    indent = ''

    if @opt.comment_nextline
      puts prefix
      if prefix.match(/\A(\s+)/)
        prefix = ' ' * $1.size
      else
        prefix = ''
      end
      puts "#{prefix}" + "#{@opt.comment_pattern}#{comment_label}#{result_lines.shift}"
    else
      if line
        puts line.sub(/#{@opt.comment_pattern}.*$/, "#{@opt.comment_pattern} #{comment_label}#{result_lines.shift.chomp}")
      else
        indent = ' ' * [0, @opt.comment_indent - prefix.size].max
        puts "#{prefix}#{indent} #=> #{comment_label}#{result_lines.shift}"
      end
    end

    cont_comment = ' #' + ' ' * @opt.comment_pattern.size + ' '

    result_lines.each{|result_line|
      puts ' ' * prefix.size + indent + "#{cont_comment}#{result_line}"
    }
  else
    puts prefix

    if prefix.match(/\A(\s+)/)
      prefix = ' ' * $1.size
    else
      prefix = ''
    end

    results.each.with_index{|r, i|
      result_lines = r.lines
      puts "#{prefix}#{@opt.comment_pattern} #{@opt.exec_command[i].label}: #{result_lines.first}"
    }
  end
end

#record_records(filename) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/rstfilter/exec.rb', line 238

def record_records filename
  @filename = filename
  src = File.read(filename)
  src, mod_src, comments = modified_src(src, filename)

  comments.each{|c|
    case c.text
    when /\A\#rstfilter\s(.+)/
      optparse! Shellwords.split($1)
    end
  } unless @opt.ignore_pragma

  return exec_mod_src(mod_src), src, comments
end

#update_opt(opt = {}) ⇒ Object



6
7
8
9
# File 'lib/rstfilter/exec.rb', line 6

def update_opt opt = {}
  opt = @opt.to_h.merge(opt)
  @opt = ConfigOption.new(**opt)
end