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