markdown_exec 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/markdown_exec.rb CHANGED
@@ -7,18 +7,36 @@ require 'English'
7
7
  require 'clipboard'
8
8
  require 'open3'
9
9
  require 'optparse'
10
+ require 'shellwords'
10
11
  require 'tty-prompt'
11
12
  require 'yaml'
12
13
 
14
+ require_relative 'colorize'
15
+ require_relative 'env'
13
16
  require_relative 'shared'
17
+ require_relative 'tap'
14
18
  require_relative 'markdown_exec/version'
15
19
 
20
+ include Tap
21
+ tap_config envvar: MarkdownExec::TAP_DEBUG
22
+
16
23
  $stderr.sync = true
17
24
  $stdout.sync = true
18
25
 
19
26
  BLOCK_SIZE = 1024
20
27
 
21
- class Object # rubocop:disable Style/Documentation
28
+ # hash with keys sorted by name
29
+ #
30
+ class Hash
31
+ def sort_by_key
32
+ keys.sort.to_h { |key| [key, self[key]] }
33
+ end
34
+ end
35
+
36
+ # is the value a non-empty string or a binary?
37
+ #
38
+ # :reek:ManualDispatch ### temp
39
+ class Object
22
40
  def present?
23
41
  case self.class.to_s
24
42
  when 'FalseClass', 'TrueClass'
@@ -29,7 +47,9 @@ class Object # rubocop:disable Style/Documentation
29
47
  end
30
48
  end
31
49
 
32
- class String # rubocop:disable Style/Documentation
50
+ # is value empty?
51
+ #
52
+ class String
33
53
  BLANK_RE = /\A[[:space:]]*\z/.freeze
34
54
  def blank?
35
55
  empty? || BLANK_RE.match?(self)
@@ -38,29 +58,257 @@ end
38
58
 
39
59
  public
40
60
 
41
- # display_level values
42
- DISPLAY_LEVEL_BASE = 0 # required output
43
- DISPLAY_LEVEL_ADMIN = 1
44
- DISPLAY_LEVEL_DEBUG = 2
45
- DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
46
- DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
47
-
48
- # @execute_files[ind] = @execute_files[ind] + [block]
49
- EF_STDOUT = 0
50
- EF_STDERR = 1
51
- EF_STDIN = 2
52
-
61
+ # execute markdown documents
62
+ #
53
63
  module MarkdownExec
64
+ # :reek:IrresponsibleModule
54
65
  class Error < StandardError; end
55
66
 
67
+ ## an imported markdown document
68
+ #
69
+ class MDoc
70
+ def initialize(table)
71
+ @table = table
72
+ end
73
+
74
+ def collect_recursively_required_code(name)
75
+ get_required_blocks(name)
76
+ .map do |block|
77
+ block.tap_inspect name: :block, format: :yaml
78
+ body = block[:body].join("\n")
79
+
80
+ if block[:cann]
81
+ xcall = block[:cann][1..-2].tap_inspect name: :xcall
82
+ mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdin
83
+ mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdout
84
+ yqcmd = if mstdin[:type]
85
+ "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
86
+ else
87
+ "yq e '#{body}' '#{mstdin[:name]}'"
88
+ end.tap_inspect name: :yqcmd
89
+ if mstdout[:type]
90
+ "export #{mstdout[:name]}=$(#{yqcmd})"
91
+ else
92
+ "#{yqcmd} > '#{mstdout[:name]}'"
93
+ end
94
+ elsif block[:stdout]
95
+ stdout = block[:stdout].tap_inspect name: :stdout
96
+ body = block[:body].join("\n").tap_inspect name: :body
97
+ if stdout[:type]
98
+ # "export #{stdout[:name]}=#{Shellwords.escape body}"
99
+ %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
100
+ else
101
+ "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
102
+ "#{body}\n" \
103
+ "EOF\n"
104
+ end
105
+ else
106
+ block[:body]
107
+ end
108
+ end.flatten(1)
109
+ .tap_inspect format: :yaml
110
+ end
111
+
112
+ def get_block_by_name(name, default = {})
113
+ name.tap_inspect name: :name
114
+ @table.select { |block| block[:name] == name }.fetch(0, default).tap_inspect format: :yaml
115
+ end
116
+
117
+ def get_required_blocks(name)
118
+ name_block = get_block_by_name(name)
119
+ raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
120
+
121
+ all = [name_block[:name]] + recursively_required(name_block[:reqs])
122
+
123
+ # in order of appearance in document
124
+ sel = @table.select { |block| all.include? block[:name] }
125
+
126
+ # insert function blocks
127
+ sel.map do |block|
128
+ block.tap_inspect name: :block, format: :yaml
129
+ if (call = block[:call])
130
+ [get_block_by_name("[#{call.match(/^\((\S+) |\)/)[1]}]").merge({ cann: call })]
131
+ else
132
+ []
133
+ end + [block]
134
+ end.flatten(1) # .tap_inspect format: :yaml
135
+ end
136
+
137
+ # :reek:UtilityFunction
138
+ def hide_menu_block_per_options(opts, block)
139
+ (opts[:hide_blocks_by_name] &&
140
+ block[:name].match(Regexp.new(opts[:block_name_excluded_match]))).tap_inspect
141
+ end
142
+
143
+ def blocks_for_menu(opts)
144
+ if opts[:hide_blocks_by_name]
145
+ @table.reject { |block| hide_menu_block_per_options opts, block }
146
+ else
147
+ @table
148
+ end
149
+ end
150
+
151
+ def recursively_required(reqs)
152
+ all = []
153
+ rem = reqs
154
+ while rem.count.positive?
155
+ rem = rem.map do |req|
156
+ next if all.include? req
157
+
158
+ all += [req]
159
+ get_block_by_name(req).fetch(:reqs, [])
160
+ end
161
+ .compact
162
+ .flatten(1)
163
+ end
164
+ all.tap_inspect format: :yaml
165
+ end
166
+ end # class MDoc
167
+
168
+ # format option defaults and values
169
+ #
170
+ # :reek:TooManyInstanceVariables
171
+ class BlockLabel
172
+ def initialize(filename:, headings:, menu_blocks_with_docname:, menu_blocks_with_headings:, title:)
173
+ @filename = filename
174
+ @headings = headings
175
+ @menu_blocks_with_docname = menu_blocks_with_docname
176
+ @menu_blocks_with_headings = menu_blocks_with_headings
177
+ @title = title
178
+ end
179
+
180
+ def make
181
+ ([@title] +
182
+ (if @menu_blocks_with_headings
183
+ [@headings.compact.join(' # ')]
184
+ else
185
+ []
186
+ end) +
187
+ (
188
+ if @menu_blocks_with_docname
189
+ [@filename]
190
+ else
191
+ []
192
+ end
193
+ )).join(' ')
194
+ end
195
+ end # class BlockLabel
196
+
197
+ FNR11 = '/'
198
+ FNR12 = ',~'
199
+
200
+ # format option defaults and values
201
+ #
202
+ class SavedAsset
203
+ def initialize(filename:, prefix:, time:, blockname:)
204
+ @filename = filename
205
+ @prefix = prefix
206
+ @time = time
207
+ @blockname = blockname
208
+ end
209
+
210
+ def script_name
211
+ fne = @filename.gsub(FNR11, FNR12)
212
+ "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',', @blockname].join('_')}.sh".tap_inspect
213
+ end
214
+
215
+ def stdout_name
216
+ "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
217
+ end
218
+ end # class SavedAsset
219
+
220
+ # format option defaults and values
221
+ #
222
+ class OptionValue
223
+ def initialize(value)
224
+ @value = value
225
+ end
226
+
227
+ # as default value in env_str()
228
+ #
229
+ def for_hash(default = nil)
230
+ return default if @value.nil?
231
+
232
+ case @value.class.to_s
233
+ when 'String', 'Integer'
234
+ @value
235
+ when 'FalseClass', 'TrueClass'
236
+ @value ? true : false
237
+ when @value.empty?
238
+ default
239
+ else
240
+ @value.to_s
241
+ end
242
+ end
243
+
244
+ # for output as default value in list_default_yaml()
245
+ #
246
+ def for_yaml(default = nil)
247
+ return default if @value.nil?
248
+
249
+ case @value.class.to_s
250
+ when 'String'
251
+ "'#{@value}'"
252
+ when 'Integer'
253
+ @value
254
+ when 'FalseClass', 'TrueClass'
255
+ @value ? true : false
256
+ when @value.empty?
257
+ default
258
+ else
259
+ @value.to_s
260
+ end
261
+ end
262
+ end # class OptionValue
263
+
264
+ # a generated list of saved files
265
+ #
266
+ class Sfiles
267
+ def initialize(folder, glob)
268
+ @folder = folder
269
+ @glob = glob
270
+ end
271
+
272
+ def list_all
273
+ Dir.glob(File.join(@folder, @glob)).tap_inspect
274
+ end
275
+
276
+ def most_recent(arr = nil)
277
+ arr = list_all if arr.nil?
278
+ return if arr.count < 1
279
+
280
+ arr.max.tap_inspect
281
+ end
282
+
283
+ def most_recent_list(list_count, arr = nil)
284
+ arr = list_all if arr.nil?
285
+ return if (ac = arr.count) < 1
286
+
287
+ arr.sort[-[ac, list_count].min..].reverse.tap_inspect
288
+ end
289
+ end # class Sfiles
290
+
56
291
  ##
