markdown_exec 1.2.0 → 1.3.0

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
@@ -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