markdown_exec 1.1.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 +26 -0
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +91 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +7 -1
- data/README.md +45 -38
- data/Rakefile +465 -11
- data/bin/tab_completion.sh +16 -36
- data/lib/colorize.rb +64 -0
- data/lib/env.rb +37 -0
- data/lib/globfiles.rb +40 -0
- data/lib/markdown_exec/version.rb +3 -1
- data/lib/markdown_exec.rb +542 -660
- data/lib/menu.yml +293 -0
- data/lib/shared.rb +16 -97
- data/lib/tap.rb +42 -0
- metadata +17 -3
data/lib/markdown_exec.rb
CHANGED
@@ -7,18 +7,36 @@ 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
|
|
14
|
+
require_relative 'colorize'
|
15
|
+
require_relative 'env'
|
13
16
|
require_relative 'shared'
|
17
|
+
require_relative 'tap'
|
14
18
|
require_relative 'markdown_exec/version'
|
15
19
|
|
20
|
+
include Tap
|
21
|
+
tap_config envvar: MarkdownExec::TAP_DEBUG
|
22
|
+
|
16
23
|
$stderr.sync = true
|
17
24
|
$stdout.sync = true
|
18
25
|
|
19
26
|
BLOCK_SIZE = 1024
|
20
27
|
|
21
|
-
|
28
|
+
# hash with keys sorted by name
|
29
|
+
#
|
30
|
+
class Hash
|
31
|
+
def sort_by_key
|
32
|
+
keys.sort.to_h { |key| [key, self[key]] }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# is the value a non-empty string or a binary?
|
37
|
+
#
|
38
|
+
# :reek:ManualDispatch ### temp
|
39
|
+
class Object
|
22
40
|
def present?
|
23
41
|
case self.class.to_s
|
24
42
|
when 'FalseClass', 'TrueClass'
|
@@ -29,7 +47,9 @@ class Object # rubocop:disable Style/Documentation
|
|
29
47
|
end
|
30
48
|
end
|
31
49
|
|
32
|
-
|
50
|
+
# is value empty?
|
51
|
+
#
|
52
|
+
class String
|
33
53
|
BLANK_RE = /\A[[:space:]]*\z/.freeze
|
34
54
|
def blank?
|
35
55
|
empty? || BLANK_RE.match?(self)
|
@@ -38,29 +58,257 @@ end
|
|
38
58
|
|
39
59
|
public
|
40
60
|
|
41
|
-
#
|
42
|
-
|
43
|
-
DISPLAY_LEVEL_ADMIN = 1
|
44
|
-
DISPLAY_LEVEL_DEBUG = 2
|
45
|
-
DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
|
46
|
-
DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
|
47
|
-
|
48
|
-
# @execute_files[ind] = @execute_files[ind] + [block]
|
49
|
-
EF_STDOUT = 0
|
50
|
-
EF_STDERR = 1
|
51
|
-
EF_STDIN = 2
|
52
|
-
|
61
|
+
# execute markdown documents
|
62
|
+
#
|
53
63
|
module MarkdownExec
|
64
|
+
# :reek:IrresponsibleModule
|
54
65
|
class Error < StandardError; end
|
55
66
|
|
67
|
+
## an imported markdown document
|
68
|
+
#
|
69
|
+
class MDoc
|
70
|
+
def initialize(table)
|
71
|
+
@table = table
|
72
|
+
end
|
73
|
+
|
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
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_block_by_name(name, default = {})
|
113
|
+
name.tap_inspect name: :name
|
114
|
+
@table.select { |block| block[:name] == name }.fetch(0, default).tap_inspect format: :yaml
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_required_blocks(name)
|
118
|
+
name_block = get_block_by_name(name)
|
119
|
+
raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
|
120
|
+
|
121
|
+
all = [name_block[:name]] + recursively_required(name_block[:reqs])
|
122
|
+
|
123
|
+
# in order of appearance in document
|
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
|
141
|
+
end
|
142
|
+
|
143
|
+
def blocks_for_menu(opts)
|
144
|
+
if opts[:hide_blocks_by_name]
|
145
|
+
@table.reject { |block| hide_menu_block_per_options opts, block }
|
146
|
+
else
|
147
|
+
@table
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def recursively_required(reqs)
|
152
|
+
all = []
|
153
|
+
rem = reqs
|
154
|
+
while rem.count.positive?
|
155
|
+
rem = rem.map do |req|
|
156
|
+
next if all.include? req
|
157
|
+
|
158
|
+
all += [req]
|
159
|
+
get_block_by_name(req).fetch(:reqs, [])
|
160
|
+
end
|
161
|
+
.compact
|
162
|
+
.flatten(1)
|
163
|
+
end
|
164
|
+
all.tap_inspect format: :yaml
|
165
|
+
end
|
166
|
+
end # class MDoc
|
167
|
+
|
168
|
+
# format option defaults and values
|
169
|
+
#
|
170
|
+
# :reek:TooManyInstanceVariables
|
171
|
+
class BlockLabel
|
172
|
+
def initialize(filename:, headings:, menu_blocks_with_docname:, menu_blocks_with_headings:, title:)
|
173
|
+
@filename = filename
|
174
|
+
@headings = headings
|
175
|
+
@menu_blocks_with_docname = menu_blocks_with_docname
|
176
|
+
@menu_blocks_with_headings = menu_blocks_with_headings
|
177
|
+
@title = title
|
178
|
+
end
|
179
|
+
|
180
|
+
def make
|
181
|
+
([@title] +
|
182
|
+
(if @menu_blocks_with_headings
|
183
|
+
[@headings.compact.join(' # ')]
|
184
|
+
else
|
185
|
+
[]
|
186
|
+
end) +
|
187
|
+
(
|
188
|
+
if @menu_blocks_with_docname
|
189
|
+
[@filename]
|
190
|
+
else
|
191
|
+
[]
|
192
|
+
end
|
193
|
+
)).join(' ')
|
194
|
+
end
|
195
|
+
end # class BlockLabel
|
196
|
+
|
197
|
+
FNR11 = '/'
|
198
|
+
FNR12 = ',~'
|
199
|
+
|
200
|
+
# format option defaults and values
|
201
|
+
#
|
202
|
+
class SavedAsset
|
203
|
+
def initialize(filename:, prefix:, time:, blockname:)
|
204
|
+
@filename = filename
|
205
|
+
@prefix = prefix
|
206
|
+
@time = time
|
207
|
+
@blockname = blockname
|
208
|
+
end
|
209
|
+
|
210
|
+
def script_name
|
211
|
+
fne = @filename.gsub(FNR11, FNR12)
|
212
|
+
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',', @blockname].join('_')}.sh".tap_inspect
|
213
|
+
end
|
214
|
+
|
215
|
+
def stdout_name
|
216
|
+
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
|
217
|
+
end
|
218
|
+
end # class SavedAsset
|
219
|
+
|
220
|
+
# format option defaults and values
|
221
|
+
#
|
222
|
+
class OptionValue
|
223
|
+
def initialize(value)
|
224
|
+
@value = value
|
225
|
+
end
|
226
|
+
|
227
|
+
# as default value in env_str()
|
228
|
+
#
|
229
|
+
def for_hash(default = nil)
|
230
|
+
return default if @value.nil?
|
231
|
+
|
232
|
+
case @value.class.to_s
|
233
|
+
when 'String', 'Integer'
|
234
|
+
@value
|
235
|
+
when 'FalseClass', 'TrueClass'
|
236
|
+
@value ? true : false
|
237
|
+
when @value.empty?
|
238
|
+
default
|
239
|
+
else
|
240
|
+
@value.to_s
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# for output as default value in list_default_yaml()
|
245
|
+
#
|
246
|
+
def for_yaml(default = nil)
|
247
|
+
return default if @value.nil?
|
248
|
+
|
249
|
+
case @value.class.to_s
|
250
|
+
when 'String'
|
251
|
+
"'#{@value}'"
|
252
|
+
when 'Integer'
|
253
|
+
@value
|
254
|
+
when 'FalseClass', 'TrueClass'
|
255
|
+
@value ? true : false
|
256
|
+
when @value.empty?
|
257
|
+
default
|
258
|
+
else
|
259
|
+
@value.to_s
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end # class OptionValue
|
263
|
+
|
264
|
+
# a generated list of saved files
|
265
|
+
#
|
266
|
+
class Sfiles
|
267
|
+
def initialize(folder, glob)
|
268
|
+
@folder = folder
|
269
|
+
@glob = glob
|
270
|
+
end
|
271
|
+
|
272
|
+
def list_all
|
273
|
+
Dir.glob(File.join(@folder, @glob)).tap_inspect
|
274
|
+
end
|
275
|
+
|
276
|
+
def most_recent(arr = nil)
|
277
|
+
arr = list_all if arr.nil?
|
278
|
+
return if arr.count < 1
|
279
|
+
|
280
|
+
arr.max.tap_inspect
|
281
|
+
end
|
282
|
+
|
283
|
+
def most_recent_list(list_count, arr = nil)
|
284
|
+
arr = list_all if arr.nil?
|
285
|
+
return if (ac = arr.count) < 1
|
286
|
+
|
287
|
+
arr.sort[-[ac, list_count].min..].reverse.tap_inspect
|
288
|
+
end
|
289
|
+
end # class Sfiles
|
290
|
+
|
56
291
|
##
|
57
292
|
#
|
293
|
+
# :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
|
294
|
+
# :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
|
295
|
+
# :reek:TooManyInstanceVariables ### temp
|
296
|
+
# :reek:TooManyMethods ### temp
|
58
297
|
class MarkParse
|
59
|
-
|
298
|
+
attr_reader :options
|
60
299
|
|
61
300
|
def initialize(options = {})
|
62
301
|
@options = options
|
63
302
|
@prompt = TTY::Prompt.new(interrupt: :exit)
|
303
|
+
@execute_aborted_at = nil
|
304
|
+
@execute_completed_at = nil
|
305
|
+
@execute_error = nil
|
306
|
+
@execute_error_message = nil
|
307
|
+
@execute_files = nil
|
308
|
+
@execute_options = nil
|
309
|
+
@execute_script_filespec = nil
|
310
|
+
@execute_started_at = nil
|
311
|
+
@option_parser = nil
|
64
312
|
end
|
65
313
|
|
66
314
|
##
|
@@ -68,20 +316,19 @@ module MarkdownExec
|
|
68
316
|
|
69
317
|
def base_options
|
70
318
|
menu_iter do |item|
|
71
|
-
item.tap_inspect name: :item, format: :yaml
|
319
|
+
# noisy item.tap_inspect name: :item, format: :yaml
|
72
320
|
next unless item[:opt_name].present?
|
73
321
|
|
74
322
|
item_default = item[:default]
|
75
|
-
item_default.tap_inspect name: :item_default
|
323
|
+
# noisy item_default.tap_inspect name: :item_default
|
76
324
|
value = if item_default.nil?
|
77
325
|
item_default
|
78
326
|
else
|
79
|
-
env_str(item[:env_var], default:
|
327
|
+
env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
|
80
328
|
end
|
81
329
|
[item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
|
82
330
|
end.compact.to_h.merge(
|
83
331
|
{
|
84
|
-
mdheadings: true, # use headings (levels 1,2,3) in block lable
|
85
332
|
menu_exit_at_top: true,
|
86
333
|
menu_with_exit: true
|
87
334
|
}
|
@@ -103,20 +350,13 @@ module MarkdownExec
|
|
103
350
|
}
|
104
351
|
end
|
105
352
|
|
106
|
-
|
107
|
-
|
108
|
-
def all_at_eof(files)
|
109
|
-
files.find { |f| !f.eof }.nil?
|
110
|
-
end
|
111
|
-
|
112
|
-
def approve_block(opts, blocks_in_file)
|
113
|
-
required_blocks = list_recursively_required_blocks(blocks_in_file, opts[:block_name])
|
353
|
+
def approve_block(opts, mdoc)
|
354
|
+
required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
|
114
355
|
display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
|
115
356
|
|
116
357
|
allow = true
|
117
358
|
if opts[:user_must_approve]
|
118
359
|
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
360
|
(sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
|
121
361
|
menu.default 1
|
122
362
|
# menu.enum '.'
|
@@ -131,10 +371,11 @@ module MarkdownExec
|
|
131
371
|
if sel == 3
|
132
372
|
text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
|
133
373
|
Clipboard.copy(text)
|
134
|
-
fout "Clipboard updated: #{required_blocks.count} blocks,
|
374
|
+
fout "Clipboard updated: #{required_blocks.count} blocks," /
|
375
|
+
" #{required_blocks.flatten.count} lines," /
|
376
|
+
" #{text.length} characters"
|
135
377
|
end
|
136
378
|
if sel == 4
|
137
|
-
# opts[:saved_script_filename] = saved_name_make(opts)
|
138
379
|
write_command_file(opts.merge(save_executed_script: true), required_blocks)
|
139
380
|
fout "File saved: #{@options[:saved_filespec]}"
|
140
381
|
end
|
@@ -143,7 +384,7 @@ module MarkdownExec
|
|
143
384
|
end
|
144
385
|
(opts[:ir_approve] = allow).tap_inspect name: :allow
|
145
386
|
|
146
|
-
selected = get_block_by_name
|
387
|
+
selected = mdoc.get_block_by_name opts[:block_name]
|
147
388
|
|
148
389
|
if opts[:ir_approve]
|
149
390
|
write_command_file opts, required_blocks
|
@@ -156,37 +397,33 @@ module MarkdownExec
|
|
156
397
|
selected[:name]
|
157
398
|
end
|
158
399
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
end
|
164
|
-
.flatten(1)
|
165
|
-
.tap_inspect
|
166
|
-
end
|
167
|
-
|
168
|
-
def command_execute(opts, cmd2)
|
400
|
+
# :reek:DuplicateMethodCall
|
401
|
+
# :reek:UncommunicativeVariableName { exclude: [ e ] }
|
402
|
+
# :reek:LongYieldList
|
403
|
+
def command_execute(opts, command)
|
169
404
|
@execute_files = Hash.new([])
|
170
405
|
@execute_options = opts
|
171
406
|
@execute_started_at = Time.now.utc
|
172
407
|
|
173
|
-
Open3.popen3(@options[:shell], '-c',
|
174
|
-
|
175
|
-
|
176
|
-
t1 = Thread.new do
|
408
|
+
Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
|
409
|
+
Thread.new do
|
177
410
|
until (line = stdout.gets).nil?
|
178
411
|
@execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
|
179
412
|
print line if opts[:output_stdout]
|
180
413
|
yield nil, line, nil, exec_thr if block_given?
|
181
414
|
end
|
415
|
+
rescue IOError
|
416
|
+
# thread killed, do nothing
|
182
417
|
end
|
183
418
|
|
184
|
-
|
419
|
+
Thread.new do
|
185
420
|
until (line = stderr.gets).nil?
|
186
421
|
@execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
|
187
422
|
print line if opts[:output_stdout]
|
188
423
|
yield nil, nil, line, exec_thr if block_given?
|
189
424
|
end
|
425
|
+
rescue IOError
|
426
|
+
# thread killed, do nothing
|
190
427
|
end
|
191
428
|
|
192
429
|
in_thr = Thread.new do
|
@@ -207,7 +444,14 @@ module MarkdownExec
|
|
207
444
|
@execute_aborted_at = Time.now.utc
|
208
445
|
@execute_error_message = e.message
|
209
446
|
@execute_error = e
|
210
|
-
@execute_files[EF_STDERR] += [
|
447
|
+
@execute_files[EF_STDERR] += [@execute_error_message]
|
448
|
+
fout "Error ENOENT: #{e.inspect}"
|
449
|
+
rescue SignalException => e
|
450
|
+
# SIGTERM triggered by user or system
|
451
|
+
@execute_aborted_at = Time.now.utc
|
452
|
+
@execute_error_message = 'SIGTERM'
|
453
|
+
@execute_error = e
|
454
|
+
@execute_files[EF_STDERR] += [@execute_error_message]
|
211
455
|
fout "Error ENOENT: #{e.inspect}"
|
212
456
|
end
|
213
457
|
|
@@ -220,12 +464,15 @@ module MarkdownExec
|
|
220
464
|
cnt / 2
|
221
465
|
end
|
222
466
|
|
467
|
+
# :reek:DuplicateMethodCall
|
223
468
|
def display_command(_opts, required_blocks)
|
224
|
-
|
469
|
+
frame = ' #=#=#'.yellow
|
470
|
+
fout frame
|
225
471
|
required_blocks.each { |cb| fout cb }
|
226
|
-
fout
|
472
|
+
fout frame
|
227
473
|
end
|
228
474
|
|
475
|
+
# :reek:DuplicateMethodCall
|
229
476
|
def exec_block(options, _block_name = '')
|
230
477
|
options = default_options.merge options
|
231
478
|
update_options options, over: false
|
@@ -244,8 +491,14 @@ module MarkdownExec
|
|
244
491
|
list_default_yaml: -> { fout_list list_default_yaml },
|
245
492
|
list_docs: -> { fout_list files },
|
246
493
|
list_default_env: -> { fout_list list_default_env },
|
247
|
-
list_recent_output:
|
248
|
-
|
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
|
+
},
|
249
502
|
pwd: -> { fout File.expand_path('..', __dir__) },
|
250
503
|
run_last_script: -> { run_last_script },
|
251
504
|
select_recent_output: -> { select_recent_output },
|
@@ -285,23 +538,30 @@ module MarkdownExec
|
|
285
538
|
puts data.to_yaml
|
286
539
|
end
|
287
540
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
541
|
+
# :reek:LongParameterList
|
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]
|
546
|
+
|
547
|
+
block_title.tap_inspect name: :block_title
|
548
|
+
call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
|
549
|
+
.map { |scanned| scanned[1..] }
|
550
|
+
&.first.tap_inspect name: :call
|
551
|
+
(titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
|
552
|
+
|
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
|
305
565
|
end
|
306
566
|
|
307
567
|
def approved_fout?(level)
|
@@ -313,34 +573,37 @@ module MarkdownExec
|
|
313
573
|
def lout(str, level: DISPLAY_LEVEL_BASE)
|
314
574
|
return unless approved_fout? level
|
315
575
|
|
316
|
-
# fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
|
317
576
|
fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
|
318
577
|
end
|
319
578
|
|
320
|
-
|
321
|
-
|
579
|
+
# :reek:DuplicateMethodCall
|
580
|
+
# :reek:LongYieldList
|
581
|
+
def iter_blocks_in_file(opts = {})
|
582
|
+
# opts = optsmerge call_options, options_block
|
322
583
|
|
323
584
|
unless opts[:filename]&.present?
|
324
585
|
fout 'No blocks found.'
|
325
|
-
|
586
|
+
return
|
326
587
|
end
|
327
588
|
|
328
589
|
unless File.exist? opts[:filename]
|
329
590
|
fout 'Document is missing.'
|
330
|
-
|
591
|
+
return
|
331
592
|
end
|
332
593
|
|
333
594
|
fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
|
334
595
|
fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
|
335
596
|
block_title = ''
|
336
|
-
|
337
|
-
current = nil
|
597
|
+
block_body = nil
|
338
598
|
headings = []
|
339
599
|
in_block = false
|
600
|
+
|
601
|
+
selected_messages = yield :filter
|
602
|
+
|
340
603
|
File.readlines(opts[:filename]).each do |line|
|
341
604
|
continue unless line
|
342
605
|
|
343
|
-
if opts[:
|
606
|
+
if opts[:menu_blocks_with_headings]
|
344
607
|
if (lm = line.match(Regexp.new(opts[:heading3_match])))
|
345
608
|
headings = [headings[0], headings[1], lm[:name]]
|
346
609
|
elsif (lm = line.match(Regexp.new(opts[:heading2_match])))
|
@@ -352,36 +615,60 @@ module MarkdownExec
|
|
352
615
|
|
353
616
|
if line.match(fenced_start_and_end_match)
|
354
617
|
if in_block
|
355
|
-
if
|
356
|
-
|
357
|
-
|
358
|
-
|
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
|
359
624
|
end
|
360
625
|
in_block = false
|
361
626
|
block_title = ''
|
362
627
|
else
|
363
|
-
#
|
628
|
+
# start block
|
364
629
|
#
|
365
630
|
lm = line.match(fenced_start_ex)
|
366
|
-
|
631
|
+
block_allow = false
|
367
632
|
if opts[:bash_only]
|
368
|
-
|
633
|
+
block_allow = true if lm && (lm[:shell] == 'bash')
|
369
634
|
else
|
370
|
-
|
371
|
-
|
635
|
+
block_allow = true
|
636
|
+
block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
|
372
637
|
end
|
373
638
|
|
374
639
|
in_block = true
|
375
|
-
if
|
376
|
-
|
640
|
+
if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
|
641
|
+
block_body = []
|
377
642
|
block_title = (lm && lm[:name])
|
378
643
|
end
|
379
644
|
end
|
380
|
-
elsif
|
381
|
-
|
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
|
651
|
+
end
|
652
|
+
end
|
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
|
382
669
|
end
|
383
670
|
end
|
384
|
-
blocks.tap_inspect
|
671
|
+
blocks.tap_inspect format: :yaml
|
385
672
|
end
|
386
673
|
|
387
674
|
def list_default_env
|
@@ -400,7 +687,7 @@ module MarkdownExec
|
|
400
687
|
next unless item[:opt_name].present? && item[:default].present?
|
401
688
|
|
402
689
|
[
|
403
|
-
"#{item[:opt_name]}: #{
|
690
|
+
"#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
|
404
691
|
item[:description].present? ? item[:description] : nil
|
405
692
|
].compact.join(' # ')
|
406
693
|
end.compact.sort
|
@@ -408,14 +695,16 @@ module MarkdownExec
|
|
408
695
|
|
409
696
|
def list_files_per_options(options)
|
410
697
|
list_files_specified(
|
411
|
-
options[:filename]&.present? ? options[:filename] : nil,
|
412
|
-
options[:path],
|
413
|
-
'README.md',
|
414
|
-
'.'
|
698
|
+
specified_filename: options[:filename]&.present? ? options[:filename] : nil,
|
699
|
+
specified_folder: options[:path],
|
700
|
+
default_filename: 'README.md',
|
701
|
+
default_folder: '.'
|
415
702
|
).tap_inspect
|
416
703
|
end
|
417
704
|
|
418
|
-
|
705
|
+
# :reek:LongParameterList
|
706
|
+
def list_files_specified(specified_filename: nil, specified_folder: nil,
|
707
|
+
default_filename: nil, default_folder: nil, filetree: nil)
|
419
708
|
fn = File.join(if specified_filename&.present?
|
420
709
|
if specified_folder&.present?
|
421
710
|
[specified_folder, specified_filename]
|
@@ -446,463 +735,104 @@ module MarkdownExec
|
|
446
735
|
|
447
736
|
def list_named_blocks_in_file(call_options = {}, &options_block)
|
448
737
|
opts = optsmerge call_options, options_block
|
449
|
-
|
738
|
+
blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
|
739
|
+
mdoc = MDoc.new(blocks_in_file)
|
740
|
+
|
450
741
|
list_blocks_in_file(opts).map do |block|
|
451
|
-
next if opts
|
742
|
+
next if mdoc.hide_menu_block_per_options(opts, block)
|
452
743
|
|
453
744
|
block
|
454
745
|
end.compact.tap_inspect
|
455
746
|
end
|
456
747
|
|
457
|
-
def
|
458
|
-
|
459
|
-
raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
|
460
|
-
|
461
|
-
all = [name_block[:name]] + recursively_required(table, name_block[:reqs])
|
462
|
-
|
463
|
-
# in order of appearance in document
|
464
|
-
table.select { |block| all.include? block[:name] }
|
465
|
-
.map { |block| block.fetch(:body, '') }
|
466
|
-
.flatten(1)
|
467
|
-
.tap_inspect
|
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)
|
468
750
|
end
|
469
751
|
|
470
|
-
def
|
471
|
-
|
472
|
-
return if arr.count < 1
|
473
|
-
|
474
|
-
arr.max.tap_inspect
|
475
|
-
end
|
476
|
-
|
477
|
-
def most_recent_list(arr)
|
478
|
-
return unless arr
|
479
|
-
return if (ac = arr.count) < 1
|
480
|
-
|
481
|
-
arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
|
482
|
-
end
|
483
|
-
|
484
|
-
def list_recent_output
|
485
|
-
most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
|
486
|
-
@options[:saved_stdout_glob]))).tap_inspect
|
487
|
-
end
|
488
|
-
|
489
|
-
def list_recent_scripts
|
490
|
-
most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
|
491
|
-
@options[:saved_script_glob]))).tap_inspect
|
492
|
-
end
|
493
|
-
|
494
|
-
def make_block_label(block, call_options = {})
|
495
|
-
opts = options.merge(call_options)
|
496
|
-
if opts[:mdheadings]
|
497
|
-
heads = block.fetch(:headings, []).compact.join(' # ')
|
498
|
-
"#{block[:title]} [#{heads}] (#{opts[:filename]})"
|
499
|
-
else
|
500
|
-
"#{block[:title]} (#{opts[:filename]})"
|
501
|
-
end
|
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)
|
502
754
|
end
|
503
755
|
|
504
756
|
def make_block_labels(call_options = {})
|
505
757
|
opts = options.merge(call_options)
|
506
758
|
list_blocks_in_file(opts).map do |block|
|
507
|
-
|
508
|
-
|
509
|
-
|
759
|
+
BlockLabel.new(filename: opts[:filename],
|
760
|
+
headings: block.fetch(:headings, []),
|
761
|
+
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
762
|
+
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
763
|
+
title: block[:title]).make
|
510
764
|
end.compact.tap_inspect
|
511
765
|
end
|
512
766
|
|
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
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
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
|
-
}
|
900
|
-
]
|
901
|
-
# commands first, options second
|
902
|
-
(set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
|
767
|
+
# :reek:DuplicateMethodCall
|
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
|
903
833
|
end
|
904
834
|
|
905
|
-
def menu_iter(data =
|
835
|
+
def menu_iter(data = menu_for_optparse, &block)
|
906
836
|
data.map(&block)
|
907
837
|
end
|
908
838
|
|
@@ -910,15 +840,6 @@ module MarkdownExec
|
|
910
840
|
@option_parser.help
|
911
841
|
end
|
912
842
|
|
913
|
-
def option_exclude_blocks(opts, blocks)
|
914
|
-
block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
|
915
|
-
if opts[:hide_blocks_by_name]
|
916
|
-
blocks.reject { |block| block[:name].match(block_name_excluded_match) }
|
917
|
-
else
|
918
|
-
blocks
|
919
|
-
end
|
920
|
-
end
|
921
|
-
|
922
843
|
## post-parse options configuration
|
923
844
|
#
|
924
845
|
def options_finalize(rest)
|
@@ -940,13 +861,14 @@ module MarkdownExec
|
|
940
861
|
@options[:block_name] = block_name if block_name.present?
|
941
862
|
end
|
942
863
|
|
864
|
+
# :reek:ControlParameter
|
943
865
|
def optsmerge(call_options = {}, options_block = nil)
|
944
866
|
class_call_options = @options.merge(call_options || {})
|
945
867
|
if options_block
|
946
868
|
options_block.call class_call_options
|
947
869
|
else
|
948
870
|
class_call_options
|
949
|
-
end
|
871
|
+
end
|
950
872
|
end
|
951
873
|
|
952
874
|
def output_execution_result
|
@@ -993,32 +915,15 @@ module MarkdownExec
|
|
993
915
|
sel == exit_option ? nil : sel
|
994
916
|
end
|
995
917
|
|
918
|
+
# :reek:UtilityFunction ### temp
|
996
919
|
def read_configuration_file!(options, configuration_path)
|
997
920
|
return unless File.exist?(configuration_path)
|
998
921
|
|
999
|
-
# rubocop:disable Security/YAMLLoad
|
1000
922
|
options.merge!((YAML.load(File.open(configuration_path)) || {})
|
1001
923
|
.transform_keys(&:to_sym))
|
1002
|
-
# rubocop:enable Security/YAMLLoad
|
1003
|
-
end
|
1004
|
-
|
1005
|
-
def recursively_required(table, reqs)
|
1006
|
-
all = []
|
1007
|
-
rem = reqs
|
1008
|
-
while rem.count.positive?
|
1009
|
-
rem = rem.map do |req|
|
1010
|
-
next if all.include? req
|
1011
|
-
|
1012
|
-
all += [req]
|
1013
|
-
get_block_by_name(table, req).fetch(:reqs, [])
|
1014
|
-
end
|
1015
|
-
.compact
|
1016
|
-
.flatten(1)
|
1017
|
-
.tap_inspect(name: 'rem')
|
1018
|
-
end
|
1019
|
-
all.tap_inspect
|
1020
924
|
end
|
1021
925
|
|
926
|
+
# :reek:NestedIterators
|
1022
927
|
def run
|
1023
928
|
## default configuration
|
1024
929
|
#
|
@@ -1062,15 +967,6 @@ module MarkdownExec
|
|
1062
967
|
exec_block options, options[:block_name]
|
1063
968
|
end
|
1064
969
|
|
1065
|
-
FNR11 = '/'
|
1066
|
-
FNR12 = ',~'
|
1067
|
-
|
1068
|
-
def saved_name_make(opts)
|
1069
|
-
fne = opts[:filename].gsub(FNR11, FNR12)
|
1070
|
-
"#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
|
1071
|
-
',', opts[:block_name]].join('_')}.sh"
|
1072
|
-
end
|
1073
|
-
|
1074
970
|
def saved_name_split(name)
|
1075
971
|
mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
|
1076
972
|
return unless mf
|
@@ -1080,127 +976,140 @@ module MarkdownExec
|
|
1080
976
|
end
|
1081
977
|
|
1082
978
|
def run_last_script
|
1083
|
-
filename =
|
1084
|
-
|
979
|
+
filename = Sfiles.new(@options[:saved_script_folder],
|
980
|
+
@options[:saved_script_glob]).most_recent
|
1085
981
|
return unless filename
|
1086
982
|
|
1087
|
-
filename.tap_inspect name: filename
|
1088
983
|
saved_name_split filename
|
1089
984
|
@options[:save_executed_script] = false
|
1090
985
|
select_and_approve_block
|
1091
986
|
end
|
1092
987
|
|
1093
988
|
def save_execution_output
|
989
|
+
@options.tap_inspect name: :options
|
1094
990
|
return unless @options[:save_execution_output]
|
1095
991
|
|
1096
|
-
fne = File.basename(@options[:filename], '.*')
|
1097
|
-
|
1098
992
|
@options[:logged_stdout_filename] =
|
1099
|
-
|
1100
|
-
|
993
|
+
SavedAsset.new(blockname: @options[:block_name],
|
994
|
+
filename: File.basename(@options[:filename], '.*'),
|
995
|
+
prefix: @options[:logged_stdout_filename_prefix],
|
996
|
+
time: Time.now.utc).stdout_name
|
997
|
+
|
1101
998
|
@options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
|
1102
999
|
@logged_stdout_filespec = @options[:logged_stdout_filespec]
|
1103
|
-
dirname = File.dirname(@options[:logged_stdout_filespec])
|
1000
|
+
(dirname = File.dirname(@options[:logged_stdout_filespec])).tap_inspect name: :dirname
|
1104
1001
|
Dir.mkdir dirname unless File.exist?(dirname)
|
1105
1002
|
|
1106
|
-
# File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, ''))
|
1107
1003
|
ol = ["-STDOUT-\n"]
|
1108
1004
|
ol += @execute_files&.fetch(EF_STDOUT, [])
|
1109
|
-
ol += ["-STDERR-\n"]
|
1005
|
+
ol += ["\n-STDERR-\n"]
|
1110
1006
|
ol += @execute_files&.fetch(EF_STDERR, [])
|
1111
|
-
ol += ["-STDIN-\n"]
|
1007
|
+
ol += ["\n-STDIN-\n"]
|
1112
1008
|
ol += @execute_files&.fetch(EF_STDIN, [])
|
1009
|
+
ol += ["\n"]
|
1113
1010
|
File.write(@options[:logged_stdout_filespec], ol.join)
|
1114
1011
|
end
|
1115
1012
|
|
1116
1013
|
def select_and_approve_block(call_options = {}, &options_block)
|
1117
1014
|
opts = optsmerge call_options, options_block
|
1118
|
-
blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
|
1119
|
-
|
1120
|
-
|
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
|
1121
1018
|
|
1019
|
+
repeat_menu = true && !opts[:block_name].present?
|
1122
1020
|
loop do
|
1123
1021
|
unless opts[:block_name].present?
|
1124
1022
|
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] }
|
1127
1023
|
|
1128
|
-
|
1024
|
+
blocks_menu.each do |block|
|
1025
|
+
next if block.fetch(:disabled, false)
|
1129
1026
|
|
1130
|
-
|
1131
|
-
|
1027
|
+
block.merge! label:
|
1028
|
+
BlockLabel.new(filename: opts[:filename],
|
1029
|
+
headings: block.fetch(:headings, []),
|
1030
|
+
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
1031
|
+
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
1032
|
+
title: block[:title]).make
|
1033
|
+
end
|
1034
|
+
return nil if blocks_menu.count.zero?
|
1132
1035
|
|
1133
|
-
|
1134
|
-
|
1135
|
-
# break
|
1136
|
-
# end
|
1036
|
+
sel = prompt_with_quit pt, blocks_menu, per_page: opts[:select_page_height]
|
1037
|
+
return nil if sel.nil?
|
1137
1038
|
|
1138
1039
|
label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
|
1139
1040
|
opts[:block_name] = @options[:block_name] = label_block[:name]
|
1140
|
-
|
1141
1041
|
end
|
1142
|
-
|
1143
|
-
|
1144
|
-
# end
|
1145
|
-
|
1146
|
-
break unless loop1
|
1042
|
+
approve_block opts, mdoc
|
1043
|
+
break unless repeat_menu
|
1147
1044
|
|
1148
1045
|
opts[:block_name] = ''
|
1149
1046
|
end
|
1150
1047
|
end
|
1151
1048
|
|
1152
|
-
def select_md_file(
|
1049
|
+
def select_md_file(files = list_markdown_files_in_path)
|
1153
1050
|
opts = options
|
1154
|
-
|
1155
|
-
if files.count == 1
|
1051
|
+
if (count = files.count) == 1
|
1156
1052
|
files[0]
|
1157
|
-
elsif
|
1053
|
+
elsif count >= 2
|
1158
1054
|
prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
|
1159
1055
|
end
|
1160
1056
|
end
|
1161
1057
|
|
1162
1058
|
def select_recent_output
|
1163
|
-
filename = prompt_with_quit
|
1164
|
-
|
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
|
+
)
|
1165
1068
|
return unless filename.present?
|
1166
1069
|
|
1167
1070
|
`open #{filename} #{options[:output_viewer_options]}`
|
1168
1071
|
end
|
1169
1072
|
|
1170
1073
|
def select_recent_script
|
1171
|
-
filename = prompt_with_quit
|
1172
|
-
|
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
|
+
)
|
1173
1083
|
return if filename.nil?
|
1174
1084
|
|
1175
|
-
saved_name_split
|
1176
|
-
select_and_approve_block(
|
1177
|
-
bash: true,
|
1178
|
-
save_executed_script: false,
|
1179
|
-
struct: true
|
1180
|
-
)
|
1181
|
-
end
|
1085
|
+
saved_name_split(filename)
|
1182
1086
|
|
1183
|
-
|
1184
|
-
|
1087
|
+
select_and_approve_block({
|
1088
|
+
bash: true,
|
1089
|
+
save_executed_script: false,
|
1090
|
+
struct: true
|
1091
|
+
})
|
1185
1092
|
end
|
1186
1093
|
|
1187
1094
|
def summarize_block(headings, title)
|
1188
1095
|
{ headings: headings, name: title, title: title }
|
1189
1096
|
end
|
1190
1097
|
|
1191
|
-
def menu_export(data =
|
1098
|
+
def menu_export(data = menu_for_optparse)
|
1192
1099
|
data.map do |item|
|
1193
1100
|
item.delete(:proc1)
|
1194
1101
|
item
|
1195
1102
|
end.to_yaml
|
1196
1103
|
end
|
1197
1104
|
|
1198
|
-
def tab_completions(data =
|
1105
|
+
def tab_completions(data = menu_for_optparse)
|
1199
1106
|
data.map do |item|
|
1200
1107
|
"--#{item[:long_name]}" if item[:long_name]
|
1201
1108
|
end.compact
|
1202
1109
|
end
|
1203
1110
|
|
1111
|
+
# :reek:BooleanParameter
|
1112
|
+
# :reek:ControlParameter
|
1204
1113
|
def update_options(opts = {}, over: true)
|
1205
1114
|
if over
|
1206
1115
|
@options = @options.merge opts
|
@@ -1210,40 +1119,17 @@ module MarkdownExec
|
|
1210
1119
|
@options.tap_inspect format: :yaml
|
1211
1120
|
end
|
1212
1121
|
|
1213
|
-
def
|
1214
|
-
return
|
1122
|
+
def write_command_file(call_options, required_blocks)
|
1123
|
+
return unless call_options[:save_executed_script]
|
1215
1124
|
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
end
|
1224
|
-
end
|
1225
|
-
|
1226
|
-
def value_for_yaml(value)
|
1227
|
-
return default if value.nil?
|
1228
|
-
|
1229
|
-
case value.class.to_s
|
1230
|
-
when 'String'
|
1231
|
-
"'#{value}'"
|
1232
|
-
when 'Integer'
|
1233
|
-
value
|
1234
|
-
when 'FalseClass', 'TrueClass'
|
1235
|
-
value ? true : false
|
1236
|
-
when value.empty?
|
1237
|
-
default
|
1238
|
-
else
|
1239
|
-
value.to_s
|
1240
|
-
end
|
1241
|
-
end
|
1125
|
+
time_now = Time.now.utc
|
1126
|
+
opts = optsmerge call_options
|
1127
|
+
opts[:saved_script_filename] =
|
1128
|
+
SavedAsset.new(blockname: opts[:block_name],
|
1129
|
+
filename: opts[:filename],
|
1130
|
+
prefix: opts[:saved_script_filename_prefix],
|
1131
|
+
time: time_now).script_name
|
1242
1132
|
|
1243
|
-
def write_command_file(opts, required_blocks)
|
1244
|
-
return unless opts[:save_executed_script]
|
1245
|
-
|
1246
|
-
opts[:saved_script_filename] = saved_name_make(opts)
|
1247
1133
|
@execute_script_filespec =
|
1248
1134
|
@options[:saved_filespec] =
|
1249
1135
|
File.join opts[:saved_script_folder], opts[:saved_script_filename]
|
@@ -1259,15 +1145,11 @@ module MarkdownExec
|
|
1259
1145
|
File.write(@options[:saved_filespec], shebang +
|
1260
1146
|
"# file_name: #{opts[:filename]}\n" \
|
1261
1147
|
"# block_name: #{opts[:block_name]}\n" \
|
1262
|
-
"# time: #{
|
1148
|
+
"# time: #{time_now}\n" \
|
1263
1149
|
"#{required_blocks.flatten.join("\n")}\n")
|
1264
|
-
|
1265
|
-
@options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
|
1266
1150
|
return if @options[:saved_script_chmod].zero?
|
1267
1151
|
|
1268
|
-
@options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
|
1269
1152
|
File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
|
1270
|
-
@options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
|
1271
1153
|
end
|
1272
|
-
end
|
1273
|
-
end
|
1154
|
+
end # class MarkParse
|
1155
|
+
end # module MarkdownExec
|