57
292
  #
293
+ # :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
294
+ # :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
295
+ # :reek:TooManyInstanceVariables ### temp
296
+ # :reek:TooManyMethods ### temp
58
297
  class MarkParse
59
- attr_accessor :options
298
+ attr_reader :options
60
299
 
61
300
  def initialize(options = {})
62
301
  @options = options
63
302
  @prompt = TTY::Prompt.new(interrupt: :exit)
303
+ @execute_aborted_at = nil
304
+ @execute_completed_at = nil
305
+ @execute_error = nil
306
+ @execute_error_message = nil
307
+ @execute_files = nil
308
+ @execute_options = nil
309
+ @execute_script_filespec = nil
310
+ @execute_started_at = nil
311
+ @option_parser = nil
64
312
  end
65
313
 
66
314
  ##
@@ -68,20 +316,19 @@ module MarkdownExec
68
316
 
69
317
  def base_options
70
318
  menu_iter do |item|
71
- item.tap_inspect name: :item, format: :yaml
319
+ # noisy item.tap_inspect name: :item, format: :yaml
72
320
  next unless item[:opt_name].present?
73
321
 
74
322
  item_default = item[:default]
75
- item_default.tap_inspect name: :item_default
323
+ # noisy item_default.tap_inspect name: :item_default
76
324
  value = if item_default.nil?
77
325
  item_default
78
326
  else
79
- env_str(item[:env_var], default: value_for_hash(item_default))
327
+ env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
80
328
  end
