markdown_exec 1.2.0 → 1.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,23 +6,32 @@
6
6
  require 'English'
7
7
  require 'clipboard'
8
8
  require 'open3'
9
- require 'optparse'
9
+ # require 'optparse'
10
+ require 'shellwords'
10
11
  require 'tty-prompt'
11
12
  require 'yaml'
12
13
 
14
+ require_relative 'cli'
13
15
  require_relative 'colorize'
14
16
  require_relative 'env'
17
+ require_relative 'environment_opt_parse'
18
+ require_relative 'object_present'
15
19
  require_relative 'shared'
16
20
  require_relative 'tap'
17
21
  require_relative 'markdown_exec/version'
18
22
 
19
- include Tap # rubocop:disable Style/MixinUsage
23
+ include CLI
24
+ include Tap
25
+
26
+ tap_config envvar: MarkdownExec::TAP_DEBUG
20
27
 
21
28
  $stderr.sync = true
22
29
  $stdout.sync = true
23
30
 
24
31
  BLOCK_SIZE = 1024
25
32
 
33
+ class FileMissingError < StandardError; end
34
+
26
35
  # hash with keys sorted by name
27
36
  #
28
37
  class Hash
@@ -31,42 +40,38 @@ class Hash
31
40
  end
32
41
  end
33
42
 
34
- # is the value a non-empty string or a binary?
43
+ # stdout manager
35
44
  #
36
- # :reek:ManualDispatch ### temp
37
- class Object
38
- def present?
39
- case self.class.to_s
40
- when 'FalseClass', 'TrueClass'
41
- true
42
- else
43
- self && (!respond_to?(:blank?) || !blank?)
44
- end
45
+ module FOUT
46
+ # standard output; not for debug
47
+ #
48
+ def fout(str)
49
+ puts str
45
50
  end
46
- end
47
51
 
48
- # is value empty?
49
- #
50
- class String
51
- BLANK_RE = /\A[[:space:]]*\z/.freeze
52
- def blank?
53
- empty? || BLANK_RE.match?(self)
52
+ def fout_list(str)
53
+ puts str
54
54
  end
55
- end
56
55
 
57
- public
56
+ def fout_section(name, data)
57
+ puts "# #{name}"
58
+ puts data.to_yaml
59
+ end
58
60
 
59
- # display_level values
60
- DISPLAY_LEVEL_BASE = 0 # required output
61
- DISPLAY_LEVEL_ADMIN = 1
62
- DISPLAY_LEVEL_DEBUG = 2
63
- DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
64
- DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
61
+ def approved_fout?(level)
62
+ level <= @options[:display_level]
63
+ end
65
64
 
66
- # @execute_files[ind] = @execute_files[ind] + [block]
67
- EF_STDOUT = 0
68
- EF_STDERR = 1
69
- EF_STDIN = 2
65
+ # display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
66
+ #
67
+ def lout(str, level: DISPLAY_LEVEL_BASE)
68
+ return unless approved_fout? level
69
+
70
+ fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
71
+ end
72
+ end
73
+
74
+ public
70
75
 
71
76
  # execute markdown documents
72
77
  #
@@ -74,6 +79,29 @@ module MarkdownExec
74
79
  # :reek:IrresponsibleModule
75
80
  class Error < StandardError; end
76
81
 
82
+ # cache lines in text file
83
+ #
84
+ class CFile
85
+ def initialize
86
+ @cache = {}
87
+ end
88
+
89
+ def readlines(filename)
90
+ if @cache[filename]
91
+ @cache[filename].each do |line|
92
+ yield line if block_given?
93
+ end
94
+ else
95
+ lines = []
96
+ File.readlines(filename).each do |line|
97
+ lines.push line
98
+ yield line if block_given?
99
+ end
100
+ @cache[filename] = lines
101
+ end
102
+ end
103
+ end # class CFile
104
+
77
105
  ## an imported markdown document
78
106
  #
79
107
  class MDoc
@@ -81,36 +109,78 @@ module MarkdownExec
81
109
  @table = table
82
110
  end
83
111
 
84
- def code(block)
85
- all = [block[:name]] + recursively_required(block[:reqs])
86
- all.reverse.map do |req|
87
- get_block_by_name(req).fetch(:body, '')
88
- end
89
- .flatten(1)
90
- .tap_inspect
112
+ def collect_recursively_required_code(name)
113
+ 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
122
+ yqcmd = if mstdin[:type]
123
+ "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
124
+ else
125
+ "yq e '#{body}' '#{mstdin[:name]}'"
126
+ end.tap_inspect name: :yqcmd
127
+ if mstdout[:type]
128
+ "export #{mstdout[:name]}=$(#{yqcmd})"
129
+ else
130
+ "#{yqcmd} > '#{mstdout[:name]}'"
131
+ end
132
+ elsif block[:stdout]
133
+ stdout = block[:stdout].tap_inspect name: :stdout
134
+ body = block[:body].join("\n").tap_inspect name: :body
135
+ if stdout[:type]
136
+ # "export #{stdout[:name]}=#{Shellwords.escape body}"
137
+ %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
138
+ else
139
+ "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
140
+ "#{body}\n" \
141
+ "EOF\n"
142
+ end
143
+ else
144
+ block[:body]
145
+ end
146
+ end.flatten(1)
147
+ .tap_yaml
91
148
  end
92
149
 
93
150
  def get_block_by_name(name, default = {})
94
- @table.select { |block| block[:name] == name }.fetch(0, default)
151
+ name.tap_inspect name: :name
152
+ @table.select { |block| block[:name] == name }.fetch(0, default).tap_yaml
95
153
  end
96
154
 
97
- def list_recursively_required_blocks(name)
155
+ def get_required_blocks(name)
98
156
  name_block = get_block_by_name(name)
99
157
  raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
100
158
 
101
159
  all = [name_block[:name]] + recursively_required(name_block[:reqs])
102
160
 
103
161
  # in order of appearance in document
