markdown_exec 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/markdown_exec.rb CHANGED
@@ -7,6 +7,7 @@ require 'English'
7
7
  require 'clipboard'
8
8
  require 'open3'
9
9
  require 'optparse'
10
+ require 'shellwords'
10
11
  require 'tty-prompt'
11
12
  require 'yaml'
12
13
 
@@ -16,7 +17,8 @@ require_relative 'shared'
16
17
  require_relative 'tap'
17
18
  require_relative 'markdown_exec/version'
18
19
 
19
- include Tap # rubocop:disable Style/MixinUsage
20
+ include Tap
21
+ tap_config envvar: MarkdownExec::TAP_DEBUG
20
22
 
21
23
  $stderr.sync = true
22
24
  $stdout.sync = true
@@ -56,18 +58,6 @@ end
56
58
 
57
59
  public
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
65
-
66
- # @execute_files[ind] = @execute_files[ind] + [block]
67
- EF_STDOUT = 0
68
- EF_STDERR = 1
69
- EF_STDIN = 2
70
-
71
61
  # execute markdown documents
72
62
  #
73
63
  module MarkdownExec
@@ -81,36 +71,78 @@ module MarkdownExec
81
71
  @table = table
82
72
  end
83
73
 
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
74
+ def collect_recursively_required_code(name)
75
+ get_required_blocks(name)
76
+ .map do |block|
77
+ block.tap_inspect name: :block, format: :yaml
78
+ body = block[:body].join("\n")
79
+
80
+ if block[:cann]
81
+ xcall = block[:cann][1..-2].tap_inspect name: :xcall
82
+ mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdin
83
+ mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_-]\S+)/).tap_inspect name: :mstdout
84
+ yqcmd = if mstdin[:type]
85
+ "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
86
+ else
87
+ "yq e '#{body}' '#{mstdin[:name]}'"
88
+ end.tap_inspect name: :yqcmd
89
+ if mstdout[:type]
90
+ "export #{mstdout[:name]}=$(#{yqcmd})"
91
+ else
92
+ "#{yqcmd} > '#{mstdout[:name]}'"
93
+ end
94
+ elsif block[:stdout]
95
+ stdout = block[:stdout].tap_inspect name: :stdout
96
+ body = block[:body].join("\n").tap_inspect name: :body
97
+ if stdout[:type]
98
+ # "export #{stdout[:name]}=#{Shellwords.escape body}"
99
+ %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
100
+ else
101
+ "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
102
+ "#{body}\n" \
103
+ "EOF\n"
104
+ end
105
+ else
106
+ block[:body]
107
+ end
108
+ end.flatten(1)
109
+ .tap_inspect format: :yaml
91
110
  end
92
111
 
93
112
  def get_block_by_name(name, default = {})
94
- @table.select { |block| block[:name] == name }.fetch(0, default)
113
+ name.tap_inspect name: :name
114
+ @table.select { |block| block[:name] == name }.fetch(0, default).tap_inspect format: :yaml
95
115
  end
96
116
 
97
- def list_recursively_required_blocks(name)
117
+ def get_required_blocks(name)
98
118
  name_block = get_block_by_name(name)
99
119
  raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
100
120
 
101
121
  all = [name_block[:name]] + recursively_required(name_block[:reqs])
102
122
 
103
123
  # 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
124
+ sel = @table.select { |block| all.include? block[:name] }
125
+
126
+ # insert function blocks
127
+ sel.map do |block|
128
+ block.tap_inspect name: :block, format: :yaml
129
+ if (call = block[:call])
130
+ [get_block_by_name("[#{call.match(/^\((\S+) |\)/)[1]}]").merge({ cann: call })]
131
+ else
132
+ []
133
+ end + [block]
134
+ end.flatten(1) # .tap_inspect format: :yaml
135
+ end
136
+
137
+ # :reek:UtilityFunction
138
+ def hide_menu_block_per_options(opts, block)
139
+ (opts[:hide_blocks_by_name] &&
140
+ block[:name].match(Regexp.new(opts[:block_name_excluded_match]))).tap_inspect
108
141
  end
109
142
 
110
- def option_exclude_blocks(opts)
111
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
143
+ def blocks_for_menu(opts)
112
144
  if opts[:hide_blocks_by_name]
113
- @table.reject { |block| block[:name].match(block_name_excluded_match) }
145
+ @table.reject { |block| hide_menu_block_per_options opts, block }
114
146
  else
115
147
  @table
116
148
  end