81
329
  [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
82
330
  end.compact.to_h.merge(
83
331
  {
84
- mdheadings: true, # use headings (levels 1,2,3) in block lable
85
332
  menu_exit_at_top: true,
86
333
  menu_with_exit: true
87
334
  }
@@ -103,20 +350,13 @@ module MarkdownExec
103
350
  }
104
351
  end
105
352
 
106
- # Returns true if all files are EOF
107
- #
108
- def all_at_eof(files)
109
- files.find { |f| !f.eof }.nil?
110
- end
111
-
112
- def approve_block(opts, blocks_in_file)
113
- required_blocks = list_recursively_required_blocks(blocks_in_file, opts[:block_name])
353
+ def approve_block(opts, mdoc)
354
+ required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
114
355
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
115
356
 
116
357
  allow = true
117
358
  if opts[:user_must_approve]
118
359
  loop do
119
- # (sel = @prompt.select(opts[:prompt_approve_block], %w(Yes No Copy_script_to_clipboard Save_script), cycle: true)).tap_inspect name: :sel
120
360
  (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
121
361
  menu.default 1
122
362
  # menu.enum '.'
@@ -131,10 +371,11 @@ module MarkdownExec
131
371
  if sel == 3
132
372
  text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
133
373
  Clipboard.copy(text)
134
- fout "Clipboard updated: #{required_blocks.count} blocks, #{required_blocks.flatten.count} lines, #{text.length} characters"
374
+ fout "Clipboard updated: #{required_blocks.count} blocks," /
375
+ " #{required_blocks.flatten.count} lines," /
376
+ " #{text.length} characters"
135
377
  end
136
378
  if sel == 4
137
- # opts[:saved_script_filename] = saved_name_make(opts)
138
379
  write_command_file(opts.merge(save_executed_script: true), required_blocks)
139
380
  fout "File saved: #{@options[:saved_filespec]}"
140
381
  end
@@ -143,7 +384,7 @@ module MarkdownExec
143
384
  end
144
385
  (opts[:ir_approve] = allow).tap_inspect name: :allow
145
386
 
146
- selected = get_block_by_name blocks_in_file, opts[:block_name]
387
+ selected = mdoc.get_block_by_name opts[:block_name]
147
388
 
148
389
  if opts[:ir_approve]
149
390
  write_command_file opts, required_blocks
@@ -156,37 +397,33 @@ module MarkdownExec
156
397
  selected[:name]
157
398
  end
158
399
 
159
- def code(table, block)
160
- all = [block[:name]] + recursively_required(table, block[:reqs])
161
- all.reverse.map do |req|
162
- get_block_by_name(table, req).fetch(:body, '')
163
- end
164
- .flatten(1)
165
- .tap_inspect
166
- end
167
-
168
- def command_execute(opts, cmd2)
400
+ # :reek:DuplicateMethodCall
401
+ # :reek:UncommunicativeVariableName { exclude: [ e ] }
402
+ # :reek:LongYieldList
403
+ def command_execute(opts, command)
169
404
  @execute_files = Hash.new([])
170
405
  @execute_options = opts
171
406
  @execute_started_at = Time.now.utc
172
407
 
173
- Open3.popen3(@options[:shell], '-c', cmd2) do |stdin, stdout, stderr, exec_thr|
174
- # pid = exec_thr.pid # pid of the started process
175
-
176
- t1 = Thread.new do
408
+ Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
409
+ Thread.new do
177
410
  until (line = stdout.gets).nil?
178
411
  @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
179
412
  print line if opts[:output_stdout]
180
413
  yield nil, line, nil, exec_thr if block_given?
181
414
  end
415
+ rescue IOError
416
+ # thread killed, do nothing
182
417
  end
183
418
 
184
- t2 = Thread.new do
419
+ Thread.new do
185
420
  until (line = stderr.gets).nil?
186
421
  @execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
187
422
  print line if opts[:output_stdout]
188
423
  yield nil, nil, line, exec_thr if block_given?
189
424
  end
425
+ rescue IOError
426
+ # thread killed, do nothing
190
427
  end
191
428
 
192
429
  in_thr = Thread.new do
@@ -207,7 +444,14 @@ module MarkdownExec
207
444
  @execute_aborted_at = Time.now.utc
208
445
  @execute_error_message = e.message
209
446
  @execute_error = e
210
- @execute_files[EF_STDERR] += [e.message]
447
+ @execute_files[EF_STDERR] += [@execute_error_message]
448
+ fout "Error ENOENT: #{e.inspect}"
449
+ rescue SignalException => e
450
+ # SIGTERM triggered by user or system
451
+ @execute_aborted_at = Time.now.utc
452
+ @execute_error_message = 'SIGTERM'
453
+ @execute_error = e
454
+ @execute_files[EF_STDERR] += [@execute_error_message]
211
455
  fout "Error ENOENT: #{e.inspect}"
212
456
  end
213
457
 
@@ -220,12 +464,15 @@ module MarkdownExec
220
464
  cnt / 2
221
465
  end
222
466
 
467
+ # :reek:DuplicateMethodCall
223
468
  def display_command(_opts, required_blocks)
224
- fout ' #=#=#'.yellow
469
+ frame = ' #=#=#'.yellow
470
+ fout frame
225
471
  required_blocks.each { |cb| fout cb }
226
- fout ' #=#=#'.yellow
472
+ fout frame
227
473
  end
228
474
 
475
+ # :reek:DuplicateMethodCall
229
476
  def exec_block(options, _block_name = '')
230
477
  options = default_options.merge options
231
478
  update_options options, over: false
@@ -244,8 +491,14 @@ module MarkdownExec
244
491
  list_default_yaml: -> { fout_list list_default_yaml },
245
492
  list_docs: -> { fout_list files },
246
493
  list_default_env: -> { fout_list list_default_env },
247
- list_recent_output: -> { fout_list list_recent_output },
248
- list_recent_scripts: -> { fout_list list_recent_scripts },
494
+ list_recent_output: lambda {
495
+ fout_list list_recent_output(@options[:saved_stdout_folder],
496
+ @options[:saved_stdout_glob], @options[:list_count])
497
+ },
498
+ list_recent_scripts: lambda {
499
+ fout_list list_recent_scripts(options[:saved_script_folder],
500
+ options[:saved_script_glob], options[:list_count])
501
+ },
249
502
  pwd: -> { fout File.expand_path('..', __dir__) },
250
503
  run_last_script: -> { run_last_script },
251
504
  select_recent_output: -> { select_recent_output },
@@ -285,23 +538,30 @@ module MarkdownExec
285
538
  puts data.to_yaml
286
539
  end
287
540
 
288
- def get_block_by_name(table, name, default = {})
289
- table.select { |block| block[:name] == name }.fetch(0, default)
290
- end
291
-
292
- def get_block_summary(opts, headings, block_title, current)
293
- return [current] unless opts[:struct]
294
-
295
- return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
296
-
297
- bm = block_title.match(Regexp.new(opts[:block_name_match]))
298
- reqs = block_title.scan(Regexp.new(opts[:block_required_scan])).map { |s| s[1..] }
299
-
300
- if bm && bm[1]
301
- [summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
302
- else
303
- [summarize_block(headings, block_title).merge({ body: current, reqs: reqs })]
304
- end
541
+ # :reek:LongParameterList
542
+ def get_block_summary(call_options = {}, headings:, block_title:, block_body:)
543
+ opts = optsmerge call_options
544
+ return [block_body] unless opts[:struct]
545
+ return [summarize_block(headings, block_title).merge({ body: block_body })] unless opts[:bash]
546
+
547
+ block_title.tap_inspect name: :block_title
548
+ call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
549
+ .map { |scanned| scanned[1..] }
550
+ &.first.tap_inspect name: :call
551
+ (titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
552
+
553
+ bm = titlexcall.match(Regexp.new(opts[:block_name_match]))
554
+ reqs = titlexcall.scan(Regexp.new(opts[:block_required_scan]))
555
+ .map { |scanned| scanned[1..] }
556
+ stdin = titlexcall.match(Regexp.new(opts[:block_stdin_scan])).tap_inspect name: :stdin
557
+ stdout = titlexcall.match(Regexp.new(opts[:block_stdout_scan])).tap_inspect name: :stdout
558
+
559
+ title = bm && bm[1] ? bm[:title] : titlexcall
560
+ [summarize_block(headings, title).merge({ body: block_body,
561
+ call: call,
562
+ reqs: reqs,
563
+ stdin: stdin,
564
+ stdout: stdout })].tap_inspect format: :yaml
305
565
  end
306
566
 
307
567
  def approved_fout?(level)
@@ -313,34 +573,37 @@ module MarkdownExec
313
573
  def lout(str, level: DISPLAY_LEVEL_BASE)
314
574
  return unless approved_fout? level
315
575
 
316
- # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
317
576
  fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
318
577
  end
319
578
 
320
- def list_blocks_in_file(call_options = {}, &options_block)
321
- opts = optsmerge call_options, options_block
579
+ # :reek:DuplicateMethodCall
580
+ # :reek:LongYieldList
581
+ def iter_blocks_in_file(opts = {})
582
+ # opts = optsmerge call_options, options_block
322
583
 
323
584
  unless opts[:filename]&.present?
324
585
  fout 'No blocks found.'
325
- exit 1
586
+ return
326
587
  end
327
588
 
328
589
  unless File.exist? opts[:filename]
329
590
  fout 'Document is missing.'
330
- exit 1
591
+ return
331
592
  end
332
593
 
333
594
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
334
595
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
335
596
  block_title = ''
336
- blocks = []
337
- current = nil
597
+ block_body = nil
338
598
  headings = []
339
599
  in_block = false
600
+
601
+ selected_messages = yield :filter
602
+
340
603
  File.readlines(opts[:filename]).each do |line|
341
604
  continue unless line
342
605
 
343
- if opts[:mdheadings]
606
+ if opts[:menu_blocks_with_headings]
344
607
  if (lm = line.match(Regexp.new(opts[:heading3_match])))
345
608
  headings = [headings[0], headings[1], lm[:name]]
346
609
  elsif (lm = line.match(Regexp.new(opts[:heading2_match])))
@@ -352,36 +615,60 @@ module MarkdownExec
352
615
 
353
616
  if line.match(fenced_start_and_end_match)
354
617
  if in_block
355
- if current
356
- block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
357
- blocks += get_block_summary opts, headings, block_title, current
358
- current = nil
618
+ if block_body
619
+ # end block
620
+ #
621
+ block_title = block_body.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
622
+ yield :blocks, headings, block_title, block_body if block_given? && selected_messages.include?(:blocks)
623
+ block_body = nil
359
624
  end
360
625
  in_block = false
361
626
  block_title = ''
362
627
  else
363
- # new block
628
+ # start block
364
629
  #
365
630
  lm = line.match(fenced_start_ex)
366
- do1 = false
631
+ block_allow = false
367
632
  if opts[:bash_only]
368
- do1 = true if lm && (lm[:shell] == 'bash')
633
+ block_allow = true if lm && (lm[:shell] == 'bash')
369
634
  else
370
- do1 = true
371
- do1 = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
635
+ block_allow = true
636
+ block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
372
637
  end
373
638
 
374
639
  in_block = true
375
- if do1 && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
376
- current = []
640
+ if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
641
+ block_body = []
377
642
  block_title = (lm && lm[:name])
378
643
  end
379
644
  end
380
- elsif current
381
- current += [line.chomp]
645
+ elsif block_body
646
+ block_body += [line.chomp]
647
+ elsif block_given? && selected_messages.include?(:line)
648
+ # text outside of block
649
+ #
650
+ yield :line, nil, nil, line
651
+ end
652
+ end
653
+ end
654
+
655
+ def list_blocks_in_file(call_options = {}, &options_block)
656
+ opts = optsmerge call_options, options_block
657
+
658
+ blocks = []
659
+ iter_blocks_in_file(opts) do |btype, headings, block_title, body|
660
+ case btype
661
+ when :filter
662
+ %i[blocks line]
663
+ when :line
664
+ if opts[:menu_divider_match] && (mbody = body.match opts[:menu_divider_match])
665
+ blocks += [{ name: (opts[:menu_divider_format] % mbody[:name]), disabled: '' }]
666
+ end
667
+ when :blocks
668
+ blocks += get_block_summary opts, headings: headings, block_title: block_title, block_body: body
382
669
  end
383
670
  end
384
- blocks.tap_inspect
671
+ blocks.tap_inspect format: :yaml
385
672
  end
386
673
 
387
674
  def list_default_env
@@ -400,7 +687,7 @@ module MarkdownExec
400
687
  next unless item[:opt_name].present? && item[:default].present?
401
688
 
402
689
  [
403
- "#{item[:opt_name]}: #{value_for_yaml item[:default]}",
690
+ "#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
404
691
  item[:description].present? ? item[:description] : nil
405
692
  ].compact.join(' # ')
406
693
  end.compact.sort
@@ -408,14 +695,16 @@ module MarkdownExec
408
695
 
409
696
  def list_files_per_options(options)
410
697
  list_files_specified(
411
- options[:filename]&.present? ? options[:filename] : nil,
412
- options[:path],
413
- 'README.md',
414
- '.'
698
+ specified_filename: options[:filename]&.present? ? options[:filename] : nil,
699
+ specified_folder: options[:path],
700
+ default_filename: 'README.md',
701
+ default_folder: '.'
415
702
  ).tap_inspect
416
703
  end
417
704
 
418
- def list_files_specified(specified_filename, specified_folder, default_filename, default_folder, filetree = nil)
705
+ # :reek:LongParameterList
706
+ def list_files_specified(specified_filename: nil, specified_folder: nil,
707
+ default_filename: nil, default_folder: nil, filetree: nil)
419
708
  fn = File.join(if specified_filename&.present?
420
709
  if specified_folder&.present?
421
710
  [specified_folder, specified_filename]
@@ -446,463 +735,104 @@ module MarkdownExec
446
735
 
447
736
  def list_named_blocks_in_file(call_options = {}, &options_block)
448
737
  opts = optsmerge call_options, options_block
449
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
738
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
739
+ mdoc = MDoc.new(blocks_in_file)
740
+
450
741
  list_blocks_in_file(opts).map do |block|
451
- next if opts[:hide_blocks_by_name] && block[:name].match(block_name_excluded_match)
742
+ next if mdoc.hide_menu_block_per_options(opts, block)
452
743
 
453
744
  block
454
745
  end.compact.tap_inspect
455
746
  end
456
747
 
457
- def list_recursively_required_blocks(table, name)
458
- name_block = get_block_by_name(table, name)
459
- raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
460
-
461
- all = [name_block[:name]] + recursively_required(table, name_block[:reqs])
462
-
463
- # in order of appearance in document
464
- table.select { |block| all.include? block[:name] }
465
- .map { |block| block.fetch(:body, '') }
466
- .flatten(1)
467
- .tap_inspect
748
+ def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
749
+ Sfiles.new(saved_stdout_folder, saved_stdout_glob).most_recent_list(list_count)
468
750
  end
469
751
 
470
- def most_recent(arr)
471
- return unless arr
472
- return if arr.count < 1
473
-
474
- arr.max.tap_inspect
475
- end
476
-
477
- def most_recent_list(arr)
478
- return unless arr
479
- return if (ac = arr.count) < 1
480
-
481
- arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
482
- end
483
-
484
- def list_recent_output
485
- most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
486
- @options[:saved_stdout_glob]))).tap_inspect
487
- end
488
-
489
- def list_recent_scripts
490
- most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
491
- @options[:saved_script_glob]))).tap_inspect
492
- end
493
-
494
- def make_block_label(block, call_options = {})
495
- opts = options.merge(call_options)
496
- if opts[:mdheadings]
497
- heads = block.fetch(:headings, []).compact.join(' # ')
498
- "#{block[:title]} [#{heads}] (#{opts[:filename]})"
499
- else
500
- "#{block[:title]} (#{opts[:filename]})"
501
- end
752
+ def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
753
+ Sfiles.new(saved_script_folder, saved_script_glob).most_recent_list(list_count)
502
754
  end