104
- @table.select { |block| all.include? block[:name] }
105
- .map { |block| block.fetch(:body, '') }
106
- .flatten(1)
107
- .tap_inspect
162
+ sel = @table.select { |block| all.include? block[:name] }
163
+
164
+ # 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 })]
169
+ else
170
+ []
171
+ end + [block]
172
+ end.flatten(1) # .tap_yaml
173
+ end
174
+
175
+ # :reek:UtilityFunction
176
+ def hide_menu_block_per_options(opts, block)
177
+ (opts[:hide_blocks_by_name] &&
178
+ block[:name].match(Regexp.new(opts[:block_name_hidden_match]))).tap_inspect
108
179
  end
109
180
 
110
- def option_exclude_blocks(opts)
111
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
181
+ def blocks_for_menu(opts)
112
182
  if opts[:hide_blocks_by_name]
113
- @table.reject { |block| block[:name].match(block_name_excluded_match) }
183
+ @table.reject { |block| hide_menu_block_per_options opts, block }
114
184
  else
115
185
  @table
116
186
  end
@@ -128,11 +198,10 @@ module MarkdownExec
128
198
  end
129
199
  .compact
130
200
  .flatten(1)
131
- .tap_inspect(name: 'rem')
132
201
  end
133
- all.tap_inspect
202
+ all.tap_yaml
134
203
  end
135
- end
204
+ end # class MDoc
136
205
 
137
206
  # format option defaults and values
138
207
  #
@@ -161,7 +230,7 @@ module MarkdownExec
161
230
  end
162
231
  )).join(' ')
163
232
  end
164
- end
233
+ end # class BlockLabel
165
234
 
166
235
  FNR11 = '/'
167
236
  FNR12 = ',~'
@@ -184,7 +253,7 @@ module MarkdownExec
184
253
  def stdout_name
185
254
  "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
186
255
  end
187
- end
256
+ end # class SavedAsset
188
257
 
189
258
  # format option defaults and values
190
259
  #
@@ -228,7 +297,7 @@ module MarkdownExec
228
297
  @value.to_s
229
298
  end
230
299
  end
231
- end
300
+ end # class OptionValue
232
301
 
233
302
  # a generated list of saved files
234
303
  #
@@ -242,20 +311,20 @@ module MarkdownExec
242
311
  Dir.glob(File.join(@folder, @glob)).tap_inspect
243
312
  end
244
313
 
245
- def most_recent(arr = list_all)
246
- return unless arr
314
+ def most_recent(arr = nil)
315
+ arr = list_all if arr.nil?
247
316
  return if arr.count < 1
248
317
 
249
318
  arr.max.tap_inspect
250
319
  end
251
320
 
252
- def most_recent_list(arr = list_all)
253
- return unless arr
321
+ def most_recent_list(list_count, arr = nil)
322
+ arr = list_all if arr.nil?
254
323
  return if (ac = arr.count) < 1
255
324
 
256
- arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
325
+ arr.sort[-[ac, list_count].min..].reverse.tap_inspect
257
326
  end
258
- end
327
+ end # class Sfiles
259
328
 
260
329
  ##
261
330
  #
@@ -266,9 +335,12 @@ module MarkdownExec
266
335
  class MarkParse
267
336
  attr_reader :options
268
337
 
338
+ include FOUT
339
+
269
340
  def initialize(options = {})
270
341
  @options = options
271
- @prompt = TTY::Prompt.new(interrupt: :exit)
342
+ @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: ' ' })
343
+ # @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: options[:menu_divider_symbol] })
272
344
  @execute_aborted_at = nil
273
345
  @execute_completed_at = nil
274
346
  @execute_error = nil
@@ -278,6 +350,7 @@ module MarkdownExec
278
350
  @execute_script_filespec = nil
279
351
  @execute_started_at = nil
280
352
  @option_parser = nil
353
+ @cfile = CFile.new
281
354
  end
282
355
 
283
356
  ##
@@ -285,7 +358,7 @@ module MarkdownExec
285
358
 
286
359
  def base_options
287
360
  menu_iter do |item|
288
- # noisy item.tap_inspect name: :item, format: :yaml
361
+ # noisy item.tap_yaml name: :item
289
362
  next unless item[:opt_name].present?
290
363
 
291
364
  item_default = item[:default]
@@ -295,13 +368,13 @@ module MarkdownExec
295
368
  else
296
369
  env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
297
370
  end
298
- [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
371
+ [item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
299
372
  end.compact.to_h.merge(
300
373
  {
301
374
  menu_exit_at_top: true,
302
375
  menu_with_exit: true
303
376
  }
304
- ).tap_inspect format: :yaml
377
+ ).tap_yaml
305
378
  end
306
379
 
307
380
  def default_options
@@ -310,18 +383,14 @@ module MarkdownExec
310
383
  exclude_expect_blocks: true,
311
384
  hide_blocks_by_name: true,
312
385
  output_saved_script_filename: false,
313
- prompt_approve_block: 'Process?',
314
- prompt_select_block: 'Choose a block:',
315
- prompt_select_md: 'Choose a file:',
316
- prompt_select_output: 'Choose a file:',
317
386
  saved_script_filename: nil, # calculated
318
387
  struct: true # allow get_block_summary()
319
388
  }
320
389
  end
321
390
 
322
391
  def approve_block(opts, mdoc)
323
- required_blocks = mdoc.list_recursively_required_blocks(opts[:block_name])
324
- display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
392
+ 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]
325
394
 
326
395
  allow = true
327
396
  if opts[:user_must_approve]
@@ -331,10 +400,10 @@ module MarkdownExec
331
400
  # menu.enum '.'
332
401
  # menu.filter true
333
402
 
334
- menu.choice 'Yes', 1
335
- menu.choice 'No', 2
336
- menu.choice 'Copy script to clipboard', 3
337
- menu.choice 'Save script', 4
403
+ menu.choice opts[:prompt_yes], 1
404
+ menu.choice opts[:prompt_no], 2
405
+ menu.choice opts[:prompt_script_to_clipboard], 3
406
+ menu.choice opts[:prompt_save_script], 4
338
407
  end).tap_inspect name: :sel
339
408
  allow = (sel == 1)
340
409
  if sel == 3
@@ -366,17 +435,22 @@ module MarkdownExec
366
435
  selected[:name]
367
436
  end
368
437
 
438
+ # def cc(str)
439
+ # puts " - - - #{Process.clock_gettime(Process::CLOCK_MONOTONIC)} - #{str}"
440
+ # end
441
+
369
442
  # :reek:DuplicateMethodCall