@@ -128,11 +160,10 @@ module MarkdownExec
128
160
  end
129
161
  .compact
130
162
  .flatten(1)
131
- .tap_inspect(name: 'rem')
132
163
  end
133
- all.tap_inspect
164
+ all.tap_inspect format: :yaml
134
165
  end
135
- end
166
+ end # class MDoc
136
167
 
137
168
  # format option defaults and values
138
169
  #
@@ -161,7 +192,7 @@ module MarkdownExec
161
192
  end
162
193
  )).join(' ')
163
194
  end
164
- end
195
+ end # class BlockLabel
165
196
 
166
197
  FNR11 = '/'
167
198
  FNR12 = ',~'
@@ -184,7 +215,7 @@ module MarkdownExec
184
215
  def stdout_name
185
216
  "#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
186
217
  end
187
- end
218
+ end # class SavedAsset
188
219
 
189
220
  # format option defaults and values
190
221
  #
@@ -228,7 +259,7 @@ module MarkdownExec
228
259
  @value.to_s
229
260
  end
230
261
  end
231
- end
262
+ end # class OptionValue
232
263
 
233
264
  # a generated list of saved files
234
265
  #
@@ -242,20 +273,20 @@ module MarkdownExec
242
273
  Dir.glob(File.join(@folder, @glob)).tap_inspect
243
274
  end
244
275
 
245
- def most_recent(arr = list_all)
246
- return unless arr
276
+ def most_recent(arr = nil)
277
+ arr = list_all if arr.nil?
247
278
  return if arr.count < 1
248
279
 
249
280
  arr.max.tap_inspect
250
281
  end
251
282
 
252
- def most_recent_list(arr = list_all)
253
- return unless arr
283
+ def most_recent_list(list_count, arr = nil)
284
+ arr = list_all if arr.nil?
254
285
  return if (ac = arr.count) < 1
255
286
 
256
- arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
287
+ arr.sort[-[ac, list_count].min..].reverse.tap_inspect
257
288
  end
258
- end
289
+ end # class Sfiles
259
290
 
260
291
  ##
261
292
  #
@@ -320,7 +351,7 @@ module MarkdownExec
320
351
  end
321
352
 
322
353
  def approve_block(opts, mdoc)
323
- required_blocks = mdoc.list_recursively_required_blocks(opts[:block_name])
354
+ required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
324
355
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
325
356
 
326
357
  allow = true
@@ -375,8 +406,6 @@ module MarkdownExec
375
406
  @execute_started_at = Time.now.utc
376
407
 
377
408
  Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
378
- # pid = exec_thr.pid # pid of the started process
379
-
380
409
  Thread.new do
381
410
  until (line = stdout.gets).nil?
382
411
  @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
@@ -462,8 +491,14 @@ module MarkdownExec
462
491
  list_default_yaml: -> { fout_list list_default_yaml },
463
492
  list_docs: -> { fout_list files },
464
493
  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 },
494
+ list_recent_output: lambda {
495
+ fout_list list_recent_output(@options[:saved_stdout_folder],
496
+ @options[:saved_stdout_glob], @options[:list_count])
497
+ },
498
+ list_recent_scripts: lambda {
499
+ fout_list list_recent_scripts(options[:saved_script_folder],
500
+ options[:saved_script_glob], options[:list_count])
501
+ },
467
502
  pwd: -> { fout File.expand_path('..', __dir__) },
468
503
  run_last_script: -> { run_last_script },
469
504
  select_recent_output: -> { select_recent_output },
@@ -504,20 +539,29 @@ module MarkdownExec
504
539
  end
505
540
 
506
541
  # :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]
542
+ def get_block_summary(call_options = {}, headings:, block_title:, block_body:)
543
+ opts = optsmerge call_options
544
+ return [block_body] unless opts[:struct]
545
+ return [summarize_block(headings, block_title).merge({ body: block_body })] unless opts[:bash]
511
546
 
512
- bm = block_title.match(Regexp.new(opts[:block_name_match]))
513
- reqs = block_title.scan(Regexp.new(opts[:block_required_scan]))
547
+ block_title.tap_inspect name: :block_title
548
+ call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
514
549
  .map { |scanned| scanned[1..] }