503
755
 
504
756
  def make_block_labels(call_options = {})
505
757
  opts = options.merge(call_options)
506
758
  list_blocks_in_file(opts).map do |block|
507
- # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
508
-
509
- make_block_label block, opts
759
+ BlockLabel.new(filename: opts[:filename],
760
+ headings: block.fetch(:headings, []),
761
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
762
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
763
+ title: block[:title]).make
510
764
  end.compact.tap_inspect
511
765
  end
512
766
 
513
- def menu_data1
514
- val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
515
- val_as_int = ->(value) { value.to_i }
516
- val_as_str = ->(value) { value.to_s }
517
- # val_true = ->(_value) { true } # for commands, sets option to true
518
- set1 = [
519
- {
520
- arg_name: 'PATH',
521
- default: '.',
522
- description: 'Read configuration file',
523
- long_name: 'config',
524
- proc1: lambda { |value|
525
- read_configuration_file! options, value
526
- }
527
- },
528
- {
529
- arg_name: 'BOOL',
530
- default: false,
531
- description: 'Debug output',
532
- env_var: 'MDE_DEBUG',
533
- long_name: 'debug',
534
- short_name: 'd',
535
- proc1: lambda { |value|
536
- $pdebug = value.to_i != 0
537
- }
538
- },
539
- {
540
- arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
541
- default: DISPLAY_LEVEL_DEFAULT,
542
- description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
543
- env_var: 'MDE_DISPLAY_LEVEL',
544
- long_name: 'display-level',
545
- opt_name: :display_level,
546
- proc1: val_as_int
547
- },
548
- {
549
- arg_name: 'NAME',
550
- compreply: false,
551
- description: 'Name of block',
552
- env_var: 'MDE_BLOCK_NAME',
553
- long_name: 'block-name',
554
- opt_name: :block_name,
555
- short_name: 'f',
556
- proc1: val_as_str
557
- },
558
- {
559
- arg_name: 'RELATIVE_PATH',
560
- compreply: '.',
561
- description: 'Name of document',
562
- env_var: 'MDE_FILENAME',
563
- long_name: 'filename',
564
- opt_name: :filename,
565
- short_name: 'f',
566
- proc1: val_as_str
567
- },
568
- {
569
- description: 'List blocks',
570
- long_name: 'list-blocks',
571
- opt_name: :list_blocks,
572
- proc1: val_as_bool
573
- },
574
- {
575
- arg_name: 'INT.1-',
576
- default: 32,
577
- description: 'Max. items to return in list',
578
- env_var: 'MDE_LIST_COUNT',
579
- long_name: 'list-count',
580
- opt_name: :list_count,
581
- proc1: val_as_int
582
- },
583
- {
584
- description: 'List default configuration as environment variables',
585
- long_name: 'list-default-env',
586
- opt_name: :list_default_env
587
- },
588
- {
589
- description: 'List default configuration as YAML',
590
- long_name: 'list-default-yaml',
591
- opt_name: :list_default_yaml
592
- },
593
- {
594
- description: 'List docs in current folder',
595
- long_name: 'list-docs',
596
- opt_name: :list_docs,
597
- proc1: val_as_bool
598
- },
599
- {
600
- description: 'List recent saved output',
601
- long_name: 'list-recent-output',
602
- opt_name: :list_recent_output,
603
- proc1: val_as_bool
604
- },
605
- {
606
- description: 'List recent saved scripts',
607
- long_name: 'list-recent-scripts',
608
- opt_name: :list_recent_scripts,
609
- proc1: val_as_bool
610
- },
611
- {
612
- arg_name: 'PREFIX',
613
- default: MarkdownExec::BIN_NAME,
614
- description: 'Name prefix for stdout files',
615
- env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
616
- long_name: 'logged-stdout-filename-prefix',
617
- opt_name: :logged_stdout_filename_prefix,
618
- proc1: val_as_str
619
- },
620
- {
621
- arg_name: 'BOOL',
622
- default: false,
623
- description: 'Display summary for execution',
624
- env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
625
- long_name: 'output-execution-summary',
626
- opt_name: :output_execution_summary,
627
- proc1: val_as_bool
628
- },
629
- {
630
- arg_name: 'BOOL',
631
- default: false,
632
- description: 'Display script prior to execution',
633
- env_var: 'MDE_OUTPUT_SCRIPT',
634
- long_name: 'output-script',
635
- opt_name: :output_script,
636
- proc1: val_as_bool
637
- },
638
- {
639
- arg_name: 'BOOL',
640
- default: true,
641
- description: 'Display standard output from execution',
642
- env_var: 'MDE_OUTPUT_STDOUT',
643
- long_name: 'output-stdout',
644
- opt_name: :output_stdout,
645
- proc1: val_as_bool
646
- },
647
- {
648
- arg_name: 'RELATIVE_PATH',
649
- default: '.',
650
- description: 'Path to documents',
651
- env_var: 'MDE_PATH',
652
- long_name: 'path',
653
- opt_name: :path,
654
- short_name: 'p',
655
- proc1: val_as_str
656
- },
657
- {
658
- description: 'Gem home folder',
659
- long_name: 'pwd',
660
- opt_name: :pwd,
661
- proc1: val_as_bool
662
- },
663
- {
664
- description: 'Run most recently saved script',
665
- long_name: 'run-last-script',
666
- opt_name: :run_last_script,
667
- proc1: val_as_bool
668
- },
669
- {
670
- arg_name: 'BOOL',
671
- default: false,
672
- description: 'Save executed script',
673
- env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
674
- long_name: 'save-executed-script',
675
- opt_name: :save_executed_script,
676
- proc1: val_as_bool
677
- },
678
- {
679
- arg_name: 'BOOL',
680
- default: false,
681
- description: 'Save standard output of the executed script',
682
- env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
683
- long_name: 'save-execution-output',
684
- opt_name: :save_execution_output,
685
- proc1: val_as_bool
686
- },
687
- {
688
- arg_name: 'INT',
689
- default: 0o755,
690
- description: 'chmod for saved scripts',
691
- env_var: 'MDE_SAVED_SCRIPT_CHMOD',
692
- long_name: 'saved-script-chmod',
693
- opt_name: :saved_script_chmod,
694
- proc1: val_as_int
695
- },
696
- {
697
- arg_name: 'PREFIX',
698
- default: MarkdownExec::BIN_NAME,
699
- description: 'Name prefix for saved scripts',
700
- env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
701
- long_name: 'saved-script-filename-prefix',
702
- opt_name: :saved_script_filename_prefix,
703
- proc1: val_as_str
704
- },
705
- {
706
- arg_name: 'RELATIVE_PATH',
707
- default: 'logs',
708
- description: 'Saved script folder',
709
- env_var: 'MDE_SAVED_SCRIPT_FOLDER',
710
- long_name: 'saved-script-folder',
711
- opt_name: :saved_script_folder,
712
- proc1: val_as_str
713
- },
714
- {
715
- arg_name: 'GLOB',
716
- default: 'mde_*.sh',
717
- description: 'Glob matching saved scripts',
718
- env_var: 'MDE_SAVED_SCRIPT_GLOB',
719
- long_name: 'saved-script-glob',
720
- opt_name: :saved_script_glob,
721
- proc1: val_as_str
722
- },
723
- {
724
- arg_name: 'RELATIVE_PATH',
725
- default: 'logs',
726
- description: 'Saved stdout folder',
727
- env_var: 'MDE_SAVED_STDOUT_FOLDER',
728
- long_name: 'saved-stdout-folder',
729
- opt_name: :saved_stdout_folder,
730
- proc1: val_as_str
731
- },
732
- {
733
- arg_name: 'GLOB',
734
- default: 'mde_*.out.txt',
735
- description: 'Glob matching saved outputs',
736
- env_var: 'MDE_SAVED_STDOUT_GLOB',
737
- long_name: 'saved-stdout-glob',
738
- opt_name: :saved_stdout_glob,
739
- proc1: val_as_str
740
- },
741
- {
742
- description: 'Select and execute a recently saved output',
743
- long_name: 'select-recent-output',
744
- opt_name: :select_recent_output,
745
- proc1: val_as_bool
746
- },
747
- {
748
- description: 'Select and execute a recently saved script',
749
- long_name: 'select-recent-script',
750
- opt_name: :select_recent_script,
751
- proc1: val_as_bool
752
- },
753
- {
754
- description: 'YAML export of menu',
755
- long_name: 'menu-export',
756
- opt_name: :menu_export,
757
- proc1: val_as_bool
758
- },
759
- {
760
- description: 'List tab completions',
761
- long_name: 'tab-completions',
762
- opt_name: :tab_completions,
763
- proc1: val_as_bool
764
- },
765
- {
766
- arg_name: 'BOOL',
767
- default: true,
768
- description: 'Pause for user to approve script',
769
- env_var: 'MDE_USER_MUST_APPROVE',
770
- long_name: 'user-must-approve',
771
- opt_name: :user_must_approve,
772
- proc1: val_as_bool
773
- },
774
- {
775
- description: 'Show current configuration values',
776
- short_name: '0',
777
- proc1: lambda { |_|
778
- options_finalize options
779
- fout sorted_keys(options).to_yaml
780
- }
781
- },
782
- {
783
- description: 'App help',
784
- long_name: 'help',
785
- short_name: 'h',
786
- proc1: lambda { |_|
787
- fout menu_help
788
- exit
789
- }
790
- },
791
- {
792
- description: "Print the gem's version",
793
- long_name: 'version',
794
- short_name: 'v',
795
- proc1: lambda { |_|
796
- fout MarkdownExec::VERSION
797
- exit
798
- }
799
- },
800
- {
801
- description: 'Exit app',
802
- long_name: 'exit',
803
- short_name: 'x',
804
- proc1: ->(_) { exit }
805
- },
806
- {
807
- default: '^\(.*\)$',
808
- description: 'Pattern for blocks to hide from user-selection',
809
- env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
810
- opt_name: :block_name_excluded_match,
811
- proc1: val_as_str
812
- },
813
- {
814
- default: ':(?<title>\S+)( |$)',
815
- env_var: 'MDE_BLOCK_NAME_MATCH',
816
- opt_name: :block_name_match,
817
- proc1: val_as_str
818
- },
819
- {
820
- default: '\+\S+',
821
- env_var: 'MDE_BLOCK_REQUIRED_SCAN',
822
- opt_name: :block_required_scan,
823
- proc1: val_as_str
824
- },
825
- {
826
- default: '> ',
827
- env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
828
- opt_name: :display_level_xbase_prefix,
829
- proc1: val_as_str
830
- },
831
- {
832
- default: '^`{3,}',
833
- env_var: 'MDE_FENCED_START_AND_END_MATCH',
834
- opt_name: :fenced_start_and_end_match,
835
- proc1: val_as_str
836
- },
837
- {
838
- default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
839
- env_var: 'MDE_FENCED_START_EX_MATCH',
840
- opt_name: :fenced_start_ex_match,
841
- proc1: val_as_str
842
- },
843
- {
844
- default: '^# *(?<name>[^#]*?) *$',
845
- env_var: 'MDE_HEADING1_MATCH',
846
- opt_name: :heading1_match,
847
- proc1: val_as_str
848
- },
849
- {
850
- default: '^## *(?<name>[^#]*?) *$',
851
- env_var: 'MDE_HEADING2_MATCH',
852
- opt_name: :heading2_match,
853
- proc1: val_as_str
854
- },
855
- {
856
- default: '^### *(?<name>.+?) *$',
857
- env_var: 'MDE_HEADING3_MATCH',
858
- opt_name: :heading3_match,
859
- proc1: val_as_str
860
- },
861
- {
862
- default: '*.[Mm][Dd]',
863
- env_var: 'MDE_MD_FILENAME_GLOB',
864
- opt_name: :md_filename_glob,
865
- proc1: val_as_str
866
- },
867
- {
868
- default: '.+\\.md',
869
- env_var: 'MDE_MD_FILENAME_MATCH',
870
- opt_name: :md_filename_match,
871
- proc1: val_as_str
872
- },
873
- {
874
- description: 'Options for viewing saved output file',
875
- env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
876
- opt_name: :output_viewer_options,
877
- proc1: val_as_str
878
- },
879
- {
880
- default: 24,
881
- description: 'Maximum # of rows in select list',
882
- env_var: 'MDE_SELECT_PAGE_HEIGHT',
883
- opt_name: :select_page_height,
884
- proc1: val_as_int
885
- },
886
- {
887
- default: '#!/usr/bin/env',
888
- description: 'Shebang for saved scripts',
889
- env_var: 'MDE_SHEBANG',
890
- opt_name: :shebang,
891
- proc1: val_as_str
892
- },
893
- {
894
- default: 'bash',
895
- description: 'Shell for launched scripts',
896
- env_var: 'MDE_SHELL',
897
- opt_name: :shell,
898
- proc1: val_as_str
899
- }
900
- ]
901
- # commands first, options second
902
- (set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
767
+ # :reek:DuplicateMethodCall
768
+ # :reek:NestedIterators
769
+ def menu_for_optparse
770
+ menu_from_yaml.map do |menu_item|
771
+ menu_item.merge(
772
+ {
773
+ opt_name: menu_item[:opt_name]&.to_sym,
774
+ proc1: case menu_item[:proc1]
775
+ when 'debug'
776
+ lambda { |value|
777
+ tap_config value: value
778
+ }
779
+ when 'exit'
780
+ lambda { |_|
781
+ exit
782
+ }
783
+ when 'help'
784
+ lambda { |_|
785
+ fout menu_help
786
+ exit
787
+ }
788
+ when 'path'
789
+ lambda { |value|
790
+ read_configuration_file! options, value
791
+ }
792
+ when 'show_config'
793
+ lambda { |_|
794
+ options_finalize options
795
+ fout options.sort_by_key.to_yaml
796
+ }
797
+ when 'val_as_bool'
798
+ ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
799
+ when 'val_as_int'
800
+ ->(value) { value.to_i }
801
+ when 'val_as_str'
802
+ ->(value) { value.to_s }
803
+ when 'version'
804
+ lambda { |_|
805
+ fout MarkdownExec::VERSION
806
+ exit
807
+ }
808
+ else
809
+ menu_item[:proc1]
810
+ end
811
+ }
812
+ )
813
+ end
814
+ end
815
+
816
+ def menu_for_blocks(menu_options)
817
+ options = default_options.merge menu_options
818
+ menu = []
819
+ iter_blocks_in_file(options) do |btype, headings, block_title, body|
820
+ case btype
821
+ when :filter
822
+ %i[blocks line]
823
+ when :line
824
+ if options[:menu_divider_match] && (mbody = body.match options[:menu_divider_match])
825
+ menu += [{ name: mbody[:name], disabled: '' }]
826
+ end
827
+ when :blocks
828
+ summ = get_block_summary options, headings: headings, block_title: block_title, block_body: body
829
+ menu += [summ[0][:name]]
830
+ end
831
+ end
832
+ menu.tap_inspect format: :yaml
903
833
  end
904
834
 
905
- def menu_iter(data = menu_data1, &block)
835
+ def menu_iter(data = menu_for_optparse, &block)
906
836
  data.map(&block)
907
837
  end
908
838
 
@@ -910,15 +840,6 @@ module MarkdownExec
910
840
  @option_parser.help
911
841
  end
912
842
 
913
- def option_exclude_blocks(opts, blocks)
914
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
915
- if opts[:hide_blocks_by_name]
916
- blocks.reject { |block| block[:name].match(block_name_excluded_match) }
917
- else
918
- blocks
919
- end
920
- end
921
-
922
843
  ## post-parse options configuration
923
844
  #
924
845
  def options_finalize(rest)
@@ -940,13 +861,14 @@ module MarkdownExec
940
861
  @options[:block_name] = block_name if block_name.present?
941
862
  end
942
863
 
864
+ # :reek:ControlParameter
943
865
  def optsmerge(call_options = {}, options_block = nil)
944
866
  class_call_options = @options.merge(call_options || {})
945
867
  if options_block
946
868
  options_block.call class_call_options
947
869
  else
948
870
  class_call_options
949
- end.tap_inspect
871
+ end
950
872
  end
951
873
 
952
874
  def output_execution_result
@@ -993,32 +915,15 @@ module MarkdownExec
993
915
  sel == exit_option ? nil : sel
994
916
  end
995
917
 
918
+ # :reek:UtilityFunction ### temp
996
919
  def read_configuration_file!(options, configuration_path)
997
920
  return unless File.exist?(configuration_path)
998
921
 
999
- # rubocop:disable Security/YAMLLoad
1000
922
  options.merge!((YAML.load(File.open(configuration_path)) || {})
1001
923
  .transform_keys(&:to_sym))
1002
- # rubocop:enable Security/YAMLLoad
1003
- end
1004
-
1005
- def recursively_required(table, reqs)
1006
- all = []
1007
- rem = reqs
1008
- while rem.count.positive?
1009
- rem = rem.map do |req|
1010
- next if all.include? req
1011
-
1012
- all += [req]
1013
- get_block_by_name(table, req).fetch(:reqs, [])
1014
- end
1015
- .compact
1016
- .flatten(1)
1017
- .tap_inspect(name: 'rem')
1018
- end
1019
- all.tap_inspect
1020
924
  end
1021
925
 
926
+ # :reek:NestedIterators
1022
927
  def run
1023
928
  ## default configuration
1024
929
  #
@@ -1062,15 +967,6 @@ module MarkdownExec
1062
967
  exec_block options, options[:block_name]
1063
968
  end
1064
969
 
1065
- FNR11 = '/'
1066
- FNR12 = ',~'
1067
-
1068
- def saved_name_make(opts)
1069
- fne = opts[:filename].gsub(FNR11, FNR12)
1070
- "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
1071
- ',', opts[:block_name]].join('_')}.sh"
1072
- end
1073
-
1074
970
  def saved_name_split(name)