370
443
  # :reek:UncommunicativeVariableName { exclude: [ e ] }
371
444
  # :reek:LongYieldList
372
445
  def command_execute(opts, command)
446
+ # dd = lambda { |s| puts 'command_execute() ' + s }
447
+ #d 'execute command and yield outputs'
373
448
  @execute_files = Hash.new([])
374
449
  @execute_options = opts
375
450
  @execute_started_at = Time.now.utc
376
451
 
377
452
  Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
378
- # pid = exec_thr.pid # pid of the started process
379
-
453
+ #d 'command started'
380
454
  Thread.new do
381
455
  until (line = stdout.gets).nil?
382
456
  @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
@@ -384,7 +458,7 @@ module MarkdownExec
384
458
  yield nil, line, nil, exec_thr if block_given?
385
459
  end
386
460
  rescue IOError
387
- # thread killed, do nothing
461
+ #d 'stdout IOError, thread killed, do nothing'
388
462
  end
389
463
 
390
464
  Thread.new do
@@ -394,7 +468,7 @@ module MarkdownExec
394
468
  yield nil, nil, line, exec_thr if block_given?
395
469
  end
396
470
  rescue IOError
397
- # thread killed, do nothing
471
+ #d 'stderr IOError, thread killed, do nothing'
398
472
  end
399
473
 
400
474
  in_thr = Thread.new do
@@ -403,22 +477,33 @@ module MarkdownExec
403
477
  @execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line]
404
478
  yield line, nil, nil, exec_thr if block_given?
405
479
  end
480
+ #d 'exec_thr now dead'
481
+ rescue
482
+ #d 'stdin error, thread killed, do nothing'
406
483
  end
407
484
 
485
+ #d 'join exec_thr'
408
486
  exec_thr.join
487
+
488
+ #d 'wait before closing stdin'
489
+ sleep 0.1
490
+
491
+ #d 'kill stdin thread'
409
492
  in_thr.kill
410
493
  # @return_code = exec_thr.value
494
+ #d 'command end'
411
495
  end
496
+ #d 'command completed'
412
497
  @execute_completed_at = Time.now.utc
413
498
  rescue Errno::ENOENT => e
414
- # error triggered by missing command in script
499
+ #d 'command error ENOENT triggered by missing command in script'
415
500
  @execute_aborted_at = Time.now.utc
416
501
  @execute_error_message = e.message
417
502
  @execute_error = e
418
503
  @execute_files[EF_STDERR] += [@execute_error_message]
419
504
  fout "Error ENOENT: #{e.inspect}"
420
505
  rescue SignalException => e
421
- # SIGTERM triggered by user or system
506
+ #d 'command SIGTERM triggered by user or system'
422
507
  @execute_aborted_at = Time.now.utc
423
508
  @execute_error_message = 'SIGTERM'
424
509
  @execute_error = e
@@ -429,15 +514,15 @@ module MarkdownExec
429
514
  def count_blocks_in_filename
430
515
  fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match]
431
516
  cnt = 0
432
- File.readlines(@options[:filename]).each do |line|
517
+ @cfile.readlines(@options[:filename]).each do |line|
433
518
  cnt += 1 if line.match(fenced_start_and_end_match)
434
519
  end
435
520
  cnt / 2
436
521
  end
437
522
 
438
523
  # :reek:DuplicateMethodCall
439
- def display_command(_opts, required_blocks)
440
- frame = ' #=#=#'.yellow
524
+ def display_required_code(opts, required_blocks)
525
+ frame = opts[:output_divider].send(opts[:output_divider_color].to_sym)
441
526
  fout frame
442
527
  required_blocks.each { |cb| fout cb }
443
528
  fout frame
@@ -462,8 +547,14 @@ module MarkdownExec
462
547
  list_default_yaml: -> { fout_list list_default_yaml },
463
548
  list_docs: -> { fout_list files },
464
549
  list_default_env: -> { fout_list list_default_env },
465
- list_recent_output: -> { fout_list list_recent_output },
466
- list_recent_scripts: -> { fout_list list_recent_scripts },
550
+ list_recent_output: lambda {
551
+ fout_list list_recent_output(@options[:saved_stdout_folder],
552
+ @options[:saved_stdout_glob], @options[:list_count])
553
+ },
554
+ list_recent_scripts: lambda {
555
+ fout_list list_recent_scripts(options[:saved_script_folder],
556
+ options[:saved_script_glob], options[:list_count])
557
+ },
467
558
  pwd: -> { fout File.expand_path('..', __dir__) },
468
559
  run_last_script: -> { run_last_script },
469
560
  select_recent_output: -> { select_recent_output },
@@ -488,73 +579,57 @@ module MarkdownExec
488
579
  fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
489
580
  end
490
581
 
491
- # standard output; not for debug
492
- #
493
- def fout(str)
494
- puts str
495
- end
496
-
497
- def fout_list(str)
498
- puts str
499
- end
500
-
501
- def fout_section(name, data)
502
- puts "# #{name}"
503
- puts data.to_yaml
504
- end
505
-
506
582
  # :reek:LongParameterList
507
- def get_block_summary(opts, headings:, block_title:, current:)
508
- return [current] unless opts[:struct]
509
-
510
- return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
583
+ def get_block_summary(call_options = {}, headings:, block_title:, block_body:)
584
+ 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]
511
587
 
512
- bm = block_title.match(Regexp.new(opts[:block_name_match]))
513
- reqs = block_title.scan(Regexp.new(opts[:block_required_scan]))
588
+ block_title.tap_inspect name: :block_title
589
+ call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
514
590
  .map { |scanned| scanned[1..] }
