markdown_exec 1.2.0 → 1.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,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