1075
971
  mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
1076
972
  return unless mf
@@ -1080,127 +976,140 @@ module MarkdownExec
1080
976
  end
1081
977
 
1082
978
  def run_last_script
1083
- filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
1084
- @options[:saved_script_glob]))
979
+ filename = Sfiles.new(@options[:saved_script_folder],
980
+ @options[:saved_script_glob]).most_recent
1085
981
  return unless filename
1086
982
 
1087
- filename.tap_inspect name: filename
1088
983
  saved_name_split filename
1089
984
  @options[:save_executed_script] = false
1090
985
  select_and_approve_block
1091
986
  end
1092
987
 
1093
988
  def save_execution_output
989
+ @options.tap_inspect name: :options
1094
990
  return unless @options[:save_execution_output]
1095
991
 
1096
- fne = File.basename(@options[:filename], '.*')
1097
-
1098
992
  @options[:logged_stdout_filename] =
1099
- "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
1100
- @options[:block_name]].join('_')}.out.txt"
993
+ SavedAsset.new(blockname: @options[:block_name],
994
+ filename: File.basename(@options[:filename], '.*'),
995
+ prefix: @options[:logged_stdout_filename_prefix],
996
+ time: Time.now.utc).stdout_name
997
+
1101
998
  @options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
1102
999
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
1103
- dirname = File.dirname(@options[:logged_stdout_filespec])
1000
+ (dirname = File.dirname(@options[:logged_stdout_filespec])).tap_inspect name: :dirname
1104
1001
  Dir.mkdir dirname unless File.exist?(dirname)