591
+ &.first.tap_inspect name: :call
592
+ (titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
515
593
 
516
- if bm && bm[1]
517
- [summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
518
- else
519
- [summarize_block(headings, block_title).merge({ body: current, reqs: reqs })]
520
- end
521
- end
522
-
523
- def approved_fout?(level)
524
- level <= @options[:display_level]
525
- end
526
-
527
- # display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
528
- #
529
- def lout(str, level: DISPLAY_LEVEL_BASE)
530
- return unless approved_fout? level
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
531
599
 
532
- # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
533
- fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
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
534
606
  end
535
607
 
536
608
  # :reek:DuplicateMethodCall
537
- def list_blocks_in_file(call_options = {}, &options_block)
538
- opts = optsmerge call_options, options_block
609
+ # :reek:LongYieldList
610
+ def iter_blocks_in_file(opts = {})
611
+ # opts = optsmerge call_options, options_block
539
612
 
540
613
  unless opts[:filename]&.present?
541
614
  fout 'No blocks found.'
542
- exit 1
615
+ return
543
616
  end
544
617
 
545
618
  unless File.exist? opts[:filename]
546
619
  fout 'Document is missing.'
547
- exit 1
620
+ return
548
621
  end
549
622
 
550
623
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
551
624
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
552
625
  block_title = ''
553
- blocks = []
554
- current = nil
626
+ block_body = nil
555
627
  headings = []
556
628
  in_block = false
557
- File.readlines(opts[:filename]).each do |line|
629
+
630
+ selected_messages = yield :filter
631
+
632
+ @cfile.readlines(opts[:filename]).each do |line|
558
633
  continue unless line
559
634
 
560
635
  if opts[:menu_blocks_with_headings]
@@ -569,15 +644,17 @@ module MarkdownExec
569
644
 
570
645
  if line.match(fenced_start_and_end_match)
571
646
  if in_block
572
- if current
573
- block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
574
- blocks += get_block_summary opts, headings: headings, block_title: block_title, current: current
575
- current = nil
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
576
653
  end
577
654
  in_block = false
578
655
  block_title = ''
579
656
  else
580
- # new block
657
+ # start block
581
658
  #
582
659
  lm = line.match(fenced_start_ex)
583
660
  block_allow = false
@@ -590,15 +667,50 @@ module MarkdownExec
590
667
 
591
668
  in_block = true
592
669
  if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
593
- current = []
670
+ block_body = []
594
671
  block_title = (lm && lm[:name])
595
672
  end
596
673
  end
597
- elsif current
598
- current += [line.chomp]
674
+ elsif block_body
675
+ block_body += [line.chomp]
676
+ elsif block_given? && selected_messages.include?(:line)
677
+ # text outside of block
678
+ #
679
+ yield :line, nil, nil, line
680
+ end
681
+ end
682
+ end
683
+
684
+ def list_blocks_in_file(call_options = {}, &options_block)
685
+ opts = optsmerge call_options, options_block
686
+
687
+ blocks = []
688
+ 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
+ }]
693
+ end
694
+ iter_blocks_in_file(opts) do |btype, headings, block_title, body|
695
+ case btype
696
+ when :filter
697
+ %i[blocks line]
698
+ 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: '' }]
702
+ end
703
+ when :blocks
704
+ blocks += get_block_summary opts, headings: headings, block_title: block_title, block_body: body
599
705
  end
600
706
  end
601
- blocks.tap_inspect
707
+ 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
+ }]
712
+ end
713
+ blocks.tap_yaml
602
714
  end
603
715
 
604
716
  def list_default_env
@@ -636,22 +748,30 @@ module MarkdownExec
636
748
  def list_files_specified(specified_filename: nil, specified_folder: nil,
637
749
  default_filename: nil, default_folder: nil, filetree: nil)
638
750
  fn = File.join(if specified_filename&.present?
639
- if specified_folder&.present?
640
- [specified_folder, specified_filename]
641
- elsif specified_filename.start_with? '/'
751
+ # puts ' LFS 01'
752
+ if specified_filename.start_with? '/'
753
+ # puts ' LFS 02'
642
754
  [specified_filename]
755
+ elsif specified_folder&.present?
756
+ # puts ' LFS 03'
757
+ [specified_folder, specified_filename]
643
758
  else
759
+ # puts ' LFS 04'
644
760
  [default_folder, specified_filename]
645
761
  end
646
762
  elsif specified_folder&.present?
763
+ # puts ' LFS 05'
647
764
  if filetree
765
+ # puts ' LFS 06'
648
766
  [specified_folder, @options[:md_filename_match]]
649
767
  else
768
+ # puts ' LFS 07'
650
769
  [specified_folder, @options[:md_filename_glob]]
651
770
  end
652
771
  else
772
+ # puts ' LFS 08'
653
773
  [default_folder, default_filename]
654
- end)
774
+ end).tap_inspect name: :fn
655
775
  if filetree
656
776
  filetree.select { |filename| filename == fn || filename.match(/^#{fn}$/) || filename.match(%r{^#{fn}/.+$}) }
657
777
  else
@@ -665,29 +785,27 @@ module MarkdownExec
665
785
 
666
786
  def list_named_blocks_in_file(call_options = {}, &options_block)
667
787
  opts = optsmerge call_options, options_block
668
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
788
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
789
+ mdoc = MDoc.new(blocks_in_file)
790
+
669
791
  list_blocks_in_file(opts).map do |block|
670
- next if opts[:hide_blocks_by_name] && block[:name].match(block_name_excluded_match)
792
+ next if mdoc.hide_menu_block_per_options(opts, block)
671
793
 
672
794
  block
673
795
  end.compact.tap_inspect
674
796
  end
675
797
 
676
- def list_recent_output
677
- Sfiles.new(@options[:saved_stdout_folder],
678
- @options[:saved_stdout_glob]).most_recent_list
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)
679
800
  end
680
801
 
681
- def list_recent_scripts
682
- Sfiles.new(@options[:saved_script_folder],
683
- @options[:saved_script_glob]).most_recent_list
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)
684
804
  end
685
805
 
686
806
  def make_block_labels(call_options = {})
687
807
  opts = options.merge(call_options)
688
808
  list_blocks_in_file(opts).map do |block|