550
+ &.first.tap_inspect name: :call
551
+ (titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
515
552
 
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
553
+ bm = titlexcall.match(Regexp.new(opts[:block_name_match]))
554
+ reqs = titlexcall.scan(Regexp.new(opts[:block_required_scan]))
555
+ .map { |scanned| scanned[1..] }
556
+ stdin = titlexcall.match(Regexp.new(opts[:block_stdin_scan])).tap_inspect name: :stdin
557
+ stdout = titlexcall.match(Regexp.new(opts[:block_stdout_scan])).tap_inspect name: :stdout
558
+
559
+ title = bm && bm[1] ? bm[:title] : titlexcall
560
+ [summarize_block(headings, title).merge({ body: block_body,
561
+ call: call,
562
+ reqs: reqs,
563
+ stdin: stdin,
564
+ stdout: stdout })].tap_inspect format: :yaml
521
565
  end
522
566
 
523
567
  def approved_fout?(level)
@@ -529,31 +573,33 @@ module MarkdownExec
529
573
  def lout(str, level: DISPLAY_LEVEL_BASE)
530
574
  return unless approved_fout? level
531
575
 
532
- # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
533
576
  fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
534
577
  end
535
578
 
536
579
  # :reek:DuplicateMethodCall
537
- def list_blocks_in_file(call_options = {}, &options_block)
538
- opts = optsmerge call_options, options_block
580
+ # :reek:LongYieldList
581
+ def iter_blocks_in_file(opts = {})
582
+ # opts = optsmerge call_options, options_block
539
583
 
540
584
  unless opts[:filename]&.present?
541
585
  fout 'No blocks found.'
542
- exit 1
586
+ return
543
587
  end
544
588
 
545
589
  unless File.exist? opts[:filename]
546
590
  fout 'Document is missing.'
547
- exit 1
591
+ return
548
592
  end
549
593
 
550
594
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
551
595
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
552
596
  block_title = ''
553
- blocks = []
554
- current = nil
597
+ block_body = nil
555
598
  headings = []
556
599
  in_block = false
600
+
601
+ selected_messages = yield :filter
602
+
557
603
  File.readlines(opts[:filename]).each do |line|
558
604
  continue unless line
559
605
 
@@ -569,15 +615,17 @@ module MarkdownExec
569
615
 
570
616
  if line.match(fenced_start_and_end_match)
571
617
  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
618
+ if block_body
619
+ # end block
620
+ #
621
+ block_title = block_body.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
622
+ yield :blocks, headings, block_title, block_body if block_given? && selected_messages.include?(:blocks)
623
+ block_body = nil
576
624
  end
577
625
  in_block = false
578
626
  block_title = ''
579
627
  else
580
- # new block
628
+ # start block
581
629
  #
582
630
  lm = line.match(fenced_start_ex)
583
631
  block_allow = false
@@ -590,15 +638,37 @@ module MarkdownExec
590
638
 
591
639
  in_block = true
592
640
  if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
593
- current = []
641
+ block_body = []
594
642
  block_title = (lm && lm[:name])
595
643
  end
596
644
  end
597
- elsif current
598
- current += [line.chomp]
645
+ elsif block_body
646
+ block_body += [line.chomp]
647
+ elsif block_given? && selected_messages.include?(:line)
648
+ # text outside of block
649
+ #
650
+ yield :line, nil, nil, line
599
651
  end
600
652
  end
601
- blocks.tap_inspect
653
+ end
654
+
655
+ def list_blocks_in_file(call_options = {}, &options_block)
656
+ opts = optsmerge call_options, options_block
657
+
658
+ blocks = []
659
+ iter_blocks_in_file(opts) do |btype, headings, block_title, body|
660
+ case btype
661
+ when :filter
662
+ %i[blocks line]
663
+ when :line
664
+ if opts[:menu_divider_match] && (mbody = body.match opts[:menu_divider_match])
665
+ blocks += [{ name: (opts[:menu_divider_format] % mbody[:name]), disabled: '' }]
666
+ end
667
+ when :blocks
668
+ blocks += get_block_summary opts, headings: headings, block_title: block_title, block_body: body
669
+ end
670
+ end
671
+ blocks.tap_inspect format: :yaml
602
672
  end
603
673
 
604
674
  def list_default_env
@@ -665,29 +735,27 @@ module MarkdownExec
665
735
 
666
736
  def list_named_blocks_in_file(call_options = {}, &options_block)
667
737
  opts = optsmerge call_options, options_block
668
- block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
738
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
739
+ mdoc = MDoc.new(blocks_in_file)
740
+
669
741
  list_blocks_in_file(opts).map do |block|
670
- next if opts[:hide_blocks_by_name] && block[:name].match(block_name_excluded_match)
742
+ next if mdoc.hide_menu_block_per_options(opts, block)
671
743
 
672
744
  block
673
745
  end.compact.tap_inspect
674
746
  end
675
747
 
676
- def list_recent_output
677
- Sfiles.new(@options[:saved_stdout_folder],
678
- @options[:saved_stdout_glob]).most_recent_list
748
+ def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
749
+ Sfiles.new(saved_stdout_folder, saved_stdout_glob).most_recent_list(list_count)
679
750
  end
680
751
 
681
- def list_recent_scripts
682
- Sfiles.new(@options[:saved_script_folder],
683
- @options[:saved_script_glob]).most_recent_list
752
+ def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
753
+ Sfiles.new(saved_script_folder, saved_script_glob).most_recent_list(list_count)
684
754
  end
685
755
 
686
756
  def make_block_labels(call_options = {})
687
757
  opts = options.merge(call_options)
688
758
  list_blocks_in_file(opts).map do |block|
689
- # next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
690
-
691
759
  BlockLabel.new(filename: opts[:filename],
692
760
  headings: block.fetch(:headings, []),
693
761
  menu_blocks_with_docname: opts[:menu_blocks_with_docname],
@@ -697,419 +765,74 @@ module MarkdownExec
697
765
  end
698
766
 
699
767
  # :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] })
768
+ # :reek:NestedIterators
769
+ def menu_for_optparse
770
+ menu_from_yaml.map do |menu_item|
771
+ menu_item.merge(
772
+ {
773
+ opt_name: menu_item[:opt_name]&.to_sym,
774
+ proc1: case menu_item[:proc1]
775
+ when 'debug'
776
+ lambda { |value|
777
+ tap_config value: value
778
+ }
779
+ when 'exit'
780
+ lambda { |_|
781
+ exit
782
+ }
783
+ when 'help'
784
+ lambda { |_|
785
+ fout menu_help
786
+ exit
787
+ }
788
+ when 'path'
789
+ lambda { |value|
790
+ read_configuration_file! options, value
791
+ }
792
+ when 'show_config'
793
+ lambda { |_|
794
+ options_finalize options
795
+ fout options.sort_by_key.to_yaml
796
+ }
797
+ when 'val_as_bool'
798
+ ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
799
+ when 'val_as_int'
800
+ ->(value) { value.to_i }
801
+ when 'val_as_str'
802
+ ->(value) { value.to_s }
803
+ when 'version'
804
+ lambda { |_|
805
+ fout MarkdownExec::VERSION
806
+ exit
807
+ }
808
+ else
809
+ menu_item[:proc1]
810
+ end
811
+ }
812
+ )
813
+ end
814
+ end
815
+
816
+ def menu_for_blocks(menu_options)
817
+ options = default_options.merge menu_options
818
+ menu = []
819
+ iter_blocks_in_file(options) do |btype, headings, block_title, body|
820
+ case btype
821
+ when :filter
822
+ %i[blocks line]
823
+ when :line
824
+ if options[:menu_divider_match] && (mbody = body.match options[:menu_divider_match])
825
+ menu += [{ name: mbody[:name], disabled: '' }]
826
+ end
827
+ when :blocks
828
+ summ = get_block_summary options, headings: headings, block_title: block_title, block_body: body
829
+ menu += [summ[0][:name]]
830
+ end
831
+ end
832
+ menu.tap_inspect format: :yaml
1110
833
  end
1111
834
 
1112
- def menu_iter(data = menu_data1, &block)
835
+ def menu_iter(data = menu_for_optparse, &block)
1113
836
  data.map(&block)
1114
837
  end
1115
838
 
@@ -1145,7 +868,7 @@ module MarkdownExec
1145
868
  options_block.call class_call_options
1146
869
  else
1147
870
  class_call_options
1148
- end.tap_inspect
871
+ end
1149
872
  end
1150
873
 
1151
874
  def output_execution_result
@@ -1196,10 +919,8 @@ module MarkdownExec
1196
919
  def read_configuration_file!(options, configuration_path)
1197
920
  return unless File.exist?(configuration_path)
1198
921
 
1199
- # rubocop:disable Security/YAMLLoad
1200
922
  options.merge!((YAML.load(File.open(configuration_path)) || {})
1201
923
  .transform_keys(&:to_sym))
1202
- # rubocop:enable Security/YAMLLoad
1203
924
  end
1204
925
 
1205
926
  # :reek:NestedIterators
@@ -1291,16 +1012,18 @@ module MarkdownExec
1291
1012
 
1292
1013
  def select_and_approve_block(call_options = {}, &options_block)
1293
1014
  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)
1015
+ blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect name: :blocks_in_file
1016
+ mdoc = MDoc.new(blocks_in_file) { |nopts| opts.merge!(nopts).tap_inspect name: :infiled_opts, format: :yaml }
1017
+ blocks_menu = mdoc.blocks_for_menu(opts.merge(struct: true)).tap_inspect name: :blocks_menu
1296
1018
 
1297
1019
  repeat_menu = true && !opts[:block_name].present?
1298
-
1299
1020
  loop do
1300
1021
  unless opts[:block_name].present?
1301
1022
  pt = (opts[:prompt_select_block]).to_s
1302
1023
 
1303
- blocks_in_file.each do |block|
1024
+ blocks_menu.each do |block|
1025
+ next if block.fetch(:disabled, false)
1026
+
1304
1027
  block.merge! label:
1305
1028
  BlockLabel.new(filename: opts[:filename],
1306
1029
  headings: block.fetch(:headings, []),
@@ -1308,27 +1031,15 @@ module MarkdownExec
1308
1031
  menu_blocks_with_headings: opts[:menu_blocks_with_headings],
1309
1032
  title: block[:title]).make
1310
1033
  end
1034
+ return nil if blocks_menu.count.zero?
1311
1035
 
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]
1036
+ sel = prompt_with_quit pt, blocks_menu, per_page: opts[:select_page_height]
1317
1037
  return nil if sel.nil?