1105
1002
 
1106
- # File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, ''))
1107
1003
  ol = ["-STDOUT-\n"]
1108
1004
  ol += @execute_files&.fetch(EF_STDOUT, [])
1109
- ol += ["-STDERR-\n"].tap_inspect name: :ol3
1005
+ ol += ["\n-STDERR-\n"]
1110
1006
  ol += @execute_files&.fetch(EF_STDERR, [])
1111
- ol += ["-STDIN-\n"]
1007
+ ol += ["\n-STDIN-\n"]
1112
1008
  ol += @execute_files&.fetch(EF_STDIN, [])
1009
+ ol += ["\n"]
1113
1010
  File.write(@options[:logged_stdout_filespec], ol.join)
1114
1011
  end
1115
1012
 
1116
1013
  def select_and_approve_block(call_options = {}, &options_block)
1117
1014
  opts = optsmerge call_options, options_block
1118
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
1119
-
1120
- loop1 = true && !opts[:block_name].present?
1015
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect name: :blocks_in_file
1016
+ mdoc = MDoc.new(blocks_in_file) { |nopts| opts.merge!(nopts).tap_inspect name: :infiled_opts, format: :yaml }
1017
+ blocks_menu = mdoc.blocks_for_menu(opts.merge(struct: true)).tap_inspect name: :blocks_menu
1121
1018
 