689
- # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
690
-
691
809
  BlockLabel.new(filename: opts[:filename],
692
810
  headings: block.fetch(:headings, []),
693
811
  menu_blocks_with_docname: opts[:menu_blocks_with_docname],
@@ -697,419 +815,74 @@ module MarkdownExec
697
815
  end
698
816
 
699
817
  # :reek:DuplicateMethodCall
700
- # :reek:UncommunicativeMethodName ### temp
701
- def menu_data1
702
- val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
703
- val_as_int = ->(value) { value.to_i }
704
- val_as_str = ->(value) { value.to_s }
705
- # val_true = ->(_value) { true } # for commands, sets option to true
706
- menu_options = [
707
- {
708
- arg_name: 'PATH',
709
- default: '.',
710
- description: 'Read configuration file',
711
- long_name: 'config',
712
- proc1: lambda { |value|
713
- read_configuration_file! options, value
714
- }
715
- },
716
- {
717
- arg_name: 'BOOL',
718
- default: false,
719
- description: 'Debug output',
720
- env_var: 'MDE_DEBUG',
721
- long_name: 'debug',
722
- short_name: 'd',
723
- proc1: lambda { |value|
724
- tap_config value.to_i != 0
725
- }
726
- },
727
- {
728
- arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
729
- default: DISPLAY_LEVEL_DEFAULT,
730
- description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
731
- env_var: 'MDE_DISPLAY_LEVEL',
732
- long_name: 'display-level',
733
- opt_name: :display_level,
734
- proc1: val_as_int
735
- },
736
- {
737
- arg_name: 'NAME',
738
- compreply: false,
739
- description: 'Name of block',
740
- env_var: 'MDE_BLOCK_NAME',
741
- long_name: 'block-name',
742
- opt_name: :block_name,
743
- short_name: 'f',
744
- proc1: val_as_str
745
- },
746
- {
747
- arg_name: 'RELATIVE_PATH',
748
- compreply: '.',
749
- description: 'Name of document',
750
- env_var: 'MDE_FILENAME',
751
- long_name: 'filename',
752
- opt_name: :filename,
753
- short_name: 'f',
754
- proc1: val_as_str
755
- },
756
- {
757
- description: 'List blocks',
758
- long_name: 'list-blocks',
759
- opt_name: :list_blocks,
760
- proc1: val_as_bool
761
- },
762
- {
763
- arg_name: 'INT.1-',
764
- default: 32,
765
- description: 'Max. items to return in list',
766
- env_var: 'MDE_LIST_COUNT',
767
- long_name: 'list-count',
768
- opt_name: :list_count,
769
- proc1: val_as_int
770
- },
771
- {
772
- description: 'List default configuration as environment variables',
773
- long_name: 'list-default-env',
774
- opt_name: :list_default_env
775
- },
776
- {
777
- description: 'List default configuration as YAML',
778
- long_name: 'list-default-yaml',
779
- opt_name: :list_default_yaml
780
- },
781
- {
782
- description: 'List docs in current folder',
783
- long_name: 'list-docs',
784
- opt_name: :list_docs,
785
- proc1: val_as_bool
786
- },
787
- {
788
- description: 'List recent saved output',
789
- long_name: 'list-recent-output',
790
- opt_name: :list_recent_output,
791
- proc1: val_as_bool
792
- },
793
- {
794
- description: 'List recent saved scripts',
795
- long_name: 'list-recent-scripts',
796
- opt_name: :list_recent_scripts,
797
- proc1: val_as_bool
798
- },
799
- {
800
- arg_name: 'PREFIX',
801
- default: MarkdownExec::BIN_NAME,
802
- description: 'Name prefix for stdout files',
803
- env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
804
- long_name: 'logged-stdout-filename-prefix',
805
- opt_name: :logged_stdout_filename_prefix,
806
- proc1: val_as_str
807
- },
808
- {
809
- arg_name: 'BOOL',
810
- default: false,
811
- description: 'Display document name in block selection menu',
812
- env_var: 'MDE_MENU_BLOCKS_WITH_DOCNAME',
813
- long_name: 'menu-blocks-with-docname',
814
- opt_name: :menu_blocks_with_docname,
815
- proc1: val_as_bool
816
- },
817
- {
818
- arg_name: 'BOOL',
819
- default: false,
820
- description: 'Display headings (levels 1,2,3) in block selection menu',
821
- env_var: 'MDE_MENU_BLOCKS_WITH_HEADINGS',
822
- long_name: 'menu-blocks-with-headings',
823
- opt_name: :menu_blocks_with_headings,
824
- proc1: val_as_bool
825
- },
826
- {
827
- arg_name: 'BOOL',
828
- default: false,
829
- description: 'Display summary for execution',
830
- env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
831
- long_name: 'output-execution-summary',
832
- opt_name: :output_execution_summary,
833
- proc1: val_as_bool
834
- },
835
- {
836
- arg_name: 'BOOL',
837
- default: false,
838
- description: 'Display script prior to execution',
839
- env_var: 'MDE_OUTPUT_SCRIPT',
840
- long_name: 'output-script',
841
- opt_name: :output_script,
842
- proc1: val_as_bool
843
- },
844
- {
845
- arg_name: 'BOOL',
846
- default: true,
847
- description: 'Display standard output from execution',
848
- env_var: 'MDE_OUTPUT_STDOUT',
849
- long_name: 'output-stdout',
850
- opt_name: :output_stdout,
851
- proc1: val_as_bool
852
- },
853
- {
854
- arg_name: 'RELATIVE_PATH',
855
- default: '.',
856
- description: 'Path to documents',
857
- env_var: 'MDE_PATH',
858
- long_name: 'path',
859
- opt_name: :path,
860
- short_name: 'p',
861
- proc1: val_as_str
862
- },
863
- {
864
- description: 'Gem home folder',
865
- long_name: 'pwd',
866
- opt_name: :pwd,
867
- proc1: val_as_bool
868
- },
869
- {
870
- description: 'Run most recently saved script',
871
- long_name: 'run-last-script',
872
- opt_name: :run_last_script,
873
- proc1: val_as_bool
874
- },
875
- {
876
- arg_name: 'BOOL',
877
- default: false,
878
- description: 'Save executed script',
879
- env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
880
- long_name: 'save-executed-script',
881
- opt_name: :save_executed_script,
882
- proc1: val_as_bool
883
- },
884
- {
885
- arg_name: 'BOOL',
886
- default: false,
887
- description: 'Save standard output of the executed script',
888
- env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
889
- long_name: 'save-execution-output',
890
- opt_name: :save_execution_output,
891
- proc1: val_as_bool
892
- },
893
- {
894
- arg_name: 'INT',
895
- default: 0o755,
896
- description: 'chmod for saved scripts',
897
- env_var: 'MDE_SAVED_SCRIPT_CHMOD',
898
- long_name: 'saved-script-chmod',
899
- opt_name: :saved_script_chmod,
900
- proc1: val_as_int
901
- },
902
- {
903
- arg_name: 'PREFIX',
904
- default: MarkdownExec::BIN_NAME,
905
- description: 'Name prefix for saved scripts',
906
- env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
907
- long_name: 'saved-script-filename-prefix',
908
- opt_name: :saved_script_filename_prefix,
909
- proc1: val_as_str
910
- },
911
- {
912
- arg_name: 'RELATIVE_PATH',
913
- default: 'logs',
914
- description: 'Saved script folder',
915
- env_var: 'MDE_SAVED_SCRIPT_FOLDER',
916
- long_name: 'saved-script-folder',
917
- opt_name: :saved_script_folder,
918
- proc1: val_as_str
919
- },
920
- {
921
- arg_name: 'GLOB',
922
- default: 'mde_*.sh',
923
- description: 'Glob matching saved scripts',
924
- env_var: 'MDE_SAVED_SCRIPT_GLOB',
925
- long_name: 'saved-script-glob',
926
- opt_name: :saved_script_glob,
927
- proc1: val_as_str
928
- },
929
- {
930
- arg_name: 'RELATIVE_PATH',
931
- default: 'logs',
932
- description: 'Saved stdout folder',
933
- env_var: 'MDE_SAVED_STDOUT_FOLDER',
934
- long_name: 'saved-stdout-folder',
935
- opt_name: :saved_stdout_folder,
936
- proc1: val_as_str
937
- },
938
- {
939
- arg_name: 'GLOB',
940
- default: 'mde_*.out.txt',
941
- description: 'Glob matching saved outputs',
942
- env_var: 'MDE_SAVED_STDOUT_GLOB',
943
- long_name: 'saved-stdout-glob',
944
- opt_name: :saved_stdout_glob,
945
- proc1: val_as_str
946
- },
947
- {
948
- description: 'Select and execute a recently saved output',
949
- long_name: 'select-recent-output',
950
- opt_name: :select_recent_output,
951
- proc1: val_as_bool
952
- },
953
- {
954
- description: 'Select and execute a recently saved script',
955
- long_name: 'select-recent-script',
956
- opt_name: :select_recent_script,
957
- proc1: val_as_bool
958
- },
959
- {
960
- description: 'YAML export of menu',
961
- long_name: 'menu-export',
962
- opt_name: :menu_export,
963
- proc1: val_as_bool
964
- },
965
- {
966
- description: 'List tab completions',
967
- long_name: 'tab-completions',
968
- opt_name: :tab_completions,
969
- proc1: val_as_bool
970
- },
971
- {
972
- arg_name: 'BOOL',
973
- default: true,
974
- description: 'Pause for user to approve script',
975
- env_var: 'MDE_USER_MUST_APPROVE',
976
- long_name: 'user-must-approve',
977
- opt_name: :user_must_approve,
978
- proc1: val_as_bool
979
- },
980
- {
981
- description: 'Show current configuration values',
982
- short_name: '0',
983
- proc1: lambda { |_|
984
- options_finalize options
985
- fout options.sort_by_key.to_yaml
986
- }
987
- },
988
- {
989
- description: 'App help',
990
- long_name: 'help',
991
- short_name: 'h',
992
- proc1: lambda { |_|
993
- fout menu_help
994
- exit
995
- }
996
- },
997
- {
998
- description: "Print the gem's version",
999
- long_name: 'version',
1000
- short_name: 'v',
1001
- proc1: lambda { |_|
1002
- fout MarkdownExec::VERSION
1003
- exit
1004
- }
1005
- },
1006
- {
1007
- description: 'Exit app',
1008
- long_name: 'exit',
1009
- short_name: 'x',
1010
- proc1: ->(_) { exit }
1011
- },
1012
- {
1013
- default: '^\(.*\)$',
1014
- description: 'Pattern for blocks to hide from user-selection',
1015
- env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
1016
- opt_name: :block_name_excluded_match,
1017
- proc1: val_as_str
1018
- },
1019
- {
1020
- default: ':(?<title>\S+)( |$)',
1021
- env_var: 'MDE_BLOCK_NAME_MATCH',
1022
- opt_name: :block_name_match,
1023
- proc1: val_as_str
1024
- },
1025
- {
1026
- default: '\+\S+',
1027
- env_var: 'MDE_BLOCK_REQUIRED_SCAN',
1028
- opt_name: :block_required_scan,
1029
- proc1: val_as_str
1030
- },
1031
- {
1032
- default: '> ',
1033
- env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
1034
- opt_name: :display_level_xbase_prefix,
1035
- proc1: val_as_str
1036
- },
1037
- {
1038
- default: '^`{3,}',
1039
- env_var: 'MDE_FENCED_START_AND_END_MATCH',
1040
- opt_name: :fenced_start_and_end_match,
1041
- proc1: val_as_str
1042
- },
1043
- {
1044
- default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
1045
- env_var: 'MDE_FENCED_START_EX_MATCH',
1046
- opt_name: :fenced_start_ex_match,
1047
- proc1: val_as_str
1048
- },
1049
- {
1050
- default: '^# *(?<name>[^#]*?) *$',
1051
- env_var: 'MDE_HEADING1_MATCH',
1052
- opt_name: :heading1_match,
1053
- proc1: val_as_str
1054
- },
1055
- {
1056
- default: '^## *(?<name>[^#]*?) *$',
1057
- env_var: 'MDE_HEADING2_MATCH',
1058
- opt_name: :heading2_match,
1059
- proc1: val_as_str
1060
- },
1061
- {
1062
- default: '^### *(?<name>.+?) *$',
1063
- env_var: 'MDE_HEADING3_MATCH',
1064
- opt_name: :heading3_match,
1065
- proc1: val_as_str
1066
- },
1067
- {
1068
- default: '*.[Mm][Dd]',
1069
- env_var: 'MDE_MD_FILENAME_GLOB',
1070
- opt_name: :md_filename_glob,
1071
- proc1: val_as_str
1072
- },
1073
- {
1074
- default: '.+\\.md',
1075
- env_var: 'MDE_MD_FILENAME_MATCH',
1076
- opt_name: :md_filename_match,
1077
- proc1: val_as_str
1078
- },
1079
- {
1080
- description: 'Options for viewing saved output file',
1081
- env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
1082
- opt_name: :output_viewer_options,
1083
- proc1: val_as_str
1084
- },
1085
- {
1086
- default: 24,
1087
- description: 'Maximum # of rows in select list',
1088
- env_var: 'MDE_SELECT_PAGE_HEIGHT',
1089
- opt_name: :select_page_height,
1090
- proc1: val_as_int
1091
- },
1092
- {
1093
- default: '#!/usr/bin/env',
1094
- description: 'Shebang for saved scripts',
1095
- env_var: 'MDE_SHEBANG',
1096
- opt_name: :shebang,
1097
- proc1: val_as_str
1098
- },
1099
- {
1100
- default: 'bash',
1101
- description: 'Shell for launched scripts',
1102
- env_var: 'MDE_SHELL',
1103
- opt_name: :shell,
1104
- proc1: val_as_str
1105
- }
1106
- ]
1107
- # commands first, options second
1108
- (menu_options.reject { |option| option[:arg_name] }) +
1109
- (menu_options.select { |option| option[:arg_name] })
818
+ # :reek:NestedIterators
819
+ def menu_for_optparse
820
+ menu_from_yaml.map do |menu_item|
821
+ menu_item.merge(
822
+ {
823
+ opt_name: menu_item[:opt_name]&.to_sym,
824
+ proccode: case menu_item[:procname]
825
+ when 'debug'
826
+ lambda { |value|
827
+ tap_config value: value
828
+ }
829
+ when 'exit'
830
+ lambda { |_|
831
+ exit
832
+ }
833
+ when 'help'
834
+ lambda { |_|
835
+ fout menu_help
836
+ exit
837
+ }
838
+ when 'path'
839
+ lambda { |value|
840
+ read_configuration_file! options, value
841
+ }
842
+ when 'show_config'
843
+ lambda { |_|
844
+ options_finalize options
845
+ fout options.sort_by_key.to_yaml
846
+ }
847
+ when 'val_as_bool'
848
+ ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
849
+ when 'val_as_int'
850
+ ->(value) { value.to_i }
851
+ when 'val_as_str'
852
+ ->(value) { value.to_s }
853
+ when 'version'
854
+ lambda { |_|
855
+ fout MarkdownExec::VERSION
856
+ exit
857
+ }
858
+ else
859
+ menu_item[:procname]
860
+ end
861
+ }
862
+ )
863
+ end
1110
864
  end
1111
865
 
1112
- def menu_iter(data = menu_data1, &block)
866
+ def menu_for_blocks(menu_options)
867
+ options = default_options.merge menu_options
868
+ menu = []
869
+ iter_blocks_in_file(options) do |btype, headings, block_title, body|
870
+ case btype
871
+ when :filter
872
+ %i[blocks line]
873
+ when :line
874
+ if options[:menu_divider_match] && (mbody = body.match options[:menu_divider_match])
875
+ menu += [{ name: mbody[:name], disabled: '' }]
876
+ end
877
+ when :blocks
878
+ summ = get_block_summary options, headings: headings, block_title: block_title, block_body: body
879
+ menu += [summ[0][:name]]
880
+ end
881
+ end
882
+ menu.tap_yaml
883
+ end
884
+
885
+ def menu_iter(data = menu_for_optparse, &block)
1113
886
  data.map(&block)
1114
887
  end
1115
888
 
@@ -1117,6 +890,33 @@ module MarkdownExec
1117
890
  @option_parser.help
1118
891
  end
1119
892
 
893
+ def menu_option_append(opts, options, item)
894
+ return unless item[:long_name].present? || item[:short_name].present?
895
+
896
+ opts.on(*[
897
+ # long name
898
+ if item[:long_name].present?
899
+ "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
900
+ end,
901
+
902
+ # short name
903
+ item[:short_name].present? ? "-#{item[:short_name]}" : nil,
904
+
905
+ # description and default
906
+ [item[:description],
907
+ item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
908
+
909
+ # apply proccode, if present, to value
910
+ # save value to options hash if option is named
911
+ #
912
+ lambda { |value|
913
+ (item[:proccode] ? item[:proccode].call(value) : value).tap do |converted|
914
+ options[item[:opt_name]] = converted if item[:opt_name]
915
+ end
916
+ }
917
+ ].compact)
918
+ end
919
+
1120
920
  ## post-parse options configuration
1121
921
  #
1122
922
  def options_finalize(rest)
@@ -1128,7 +928,7 @@ module MarkdownExec
1128
928
  elsif File.exist?(pos)
1129
929
  @options[:filename] = pos
1130
930
  else
1131
- raise "Invalid parameter: #{pos}"
931
+ raise FileMissingError, pos, caller
1132
932
  end
1133
933
  end
1134
934
 
@@ -1145,7 +945,7 @@ module MarkdownExec
1145
945
  options_block.call class_call_options
1146
946
  else
1147
947
  class_call_options
1148
- end.tap_inspect
948
+ end
1149
949
  end
1150
950
 
1151
951
  def output_execution_result
@@ -1196,14 +996,27 @@ module MarkdownExec
1196
996
  def read_configuration_file!(options, configuration_path)
1197
997
  return unless File.exist?(configuration_path)
1198
998
 
1199
- # rubocop:disable Security/YAMLLoad
1200
999
  options.merge!((YAML.load(File.open(configuration_path)) || {})
1201
1000
  .transform_keys(&:to_sym))
1202
- # rubocop:enable Security/YAMLLoad
1203
1001
  end
1204
1002
 
1205
1003
  # :reek:NestedIterators
1206
1004
  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
+
1207
1020
  ## default configuration
1208
1021
  #
1209
1022
  @options = base_options
@@ -1221,29 +1034,20 @@ module MarkdownExec
1221
1034
  ].join("\n")
1222
1035
 
1223
1036
  menu_iter do |item|
1224
- next unless item[:long_name].present? || item[:short_name].present?
1225
-
1226
- opts.on(*[if item[:long_name].present?
1227
- "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
1228
- end,
1229
- item[:short_name].present? ? "-#{item[:short_name]}" : nil,
1230
- [item[:description],
1231
- item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
1232
- lambda { |value|
1233
- # ret = item[:proc1].call(value)
1234
- ret = item[:proc1] ? item[:proc1].call(value) : value
1235
- options[item[:opt_name]] = ret if item[:opt_name]
1236
- ret
1237
- }].compact)
1037
+ item.tap_yaml 'item'
1038
+ menu_option_append opts, options, item
1238
1039
  end
1239
1040
  end
1240
1041
  option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options
1241
1042
  option_parser.environment # env defaults to the basename of the program.
1242
1043
  rest = option_parser.parse! # (into: options)
1243
1044
 
1244
- options_finalize rest
1245
-
1246
- exec_block options, options[:block_name]
1045
+ begin
1046
+ options_finalize rest
1047
+ exec_block options, options[:block_name]
1048
+ rescue FileMissingError => e
1049
+ puts "File missing: #{e}"
1050
+ end
1247
1051
  end
1248
1052
 
1249
1053
  def saved_name_split(name)
@@ -1291,16 +1095,18 @@ module MarkdownExec
1291
1095
 
1292
1096
  def select_and_approve_block(call_options = {}, &options_block)
1293
1097
  opts = optsmerge call_options, options_block
1294
- blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
1295
- mdoc = MDoc.new(blocks_in_file)
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
1296
1101
 
1297
1102
  repeat_menu = true && !opts[:block_name].present?
1298
-
1299
1103
  loop do
1300
1104
  unless opts[:block_name].present?
1301
1105
  pt = (opts[:prompt_select_block]).to_s
1302
1106
 
1303
- blocks_in_file.each do |block|
1107
+ blocks_menu.each do |block|
1108
+ next if block.fetch(:disabled, false)
1109
+
1304
1110
  block.merge! label:
1305
1111
  BlockLabel.new(filename: opts[:filename],
1306
1112
  headings: block.fetch(:headings, []),
@@ -1308,27 +1114,15 @@ module MarkdownExec
1308
1114
  menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1309
1115
  title: block[:title]).make
1310
1116
  end
1117
+ return nil if blocks_menu.count.zero?
1311
1118
 
1312
- block_labels = mdoc.option_exclude_blocks(opts).map { |block| block[:label] }
1313
-
1314
- return nil if block_labels.count.zero?
1315
-
1316
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
1119
+ sel = prompt_with_quit pt, blocks_menu, per_page: opts[:select_page_height]
1317
1120
  return nil if sel.nil?
1318
1121
 
1319
- # if sel.nil?
1320
- # repeat_menu = false
1321
- # break
1322
- # end
1323
-
1324
1122
  label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1325
1123
  opts[:block_name] = @options[:block_name] = label_block[:name]
1326
-
1327
1124
  end
1328
- # if repeat_menu
1329
1125
  approve_block opts, mdoc
1330
- # end
1331
-
1332
1126
  break unless repeat_menu
1333
1127
 
1334
1128
  opts[:block_name] = ''
@@ -1345,38 +1139,53 @@ module MarkdownExec
1345
1139
  end
1346
1140
 
1347
1141
  def select_recent_output
1348
- filename = prompt_with_quit @options[:prompt_select_output].to_s, list_recent_output,
1349
- per_page: @options[:select_page_height]
1142
+ filename = prompt_with_quit(
1143
+ @options[:prompt_select_output].to_s,
1144
+ list_recent_output(
1145
+ @options[:saved_stdout_folder],
1146
+ @options[:saved_stdout_glob],
1147
+ @options[:list_count]
1148
+ ),
1149
+ { per_page: @options[:select_page_height] }
1150
+ )
1350
1151
  return unless filename.present?
1351
1152
 
1352
1153
  `open #{filename} #{options[:output_viewer_options]}`
1353
1154
  end
1354
1155
 
1355
1156
  def select_recent_script
1356
- filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
1357
- per_page: @options[:select_page_height]
1157
+ filename = prompt_with_quit(
1158
+ @options[:prompt_select_md].to_s,
1159
+ list_recent_scripts(
1160
+ @options[:saved_script_folder],
1161
+ @options[:saved_script_glob],
1162
+ @options[:list_count]
1163
+ ),
1164
+ { per_page: @options[:select_page_height] }
1165
+ )
1358
1166
  return if filename.nil?
1359
1167
 
1360
- saved_name_split filename
1361
- select_and_approve_block(
1362
- bash: true,
1363
- save_executed_script: false,
1364
- struct: true
1365
- )
1168
+ saved_name_split(filename)
1169
+
1170
+ select_and_approve_block({
1171
+ bash: true,
1172
+ save_executed_script: false,
1173
+ struct: true
1174
+ })
1366
1175
  end
1367
1176
 
1368
1177
  def summarize_block(headings, title)
1369
1178
  { headings: headings, name: title, title: title }
1370
1179
  end
1371
1180
 
1372
- def menu_export(data = menu_data1)
1181
+ def menu_export(data = menu_for_optparse)
1373
1182
  data.map do |item|
1374
- item.delete(:proc1)
1183
+ item.delete(:procname)
1375
1184
  item
1376
1185
  end.to_yaml
1377
1186
  end
1378
1187
 
1379
- def tab_completions(data = menu_data1)
1188
+ def tab_completions(data = menu_for_optparse)
1380
1189
  data.map do |item|
1381
1190
  "--#{item[:long_name]}" if item[:long_name]
1382
1191
  end.compact
@@ -1390,7 +1199,7 @@ module MarkdownExec
1390
1199
  else
1391
1200
  @options.merge! opts
1392
1201
  end
1393
- @options.tap_inspect format: :yaml
1202
+ @options.tap_yaml
1394
1203
  end
1395
1204
 
1396
1205
  def write_command_file(call_options, required_blocks)
@@ -1425,5 +1234,5 @@ module MarkdownExec
1425
1234
 
1426
1235
  File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
1427
1236
  end
1428
- end
1429
- end
1237
+ end # class MarkParse
1238
+ end # module MarkdownExec