markdown_exec 1.3.1 → 1.3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/markdown_exec.rb CHANGED
@@ -6,16 +6,15 @@
6
6
  require 'English'
7
7
  require 'clipboard'
8
8
  require 'open3'
9
- # require 'optparse'
9
+ require 'optparse'
10
10
  require 'shellwords'
11
11
  require 'tty-prompt'
12
12
  require 'yaml'
13
13
 
14
+ require_relative 'cached_nested_file_reader'
14
15
  require_relative 'cli'
15
16
  require_relative 'colorize'
16
17
  require_relative 'env'
17
- require_relative 'environment_opt_parse'
18
- require_relative 'object_present'
19
18
  require_relative 'shared'
20
19
  require_relative 'tap'
21
20
  require_relative 'markdown_exec/version'
@@ -30,13 +29,24 @@ $stdout.sync = true
30
29
 
31
30
  BLOCK_SIZE = 1024
32
31
 
32
+ # custom error: file specified is missing
33
+ #
33
34
  class FileMissingError < StandardError; end
34
35
 
35
36
  # hash with keys sorted by name
37
+ # add Hash.sym_keys
36
38
  #
37
39
  class Hash
38
- def sort_by_key
39
- keys.sort.to_h { |key| [key, self[key]] }
40
+ unless defined?(sort_by_key)
41
+ def sort_by_key
42
+ keys.sort.to_h { |key| [key, self[key]] }
43
+ end
44
+ end
45
+
46
+ unless defined?(sym_keys)
47
+ def sym_keys
48
+ transform_keys(&:to_sym)
49
+ end
40
50
  end
41
51
  end
42
52
 
@@ -71,69 +81,226 @@ module FOUT
71
81
  end
72
82
  end
73
83
 
84
+ def dp(str)
85
+ lout " => #{str}", level: DISPLAY_LEVEL_DEBUG
86
+ end
87
+
74
88
  public
75
89
 
90
+ # :reek:UtilityFunction
91
+ def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
92
+ Sfiles.new(saved_stdout_folder,
93
+ saved_stdout_glob).most_recent_list(list_count)
94
+ end
95
+
96
+ # :reek:UtilityFunction
97
+ def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
98
+ Sfiles.new(saved_script_folder,
99
+ saved_script_glob).most_recent_list(list_count)
100
+ end
101
+
102
+ # convert regex match groups to a hash with symbol keys
103
+ #
104
+ # :reek:UtilityFunction
105
+ def option_match_groups(str, option)
106
+ str.match(Regexp.new(option))&.named_captures&.sym_keys
107
+ end
108
+
76
109
  # execute markdown documents
77
110
  #
78
111
  module MarkdownExec
79
112
  # :reek:IrresponsibleModule
80
113
  class Error < StandardError; end
81
114
 
82
- # cache lines in text file
115
+ # fenced code block
83
116
  #
84
- class CFile
85
- def initialize
86
- @cache = {}
117
+ class FCB
118
+ def initialize(options = {})
119
+ @attrs = {
120
+ body: nil,
121
+ call: nil,
122
+ headings: [],
123
+ name: nil,
124
+ reqs: [],
125
+ shell: '',
126
+ title: '',
127
+ random: Random.new.rand,
128
+ text: nil # displayable in menu
129
+ }.merge options
87
130
  end
88
131
 
89
- def readlines(filename)
90
- if @cache[filename]
91
- @cache[filename].each do |line|
92
- yield line if block_given?
93
- end
132
+ def to_h
133
+ @attrs
134
+ end
135
+
136
+ def to_yaml
137
+ @attrs.to_yaml
138
+ end
139
+
140
+ private
141
+
142
+ # :reek:ManualDispatch
143
+ def method_missing(method, *args, &block)
144
+ method_name = method.to_s
145
+
146
+ if @attrs.respond_to?(method_name)
147
+ @attrs.send(method_name, *args, &block)
148
+ elsif method_name[-1] == '='
149
+ @attrs[method_name.chop.to_sym] = args[0]
94
150
  else
95
- lines = []
96
- File.readlines(filename).each do |line|
97
- lines.push line
98
- yield line if block_given?
151
+ @attrs[method_name.to_sym]
152
+ end
153
+ rescue StandardError => err
154
+ warn(error = "ERROR ** FCB.method_missing(method: #{method_name}," \
155
+ " *args: #{args.inspect}, &block)")
156
+ warn err.inspect
157
+ warn(caller[0..4])
158
+ raise StandardError, error
159
+ end
160
+
161
+ # option names are available as methods
162
+ #
163
+ def respond_to_missing?(_method_name, _include_private = false)
164
+ true # recognize all hash methods, rest are treated as hash keys
165
+ end
166
+ end
167
+
168
+ # select fcb per options
169
+ #
170
+ # :reek:UtilityFunction
171
+ class Filter
172
+ # def self.fcb_title_parse(opts, fcb_title)
173
+ # fcb_title.match(Regexp.new(opts[:fenced_start_ex_match])).named_captures.sym_keys
174
+ # end
175
+
176
+ def self.fcb_select?(options, fcb)
177
+ # options.tap_yaml 'options'
178
+ # fcb.tap_inspect 'fcb'
179
+ name = fcb.fetch(:name, '').tap_inspect 'name'
180
+ shell = fcb.fetch(:shell, '').tap_inspect 'shell'
181
+
182
+ ## include hidden blocks for later use
183
+ #
184
+ name_default = true
185
+ name_exclude = nil
186
+ name_select = nil
187
+ shell_default = true
188
+ shell_exclude = nil
189
+ shell_select = nil
190
+ hidden_name = nil
191
+
192
+ if name.present? && options[:block_name]
193
+ if name =~ /#{options[:block_name]}/
194
+ '=~ block_name'.tap_puts
195
+ name_select = true
196
+ name_exclude = false
197
+ else
198
+ '!~ block_name'.tap_puts
199
+ name_exclude = true
200
+ name_select = false
99
201
  end
100
- @cache[filename] = lines
101
202
  end