1019
+ repeat_menu = true && !opts[:block_name].present?
1122
1020
  loop do
1123
1021
  unless opts[:block_name].present?
1124
1022
  pt = (opts[:prompt_select_block]).to_s
1125
- blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
1126
- block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
1127
1023
 
1128
- return nil if block_labels.count.zero?
1024
+ blocks_menu.each do |block|
1025
+ next if block.fetch(:disabled, false)
1129
1026
 
1130
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
1131
- return nil if sel.nil?
1027
+ block.merge! label:
1028
+ BlockLabel.new(filename: opts[:filename],
1029
+ headings: block.fetch(:headings, []),
1030
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
1031
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1032
+ title: block[:title]).make
1033
+ end
1034
+ return nil if blocks_menu.count.zero?
1132
1035
 
1133
- # if sel.nil?
1134
- # loop1 = false
1135
- # break
1136
- # end
1036
+ sel = prompt_with_quit pt, blocks_menu, per_page: opts[:select_page_height]
1037
+ return nil if sel.nil?
1137
1038
 
1138
1039
  label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1139
1040
  opts[:block_name] = @options[:block_name] = label_block[:name]
1140
-
1141
1041
  end
1142
- # if loop1
1143
- approve_block opts, blocks_in_file
1144
- # end
1145
-
1146
- break unless loop1
1042
+ approve_block opts, mdoc
1043
+ break unless repeat_menu
1147
1044
 