1318
1038
 
1319
- # if sel.nil?
1320
- # repeat_menu = false
1321
- # break
1322
- # end
1323
-
1324
1039
  label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1325
1040
  opts[:block_name] = @options[:block_name] = label_block[:name]
1326
-
1327
1041
  end
1328
- # if repeat_menu
1329
1042
  approve_block opts, mdoc
1330
- # end
1331
-
1332
1043
  break unless repeat_menu
1333
1044
 
1334
1045
  opts[:block_name] = ''
@@ -1345,38 +1056,53 @@ module MarkdownExec
1345
1056
  end
1346
1057
 
1347
1058
  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]
1059
+ filename = prompt_with_quit(
1060
+ @options[:prompt_select_output].to_s,
1061
+ list_recent_output(
1062
+ @options[:saved_stdout_folder],
1063
+ @options[:saved_stdout_glob],
1064
+ @options[:list_count]
1065
+ ),
1066
+ { per_page: @options[:select_page_height] }
1067
+ )
1350
1068
  return unless filename.present?
1351
1069
 
1352
1070
  `open #{filename} #{options[:output_viewer_options]}`
1353
1071
  end
1354
1072
 
1355
1073
  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]
1074
+ filename = prompt_with_quit(
1075
+ @options[:prompt_select_md].to_s,
1076
+ list_recent_scripts(
1077
+ @options[:saved_script_folder],
1078
+ @options[:saved_script_glob],
1079
+ @options[:list_count]
1080
+ ),
1081
+ { per_page: @options[:select_page_height] }
1082
+ )
1358
1083
  return if filename.nil?
