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.
- checksums.yaml +4 -4
- data/.reek +3 -1
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +61 -0
- data/Gemfile.lock +1 -1
- data/README.md +25 -4
- data/Rakefile +450 -16
- data/bin/tab_completion.sh +13 -41
- data/lib/globfiles.rb +40 -0
- data/lib/markdown_exec/version.rb +3 -1
- data/lib/markdown_exec.rb +255 -529
- data/lib/menu.yml +293 -0
- data/lib/shared.rb +21 -0
- data/lib/tap.rb +13 -7
- metadata +4 -2
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
|
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
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
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
|
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 =
|
246
|
-
|
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 =
|
253
|
-
|
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,
|
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.
|
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:
|
466
|
-
|
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(
|
508
|
-
|
509
|
-
|
510
|
-
return [summarize_block(headings, block_title).merge({ body:
|
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
|
-
|
513
|
-
|
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
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
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
|
-
|
538
|
-
|
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
|
-
|
586
|
+
return
|
543
587
|
end
|
544
588
|
|
545
589
|
unless File.exist? opts[:filename]
|
546
590
|
fout 'Document is missing.'
|
547
|
-
|
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
|
-
|
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
|
573
|
-
|
574
|
-
|
575
|
-
|
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
|
-
#
|
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
|
-
|
641
|
+
block_body = []
|
594
642
|
block_title = (lm && lm[:name])
|
595
643
|
end
|
596
644
|
end
|
597
|
-
elsif
|
598
|
-
|
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
|
-
|
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
|
-
|
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
|
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(
|
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(
|
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:
|
701
|
-
def
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
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 =
|
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
|
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
|
-
|
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
|
-
|
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
|
1349
|
-
|
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
|
1357
|
-
|
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
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
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 =
|
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 =
|
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
|