markdown_exec 1.3.1 → 1.3.3.1

Sign up to get free protection for your applications and to get access to all the features.
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