markdown_exec 1.0.0 → 1.1.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/CHANGELOG.md +49 -11
- data/Gemfile +6 -5
- data/Gemfile.lock +13 -1
- data/README.md +32 -1
- data/Rakefile +35 -1
- data/bin/tab_completion.sh +123 -3
- data/bin/tab_completion.sh.erb +85 -0
- data/lib/markdown_exec/version.rb +3 -2
- data/lib/markdown_exec.rb +594 -203
- data/lib/shared.rb +121 -0
- metadata +18 -10
- data/fixtures/bash1.md +0 -12
- data/fixtures/bash2.md +0 -15
- data/fixtures/exclude1.md +0 -6
- data/fixtures/exclude2.md +0 -12
- data/fixtures/exec1.md +0 -8
- data/fixtures/heading1.md +0 -19
- data/fixtures/sample1.md +0 -9
- data/fixtures/title1.md +0 -6
data/lib/markdown_exec.rb
CHANGED
@@ -3,38 +3,14 @@
|
|
3
3
|
|
4
4
|
# encoding=utf-8
|
5
5
|
|
6
|
+
require 'English'
|
7
|
+
require 'clipboard'
|
6
8
|
require 'open3'
|
7
9
|
require 'optparse'
|
8
10
|
require 'tty-prompt'
|
9
11
|
require 'yaml'
|
10
12
|
|
11
|
-
|
12
|
-
# default if nil
|
13
|
-
# false if empty or '0'
|
14
|
-
# else true
|
15
|
-
|
16
|
-
def env_bool(name, default: false)
|
17
|
-
return default if name.nil? || (val = ENV[name]).nil?
|
18
|
-
return false if val.empty? || val == '0'
|
19
|
-
|
20
|
-
true
|
21
|
-
end
|
22
|
-
|
23
|
-
def env_int(name, default: 0)
|
24
|
-
return default if name.nil? || (val = ENV[name]).nil?
|
25
|
-
return default if val.empty?
|
26
|
-
|
27
|
-
val.to_i
|
28
|
-
end
|
29
|
-
|
30
|
-
def env_str(name, default: '')
|
31
|
-
return default if name.nil? || (val = ENV[name]).nil?
|
32
|
-
|
33
|
-
val || default
|
34
|
-
end
|
35
|
-
|
36
|
-
$pdebug = env_bool 'MDE_DEBUG'
|
37
|
-
|
13
|
+
require_relative 'shared'
|
38
14
|
require_relative 'markdown_exec/version'
|
39
15
|
|
40
16
|
$stderr.sync = true
|
@@ -62,24 +38,17 @@ end
|
|
62
38
|
|
63
39
|
public
|
64
40
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
json: :to_json,
|
72
|
-
string: :to_s,
|
73
|
-
yaml: :to_yaml,
|
74
|
-
else: :inspect
|
75
|
-
}
|
76
|
-
fn = cvt.fetch(format, cvt[:else])
|
41
|
+
# display_level values
|
42
|
+
DISPLAY_LEVEL_BASE = 0 # required output
|
43
|
+
DISPLAY_LEVEL_ADMIN = 1
|
44
|
+
DISPLAY_LEVEL_DEBUG = 2
|
45
|
+
DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
|
46
|
+
DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
|
77
47
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
48
|
+
# @execute_files[ind] = @execute_files[ind] + [block]
|
49
|
+
EF_STDOUT = 0
|
50
|
+
EF_STDERR = 1
|
51
|
+
EF_STDIN = 2
|
83
52
|
|
84
53
|
module MarkdownExec
|
85
54
|
class Error < StandardError; end
|
@@ -98,15 +67,22 @@ module MarkdownExec
|
|
98
67
|
# options necessary to start, parse input, defaults for cli options
|
99
68
|
|
100
69
|
def base_options
|
101
|
-
|
102
|
-
.
|
103
|
-
next unless opt_name.present?
|
104
|
-
|
105
|
-
|
106
|
-
|
70
|
+
menu_iter do |item|
|
71
|
+
item.tap_inspect name: :item, format: :yaml
|
72
|
+
next unless item[:opt_name].present?
|
73
|
+
|
74
|
+
item_default = item[:default]
|
75
|
+
item_default.tap_inspect name: :item_default
|
76
|
+
value = if item_default.nil?
|
77
|
+
item_default
|
78
|
+
else
|
79
|
+
env_str(item[:env_var], default: value_for_hash(item_default))
|
80
|
+
end
|
81
|
+
[item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
|
107
82
|
end.compact.to_h.merge(
|
108
83
|
{
|
109
84
|
mdheadings: true, # use headings (levels 1,2,3) in block lable
|
85
|
+
menu_exit_at_top: true,
|
110
86
|
menu_with_exit: true
|
111
87
|
}
|
112
88
|
).tap_inspect format: :yaml
|
@@ -138,8 +114,35 @@ module MarkdownExec
|
|
138
114
|
display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
|
139
115
|
|
140
116
|
allow = true
|
141
|
-
|
142
|
-
|
117
|
+
if opts[:user_must_approve]
|
118
|
+
loop do
|
119
|
+
# (sel = @prompt.select(opts[:prompt_approve_block], %w(Yes No Copy_script_to_clipboard Save_script), cycle: true)).tap_inspect name: :sel
|
120
|
+
(sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
|
121
|
+
menu.default 1
|
122
|
+
# menu.enum '.'
|
123
|
+
# menu.filter true
|
124
|
+
|
125
|
+
menu.choice 'Yes', 1
|
126
|
+
menu.choice 'No', 2
|
127
|
+
menu.choice 'Copy script to clipboard', 3
|
128
|
+
menu.choice 'Save script', 4
|
129
|
+
end).tap_inspect name: :sel
|
130
|
+
allow = (sel == 1)
|
131
|
+
if sel == 3
|
132
|
+
text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
|
133
|
+
Clipboard.copy(text)
|
134
|
+
fout "Clipboard updated: #{required_blocks.count} blocks, #{required_blocks.flatten.count} lines, #{text.length} characters"
|
135
|
+
end
|
136
|
+
if sel == 4
|
137
|
+
# opts[:saved_script_filename] = saved_name_make(opts)
|
138
|
+
write_command_file(opts.merge(save_executed_script: true), required_blocks)
|
139
|
+
fout "File saved: #{@options[:saved_filespec]}"
|
140
|
+
end
|
141
|
+
break if [1, 2].include? sel
|
142
|
+
end
|
143
|
+
end
|
144
|
+
(opts[:ir_approve] = allow).tap_inspect name: :allow
|
145
|
+
|
143
146
|
selected = get_block_by_name blocks_in_file, opts[:block_name]
|
144
147
|
|
145
148
|
if opts[:ir_approve]
|
@@ -147,6 +150,7 @@ module MarkdownExec
|
|
147
150
|
command_execute opts, required_blocks.flatten.join("\n")
|
148
151
|
save_execution_output
|
149
152
|
output_execution_summary
|
153
|
+
output_execution_result
|
150
154
|
end
|
151
155
|
|
152
156
|
selected[:name]
|
@@ -165,40 +169,45 @@ module MarkdownExec
|
|
165
169
|
@execute_files = Hash.new([])
|
166
170
|
@execute_options = opts
|
167
171
|
@execute_started_at = Time.now.utc
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
until
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
# readable = ready[0]
|
179
|
-
# # writable = ready[1]
|
180
|
-
# # exceptions = ready[2]
|
181
|
-
ready.each.with_index do |readable, ind|
|
182
|
-
readable.each do |f|
|
183
|
-
block = f.read_nonblock(BLOCK_SIZE)
|
184
|
-
@execute_files[ind] = @execute_files[ind] + [block]
|
185
|
-
print block if opts[:output_stdout]
|
186
|
-
rescue EOFError #=> e
|
187
|
-
# do nothing at EOF
|
188
|
-
end
|
189
|
-
end
|
172
|
+
|
173
|
+
Open3.popen3(@options[:shell], '-c', cmd2) do |stdin, stdout, stderr, exec_thr|
|
174
|
+
# pid = exec_thr.pid # pid of the started process
|
175
|
+
|
176
|
+
t1 = Thread.new do
|
177
|
+
until (line = stdout.gets).nil?
|
178
|
+
@execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
|
179
|
+
print line if opts[:output_stdout]
|
180
|
+
yield nil, line, nil, exec_thr if block_given?
|
190
181
|
end
|
191
|
-
rescue IOError => e
|
192
|
-
fout "IOError: #{e}"
|
193
182
|
end
|
194
|
-
|
183
|
+
|
184
|
+
t2 = Thread.new do
|
185
|
+
until (line = stderr.gets).nil?
|
186
|
+
@execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
|
187
|
+
print line if opts[:output_stdout]
|
188
|
+
yield nil, nil, line, exec_thr if block_given?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
in_thr = Thread.new do
|
193
|
+
while exec_thr.alive? # reading input until the child process ends
|
194
|
+
stdin.puts(line = $stdin.gets)
|
195
|
+
@execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line]
|
196
|
+
yield line, nil, nil, exec_thr if block_given?
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
exec_thr.join
|
201
|
+
in_thr.kill
|
202
|
+
# @return_code = exec_thr.value
|
195
203
|
end
|
204
|
+
@execute_completed_at = Time.now.utc
|
196
205
|
rescue Errno::ENOENT => e
|
197
206
|
# error triggered by missing command in script
|
198
207
|
@execute_aborted_at = Time.now.utc
|
199
208
|
@execute_error_message = e.message
|
200
209
|
@execute_error = e
|
201
|
-
@execute_files[
|
210
|
+
@execute_files[EF_STDERR] += [e.message]
|
202
211
|
fout "Error ENOENT: #{e.inspect}"
|
203
212
|
end
|
204
213
|
|
@@ -212,7 +221,9 @@ module MarkdownExec
|
|
212
221
|
end
|
213
222
|
|
214
223
|
def display_command(_opts, required_blocks)
|
224
|
+
fout ' #=#=#'.yellow
|
215
225
|
required_blocks.each { |cb| fout cb }
|
226
|
+
fout ' #=#=#'.yellow
|
216
227
|
end
|
217
228
|
|
218
229
|
def exec_block(options, _block_name = '')
|
@@ -239,7 +250,8 @@ module MarkdownExec
|
|
239
250
|
run_last_script: -> { run_last_script },
|
240
251
|
select_recent_output: -> { select_recent_output },
|
241
252
|
select_recent_script: -> { select_recent_script },
|
242
|
-
tab_completions: -> { fout tab_completions }
|
253
|
+
tab_completions: -> { fout tab_completions },
|
254
|
+
menu_export: -> { fout menu_export }
|
243
255
|
}
|
244
256
|
simple_commands.each_key do |key|
|
245
257
|
if @options[key]
|
@@ -292,6 +304,19 @@ module MarkdownExec
|
|
292
304
|
end
|
293
305
|
end
|
294
306
|
|
307
|
+
def approved_fout?(level)
|
308
|
+
level <= @options[:display_level]
|
309
|
+
end
|
310
|
+
|
311
|
+
# display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
|
312
|
+
#
|
313
|
+
def lout(str, level: DISPLAY_LEVEL_BASE)
|
314
|
+
return unless approved_fout? level
|
315
|
+
|
316
|
+
# fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
|
317
|
+
fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
|
318
|
+
end
|
319
|
+
|
295
320
|
def list_blocks_in_file(call_options = {}, &options_block)
|
296
321
|
opts = optsmerge call_options, options_block
|
297
322
|
|
@@ -360,25 +385,23 @@ module MarkdownExec
|
|
360
385
|
end
|
361
386
|
|
362
387
|
def list_default_env
|
363
|
-
|
364
|
-
|
365
|
-
next unless env_var.present?
|
388
|
+
menu_iter do |item|
|
389
|
+
next unless item[:env_var].present?
|
366
390
|
|
367
391
|
[
|
368
|
-
"#{env_var}=#{value_for_cli default}",
|
369
|
-
description.present? ? description : nil
|
392
|
+
"#{item[:env_var]}=#{value_for_cli item[:default]}",
|
393
|
+
item[:description].present? ? item[:description] : nil
|
370
394
|
].compact.join(' # ')
|
371
395
|
end.compact.sort
|
372
396
|
end
|
373
397
|
|
374
398
|
def list_default_yaml
|
375
|
-
|
376
|
-
|
377
|
-
next unless opt_name.present? && default.present?
|
399
|
+
menu_iter do |item|
|
400
|
+
next unless item[:opt_name].present? && item[:default].present?
|
378
401
|
|
379
402
|
[
|
380
|
-
"#{opt_name}: #{value_for_yaml default}",
|
381
|
-
description.present? ? description : nil
|
403
|
+
"#{item[:opt_name]}: #{value_for_yaml item[:default]}",
|
404
|
+
item[:description].present? ? item[:description] : nil
|
382
405
|
].compact.join(' # ')
|
383
406
|
end.compact.sort
|
384
407
|
end
|
@@ -487,83 +510,400 @@ module MarkdownExec
|
|
487
510
|
end.compact.tap_inspect
|
488
511
|
end
|
489
512
|
|
490
|
-
def
|
513
|
+
def menu_data1
|
491
514
|
val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
|
492
515
|
val_as_int = ->(value) { value.to_i }
|
493
516
|
val_as_str = ->(value) { value.to_s }
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
517
|
+
# val_true = ->(_value) { true } # for commands, sets option to true
|
518
|
+
set1 = [
|
519
|
+
{
|
520
|
+
arg_name: 'PATH',
|
521
|
+
default: '.',
|
522
|
+
description: 'Read configuration file',
|
523
|
+
long_name: 'config',
|
524
|
+
proc1: lambda { |value|
|
525
|
+
read_configuration_file! options, value
|
526
|
+
}
|
527
|
+
},
|
528
|
+
{
|
529
|
+
arg_name: 'BOOL',
|
530
|
+
default: false,
|
531
|
+
description: 'Debug output',
|
532
|
+
env_var: 'MDE_DEBUG',
|
533
|
+
long_name: 'debug',
|
534
|
+
short_name: 'd',
|
535
|
+
proc1: lambda { |value|
|
536
|
+
$pdebug = value.to_i != 0
|
537
|
+
}
|
538
|
+
},
|
539
|
+
{
|
540
|
+
arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
|
541
|
+
default: DISPLAY_LEVEL_DEFAULT,
|
542
|
+
description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
|
543
|
+
env_var: 'MDE_DISPLAY_LEVEL',
|
544
|
+
long_name: 'display-level',
|
545
|
+
opt_name: :display_level,
|
546
|
+
proc1: val_as_int
|
547
|
+
},
|
548
|
+
{
|
549
|
+
arg_name: 'NAME',
|
550
|
+
compreply: false,
|
551
|
+
description: 'Name of block',
|
552
|
+
env_var: 'MDE_BLOCK_NAME',
|
553
|
+
long_name: 'block-name',
|
554
|
+
opt_name: :block_name,
|
555
|
+
short_name: 'f',
|
556
|
+
proc1: val_as_str
|
557
|
+
},
|
558
|
+
{
|
559
|
+
arg_name: 'RELATIVE_PATH',
|
560
|
+
compreply: '.',
|
561
|
+
description: 'Name of document',
|
562
|
+
env_var: 'MDE_FILENAME',
|
563
|
+
long_name: 'filename',
|
564
|
+
opt_name: :filename,
|
565
|
+
short_name: 'f',
|
566
|
+
proc1: val_as_str
|
567
|
+
},
|
568
|
+
{
|
569
|
+
description: 'List blocks',
|
570
|
+
long_name: 'list-blocks',
|
571
|
+
opt_name: :list_blocks,
|
572
|
+
proc1: val_as_bool
|
573
|
+
},
|
574
|
+
{
|
575
|
+
arg_name: 'INT.1-',
|
576
|
+
default: 32,
|
577
|
+
description: 'Max. items to return in list',
|
578
|
+
env_var: 'MDE_LIST_COUNT',
|
579
|
+
long_name: 'list-count',
|
580
|
+
opt_name: :list_count,
|
581
|
+
proc1: val_as_int
|
582
|
+
},
|
583
|
+
{
|
584
|
+
description: 'List default configuration as environment variables',
|
585
|
+
long_name: 'list-default-env',
|
586
|
+
opt_name: :list_default_env
|
587
|
+
},
|
588
|
+
{
|
589
|
+
description: 'List default configuration as YAML',
|
590
|
+
long_name: 'list-default-yaml',
|
591
|
+
opt_name: :list_default_yaml
|
592
|
+
},
|
593
|
+
{
|
594
|
+
description: 'List docs in current folder',
|
595
|
+
long_name: 'list-docs',
|
596
|
+
opt_name: :list_docs,
|
597
|
+
proc1: val_as_bool
|
598
|
+
},
|
599
|
+
{
|
600
|
+
description: 'List recent saved output',
|
601
|
+
long_name: 'list-recent-output',
|
602
|
+
opt_name: :list_recent_output,
|
603
|
+
proc1: val_as_bool
|
604
|
+
},
|
605
|
+
{
|
606
|
+
description: 'List recent saved scripts',
|
607
|
+
long_name: 'list-recent-scripts',
|
608
|
+
opt_name: :list_recent_scripts,
|
609
|
+
proc1: val_as_bool
|
610
|
+
},
|
611
|
+
{
|
612
|
+
arg_name: 'PREFIX',
|
613
|
+
default: MarkdownExec::BIN_NAME,
|
614
|
+
description: 'Name prefix for stdout files',
|
615
|
+
env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
|
616
|
+
long_name: 'logged-stdout-filename-prefix',
|
617
|
+
opt_name: :logged_stdout_filename_prefix,
|
618
|
+
proc1: val_as_str
|
619
|
+
},
|
620
|
+
{
|
621
|
+
arg_name: 'BOOL',
|
622
|
+
default: false,
|
623
|
+
description: 'Display summary for execution',
|
624
|
+
env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
|
625
|
+
long_name: 'output-execution-summary',
|
626
|
+
opt_name: :output_execution_summary,
|
627
|
+
proc1: val_as_bool
|
628
|
+
},
|
629
|
+
{
|
630
|
+
arg_name: 'BOOL',
|
631
|
+
default: false,
|
632
|
+
description: 'Display script prior to execution',
|
633
|
+
env_var: 'MDE_OUTPUT_SCRIPT',
|
634
|
+
long_name: 'output-script',
|
635
|
+
opt_name: :output_script,
|
636
|
+
proc1: val_as_bool
|
637
|
+
},
|
638
|
+
{
|
639
|
+
arg_name: 'BOOL',
|
640
|
+
default: true,
|
641
|
+
description: 'Display standard output from execution',
|
642
|
+
env_var: 'MDE_OUTPUT_STDOUT',
|
643
|
+
long_name: 'output-stdout',
|
644
|
+
opt_name: :output_stdout,
|
645
|
+
proc1: val_as_bool
|
646
|
+
},
|
647
|
+
{
|
648
|
+
arg_name: 'RELATIVE_PATH',
|
649
|
+
default: '.',
|
650
|
+
description: 'Path to documents',
|
651
|
+
env_var: 'MDE_PATH',
|
652
|
+
long_name: 'path',
|
653
|
+
opt_name: :path,
|
654
|
+
short_name: 'p',
|
655
|
+
proc1: val_as_str
|
656
|
+
},
|
657
|
+
{
|
658
|
+
description: 'Gem home folder',
|
659
|
+
long_name: 'pwd',
|
660
|
+
opt_name: :pwd,
|
661
|
+
proc1: val_as_bool
|
662
|
+
},
|
663
|
+
{
|
664
|
+
description: 'Run most recently saved script',
|
665
|
+
long_name: 'run-last-script',
|
666
|
+
opt_name: :run_last_script,
|
667
|
+
proc1: val_as_bool
|
668
|
+
},
|
669
|
+
{
|
670
|
+
arg_name: 'BOOL',
|
671
|
+
default: false,
|
672
|
+
description: 'Save executed script',
|
673
|
+
env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
|
674
|
+
long_name: 'save-executed-script',
|
675
|
+
opt_name: :save_executed_script,
|
676
|
+
proc1: val_as_bool
|
677
|
+
},
|
678
|
+
{
|
679
|
+
arg_name: 'BOOL',
|
680
|
+
default: false,
|
681
|
+
description: 'Save standard output of the executed script',
|
682
|
+
env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
|
683
|
+
long_name: 'save-execution-output',
|
684
|
+
opt_name: :save_execution_output,
|
685
|
+
proc1: val_as_bool
|
686
|
+
},
|
687
|
+
{
|
688
|
+
arg_name: 'INT',
|
689
|
+
default: 0o755,
|
690
|
+
description: 'chmod for saved scripts',
|
691
|
+
env_var: 'MDE_SAVED_SCRIPT_CHMOD',
|
692
|
+
long_name: 'saved-script-chmod',
|
693
|
+
opt_name: :saved_script_chmod,
|
694
|
+
proc1: val_as_int
|
695
|
+
},
|
696
|
+
{
|
697
|
+
arg_name: 'PREFIX',
|
698
|
+
default: MarkdownExec::BIN_NAME,
|
699
|
+
description: 'Name prefix for saved scripts',
|
700
|
+
env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
|
701
|
+
long_name: 'saved-script-filename-prefix',
|
702
|
+
opt_name: :saved_script_filename_prefix,
|
703
|
+
proc1: val_as_str
|
704
|
+
},
|
705
|
+
{
|
706
|
+
arg_name: 'RELATIVE_PATH',
|
707
|
+
default: 'logs',
|
708
|
+
description: 'Saved script folder',
|
709
|
+
env_var: 'MDE_SAVED_SCRIPT_FOLDER',
|
710
|
+
long_name: 'saved-script-folder',
|
711
|
+
opt_name: :saved_script_folder,
|
712
|
+
proc1: val_as_str
|
713
|
+
},
|
714
|
+
{
|
715
|
+
arg_name: 'GLOB',
|
716
|
+
default: 'mde_*.sh',
|
717
|
+
description: 'Glob matching saved scripts',
|
718
|
+
env_var: 'MDE_SAVED_SCRIPT_GLOB',
|
719
|
+
long_name: 'saved-script-glob',
|
720
|
+
opt_name: :saved_script_glob,
|
721
|
+
proc1: val_as_str
|
722
|
+
},
|
723
|
+
{
|
724
|
+
arg_name: 'RELATIVE_PATH',
|
725
|
+
default: 'logs',
|
726
|
+
description: 'Saved stdout folder',
|
727
|
+
env_var: 'MDE_SAVED_STDOUT_FOLDER',
|
728
|
+
long_name: 'saved-stdout-folder',
|
729
|
+
opt_name: :saved_stdout_folder,
|
730
|
+
proc1: val_as_str
|
731
|
+
},
|
732
|
+
{
|
733
|
+
arg_name: 'GLOB',
|
734
|
+
default: 'mde_*.out.txt',
|
735
|
+
description: 'Glob matching saved outputs',
|
736
|
+
env_var: 'MDE_SAVED_STDOUT_GLOB',
|
737
|
+
long_name: 'saved-stdout-glob',
|
738
|
+
opt_name: :saved_stdout_glob,
|
739
|
+
proc1: val_as_str
|
740
|
+
},
|
741
|
+
{
|
742
|
+
description: 'Select and execute a recently saved output',
|
743
|
+
long_name: 'select-recent-output',
|
744
|
+
opt_name: :select_recent_output,
|
745
|
+
proc1: val_as_bool
|
746
|
+
},
|
747
|
+
{
|
748
|
+
description: 'Select and execute a recently saved script',
|
749
|
+
long_name: 'select-recent-script',
|
750
|
+
opt_name: :select_recent_script,
|
751
|
+
proc1: val_as_bool
|
752
|
+
},
|
753
|
+
{
|
754
|
+
description: 'YAML export of menu',
|
755
|
+
long_name: 'menu-export',
|
756
|
+
opt_name: :menu_export,
|
757
|
+
proc1: val_as_bool
|
758
|
+
},
|
759
|
+
{
|
760
|
+
description: 'List tab completions',
|
761
|
+
long_name: 'tab-completions',
|
762
|
+
opt_name: :tab_completions,
|
763
|
+
proc1: val_as_bool
|
764
|
+
},
|
765
|
+
{
|
766
|
+
arg_name: 'BOOL',
|
767
|
+
default: true,
|
768
|
+
description: 'Pause for user to approve script',
|
769
|
+
env_var: 'MDE_USER_MUST_APPROVE',
|
770
|
+
long_name: 'user-must-approve',
|
771
|
+
opt_name: :user_must_approve,
|
772
|
+
proc1: val_as_bool
|
773
|
+
},
|
774
|
+
{
|
775
|
+
description: 'Show current configuration values',
|
776
|
+
short_name: '0',
|
777
|
+
proc1: lambda { |_|
|
778
|
+
options_finalize options
|
779
|
+
fout sorted_keys(options).to_yaml
|
780
|
+
}
|
781
|
+
},
|
782
|
+
{
|
783
|
+
description: 'App help',
|
784
|
+
long_name: 'help',
|
785
|
+
short_name: 'h',
|
786
|
+
proc1: lambda { |_|
|
787
|
+
fout menu_help
|
788
|
+
exit
|
789
|
+
}
|
790
|
+
},
|
791
|
+
{
|
792
|
+
description: "Print the gem's version",
|
793
|
+
long_name: 'version',
|
794
|
+
short_name: 'v',
|
795
|
+
proc1: lambda { |_|
|
796
|
+
fout MarkdownExec::VERSION
|
797
|
+
exit
|
798
|
+
}
|
799
|
+
},
|
800
|
+
{
|
801
|
+
description: 'Exit app',
|
802
|
+
long_name: 'exit',
|
803
|
+
short_name: 'x',
|
804
|
+
proc1: ->(_) { exit }
|
805
|
+
},
|
806
|
+
{
|
807
|
+
default: '^\(.*\)$',
|
808
|
+
description: 'Pattern for blocks to hide from user-selection',
|
809
|
+
env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
|
810
|
+
opt_name: :block_name_excluded_match,
|
811
|
+
proc1: val_as_str
|
812
|
+
},
|
813
|
+
{
|
814
|
+
default: ':(?<title>\S+)( |$)',
|
815
|
+
env_var: 'MDE_BLOCK_NAME_MATCH',
|
816
|
+
opt_name: :block_name_match,
|
817
|
+
proc1: val_as_str
|
818
|
+
},
|
819
|
+
{
|
820
|
+
default: '\+\S+',
|
821
|
+
env_var: 'MDE_BLOCK_REQUIRED_SCAN',
|
822
|
+
opt_name: :block_required_scan,
|
823
|
+
proc1: val_as_str
|
824
|
+
},
|
825
|
+
{
|
826
|
+
default: '> ',
|
827
|
+
env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
|
828
|
+
opt_name: :display_level_xbase_prefix,
|
829
|
+
proc1: val_as_str
|
830
|
+
},
|
831
|
+
{
|
832
|
+
default: '^`{3,}',
|
833
|
+
env_var: 'MDE_FENCED_START_AND_END_MATCH',
|
834
|
+
opt_name: :fenced_start_and_end_match,
|
835
|
+
proc1: val_as_str
|
836
|
+
},
|
837
|
+
{
|
838
|
+
default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
|
839
|
+
env_var: 'MDE_FENCED_START_EX_MATCH',
|
840
|
+
opt_name: :fenced_start_ex_match,
|
841
|
+
proc1: val_as_str
|
842
|
+
},
|
843
|
+
{
|
844
|
+
default: '^# *(?<name>[^#]*?) *$',
|
845
|
+
env_var: 'MDE_HEADING1_MATCH',
|
846
|
+
opt_name: :heading1_match,
|
847
|
+
proc1: val_as_str
|
848
|
+
},
|
849
|
+
{
|
850
|
+
default: '^## *(?<name>[^#]*?) *$',
|
851
|
+
env_var: 'MDE_HEADING2_MATCH',
|
852
|
+
opt_name: :heading2_match,
|
853
|
+
proc1: val_as_str
|
854
|
+
},
|
855
|
+
{
|
856
|
+
default: '^### *(?<name>.+?) *$',
|
857
|
+
env_var: 'MDE_HEADING3_MATCH',
|
858
|
+
opt_name: :heading3_match,
|
859
|
+
proc1: val_as_str
|
860
|
+
},
|
861
|
+
{
|
862
|
+
default: '*.[Mm][Dd]',
|
863
|
+
env_var: 'MDE_MD_FILENAME_GLOB',
|
864
|
+
opt_name: :md_filename_glob,
|
865
|
+
proc1: val_as_str
|
866
|
+
},
|
867
|
+
{
|
868
|
+
default: '.+\\.md',
|
869
|
+
env_var: 'MDE_MD_FILENAME_MATCH',
|
870
|
+
opt_name: :md_filename_match,
|
871
|
+
proc1: val_as_str
|
872
|
+
},
|
873
|
+
{
|
874
|
+
description: 'Options for viewing saved output file',
|
875
|
+
env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
|
876
|
+
opt_name: :output_viewer_options,
|
877
|
+
proc1: val_as_str
|
878
|
+
},
|
879
|
+
{
|
880
|
+
default: 24,
|
881
|
+
description: 'Maximum # of rows in select list',
|
882
|
+
env_var: 'MDE_SELECT_PAGE_HEIGHT',
|
883
|
+
opt_name: :select_page_height,
|
884
|
+
proc1: val_as_int
|
885
|
+
},
|
886
|
+
{
|
887
|
+
default: '#!/usr/bin/env',
|
888
|
+
description: 'Shebang for saved scripts',
|
889
|
+
env_var: 'MDE_SHEBANG',
|
890
|
+
opt_name: :shebang,
|
891
|
+
proc1: val_as_str
|
892
|
+
},
|
893
|
+
{
|
894
|
+
default: 'bash',
|
895
|
+
description: 'Shell for launched scripts',
|
896
|
+
env_var: 'MDE_SHELL',
|
897
|
+
opt_name: :shell,
|
898
|
+
proc1: val_as_str
|
899
|
+
}
|
564
900
|
]
|
901
|
+
# commands first, options second
|
902
|
+
(set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
|
903
|
+
end
|
565
904
|
|
566
|
-
|
905
|
+
def menu_iter(data = menu_data1, &block)
|
906
|
+
data.map(&block)
|
567
907
|
end
|
568
908
|
|
569
909
|
def menu_help
|
@@ -609,6 +949,24 @@ module MarkdownExec
|
|
609
949
|
end.tap_inspect
|
610
950
|
end
|
611
951
|
|
952
|
+
def output_execution_result
|
953
|
+
oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN],
|
954
|
+
['Command',
|
955
|
+
[MarkdownExec::BIN_NAME,
|
956
|
+
@options[:filename],
|
957
|
+
@options[:block_name]].join(' '),
|
958
|
+
DISPLAY_LEVEL_ADMIN]]
|
959
|
+
|
960
|
+
[['Script', :saved_filespec],
|
961
|
+
['StdOut', :logged_stdout_filespec]].each do |label, name|
|
962
|
+
oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name]
|
963
|
+
end
|
964
|
+
|
965
|
+
oq.map do |label, value, level|
|
966
|
+
lout ["#{label}:".yellow, value.to_s].join(' '), level: level
|
967
|
+
end
|
968
|
+
end
|
969
|
+
|
612
970
|
def output_execution_summary
|
613
971
|
return unless @options[:output_execution_summary]
|
614
972
|
|
@@ -626,9 +984,12 @@ module MarkdownExec
|
|
626
984
|
|
627
985
|
def prompt_with_quit(prompt_text, items, opts = {})
|
628
986
|
exit_option = '* Exit'
|
629
|
-
|
630
|
-
|
631
|
-
|
987
|
+
all_items = if @options[:menu_exit_at_top]
|
988
|
+
(@options[:menu_with_exit] ? [exit_option] : []) + items
|
989
|
+
else
|
990
|
+
items + (@options[:menu_with_exit] ? [exit_option] : [])
|
991
|
+
end
|
992
|
+
sel = @prompt.select(prompt_text, all_items, opts.merge(filter: true))
|
632
993
|
sel == exit_option ? nil : sel
|
633
994
|
end
|
634
995
|
|
@@ -675,19 +1036,19 @@ module MarkdownExec
|
|
675
1036
|
"Usage: #{executable_name} [(path | filename [block_name])] [options]"
|
676
1037
|
].join("\n")
|
677
1038
|
|
678
|
-
|
679
|
-
|
680
|
-
next unless long_name.present? || short_name.present?
|
1039
|
+
menu_iter do |item|
|
1040
|
+
next unless item[:long_name].present? || item[:short_name].present?
|
681
1041
|
|
682
|
-
opts.on(*[if long_name.present?
|
683
|
-
"--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
|
1042
|
+
opts.on(*[if item[:long_name].present?
|
1043
|
+
"--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
684
1044
|
end,
|
685
|
-
short_name.present? ? "-#{short_name}" : nil,
|
686
|
-
[description,
|
687
|
-
default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
|
1045
|
+
item[:short_name].present? ? "-#{item[:short_name]}" : nil,
|
1046
|
+
[item[:description],
|
1047
|
+
item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
|
688
1048
|
lambda { |value|
|
689
|
-
ret = proc1.call(value)
|
690
|
-
|
1049
|
+
# ret = item[:proc1].call(value)
|
1050
|
+
ret = item[:proc1] ? item[:proc1].call(value) : value
|
1051
|
+
options[item[:opt_name]] = ret if item[:opt_name]
|
691
1052
|
ret
|
692
1053
|
}].compact)
|
693
1054
|
end
|
@@ -702,7 +1063,7 @@ module MarkdownExec
|
|
702
1063
|
end
|
703
1064
|
|
704
1065
|
FNR11 = '/'
|
705
|
-
FNR12 = '
|
1066
|
+
FNR12 = ',~'
|
706
1067
|
|
707
1068
|
def saved_name_make(opts)
|
708
1069
|
fne = opts[:filename].gsub(FNR11, FNR12)
|
@@ -741,28 +1102,51 @@ module MarkdownExec
|
|
741
1102
|
@logged_stdout_filespec = @options[:logged_stdout_filespec]
|
742
1103
|
dirname = File.dirname(@options[:logged_stdout_filespec])
|
743
1104
|
Dir.mkdir dirname unless File.exist?(dirname)
|
744
|
-
|
1105
|
+
|
1106
|
+
# File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, ''))
|
1107
|
+
ol = ["-STDOUT-\n"]
|
1108
|
+
ol += @execute_files&.fetch(EF_STDOUT, [])
|
1109
|
+
ol += ["-STDERR-\n"].tap_inspect name: :ol3
|
1110
|
+
ol += @execute_files&.fetch(EF_STDERR, [])
|
1111
|
+
ol += ["-STDIN-\n"]
|
1112
|
+
ol += @execute_files&.fetch(EF_STDIN, [])
|
1113
|
+
File.write(@options[:logged_stdout_filespec], ol.join)
|
745
1114
|
end
|
746
1115
|
|
747
1116
|
def select_and_approve_block(call_options = {}, &options_block)
|
748
1117
|
opts = optsmerge call_options, options_block
|
749
1118
|
blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
|
750
1119
|
|
751
|
-
|
752
|
-
pt = (opts[:prompt_select_block]).to_s
|
753
|
-
blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
|
754
|
-
block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
|
1120
|
+
loop1 = true && !opts[:block_name].present?
|
755
1121
|
|
756
|
-
|
1122
|
+
loop do
|
1123
|
+
unless opts[:block_name].present?
|
1124
|
+
pt = (opts[:prompt_select_block]).to_s
|
1125
|
+
blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
|
1126
|
+
block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
|
757
1127
|
|
758
|
-
|
759
|
-
return nil if sel.nil?
|
1128
|
+
return nil if block_labels.count.zero?
|
760
1129
|
|
761
|
-
|
762
|
-
|
763
|
-
end
|
1130
|
+
sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
|
1131
|
+
return nil if sel.nil?
|
764
1132
|
|
765
|
-
|
1133
|
+
# if sel.nil?
|
1134
|
+
# loop1 = false
|
1135
|
+
# break
|
1136
|
+
# end
|
1137
|
+
|
1138
|
+
label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
|
1139
|
+
opts[:block_name] = @options[:block_name] = label_block[:name]
|
1140
|
+
|
1141
|
+
end
|
1142
|
+
# if loop1
|
1143
|
+
approve_block opts, blocks_in_file
|
1144
|
+
# end
|
1145
|
+
|
1146
|
+
break unless loop1
|
1147
|
+
|
1148
|
+
opts[:block_name] = ''
|
1149
|
+
end
|
766
1150
|
end
|
767
1151
|
|
768
1152
|
def select_md_file(files_ = nil)
|
@@ -804,9 +1188,16 @@ module MarkdownExec
|
|
804
1188
|
{ headings: headings, name: title, title: title }
|
805
1189
|
end
|
806
1190
|
|
807
|
-
def
|
1191
|
+
def menu_export(data = menu_data1)
|
1192
|
+
data.map do |item|
|
1193
|
+
item.delete(:proc1)
|
1194
|
+
item
|
1195
|
+
end.to_yaml
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
def tab_completions(data = menu_data1)
|
808
1199
|
data.map do |item|
|
809
|
-
"--#{item[
|
1200
|
+
"--#{item[:long_name]}" if item[:long_name]
|
810
1201
|
end.compact
|
811
1202
|
end
|
812
1203
|
|
@@ -819,19 +1210,6 @@ module MarkdownExec
|
|
819
1210
|
@options.tap_inspect format: :yaml
|
820
1211
|
end
|
821
1212
|
|
822
|
-
def value_for_cli(value)
|
823
|
-
case value.class.to_s
|
824
|
-
when 'String'
|
825
|
-
"'#{value}'"
|
826
|
-
when 'FalseClass', 'TrueClass'
|
827
|
-
value ? '1' : '0'
|
828
|
-
when 'Integer'
|
829
|
-
value
|
830
|
-
else
|
831
|
-
value.to_s
|
832
|
-
end
|
833
|
-
end
|
834
|
-
|
835
1213
|
def value_for_hash(value, default = nil)
|
836
1214
|
return default if value.nil?
|
837
1215
|
|
@@ -872,11 +1250,24 @@ module MarkdownExec
|
|
872
1250
|
|
873
1251
|
dirname = File.dirname(@options[:saved_filespec])
|
874
1252
|
Dir.mkdir dirname unless File.exist?(dirname)
|
875
|
-
|
1253
|
+
(shebang = if @options[:shebang]&.present?
|
1254
|
+
"#{@options[:shebang]} #{@options[:shell]}\n"
|
1255
|
+
else
|
1256
|
+
''
|
1257
|
+
end
|
1258
|
+
).tap_inspect name: :shebang
|
1259
|
+
File.write(@options[:saved_filespec], shebang +
|
876
1260
|
"# file_name: #{opts[:filename]}\n" \
|
877
1261
|
"# block_name: #{opts[:block_name]}\n" \
|
878
1262
|
"# time: #{Time.now.utc}\n" \
|
879
1263
|
"#{required_blocks.flatten.join("\n")}\n")
|
1264
|
+
|
1265
|
+
@options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
|
1266
|
+
return if @options[:saved_script_chmod].zero?
|
1267
|
+
|
1268
|
+
@options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
|
1269
|
+
File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
|
1270
|
+
@options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
|
880
1271
|
end
|
881
1272
|
end
|
882
1273
|
end
|