1359
1084
 
1360
- saved_name_split filename
1361
- select_and_approve_block(
1362
- bash: true,
1363
- save_executed_script: false,
1364
- struct: true
1365
- )
1085
+ saved_name_split(filename)
1086
+
1087
+ select_and_approve_block({
1088
+ bash: true,
1089
+ save_executed_script: false,
1090
+ struct: true
1091
+ })
1366
1092
  end
1367
1093
 
1368
1094
  def summarize_block(headings, title)
1369
1095
  { headings: headings, name: title, title: title }
1370
1096
  end
1371
1097
 
1372
- def menu_export(data = menu_data1)
1098
+ def menu_export(data = menu_for_optparse)
1373
1099
  data.map do |item|
1374
1100
  item.delete(:proc1)
1375
1101
  item
1376
1102
  end.to_yaml
1377
1103
  end
1378
1104
 
1379
- def tab_completions(data = menu_data1)
1105
+ def tab_completions(data = menu_for_optparse)
1380
1106
  data.map do |item|
1381
1107
  "--#{item[:long_name]}" if item[:long_name]
1382
1108
  end.compact
@@ -1425,5 +1151,5 @@ module MarkdownExec
1425
1151
 
1426
1152
  File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
1427
1153
  end
1428
- end
1429
- end
1154
+ end # class MarkParse
1155
+ end # module MarkdownExec