1148
1045
  opts[:block_name] = ''
1149
1046
  end
1150
1047
  end
1151
1048
 
1152
- def select_md_file(files_ = nil)
1049
+ def select_md_file(files = list_markdown_files_in_path)
1153
1050
  opts = options
1154
- files = files_ || list_markdown_files_in_path
1155
- if files.count == 1
1051
+ if (count = files.count) == 1
1156
1052
  files[0]
1157
- elsif files.count >= 2
1053
+ elsif count >= 2
1158
1054
  prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
1159
1055
  end
1160
1056
  end
1161
1057
 
1162
1058
  def select_recent_output
1163
- filename = prompt_with_quit @options[:prompt_select_output].to_s, list_recent_output,
1164
- per_page: @options[:select_page_height]
1059
+ filename = prompt_with_quit(
1060
+ @options[:prompt_select_output].to_s,
1061
+ list_recent_output(
1062
+ @options[:saved_stdout_folder],
1063
+ @options[:saved_stdout_glob],
1064
+ @options[:list_count]
1065
+ ),
1066
+ { per_page: @options[:select_page_height] }
1067
+ )
1165
1068
  return unless filename.present?
1166
1069
 
1167
1070
  `open #{filename} #{options[:output_viewer_options]}`
1168
1071
  end
1169
1072
 
1170
1073
  def select_recent_script
1171
- filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
1172
- per_page: @options[:select_page_height]
1074
+ filename = prompt_with_quit(
1075
+ @options[:prompt_select_md].to_s,
1076
+ list_recent_scripts(
1077
+ @options[:saved_script_folder],
1078
+ @options[:saved_script_glob],
1079
+ @options[:list_count]
1080
+ ),
1081
+ { per_page: @options[:select_page_height] }
1082
+ )
1173
1083
  return if filename.nil?
1174
1084
 
1175
- saved_name_split filename
1176
- select_and_approve_block(
1177
- bash: true,
1178
- save_executed_script: false,
1179
- struct: true
1180
- )
1181
- end
1085
+ saved_name_split(filename)
1182
1086
 
1183
- def sorted_keys(hash1)
1184
- hash1.keys.sort.to_h { |k| [k, hash1[k]] }
1087
+ select_and_approve_block({
1088
+ bash: true,
1089
+ save_executed_script: false,
1090
+ struct: true
1091
+ })
1185
1092
  end
1186
1093
 
1187
1094
  def summarize_block(headings, title)
1188
1095
  { headings: headings, name: title, title: title }
1189
1096
  end
1190
1097
 
1191
- def menu_export(data = menu_data1)
1098
+ def menu_export(data = menu_for_optparse)
1192
1099
  data.map do |item|
1193
1100
  item.delete(:proc1)
1194
1101
  item
1195
1102
  end.to_yaml
1196
1103
  end
1197
1104
 
1198
- def tab_completions(data = menu_data1)
1105
+ def tab_completions(data = menu_for_optparse)
1199
1106
  data.map do |item|
1200
1107
  "--#{item[:long_name]}" if item[:long_name]
1201
1108
  end.compact
1202
1109
  end
1203
1110
 
1111
+ # :reek:BooleanParameter
1112
+ # :reek:ControlParameter
1204
1113
  def update_options(opts = {}, over: true)
1205
1114
  if over
1206
1115
  @options = @options.merge opts
@@ -1210,40 +1119,17 @@ module MarkdownExec
1210
1119
  @options.tap_inspect format: :yaml
1211
1120
  end
1212
1121
 
1213
- def value_for_hash(value, default = nil)
1214
- return default if value.nil?
1122
+ def write_command_file(call_options, required_blocks)
1123
+ return unless call_options[:save_executed_script]
1215
1124
 
1216
- case value.class.to_s
1217
- when 'String', 'Integer', 'FalseClass', 'TrueClass'
1218
- value
1219
- when value.empty?
1220
- default
1221
- else
1222
- value.to_s
1223
- end
1224
- end
1225
-
1226
- def value_for_yaml(value)
1227
- return default if value.nil?
1228
-
1229
- case value.class.to_s
1230
- when 'String'
1231
- "'#{value}'"
1232
- when 'Integer'
1233
- value
1234
- when 'FalseClass', 'TrueClass'
1235
- value ? true : false
1236
- when value.empty?
1237
- default
1238
- else
1239
- value.to_s
1240
- end
1241
- end
1125
+ time_now = Time.now.utc
1126
+ opts = optsmerge call_options
1127
+ opts[:saved_script_filename] =
1128
+ SavedAsset.new(blockname: opts[:block_name],
1129
+ filename: opts[:filename],
1130
+ prefix: opts[:saved_script_filename_prefix],
1131
+ time: time_now).script_name
1242
1132
 
1243
- def write_command_file(opts, required_blocks)
1244
- return unless opts[:save_executed_script]
1245
-
1246
- opts[:saved_script_filename] = saved_name_make(opts)
1247
1133
  @execute_script_filespec =
1248
1134
  @options[:saved_filespec] =
1249
1135
  File.join opts[:saved_script_folder], opts[:saved_script_filename]
@@ -1259,15 +1145,11 @@ module MarkdownExec
1259
1145
  File.write(@options[:saved_filespec], shebang +
1260
1146
  "# file_name: #{opts[:filename]}\n" \
1261
1147
  "# block_name: #{opts[:block_name]}\n" \
1262
- "# time: #{Time.now.utc}\n" \
1148
+ "# time: #{time_now}\n" \
1263
1149
  "#{required_blocks.flatten.join("\n")}\n")
1264
-
1265
- @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1266
1150
  return if @options[:saved_script_chmod].zero?
1267
1151
 
1268
- @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1269
1152
  File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
1270
- @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1271
1153
  end
1272
- end
1273
- end
1154
+ end # class MarkParse
1155
+ end # module MarkdownExec