203
+
204
+ if name.present? && name_select.nil? && options[:select_by_name_regex].present?
205
+ '+select_by_name_regex'.tap_puts
206
+ name_select = (!!(name =~ /#{options[:select_by_name_regex]}/)).tap_inspect 'name_select'
207
+ end
208
+
209
+ if shell.present? && options[:select_by_shell_regex].present?
210
+ '+select_by_shell_regex'.tap_puts
211
+ shell_select = (!!(shell =~ /#{options[:select_by_shell_regex]}/)).tap_inspect 'shell_select'
212
+ end
213
+
214
+ if name.present? && name_exclude.nil? && options[:exclude_by_name_regex].present?
215
+ '-exclude_by_name_regex'.tap_puts
216
+ name_exclude = (!!(name =~ /#{options[:exclude_by_name_regex]}/)).tap_inspect 'name_exclude'
217
+ end
218
+
219
+ if shell.present? && options[:exclude_by_shell_regex].present?
220
+ '-exclude_by_shell_regex'.tap_puts
221
+ shell_exclude = (!!(shell =~ /#{options[:exclude_by_shell_regex]}/)).tap_inspect 'shell_exclude'
222
+ end
223
+
224
+ if name.present? && options[:hide_blocks_by_name] &&
225
+ options[:block_name_hidden_match].present?
226
+ '+block_name_hidden_match'.tap_puts
227
+ hidden_name = (!!(name =~ /#{options[:block_name_hidden_match]}/)).tap_inspect 'hidden_name'
228
+ end
229
+
230
+ if shell.present? && options[:hide_blocks_by_shell] &&
231
+ options[:block_shell_hidden_match].present?
232
+ '-hide_blocks_by_shell'.tap_puts
233
+ (!!(shell =~ /#{options[:block_shell_hidden_match]}/)).tap_inspect 'hidden_shell'
234
+ end
235
+
236
+ if options[:bash_only]
237
+ '-bash_only'.tap_puts
238
+ shell_default = (shell == 'bash').tap_inspect 'shell_default'
239
+ end
240
+
241
+ ## name matching does not filter hidden blocks
242
+ #
243
+ case
244
+ when options[:no_chrome] && fcb.fetch(:chrome, false)
245
+ '-no_chrome'.tap_puts
246
+ false
247
+ when options[:exclude_expect_blocks] && shell == 'expect'
248
+ '-exclude_expect_blocks'.tap_puts
249
+ false
250
+ when hidden_name == true
251
+ true
252
+ when name_exclude == true, shell_exclude == true,
253
+ name_select == false, shell_select == false
254
+ false
255
+ when name_select == true, shell_select == true
256
+ true
257
+ when name_default == false, shell_default == false
258
+ false
259
+ else
260
+ true
261
+ end.tap_inspect
262
+ # binding.pry
263
+ rescue StandardError => err
264
+ warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
265
+ raise err
102
266
  end
103
- end # class CFile
267
+ end # class Filter
104
268
 
105
269
  ## an imported markdown document
106
270
  #
107
271
  class MDoc
272
+ attr_reader :table
273
+
274
+ # convert block name to fcb_parse
275
+ #
108
276
  def initialize(table)
109
277
  @table = table
110
278
  end
111
279
 
112
280
  def collect_recursively_required_code(name)
113
281
  get_required_blocks(name)
114
- .map do |block|
115
- block.tap_yaml name: :block
116
- body = block[:body].join("\n")
117
-
118
- if block[:cann]
119
- xcall = block[:cann][1..-2].tap_inspect name: :xcall
120
- mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdin
121
- mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdout
282
+ .map do |fcb|
283
+ body = fcb[:body].join("\n")
284
+
285
+ if fcb[:cann]
286
+ xcall = fcb[:cann][1..-2]
287
+ mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
288
+ mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
289
+
122
290
  yqcmd = if mstdin[:type]
123
291
  "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
124
292
  else
125
293
  "yq e '#{body}' '#{mstdin[:name]}'"
126
- end.tap_inspect name: :yqcmd
294
+ end
127
295
  if mstdout[:type]
128
296
  "export #{mstdout[:name]}=$(#{yqcmd})"
129
297
  else
130
298
  "#{yqcmd} > '#{mstdout[:name]}'"
131
299
  end
132
- elsif block[:stdout]
133
- stdout = block[:stdout].tap_inspect name: :stdout
134
- body = block[:body].join("\n").tap_inspect name: :body
300
+ elsif fcb[:stdout]
301
+ stdout = fcb[:stdout]
302
+ body = fcb[:body].join("\n")
135
303
  if stdout[:type]
136
- # "export #{stdout[:name]}=#{Shellwords.escape body}"
137
304
  %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
138
305
  else
139
306
  "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
@@ -141,65 +308,84 @@ module MarkdownExec
141
308
  "EOF\n"
142
309
  end
143
310
  else
144
- block[:body]
311
+ fcb[:body]
145
312
  end
146
313
  end.flatten(1)
147
- .tap_yaml
148
314
  end
149
315
 
150
316
  def get_block_by_name(name, default = {})
151
- name.tap_inspect name: :name
152
- @table.select { |block| block[:name] == name }.fetch(0, default).tap_yaml
317
+ @table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
153
318
  end
154
319
 
155
320
  def get_required_blocks(name)
156
321
  name_block = get_block_by_name(name)
157
322
  raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
158
323
 
159
- all = [name_block[:name]] + recursively_required(name_block[:reqs])
324
+ all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
160
325
 
161
326
  # in order of appearance in document
162
- sel = @table.select { |block| all.include? block[:name] }
163
-
164
327
  # insert function blocks
165
- sel.map do |block|
166
- block.tap_yaml name: :block
167
- if (call = block[:call])
168
- [get_block_by_name("[#{call.match(/^\((\S+) |\)/)[1]}]").merge({ cann: call })]
328
+ @table.select { |fcb| all.include? fcb.fetch(:name, '') }
329
+ .map do |fcb|
330
+ if (call = fcb[:call])
331
+ [get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
332
+ .merge({ cann: call })]
169
333
  else
170
334
  []
171
- end + [block]
172
- end.flatten(1) # .tap_yaml
335
+ end + [fcb]
336
+ end.flatten(1)
173
337
  end
174
338
 
175
339
  # :reek:UtilityFunction
176
340
  def hide_menu_block_per_options(opts, block)
177
341
  (opts[:hide_blocks_by_name] &&
178
- block[:name].match(Regexp.new(opts[:block_name_hidden_match]))).tap_inspect
342
+ block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
343
+ (block[:name]&.present? || block[:label]&.present?)
344
+ ).tap_inspect
179
345
  end
180
346
 
181
- def blocks_for_menu(opts)
347
+ # def blocks_for_menu(opts)
348
+ # if opts[:hide_blocks_by_name]
349
+ # @table.reject { |block| hide_menu_block_per_options opts, block }
350
+ # else
351
+ # @table
352
+ # end
353
+ # end
354
+
355
+ def fcbs_per_options(opts = {})
356
+ options = opts.merge(block_name_hidden_match: nil)
357
+ selrows = @table.select do |fcb_title_groups|
358
+ Filter.fcb_select? options, fcb_title_groups
359
+ end
360
+ # binding.pry
361
+ ### hide rows correctly
362
+
182
363
  if opts[:hide_blocks_by_name]
183
- @table.reject { |block| hide_menu_block_per_options opts, block }
364
+ selrows.reject { |block| hide_menu_block_per_options opts, block }
184
365
  else
185
- @table
366
+ selrows
367
+ end.map do |block|
368
+ # block[:name] = block[:text] if block[:name].nil?
369
+ block
186
370
  end
187
371
  end
188
372
 
189
373
  def recursively_required(reqs)
190
- all = []
374
+ return [] unless reqs
375
+
191
376
  rem = reqs
377
+ memo = []
192
378
  while rem.count.positive?
193
379
  rem = rem.map do |req|
194
- next if all.include? req
380
+ next if memo.include? req
195
381
 
196
- all += [req]
382
+ memo += [req]
197
383
  get_block_by_name(req).fetch(:reqs, [])
198
384
  end
199
385
  .compact
200
386
  .flatten(1)
201
387
  end
202
- all.tap_yaml
388
+ memo
203
389
  end
204
390
  end # class MDoc
205
391
 
@@ -207,16 +393,30 @@ module MarkdownExec
207
393
  #
208
394
  # :reek:TooManyInstanceVariables
209
395
  class BlockLabel
210
- def initialize(filename:, headings:, menu_blocks_with_docname:, menu_blocks_with_headings:, title:)
396
+ def initialize(filename:, headings:, menu_blocks_with_docname:,
397
+ menu_blocks_with_headings:, title:, body:, text:)
211
398
  @filename = filename
212
399
  @headings = headings
213
400
  @menu_blocks_with_docname = menu_blocks_with_docname
214
401
  @menu_blocks_with_headings = menu_blocks_with_headings
402
+ # @title = title.present? ? title : body
215
403
  @title = title
404
+ @body = body
405
+ @text = text
406
+ rescue StandardError => err
407
+ warn(error = "ERROR ** BlockLabel.initialize(); #{err.inspect}")
408
+ binding.pry if $tap_enable
409
+ raise ArgumentError, error
216
410
  end
217
411
 
412
+ # join title, headings, filename
413
+ #
218
414
  def make
219
- ([@title] +
415
+ label = @title
416
+ label = @body unless label.present?
417
+ label = @text unless label.present?
418
+ label.tap_inspect
419
+ ([label] +
220
420
  (if @menu_blocks_with_headings
221
421
  [@headings.compact.join(' # ')]
222
422
  else
@@ -229,6 +429,10 @@ module MarkdownExec
229
429
  []
230
430
  end
231
431
  )).join(' ')
432
+ rescue StandardError => err
433
+ warn(error = "ERROR ** BlockLabel.make(); #{err.inspect}")
434
+ binding.pry if $tap_enable
435
+ raise ArgumentError, error
232
436
  end
233
437
  end # class BlockLabel
234
438
 
@@ -247,11 +451,13 @@ module MarkdownExec
247
451
 
248
452
  def script_name
249
453
  fne = @filename.gsub(FNR11, FNR12)
250
- "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',', @blockname].join('_')}.sh".tap_inspect
454
+ "#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',',
455
+ @blockname].join('_')}.sh"
251
456
  end
252
457
 
253
458
  def stdout_name
254
- "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
459
+ "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename,
460
+ @blockname].join('_')}.out.txt"
255
461
  end
256
462
  end # class SavedAsset
257
463
 
@@ -308,27 +514,29 @@ module MarkdownExec
308
514
  end
309
515
 
310
516
  def list_all
311
- Dir.glob(File.join(@folder, @glob)).tap_inspect
517
+ Dir.glob(File.join(@folder, @glob))
312
518
  end
313
519
 
314
520
  def most_recent(arr = nil)
315
521
  arr = list_all if arr.nil?
316
522
  return if arr.count < 1
317
523
 
318
- arr.max.tap_inspect
524
+ arr.max
319
525
  end
320
526
 
321
527
  def most_recent_list(list_count, arr = nil)
322
528
  arr = list_all if arr.nil?
323
529
  return if (ac = arr.count) < 1
324
530
 
325
- arr.sort[-[ac, list_count].min..].reverse.tap_inspect
531
+ arr.sort[-[ac, list_count].min..].reverse
326
532
  end
327
533
  end # class Sfiles
328
534
 
329
535
  ##
330
536
  #
537
+ # rubocop:disable Layout/LineLength
331
538
  # :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
539
+ # rubocop:enable Layout/LineLength
332
540
  # :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
333
541
  # :reek:TooManyInstanceVariables ### temp
334
542
  # :reek:TooManyMethods ### temp
@@ -339,8 +547,8 @@ module MarkdownExec
339
547
 
340
548
  def initialize(options = {})
341
549
  @options = options
550
+ # hide disabled symbol
342
551
  @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: ' ' })
343
- # @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: options[:menu_divider_symbol] })
344
552
  @execute_aborted_at = nil
345
553
  @execute_completed_at = nil
346
554
  @execute_error = nil
@@ -350,12 +558,35 @@ module MarkdownExec
350
558
  @execute_script_filespec = nil
351
559
  @execute_started_at = nil
352
560
  @option_parser = nil
353
- @cfile = CFile.new
561
+ end
562
+
563
+ # return arguments before `--`
564
+ #
565
+ def arguments_for_mde(argv = ARGV)
566
+ case ind = argv.find_index('--')
567
+ when nil
568
+ argv
569
+ when 0
570
+ []
571
+ else
572
+ argv[0..ind - 1]
573
+ end #.tap_inspect
574
+ end
575
+
576
+ # return arguments after `--`
577
+ #
578
+ def arguments_for_child(argv = ARGV)
579
+ case ind = argv.find_index('--')
580
+ when nil, argv.count - 1
581
+ []
582
+ else
583
+ argv[ind + 1..-1]
584
+ end #.tap_inspect
354
585
  end
355
586
 
356
587
  ##
357
588
  # options necessary to start, parse input, defaults for cli options
358
-
589
+ #
359
590
  def base_options
360
591
  menu_iter do |item|
361
592
  # noisy item.tap_yaml name: :item
@@ -366,45 +597,39 @@ module MarkdownExec
366
597
  value = if item_default.nil?
367
598
  item_default
368
599
  else
369
- env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
600
+ env_str(item[:env_var],
601
+ default: OptionValue.new(item_default).for_hash)
370
602
  end
371
603
  [item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
372
- end.compact.to_h.merge(
373
- {
374
- menu_exit_at_top: true,
375
- menu_with_exit: true
376
- }
377
- ).tap_yaml
604
+ end.compact.to_h
378
605
  end
379
606
 
380
- def default_options
607
+ def calculated_options
381
608
  {
382
609
  bash: true, # bash block parsing in get_block_summary()
383
- exclude_expect_blocks: true,
384
- hide_blocks_by_name: true,
385
- output_saved_script_filename: false,
386
610
  saved_script_filename: nil, # calculated
387
611
  struct: true # allow get_block_summary()
388
612
  }
389
613
  end
390
614
 
391
- def approve_block(opts, mdoc)
615
+ def approve_and_execute_block(opts, mdoc)
392
616
  required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
393
- display_required_code(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
617
+ if opts[:output_script] || opts[:user_must_approve]
618
+ display_required_code(opts,
619
+ required_blocks)
620
+ end
394
621
 
395
622
  allow = true
396
623
  if opts[:user_must_approve]
397
624
  loop do
398
- (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
625
+ (sel = @prompt.select(opts[:prompt_approve_block],
626
+ filter: true) do |menu|
399
627
  menu.default 1
400
- # menu.enum '.'
401
- # menu.filter true
402
-
403
628
  menu.choice opts[:prompt_yes], 1
404
629
  menu.choice opts[:prompt_no], 2
405
630
  menu.choice opts[:prompt_script_to_clipboard], 3
406
631
  menu.choice opts[:prompt_save_script], 4
407
- end).tap_inspect name: :sel
632
+ end)
408
633
  allow = (sel == 1)
409
634
  if sel == 3
410
635
  text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
@@ -414,13 +639,14 @@ module MarkdownExec
414
639
  " #{text.length} characters"
415
640
  end
416
641
  if sel == 4
417
- write_command_file(opts.merge(save_executed_script: true), required_blocks)
642
+ write_command_file(opts.merge(save_executed_script: true),
643
+ required_blocks)
418
644
  fout "File saved: #{@options[:saved_filespec]}"
419
645
  end
420
646
  break if [1, 2].include? sel
421
647
  end
422
648
  end
423
- (opts[:ir_approve] = allow).tap_inspect name: :allow
649
+ (opts[:ir_approve] = allow)
424
650
 
425
651
  selected = mdoc.get_block_by_name opts[:block_name]
426
652
 
@@ -435,21 +661,25 @@ module MarkdownExec
435
661
  selected[:name]
436
662
  end
437
663
 
438
- # def cc(str)
439
- # puts " - - - #{Process.clock_gettime(Process::CLOCK_MONOTONIC)} - #{str}"
440
- # end
664
+ def cfile
665
+ # puts @options.inspect
666
+ # binding.pry
667
+ @cfile ||= CachedNestedFileReader.new(import_pattern: @options.fetch(:import_pattern))
668
+ # @cfile ||= CachedNestedFileReader.new(import_pattern: /^ *#insert (.+)$/)
669
+ end
441
670
 
442
671
  # :reek:DuplicateMethodCall
443
672
  # :reek:UncommunicativeVariableName { exclude: [ e ] }
444
673
  # :reek:LongYieldList
445
674
  def command_execute(opts, command)
446
- # dd = lambda { |s| puts 'command_execute() ' + s }
447
675
  #d 'execute command and yield outputs'
448
676
  @execute_files = Hash.new([])
449
677
  @execute_options = opts
450
678
  @execute_started_at = Time.now.utc
451
679
 
452
- Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
680
+ args = []
681
+ Open3.popen3(@options[:shell], '-c',
682
+ command, ARGV[0], *args) do |stdin, stdout, stderr, exec_thr|
453
683
  #d 'command started'
454
684
  Thread.new do
455
685
  until (line = stdout.gets).nil?
@@ -478,7 +708,7 @@ module MarkdownExec
478
708
  yield line, nil, nil, exec_thr if block_given?
479
709
  end
480
710
  #d 'exec_thr now dead'
481
- rescue
711
+ rescue StandardError
482
712
  #d 'stdin error, thread killed, do nothing'
483
713
  end
484
714
 
@@ -495,26 +725,26 @@ module MarkdownExec
495
725
  end
496
726
  #d 'command completed'
497
727
  @execute_completed_at = Time.now.utc
498
- rescue Errno::ENOENT => e
728
+ rescue Errno::ENOENT => err
499
729
  #d 'command error ENOENT triggered by missing command in script'
500
730
  @execute_aborted_at = Time.now.utc
501
- @execute_error_message = e.message
502
- @execute_error = e
731
+ @execute_error_message = err.message
732
+ @execute_error = err
503
733
  @execute_files[EF_STDERR] += [@execute_error_message]
504
- fout "Error ENOENT: #{e.inspect}"
505
- rescue SignalException => e
734
+ fout "Error ENOENT: #{err.inspect}"
735
+ rescue SignalException => err
506
736
  #d 'command SIGTERM triggered by user or system'
507
737
  @execute_aborted_at = Time.now.utc
508
738
  @execute_error_message = 'SIGTERM'
509
- @execute_error = e
739
+ @execute_error = err
510
740
  @execute_files[EF_STDERR] += [@execute_error_message]
511
- fout "Error ENOENT: #{e.inspect}"
741
+ fout "Error ENOENT: #{err.inspect}"
512
742
  end
513
743
 
514
744
  def count_blocks_in_filename
515
745
  fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match]
516
746
  cnt = 0
517
- @cfile.readlines(@options[:filename]).each do |line|
747
+ cfile.readlines(@options[:filename]).each do |line|
518
748
  cnt += 1 if line.match(fenced_start_and_end_match)
519
749
  end
520
750
  cnt / 2
@@ -530,7 +760,7 @@ module MarkdownExec
530
760
 
531
761
  # :reek:DuplicateMethodCall
532
762
  def exec_block(options, _block_name = '')
533
- options = default_options.merge options
763
+ options = calculated_options.merge(options).tap_yaml 'options'
534
764
  update_options options, over: false
535
765
 
536
766
  # document and block reports
@@ -548,12 +778,16 @@ module MarkdownExec
548
778
  list_docs: -> { fout_list files },
549
779
  list_default_env: -> { fout_list list_default_env },
550
780
  list_recent_output: lambda {
551
- fout_list list_recent_output(@options[:saved_stdout_folder],
552
- @options[:saved_stdout_glob], @options[:list_count])
781
+ fout_list list_recent_output(
782
+ @options[:saved_stdout_folder],
783
+ @options[:saved_stdout_glob], @options[:list_count]
784
+ )
553
785
  },
554
786
  list_recent_scripts: lambda {
555
- fout_list list_recent_scripts(options[:saved_script_folder],
556
- options[:saved_script_glob], options[:list_count])
787
+ fout_list list_recent_scripts(
788
+ options[:saved_script_folder],
789
+ options[:saved_script_glob], options[:list_count]
790
+ )
557
791
  },
558
792
  pwd: -> { fout File.expand_path('..', __dir__) },
559
793
  run_last_script: -> { run_last_script },
@@ -572,41 +806,43 @@ module MarkdownExec
572
806
  # process
573
807
  #
574
808
  @options[:filename] = select_md_file(files)
575
- select_and_approve_block(
576
- bash: true,
577
- struct: true
578
- )
579
- fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
809
+ select_approve_and_execute_block({
810
+ bash: true,
811
+ struct: true
812
+ })
813
+ return unless @options[:output_saved_script_filename]
814
+
815
+ fout "saved_filespec: #{@execute_script_filespec}"
816
+ rescue StandardError => err
817
+ warn(error = "ERROR ** MarkParse.exec_block(); #{err.inspect}")
818
+ binding.pry if $tap_enable
819
+ raise ArgumentError, error
580
820
  end
581
821
 
582
- # :reek:LongParameterList
583
- def get_block_summary(call_options = {}, headings:, block_title:, block_body:)
822
+ ## summarize blocks
823
+ #
824
+ def get_block_summary(call_options, fcb)
584
825
  opts = optsmerge call_options
585
- return [block_body] unless opts[:struct]
586
- return [summarize_block(headings, block_title).merge({ body: block_body })] unless opts[:bash]
826
+ # return fcb.body unless opts[:struct]
587
827
 
588
- block_title.tap_inspect name: :block_title
589
- call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
590
- .map { |scanned| scanned[1..] }
591
- &.first.tap_inspect name: :call
592
- (titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
828
+ return fcb unless opts[:bash]
593
829
 
594
- bm = titlexcall.match(Regexp.new(opts[:block_name_match]))
595
- reqs = titlexcall.scan(Regexp.new(opts[:block_required_scan]))
596
- .map { |scanned| scanned[1..] }
597
- stdin = titlexcall.match(Regexp.new(opts[:block_stdin_scan])).tap_inspect name: :stdin
598
- stdout = titlexcall.match(Regexp.new(opts[:block_stdout_scan])).tap_inspect name: :stdout
599
-
600
- title = bm && bm[1] ? bm[:title] : titlexcall
601
- [summarize_block(headings, title).merge({ body: block_body,
602
- call: call,
603
- reqs: reqs,
604
- stdin: stdin,
605
- stdout: stdout })].tap_yaml
830
+ fcb.call = fcb.title.match(Regexp.new(opts[:block_calls_scan]))&.fetch(1, nil)
831
+ titlexcall = if fcb.call
832
+ fcb.title.sub("%#{fcb.call}", '')
833
+ else
834
+ fcb.title
835
+ end
836
+ bm = option_match_groups(titlexcall, opts[:block_name_match])
837
+ fcb.stdin = option_match_groups(titlexcall, opts[:block_stdin_scan])
838
+ fcb.stdout = option_match_groups(titlexcall, opts[:block_stdout_scan])
839
+ fcb.title = fcb.name = (bm && bm[1] ? bm[:title] : titlexcall)
840
+ fcb
606
841
  end
607
842
 
608
843
  # :reek:DuplicateMethodCall
609
844
  # :reek:LongYieldList
845
+ # :reek:NestedIterators
610
846
  def iter_blocks_in_file(opts = {})
611
847
  # opts = optsmerge call_options, options_block
612
848
 
@@ -622,14 +858,15 @@ module MarkdownExec
622
858
 
623
859
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
624
860
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
625
- block_title = ''
626
- block_body = nil
627
- headings = []
861
+ fcb = FCB.new
628
862
  in_block = false
863
+ headings = []
629
864
 
865
+ ## get type of messages to select
866
+ #
630
867
  selected_messages = yield :filter
631
868
 
632
- @cfile.readlines(opts[:filename]).each do |line|
869
+ cfile.readlines(opts[:filename]).each.with_index do |line, _line_num|
633
870
  continue unless line
634
871
 
635
872
  if opts[:menu_blocks_with_headings]
@@ -644,73 +881,130 @@ module MarkdownExec
644
881
 
645
882
  if line.match(fenced_start_and_end_match)
646
883
  if in_block
647
- if block_body
648
- # end block
649
- #
650
- block_title = block_body.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
651
- yield :blocks, headings, block_title, block_body if block_given? && selected_messages.include?(:blocks)
652
- block_body = nil
884
+ # end fcb
885
+ #
886
+ fcb.name = fcb.title || ''
887
+ if fcb.body
888
+ if fcb.title.nil? || fcb.title.empty?
889
+ fcb.title = fcb.body.join(' ').gsub(/ +/, ' ')[0..64]
890
+ end
891
+
892
+ if block_given? && selected_messages.include?(:blocks) &&
893
+ Filter.fcb_select?(opts, fcb)
894
+ yield :blocks, fcb
895
+ end
653
896
  end
654
897
  in_block = false
655
- block_title = ''
656
898
  else
657
- # start block
899
+ # start fcb
658
900
  #
659
- lm = line.match(fenced_start_ex)
660
- block_allow = false
661
- if opts[:bash_only]
662
- block_allow = true if lm && (lm[:shell] == 'bash')
663
- else
664
- block_allow = true
665
- block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
666
- end
667
-
668
901
  in_block = true
669
- if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
670
- block_body = []
671
- block_title = (lm && lm[:name])
672
- end
902
+
903
+ fcb_title_groups = line.match(fenced_start_ex).named_captures.sym_keys
904
+ fcb = FCB.new
905
+ fcb.headings = headings
906
+ fcb.name = fcb_title_groups.fetch(:name, '')
907
+ fcb.shell = fcb_title_groups.fetch(:shell, '')
908
+ fcb.title = fcb_title_groups.fetch(:name, '')
909
+
910
+ # selected fcb
911
+ #
912
+ fcb.body = []
913
+
914
+ rest = fcb_title_groups.fetch(:rest, '')
915
+ fcb.reqs = rest.scan(/\+[^\s]+/).map { |req| req[1..-1] }
916
+
917
+ fcb.call = rest.match(Regexp.new(opts[:block_calls_scan]))&.to_a&.first
918
+ fcb.stdin = if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
919
+ tn.named_captures.sym_keys
920
+ end
921
+ fcb.stdout = if (tn = rest.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/))
922
+ tn.named_captures.sym_keys
923
+ end
673
924
  end
674
- elsif block_body
675
- block_body += [line.chomp]
925
+ elsif in_block && fcb.body
926
+ dp 'append line to fcb body'
927
+ fcb.body += [line.chomp]
676
928
  elsif block_given? && selected_messages.include?(:line)
677
- # text outside of block
678
- #
679
- yield :line, nil, nil, line
929
+ dp 'text outside of fcb'
930
+ fcb = FCB.new
931
+ fcb.body = [line]
932
+ yield :line, fcb
680
933
  end
681
934
  end
682
935
  end
683
936
 
937
+ # return body, title if option.struct
938
+ # return body if not struct
939
+ #
684
940
  def list_blocks_in_file(call_options = {}, &options_block)
685
- opts = optsmerge call_options, options_block
686
-
941
+ opts = optsmerge(call_options, options_block) #.tap_yaml 'opts'
687
942
  blocks = []
688
943
  if opts[:menu_initial_divider].present?
689
- blocks += [{
690
- name: format(opts[:menu_divider_format],
691
- opts[:menu_initial_divider]).send(opts[:menu_divider_color].to_sym), disabled: ''
692
- }]
944
+ blocks.push FCB.new({
945
+ # name: '',
946
+ chrome: true,
947
+ text: format(
948
+ opts[:menu_divider_format],
949
+ opts[:menu_initial_divider]
950
+ ).send(opts[:menu_divider_color].to_sym),
951
+ disabled: '' # __LINE__.to_s
952
+ })
693
953
  end
694
- iter_blocks_in_file(opts) do |btype, headings, block_title, body|
954
+
955
+ iter_blocks_in_file(opts) do |btype, fcb|
956
+ # binding.pry
695
957
  case btype
696
958
  when :filter
959
+ ## return type of blocks to select
960
+ #
697
961
  %i[blocks line]
962
+
698
963
  when :line
699
- if opts[:menu_divider_match] && (mbody = body.match opts[:menu_divider_match])
700
- blocks += [{ name: format(opts[:menu_divider_format], mbody[:name]).send(opts[:menu_divider_color].to_sym),
701
- disabled: '' }]
964
+ ## convert line to block
965
+ #
966
+ # binding.pry
967
+ if opts[:menu_divider_match].present? &&
968
+ (mbody = fcb.body[0].match opts[:menu_divider_match])
969
+ # binding.pry
970
+ blocks.push FCB.new(
971
+ { chrome: true,
972
+ disabled: '',
973
+ text: format(opts[:menu_divider_format],
974
+ mbody[:name]).send(opts[:menu_divider_color].to_sym) }
975
+ )
976
+ elsif opts[:menu_task_match].present? &&
977
+ (mbody = fcb.body[0].match opts[:menu_task_match])
978
+ blocks.push FCB.new(
979
+ { chrome: true,
980
+ disabled: '',
981
+ text: format(opts[:menu_task_format],
982
+ mbody[:name]).send(opts[:menu_task_color].to_sym) }
983
+ )
984
+ else
985
+ # line not added
702
986
  end
703
987
  when :blocks
704
- blocks += get_block_summary opts, headings: headings, block_title: block_title, block_body: body
988
+ ## enhance fcb with block summary
989
+ #
990
+ blocks.push get_block_summary(opts, fcb) ### if Filter.fcb_select? opts, fcb
705
991
  end
706
992
  end
993
+
707
994
  if opts[:menu_divider_format].present? && opts[:menu_final_divider].present?
708
- blocks += [{
709
- name: format(opts[:menu_divider_format],
710
- opts[:menu_final_divider]).send(opts[:menu_divider_color].to_sym), disabled: ''
711
- }]
995
+ blocks.push FCB.new(
996
+ { chrome: true,
997
+ disabled: '',
998
+ text: format(opts[:menu_divider_format],
999
+ opts[:menu_final_divider])
1000
+ .send(opts[:menu_divider_color].to_sym) }
1001
+ )
712
1002
  end
713
- blocks.tap_yaml
1003
+ blocks.tap_inspect
1004
+ rescue StandardError => err
1005
+ warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
1006
+ warn(caller[0..4])
1007
+ raise StandardError, error
714
1008
  end
715
1009
 
716
1010
  def list_default_env
@@ -741,77 +1035,77 @@ module MarkdownExec
741
1035
  specified_folder: options[:path],
742
1036
  default_filename: 'README.md',
743
1037
  default_folder: '.'
744
- ).tap_inspect
1038
+ )
745
1039
  end
746
1040
 
747
1041
  # :reek:LongParameterList
748
1042
  def list_files_specified(specified_filename: nil, specified_folder: nil,
749
1043
  default_filename: nil, default_folder: nil, filetree: nil)
750
1044
  fn = File.join(if specified_filename&.present?
751
- # puts ' LFS 01'
752
1045
  if specified_filename.start_with? '/'
753
- # puts ' LFS 02'
754
1046
  [specified_filename]
755
1047
  elsif specified_folder&.present?
756
- # puts ' LFS 03'
757
1048
  [specified_folder, specified_filename]
758
1049
  else
759
- # puts ' LFS 04'
760
1050
  [default_folder, specified_filename]
761
1051
  end
762
1052
  elsif specified_folder&.present?
763
- # puts ' LFS 05'
764
1053
  if filetree
765
- # puts ' LFS 06'
766
1054
  [specified_folder, @options[:md_filename_match]]
767
1055
  else
768
- # puts ' LFS 07'
769
1056
  [specified_folder, @options[:md_filename_glob]]
770
1057
  end
771
1058
  else
772
- # puts ' LFS 08'
773
1059
  [default_folder, default_filename]
774
- end).tap_inspect name: :fn
1060
+ end)
775
1061
  if filetree
776
- filetree.select { |filename| filename == fn || filename.match(/^#{fn}$/) || filename.match(%r{^#{fn}/.+$}) }
1062
+ filetree.select do |filename|
1063
+ filename == fn || filename.match(/^#{fn}$/) || filename.match(%r{^#{fn}/.+$})
1064
+ end
777
1065
  else
778
1066
  Dir.glob(fn)
779
- end.tap_inspect
1067
+ end
780
1068
  end
781
1069
 
782
1070
  def list_markdown_files_in_path
783
- Dir.glob(File.join(@options[:path], @options[:md_filename_glob])).tap_inspect
1071
+ Dir.glob(File.join(@options[:path],
1072
+ @options[:md_filename_glob]))
784
1073
  end
785
1074
 
786
- def list_named_blocks_in_file(call_options = {}, &options_block)
787
- opts = optsmerge call_options, options_block
788
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
789
- mdoc = MDoc.new(blocks_in_file)
790
-
791
- list_blocks_in_file(opts).map do |block|
792
- next if mdoc.hide_menu_block_per_options(opts, block)
793
-
794
- block
795
- end.compact.tap_inspect
1075
+ def blocks_per_opts(blocks, opts)
1076
+ if opts[:struct]
1077
+ blocks
1078
+ else
1079
+ # blocks.map(&:name)
1080
+ blocks.map do |block|
1081
+ block.fetch(:text, nil) || block.fetch(:name, nil)
1082
+ end
1083
+ end.compact.reject(&:empty?).tap_inspect
796
1084
  end
797
1085
 
798
- def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
799
- Sfiles.new(saved_stdout_folder, saved_stdout_glob).most_recent_list(list_count)
800
- end
1086
+ ## output type (body string or full object) per option struct and bash
1087
+ #
1088
+ def list_named_blocks_in_file(call_options = {}, &options_block)
1089
+ opts = optsmerge call_options, options_block
801
1090
 
802
- def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
803
- Sfiles.new(saved_script_folder, saved_script_glob).most_recent_list(list_count)
1091
+ blocks = list_blocks_in_file(opts.merge(struct: true)).select do |fcb|
1092
+ # fcb.fetch(:name, '') != '' && Filter.fcb_select?(opts, fcb)
1093
+ Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
1094
+ end
1095
+ blocks_per_opts(blocks, opts).tap_inspect
804
1096
  end
805
1097
 
806
1098
  def make_block_labels(call_options = {})
807
1099
  opts = options.merge(call_options)
808
- list_blocks_in_file(opts).map do |block|
1100
+ list_blocks_in_file(opts).map do |fcb|
809
1101
  BlockLabel.new(filename: opts[:filename],
810
- headings: block.fetch(:headings, []),
1102
+ headings: fcb.fetch(:headings, []),
811
1103
  menu_blocks_with_docname: opts[:menu_blocks_with_docname],
812
1104
  menu_blocks_with_headings: opts[:menu_blocks_with_headings],
813
- title: block[:title]).make
814
- end.compact.tap_inspect
1105
+ title: fcb[:title],
1106
+ text: fcb[:text],
1107
+ body: fcb[:body]).make
1108
+ end.compact
815
1109
  end
816
1110
 
817
1111
  # :reek:DuplicateMethodCall
@@ -845,7 +1139,9 @@ module MarkdownExec
845
1139
  fout options.sort_by_key.to_yaml
846
1140
  }
847
1141
  when 'val_as_bool'
848
- ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
1142
+ lambda { |value|
1143
+ value.instance_of?(::String) ? (value.chomp != '0') : value
1144
+ }
849
1145
  when 'val_as_int'
850
1146
  ->(value) { value.to_i }
851
1147
  when 'val_as_str'
@@ -864,22 +1160,22 @@ module MarkdownExec
864
1160
  end
865
1161
 
866
1162
  def menu_for_blocks(menu_options)
867
- options = default_options.merge menu_options
1163
+ options = calculated_options.merge menu_options
868
1164
  menu = []
869
- iter_blocks_in_file(options) do |btype, headings, block_title, body|
1165
+ iter_blocks_in_file(options) do |btype, fcb|
870
1166
  case btype
871
1167
  when :filter
872
1168
  %i[blocks line]
873
1169
  when :line
874
- if options[:menu_divider_match] && (mbody = body.match options[:menu_divider_match])
875
- menu += [{ name: mbody[:name], disabled: '' }]
1170
+ if options[:menu_divider_match] &&
1171
+ (mbody = fcb.body[0].match(options[:menu_divider_match]))
1172
+ menu.push FCB.new({ name: mbody[:name], disabled: '' })
876
1173
  end
877
1174
  when :blocks
878
- summ = get_block_summary options, headings: headings, block_title: block_title, block_body: body
879
- menu += [summ[0][:name]]
1175
+ menu += [fcb.name]
880
1176
  end
881
1177
  end
882
- menu.tap_yaml
1178
+ menu
883
1179
  end
884
1180
 
885
1181
  def menu_iter(data = menu_for_optparse, &block)
@@ -894,17 +1190,17 @@ module MarkdownExec
894
1190
  return unless item[:long_name].present? || item[:short_name].present?
895
1191
 
896
1192
  opts.on(*[
897
- # long name
1193
+ # - long name
898
1194
  if item[:long_name].present?
899
1195
  "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
900
1196
  end,
901
1197
 
902
- # short name
1198
+ # - short name
903
1199
  item[:short_name].present? ? "-#{item[:short_name]}" : nil,
904
1200
 
905
- # description and default
1201
+ # - description and default
906
1202
  [item[:description],
907
- item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
1203
+ ("[#{value_for_cli item[:default]}]" if item[:default].present?)].compact.join(' '),
908
1204
 
909
1205
  # apply proccode, if present, to value
910
1206
  # save value to options hash if option is named
@@ -981,6 +1277,10 @@ module MarkdownExec
981
1277
  }
982
1278
  end
983
1279
 
1280
+ ## tty prompt to select
1281
+ # insert exit option at head or tail
1282
+ # return selected option or nil
1283
+ #
984
1284
  def prompt_with_quit(prompt_text, items, opts = {})
985
1285
  exit_option = '* Exit'
986
1286
  all_items = if @options[:menu_exit_at_top]
@@ -1002,28 +1302,14 @@ module MarkdownExec
1002
1302
 
1003
1303
  # :reek:NestedIterators
1004
1304
  def run
1005
- # eop = EnvironmentOptParse.new(
1006
- # menu: File.join(File.expand_path(__dir__), 'menu.yml'),
1007
- # options: {
1008
- # menu_exit_at_top: true,
1009
- # menu_with_exit: true
1010
- # }
1011
- # ).tap_yaml '** eop'
1012
- # # eop = EnvironmentOptParse.new(menu: 'lib/menu.yml', options: ".#{MarkdownExec::APP_NAME.downcase}.yml", version: MarkdownExec::VERSION).tap_yaml '** eop'
1013
- # eop.options.tap_inspect 'eop.options'
1014
- # eop.remainder.tap_inspect 'eop.remainder'
1015
-
1016
- # exec_block eop.options, eop.options[:block_name]
1017
-
1018
- # return
1019
-
1020
1305
  ## default configuration
1021
1306
  #
1022
1307
  @options = base_options
1023
1308
 
1024
1309
  ## read local configuration file
1025
1310
  #
1026
- read_configuration_file! @options, ".#{MarkdownExec::APP_NAME.downcase}.yml"
1311
+ read_configuration_file! @options,
1312
+ ".#{MarkdownExec::APP_NAME.downcase}.yml"
1027
1313
 
1028
1314
  @option_parser = option_parser = OptionParser.new do |opts|
1029
1315
  executable_name = File.basename($PROGRAM_NAME)
@@ -1034,28 +1320,35 @@ module MarkdownExec
1034
1320
  ].join("\n")
1035
1321
 
1036
1322
  menu_iter do |item|
1037
- item.tap_yaml 'item'
1038
1323
  menu_option_append opts, options, item
1039
1324
  end
1040
1325
  end
1041
- option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options
1042
- option_parser.environment # env defaults to the basename of the program.
1043
- rest = option_parser.parse! # (into: options)
1326
+ option_parser.load # filename defaults to basename of the program
1327
+ # without suffix in a directory ~/.options
1328
+ option_parser.environment # env defaults to the basename of the program
1329
+ # child_argv = arguments_for_child
1330
+ rest = option_parser.parse!(arguments_for_mde) # (into: options)
1044
1331
 
1045
1332
  begin
1046
1333
  options_finalize rest
1047
1334
  exec_block options, options[:block_name]
1048
- rescue FileMissingError => e
1049
- puts "File missing: #{e}"
1335
+ rescue FileMissingError => err
1336
+ puts "File missing: #{err}"
1050
1337
  end
1338
+ rescue StandardError => err
1339
+ warn(error = "ERROR ** MarkParse.run(); #{err.inspect}")
1340
+ binding.pry if $tap_enable
1341
+ raise ArgumentError, error
1051
1342
  end
1052
1343
 
1053
1344
  def saved_name_split(name)
1054
- mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
1345
+ # rubocop:disable Layout/LineLength
1346
+ mf = /#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/.match name
1347
+ # rubocop:enable Layout/LineLength
1055
1348
  return unless mf
1056
1349
 
1057
- @options[:block_name] = mf[:block].tap_inspect name: :options_block_name
1058
- @options[:filename] = mf[:file].gsub(FNR12, FNR11).tap_inspect name: :options_filename
1350
+ @options[:block_name] = mf[:block]
1351
+ @options[:filename] = mf[:file].gsub(FNR12, FNR11)
1059
1352
  end
1060
1353
 
1061
1354
  def run_last_script
@@ -1065,11 +1358,10 @@ module MarkdownExec
1065
1358
 
1066
1359
  saved_name_split filename
1067
1360
  @options[:save_executed_script] = false
1068
- select_and_approve_block
1361
+ select_approve_and_execute_block({})
1069
1362
  end
1070
1363
 
1071
1364
  def save_execution_output
1072
- @options.tap_inspect name: :options
1073
1365
  return unless @options[:save_execution_output]
1074
1366
 
1075
1367
  @options[:logged_stdout_filename] =
@@ -1078,10 +1370,12 @@ module MarkdownExec
1078
1370
  prefix: @options[:logged_stdout_filename_prefix],
1079
1371
  time: Time.now.utc).stdout_name
1080
1372
 
1081
- @options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
1373
+ @options[:logged_stdout_filespec] =
1374
+ File.join @options[:saved_stdout_folder],
1375
+ @options[:logged_stdout_filename]
1082
1376
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
1083
- (dirname = File.dirname(@options[:logged_stdout_filespec])).tap_inspect name: :dirname
1084
- Dir.mkdir dirname unless File.exist?(dirname)
1377
+ (dirname = File.dirname(@options[:logged_stdout_filespec]))
1378
+ Dir.mkdir_p dirname
1085
1379
 
1086
1380
  ol = ["-STDOUT-\n"]
1087
1381
  ol += @execute_files&.fetch(EF_STDOUT, [])
@@ -1093,40 +1387,61 @@ module MarkdownExec
1093
1387
  File.write(@options[:logged_stdout_filespec], ol.join)
1094
1388
  end
1095
1389
 
1096
- def select_and_approve_block(call_options = {}, &options_block)
1390
+ def select_approve_and_execute_block(call_options, &options_block)
1097
1391
  opts = optsmerge call_options, options_block
1098
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect name: :blocks_in_file
1099
- mdoc = MDoc.new(blocks_in_file) { |nopts| opts.merge!(nopts).tap_yaml name: :infiled_opts }
1100
- blocks_menu = mdoc.blocks_for_menu(opts.merge(struct: true)).tap_inspect name: :blocks_menu
1392
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
1393
+ mdoc = MDoc.new(blocks_in_file) do |nopts|
1394
+ opts.merge!(nopts)
1395
+ end
1396
+ blocks_menu = mdoc.fcbs_per_options(opts.merge(struct: true))
1101
1397
 
1102
1398
  repeat_menu = true && !opts[:block_name].present?
1103
1399
  loop do
1104
1400
  unless opts[:block_name].present?
1105
1401
  pt = (opts[:prompt_select_block]).to_s
1106
1402
 
1107
- blocks_menu.each do |block|
1108
- next if block.fetch(:disabled, false)
1109
-
1110
- block.merge! label:
1111
- BlockLabel.new(filename: opts[:filename],
1112
- headings: block.fetch(:headings, []),
1113
- menu_blocks_with_docname: opts[:menu_blocks_with_docname],
1114
- menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1115
- title: block[:title]).make
1116
- end
1117
- return nil if blocks_menu.count.zero?
1118
-
1119
- sel = prompt_with_quit pt, blocks_menu, per_page: opts[:select_page_height]
1403
+ bm = blocks_menu.map do |fcb|
1404
+ # next if fcb.fetch(:disabled, false)
1405
+ # next unless fcb.fetch(:name, '').present?
1406
+
1407
+ fcb.merge!(
1408
+ label: BlockLabel.new(
1409
+ body: fcb[:body],
1410
+ filename: opts[:filename],
1411
+ headings: fcb.fetch(:headings, []),
1412
+ menu_blocks_with_docname: opts[:menu_blocks_with_docname],
1413
+ menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1414
+ text: fcb[:text],
1415
+ title: fcb[:title]
1416
+ ).make
1417
+ )
1418
+
1419
+ fcb.to_h
1420
+ end.compact
1421
+ return nil if bm.count.zero?
1422
+
1423
+ # binding.pry
1424
+
1425
+ sel = prompt_with_quit pt, bm,
1426
+ per_page: opts[:select_page_height]
1120
1427
  return nil if sel.nil?
1121
1428
 
1122
- label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1123
- opts[:block_name] = @options[:block_name] = label_block[:name]
1429
+ ## store selected option
1430
+ #
1431
+ label_block = blocks_in_file.select do |fcb|
1432
+ fcb[:label] == sel
1433
+ end.fetch(0, nil)
1434
+ opts[:block_name] = @options[:block_name] = label_block.fetch(:name, '')
1124
1435
  end
1125
- approve_block opts, mdoc
1436
+ approve_and_execute_block opts, mdoc
1126
1437
  break unless repeat_menu
1127
1438
 
1128
1439
  opts[:block_name] = ''
1129
1440
  end
1441
+ rescue StandardError => err
1442
+ warn(error = "ERROR ** MarkParse.select_approve_and_execute_block(); #{err.inspect}")
1443
+ binding.pry if $tap_enable
1444
+ raise ArgumentError, error
1130
1445
  end
1131
1446
 
1132
1447
  def select_md_file(files = list_markdown_files_in_path)
@@ -1134,7 +1449,8 @@ module MarkdownExec
1134
1449
  if (count = files.count) == 1
1135
1450
  files[0]
1136
1451
  elsif count >= 2
1137
- prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
1452
+ prompt_with_quit opts[:prompt_select_md].to_s, files,
1453
+ per_page: opts[:select_page_height]
1138
1454
  end
1139
1455
  end
1140
1456
 
@@ -1167,15 +1483,11 @@ module MarkdownExec
1167
1483
 
1168
1484
  saved_name_split(filename)
1169
1485
 
1170
- select_and_approve_block({
1171
- bash: true,
1172
- save_executed_script: false,
1173
- struct: true
1174
- })
1175
- end
1176
-
1177
- def summarize_block(headings, title)
1178
- { headings: headings, name: title, title: title }
1486
+ select_approve_and_execute_block({
1487
+ bash: true,
1488
+ save_executed_script: false,
1489
+ struct: true
1490
+ })
1179
1491
  end
1180
1492
 
1181
1493
  def menu_export(data = menu_for_optparse)
@@ -1199,7 +1511,7 @@ module MarkdownExec
1199
1511
  else
1200
1512
  @options.merge! opts
1201
1513
  end
1202
- @options.tap_yaml
1514
+ @options
1203
1515
  end
1204
1516
 
1205
1517
  def write_command_file(call_options, required_blocks)
@@ -1218,7 +1530,7 @@ module MarkdownExec
1218
1530
  File.join opts[:saved_script_folder], opts[:saved_script_filename]
1219
1531
 
1220
1532
  dirname = File.dirname(@options[:saved_filespec])
1221
- Dir.mkdir dirname unless File.exist?(dirname)
1533
+ Dir.mkdir_p dirname
1222
1534
  (shebang = if @options[:shebang]&.present?
1223
1535
  "#{@options[:shebang]} #{@options[:shell]}\n"
1224
1536
  else