markdown_exec 1.3.2 → 1.3.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -11
- data/CHANGELOG.md +40 -3
- data/Gemfile +6 -3
- data/Gemfile.lock +112 -34
- data/README.md +5 -84
- data/Rakefile +105 -28
- data/bin/tab_completion.sh +2 -2
- data/lib/cached_nested_file_reader.rb +115 -0
- data/lib/colorize.rb +4 -0
- data/lib/env.rb +1 -0
- data/lib/env_opts.rb +54 -56
- data/lib/environment_opt_parse.rb +24 -19
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +589 -260
- data/lib/menu.yml +70 -4
- data/lib/object_present.rb +44 -0
- data/lib/rspec_helpers.rb +10 -0
- data/lib/tap.rb +51 -7
- metadata +4 -4
- data/lib/globfiles.rb +0 -40
- data/lib/yaml_env_opts.rb +0 -60
data/lib/markdown_exec.rb
CHANGED
@@ -11,11 +11,10 @@ require 'shellwords'
|
|
11
11
|
require 'tty-prompt'
|
12
12
|
require 'yaml'
|
13
13
|
|
14
|
+
require_relative 'cached_nested_file_reader'
|
14
15
|
require_relative 'cli'
|
15
16
|
require_relative 'colorize'
|
16
17
|
require_relative 'env'
|
17
|
-
# require_relative 'environment_opt_parse'
|
18
|
-
require_relative 'object_present'
|
19
18
|
require_relative 'shared'
|
20
19
|
require_relative 'tap'
|
21
20
|
require_relative 'markdown_exec/version'
|
@@ -30,13 +29,24 @@ $stdout.sync = true
|
|
30
29
|
|
31
30
|
BLOCK_SIZE = 1024
|
32
31
|
|
32
|
+
# custom error: file specified is missing
|
33
|
+
#
|
33
34
|
class FileMissingError < StandardError; end
|
34
35
|
|
35
36
|
# hash with keys sorted by name
|
37
|
+
# add Hash.sym_keys
|
36
38
|
#
|
37
39
|
class Hash
|
38
|
-
|
39
|
-
|
40
|
+
unless defined?(sort_by_key)
|
41
|
+
def sort_by_key
|
42
|
+
keys.sort.to_h { |key| [key, self[key]] }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
unless defined?(sym_keys)
|
47
|
+
def sym_keys
|
48
|
+
transform_keys(&:to_sym)
|
49
|
+
end
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
@@ -71,69 +81,226 @@ module FOUT
|
|
71
81
|
end
|
72
82
|
end
|
73
83
|
|
84
|
+
def dp(str)
|
85
|
+
lout " => #{str}", level: DISPLAY_LEVEL_DEBUG
|
86
|
+
end
|
87
|
+
|
74
88
|
public
|
75
89
|
|
90
|
+
# :reek:UtilityFunction
|
91
|
+
def list_recent_output(saved_stdout_folder, saved_stdout_glob, list_count)
|
92
|
+
Sfiles.new(saved_stdout_folder,
|
93
|
+
saved_stdout_glob).most_recent_list(list_count)
|
94
|
+
end
|
95
|
+
|
96
|
+
# :reek:UtilityFunction
|
97
|
+
def list_recent_scripts(saved_script_folder, saved_script_glob, list_count)
|
98
|
+
Sfiles.new(saved_script_folder,
|
99
|
+
saved_script_glob).most_recent_list(list_count)
|
100
|
+
end
|
101
|
+
|
102
|
+
# convert regex match groups to a hash with symbol keys
|
103
|
+
#
|
104
|
+
# :reek:UtilityFunction
|
105
|
+
def option_match_groups(str, option)
|
106
|
+
str.match(Regexp.new(option))&.named_captures&.sym_keys
|
107
|
+
end
|
108
|
+
|
76
109
|
# execute markdown documents
|
77
110
|
#
|
78
111
|
module MarkdownExec
|
79
112
|
# :reek:IrresponsibleModule
|
80
113
|
class Error < StandardError; end
|
81
114
|
|
82
|
-
#
|
115
|
+
# fenced code block
|
83
116
|
#
|
84
|
-
class
|
85
|
-
def initialize
|
86
|
-
@
|
117
|
+
class FCB
|
118
|
+
def initialize(options = {})
|
119
|
+
@attrs = {
|
120
|
+
body: nil,
|
121
|
+
call: nil,
|
122
|
+
headings: [],
|
123
|
+
name: nil,
|
124
|
+
reqs: [],
|
125
|
+
shell: '',
|
126
|
+
title: '',
|
127
|
+
random: Random.new.rand,
|
128
|
+
text: nil # displayable in menu
|
129
|
+
}.merge options
|
87
130
|
end
|
88
131
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
132
|
+
def to_h
|
133
|
+
@attrs
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_yaml
|
137
|
+
@attrs.to_yaml
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# :reek:ManualDispatch
|
143
|
+
def method_missing(method, *args, &block)
|
144
|
+
method_name = method.to_s
|
145
|
+
|
146
|
+
if @attrs.respond_to?(method_name)
|
147
|
+
@attrs.send(method_name, *args, &block)
|
148
|
+
elsif method_name[-1] == '='
|
149
|
+
@attrs[method_name.chop.to_sym] = args[0]
|
94
150
|
else
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
151
|
+
@attrs[method_name.to_sym]
|
152
|
+
end
|
153
|
+
rescue StandardError => err
|
154
|
+
warn(error = "ERROR ** FCB.method_missing(method: #{method_name}," \
|
155
|
+
" *args: #{args.inspect}, &block)")
|
156
|
+
warn err.inspect
|
157
|
+
warn(caller[0..4])
|
158
|
+
raise StandardError, error
|
159
|
+
end
|
160
|
+
|
161
|
+
# option names are available as methods
|
162
|
+
#
|
163
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
164
|
+
true # recognize all hash methods, rest are treated as hash keys
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# select fcb per options
|
169
|
+
#
|
170
|
+
# :reek:UtilityFunction
|
171
|
+
class Filter
|
172
|
+
# def self.fcb_title_parse(opts, fcb_title)
|
173
|
+
# fcb_title.match(Regexp.new(opts[:fenced_start_ex_match])).named_captures.sym_keys
|
174
|
+
# end
|
175
|
+
|
176
|
+
def self.fcb_select?(options, fcb)
|
177
|
+
# options.tap_yaml 'options'
|
178
|
+
# fcb.tap_inspect 'fcb'
|
179
|
+
name = fcb.fetch(:name, '').tap_inspect 'name'
|
180
|
+
shell = fcb.fetch(:shell, '').tap_inspect 'shell'
|
181
|
+
|
182
|
+
## include hidden blocks for later use
|
183
|
+
#
|
184
|
+
name_default = true
|
185
|
+
name_exclude = nil
|
186
|
+
name_select = nil
|
187
|
+
shell_default = true
|
188
|
+
shell_exclude = nil
|
189
|
+
shell_select = nil
|
190
|
+
hidden_name = nil
|
191
|
+
|
192
|
+
if name.present? && options[:block_name]
|
193
|
+
if name =~ /#{options[:block_name]}/
|
194
|
+
'=~ block_name'.tap_puts
|
195
|
+
name_select = true
|
196
|
+
name_exclude = false
|
197
|
+
else
|
198
|
+
'!~ block_name'.tap_puts
|
199
|
+
name_exclude = true
|
200
|
+
name_select = false
|
99
201
|
end
|
100
|
-
@cache[filename] = lines
|
101
202
|
end
|
203
|
+
|
204
|
+
if name.present? && name_select.nil? && options[:select_by_name_regex].present?
|
205
|
+
'+select_by_name_regex'.tap_puts
|
206
|
+
name_select = (!!(name =~ /#{options[:select_by_name_regex]}/)).tap_inspect 'name_select'
|
207
|
+
end
|
208
|
+
|
209
|
+
if shell.present? && options[:select_by_shell_regex].present?
|
210
|
+
'+select_by_shell_regex'.tap_puts
|
211
|
+
shell_select = (!!(shell =~ /#{options[:select_by_shell_regex]}/)).tap_inspect 'shell_select'
|
212
|
+
end
|
213
|
+
|
214
|
+
if name.present? && name_exclude.nil? && options[:exclude_by_name_regex].present?
|
215
|
+
'-exclude_by_name_regex'.tap_puts
|
216
|
+
name_exclude = (!!(name =~ /#{options[:exclude_by_name_regex]}/)).tap_inspect 'name_exclude'
|
217
|
+
end
|
218
|
+
|
219
|
+
if shell.present? && options[:exclude_by_shell_regex].present?
|
220
|
+
'-exclude_by_shell_regex'.tap_puts
|
221
|
+
shell_exclude = (!!(shell =~ /#{options[:exclude_by_shell_regex]}/)).tap_inspect 'shell_exclude'
|
222
|
+
end
|
223
|
+
|
224
|
+
if name.present? && options[:hide_blocks_by_name] &&
|
225
|
+
options[:block_name_hidden_match].present?
|
226
|
+
'+block_name_hidden_match'.tap_puts
|
227
|
+
hidden_name = (!!(name =~ /#{options[:block_name_hidden_match]}/)).tap_inspect 'hidden_name'
|
228
|
+
end
|
229
|
+
|
230
|
+
if shell.present? && options[:hide_blocks_by_shell] &&
|
231
|
+
options[:block_shell_hidden_match].present?
|
232
|
+
'-hide_blocks_by_shell'.tap_puts
|
233
|
+
(!!(shell =~ /#{options[:block_shell_hidden_match]}/)).tap_inspect 'hidden_shell'
|
234
|
+
end
|
235
|
+
|
236
|
+
if options[:bash_only]
|
237
|
+
'-bash_only'.tap_puts
|
238
|
+
shell_default = (shell == 'bash').tap_inspect 'shell_default'
|
239
|
+
end
|
240
|
+
|
241
|
+
## name matching does not filter hidden blocks
|
242
|
+
#
|
243
|
+
case
|
244
|
+
when options[:no_chrome] && fcb.fetch(:chrome, false)
|
245
|
+
'-no_chrome'.tap_puts
|
246
|
+
false
|
247
|
+
when options[:exclude_expect_blocks] && shell == 'expect'
|
248
|
+
'-exclude_expect_blocks'.tap_puts
|
249
|
+
false
|
250
|
+
when hidden_name == true
|
251
|
+
true
|
252
|
+
when name_exclude == true, shell_exclude == true,
|
253
|
+
name_select == false, shell_select == false
|
254
|
+
false
|
255
|
+
when name_select == true, shell_select == true
|
256
|
+
true
|
257
|
+
when name_default == false, shell_default == false
|
258
|
+
false
|
259
|
+
else
|
260
|
+
true
|
261
|
+
end.tap_inspect
|
262
|
+
# binding.pry
|
263
|
+
rescue StandardError => err
|
264
|
+
warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
|
265
|
+
raise err
|
102
266
|
end
|
103
|
-
end # class
|
267
|
+
end # class Filter
|
104
268
|
|
105
269
|
## an imported markdown document
|
106
270
|
#
|
107
271
|
class MDoc
|
272
|
+
attr_reader :table
|
273
|
+
|
274
|
+
# convert block name to fcb_parse
|
275
|
+
#
|
108
276
|
def initialize(table)
|
109
277
|
@table = table
|
110
278
|
end
|
111
279
|
|
112
280
|
def collect_recursively_required_code(name)
|
113
281
|
get_required_blocks(name)
|
114
|
-
.map do |
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
282
|
+
.map do |fcb|
|
283
|
+
body = fcb[:body].join("\n")
|
284
|
+
|
285
|
+
if fcb[:cann]
|
286
|
+
xcall = fcb[:cann][1..-2]
|
287
|
+
mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
|
288
|
+
mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
|
289
|
+
|
122
290
|
yqcmd = if mstdin[:type]
|
123
291
|
"echo \"$#{mstdin[:name]}\" | yq '#{body}'"
|
124
292
|
else
|
125
293
|
"yq e '#{body}' '#{mstdin[:name]}'"
|
126
|
-
end
|
294
|
+
end
|
127
295
|
if mstdout[:type]
|
128
296
|
"export #{mstdout[:name]}=$(#{yqcmd})"
|
129
297
|
else
|
130
298
|
"#{yqcmd} > '#{mstdout[:name]}'"
|
131
299
|
end
|
132
|
-
elsif
|
133
|
-
stdout =
|
134
|
-
body =
|
300
|
+
elsif fcb[:stdout]
|
301
|
+
stdout = fcb[:stdout]
|
302
|
+
body = fcb[:body].join("\n")
|
135
303
|
if stdout[:type]
|
136
|
-
# "export #{stdout[:name]}=#{Shellwords.escape body}"
|
137
304
|
%(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
|
138
305
|
else
|
139
306
|
"cat > '#{stdout[:name]}' <<\"EOF\"\n" \
|
@@ -141,65 +308,84 @@ module MarkdownExec
|
|
141
308
|
"EOF\n"
|
142
309
|
end
|
143
310
|
else
|
144
|
-
|
311
|
+
fcb[:body]
|
145
312
|
end
|
146
313
|
end.flatten(1)
|
147
|
-
.tap_yaml
|
148
314
|
end
|
149
315
|
|
150
316
|
def get_block_by_name(name, default = {})
|
151
|
-
|
152
|
-
@table.select { |block| block[:name] == name }.fetch(0, default).tap_yaml
|
317
|
+
@table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
|
153
318
|
end
|
154
319
|
|
155
320
|
def get_required_blocks(name)
|
156
321
|
name_block = get_block_by_name(name)
|
157
322
|
raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
|
158
323
|
|
159
|
-
all = [name_block
|
324
|
+
all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
|
160
325
|
|
161
326
|
# in order of appearance in document
|
162
|
-
sel = @table.select { |block| all.include? block[:name] }
|
163
|
-
|
164
327
|
# insert function blocks
|
165
|
-
|
166
|
-
|
167
|
-
if (call =
|
168
|
-
[get_block_by_name("[#{call.match(
|
328
|
+
@table.select { |fcb| all.include? fcb.fetch(:name, '') }
|
329
|
+
.map do |fcb|
|
330
|
+
if (call = fcb[:call])
|
331
|
+
[get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
|
332
|
+
.merge({ cann: call })]
|
169
333
|
else
|
170
334
|
[]
|
171
|
-
end + [
|
172
|
-
end.flatten(1)
|
335
|
+
end + [fcb]
|
336
|
+
end.flatten(1)
|
173
337
|
end
|
174
338
|
|
175
339
|
# :reek:UtilityFunction
|
176
340
|
def hide_menu_block_per_options(opts, block)
|
177
341
|
(opts[:hide_blocks_by_name] &&
|
178
|
-
block[:name]
|
342
|
+
block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
|
343
|
+
(block[:name]&.present? || block[:label]&.present?)
|
344
|
+
).tap_inspect
|
179
345
|
end
|
180
346
|
|
181
|
-
def blocks_for_menu(opts)
|
347
|
+
# def blocks_for_menu(opts)
|
348
|
+
# if opts[:hide_blocks_by_name]
|
349
|
+
# @table.reject { |block| hide_menu_block_per_options opts, block }
|
350
|
+
# else
|
351
|
+
# @table
|
352
|
+
# end
|
353
|
+
# end
|
354
|
+
|
355
|
+
def fcbs_per_options(opts = {})
|
356
|
+
options = opts.merge(block_name_hidden_match: nil)
|
357
|
+
selrows = @table.select do |fcb_title_groups|
|
358
|
+
Filter.fcb_select? options, fcb_title_groups
|
359
|
+
end
|
360
|
+
# binding.pry
|
361
|
+
### hide rows correctly
|
362
|
+
|
182
363
|
if opts[:hide_blocks_by_name]
|
183
|
-
|
364
|
+
selrows.reject { |block| hide_menu_block_per_options opts, block }
|
184
365
|
else
|
185
|
-
|
366
|
+
selrows
|
367
|
+
end.map do |block|
|
368
|
+
# block[:name] = block[:text] if block[:name].nil?
|
369
|
+
block
|
186
370
|
end
|
187
371
|
end
|
188
372
|
|
189
373
|
def recursively_required(reqs)
|
190
|
-
|
374
|
+
return [] unless reqs
|
375
|
+
|
191
376
|
rem = reqs
|
377
|
+
memo = []
|
192
378
|
while rem.count.positive?
|
193
379
|
rem = rem.map do |req|
|
194
|
-
next if
|
380
|
+
next if memo.include? req
|
195
381
|
|
196
|
-
|
382
|
+
memo += [req]
|
197
383
|
get_block_by_name(req).fetch(:reqs, [])
|
198
384
|
end
|
199
385
|
.compact
|
200
386
|
.flatten(1)
|
201
387
|
end
|
202
|
-
|
388
|
+
memo
|
203
389
|
end
|
204
390
|
end # class MDoc
|
205
391
|
|
@@ -207,16 +393,30 @@ module MarkdownExec
|
|
207
393
|
#
|
208
394
|
# :reek:TooManyInstanceVariables
|
209
395
|
class BlockLabel
|
210
|
-
def initialize(filename:, headings:, menu_blocks_with_docname:,
|
396
|
+
def initialize(filename:, headings:, menu_blocks_with_docname:,
|
397
|
+
menu_blocks_with_headings:, title:, body:, text:)
|
211
398
|
@filename = filename
|
212
399
|
@headings = headings
|
213
400
|
@menu_blocks_with_docname = menu_blocks_with_docname
|
214
401
|
@menu_blocks_with_headings = menu_blocks_with_headings
|
402
|
+
# @title = title.present? ? title : body
|
215
403
|
@title = title
|
404
|
+
@body = body
|
405
|
+
@text = text
|
406
|
+
rescue StandardError => err
|
407
|
+
warn(error = "ERROR ** BlockLabel.initialize(); #{err.inspect}")
|
408
|
+
binding.pry if $tap_enable
|
409
|
+
raise ArgumentError, error
|
216
410
|
end
|
217
411
|
|
412
|
+
# join title, headings, filename
|
413
|
+
#
|
218
414
|
def make
|
219
|
-
|
415
|
+
label = @title
|
416
|
+
label = @body unless label.present?
|
417
|
+
label = @text unless label.present?
|
418
|
+
label.tap_inspect
|
419
|
+
([label] +
|
220
420
|
(if @menu_blocks_with_headings
|
221
421
|
[@headings.compact.join(' # ')]
|
222
422
|
else
|
@@ -229,6 +429,10 @@ module MarkdownExec
|
|
229
429
|
[]
|
230
430
|
end
|
231
431
|
)).join(' ')
|
432
|
+
rescue StandardError => err
|
433
|
+
warn(error = "ERROR ** BlockLabel.make(); #{err.inspect}")
|
434
|
+
binding.pry if $tap_enable
|
435
|
+
raise ArgumentError, error
|
232
436
|
end
|
233
437
|
end # class BlockLabel
|
234
438
|
|
@@ -247,11 +451,13 @@ module MarkdownExec
|
|
247
451
|
|
248
452
|
def script_name
|
249
453
|
fne = @filename.gsub(FNR11, FNR12)
|
250
|
-
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',',
|
454
|
+
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',',
|
455
|
+
@blockname].join('_')}.sh"
|
251
456
|
end
|
252
457
|
|
253
458
|
def stdout_name
|
254
|
-
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename,
|
459
|
+
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename,
|
460
|
+
@blockname].join('_')}.out.txt"
|
255
461
|
end
|
256
462
|
end # class SavedAsset
|
257
463
|
|
@@ -308,27 +514,29 @@ module MarkdownExec
|
|
308
514
|
end
|
309
515
|
|
310
516
|
def list_all
|
311
|
-
Dir.glob(File.join(@folder, @glob))
|
517
|
+
Dir.glob(File.join(@folder, @glob))
|
312
518
|
end
|
313
519
|
|
314
520
|
def most_recent(arr = nil)
|
315
521
|
arr = list_all if arr.nil?
|
316
522
|
return if arr.count < 1
|
317
523
|
|
318
|
-
arr.max
|
524
|
+
arr.max
|
319
525
|
end
|
320
526
|
|
321
527
|
def most_recent_list(list_count, arr = nil)
|
322
528
|
arr = list_all if arr.nil?
|
323
529
|
return if (ac = arr.count) < 1
|
324
530
|
|
325
|
-
arr.sort[-[ac, list_count].min..].reverse
|
531
|
+
arr.sort[-[ac, list_count].min..].reverse
|
326
532
|
end
|
327
533
|
end # class Sfiles
|
328
534
|
|
329
535
|
##
|
330
536
|
#
|
537
|
+
# rubocop:disable Layout/LineLength
|
331
538
|
# :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
|
539
|
+
# rubocop:enable Layout/LineLength
|
332
540
|
# :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
|
333
541
|
# :reek:TooManyInstanceVariables ### temp
|
334
542
|
# :reek:TooManyMethods ### temp
|
@@ -339,8 +547,8 @@ module MarkdownExec
|
|
339
547
|
|
340
548
|
def initialize(options = {})
|
341
549
|
@options = options
|
550
|
+
# hide disabled symbol
|
342
551
|
@prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: ' ' })
|
343
|
-
# @prompt = TTY::Prompt.new(interrupt: :exit, symbols: { cross: options[:menu_divider_symbol] })
|
344
552
|
@execute_aborted_at = nil
|
345
553
|
@execute_completed_at = nil
|
346
554
|
@execute_error = nil
|
@@ -350,12 +558,35 @@ module MarkdownExec
|
|
350
558
|
@execute_script_filespec = nil
|
351
559
|
@execute_started_at = nil
|
352
560
|
@option_parser = nil
|
353
|
-
|
561
|
+
end
|
562
|
+
|
563
|
+
# return arguments before `--`
|
564
|
+
#
|
565
|
+
def arguments_for_mde(argv = ARGV)
|
566
|
+
case ind = argv.find_index('--')
|
567
|
+
when nil
|
568
|
+
argv
|
569
|
+
when 0
|
570
|
+
[]
|
571
|
+
else
|
572
|
+
argv[0..ind - 1]
|
573
|
+
end #.tap_inspect
|
574
|
+
end
|
575
|
+
|
576
|
+
# return arguments after `--`
|
577
|
+
#
|
578
|
+
def arguments_for_child(argv = ARGV)
|
579
|
+
case ind = argv.find_index('--')
|
580
|
+
when nil, argv.count - 1
|
581
|
+
[]
|
582
|
+
else
|
583
|
+
argv[ind + 1..-1]
|
584
|
+
end #.tap_inspect
|
354
585
|
end
|
355
586
|
|
356
587
|
##
|
357
588
|
# options necessary to start, parse input, defaults for cli options
|
358
|
-
|
589
|
+
#
|
359
590
|
def base_options
|
360
591
|
menu_iter do |item|
|
361
592
|
# noisy item.tap_yaml name: :item
|
@@ -366,45 +597,39 @@ module MarkdownExec
|
|
366
597
|
value = if item_default.nil?
|
367
598
|
item_default
|
368
599
|
else
|
369
|
-
env_str(item[:env_var],
|
600
|
+
env_str(item[:env_var],
|
601
|
+
default: OptionValue.new(item_default).for_hash)
|
370
602
|
end
|
371
603
|
[item[:opt_name], item[:proccode] ? item[:proccode].call(value) : value]
|
372
|
-
end.compact.to_h
|
373
|
-
{
|
374
|
-
menu_exit_at_top: true,
|
375
|
-
menu_with_exit: true
|
376
|
-
}
|
377
|
-
).tap_yaml
|
604
|
+
end.compact.to_h
|
378
605
|
end
|
379
606
|
|
380
|
-
def
|
607
|
+
def calculated_options
|
381
608
|
{
|
382
609
|
bash: true, # bash block parsing in get_block_summary()
|
383
|
-
exclude_expect_blocks: true,
|
384
|
-
hide_blocks_by_name: true,
|
385
|
-
output_saved_script_filename: false,
|
386
610
|
saved_script_filename: nil, # calculated
|
387
611
|
struct: true # allow get_block_summary()
|
388
612
|
}
|
389
613
|
end
|
390
614
|
|
391
|
-
def
|
615
|
+
def approve_and_execute_block(opts, mdoc)
|
392
616
|
required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
|
393
|
-
|
617
|
+
if opts[:output_script] || opts[:user_must_approve]
|
618
|
+
display_required_code(opts,
|
619
|
+
required_blocks)
|
620
|
+
end
|
394
621
|
|
395
622
|
allow = true
|
396
623
|
if opts[:user_must_approve]
|
397
624
|
loop do
|
398
|
-
(sel = @prompt.select(opts[:prompt_approve_block],
|
625
|
+
(sel = @prompt.select(opts[:prompt_approve_block],
|
626
|
+
filter: true) do |menu|
|
399
627
|
menu.default 1
|
400
|
-
# menu.enum '.'
|
401
|
-
# menu.filter true
|
402
|
-
|
403
628
|
menu.choice opts[:prompt_yes], 1
|
404
629
|
menu.choice opts[:prompt_no], 2
|
405
630
|
menu.choice opts[:prompt_script_to_clipboard], 3
|
406
631
|
menu.choice opts[:prompt_save_script], 4
|
407
|
-
end)
|
632
|
+
end)
|
408
633
|
allow = (sel == 1)
|
409
634
|
if sel == 3
|
410
635
|
text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
|
@@ -414,13 +639,14 @@ module MarkdownExec
|
|
414
639
|
" #{text.length} characters"
|
415
640
|
end
|
416
641
|
if sel == 4
|
417
|
-
write_command_file(opts.merge(save_executed_script: true),
|
642
|
+
write_command_file(opts.merge(save_executed_script: true),
|
643
|
+
required_blocks)
|
418
644
|
fout "File saved: #{@options[:saved_filespec]}"
|
419
645
|
end
|
420
646
|
break if [1, 2].include? sel
|
421
647
|
end
|
422
648
|
end
|
423
|
-
(opts[:ir_approve] = allow)
|
649
|
+
(opts[:ir_approve] = allow)
|
424
650
|
|
425
651
|
selected = mdoc.get_block_by_name opts[:block_name]
|
426
652
|
|
@@ -435,21 +661,25 @@ module MarkdownExec
|
|
435
661
|
selected[:name]
|
436
662
|
end
|
437
663
|
|
438
|
-
|
439
|
-
|
440
|
-
|
664
|
+
def cfile
|
665
|
+
# puts @options.inspect
|
666
|
+
# binding.pry
|
667
|
+
@cfile ||= CachedNestedFileReader.new(import_pattern: @options.fetch(:import_pattern))
|
668
|
+
# @cfile ||= CachedNestedFileReader.new(import_pattern: /^ *#insert (.+)$/)
|
669
|
+
end
|
441
670
|
|
442
671
|
# :reek:DuplicateMethodCall
|
443
672
|
# :reek:UncommunicativeVariableName { exclude: [ e ] }
|
444
673
|
# :reek:LongYieldList
|
445
674
|
def command_execute(opts, command)
|
446
|
-
# dd = lambda { |s| puts 'command_execute() ' + s }
|
447
675
|
#d 'execute command and yield outputs'
|
448
676
|
@execute_files = Hash.new([])
|
449
677
|
@execute_options = opts
|
450
678
|
@execute_started_at = Time.now.utc
|
451
679
|
|
452
|
-
|
680
|
+
args = []
|
681
|
+
Open3.popen3(@options[:shell], '-c',
|
682
|
+
command, ARGV[0], *args) do |stdin, stdout, stderr, exec_thr|
|
453
683
|
#d 'command started'
|
454
684
|
Thread.new do
|
455
685
|
until (line = stdout.gets).nil?
|
@@ -495,26 +725,26 @@ module MarkdownExec
|
|
495
725
|
end
|
496
726
|
#d 'command completed'
|
497
727
|
@execute_completed_at = Time.now.utc
|
498
|
-
rescue Errno::ENOENT =>
|
728
|
+
rescue Errno::ENOENT => err
|
499
729
|
#d 'command error ENOENT triggered by missing command in script'
|
500
730
|
@execute_aborted_at = Time.now.utc
|
501
|
-
@execute_error_message =
|
502
|
-
@execute_error =
|
731
|
+
@execute_error_message = err.message
|
732
|
+
@execute_error = err
|
503
733
|
@execute_files[EF_STDERR] += [@execute_error_message]
|
504
|
-
fout "Error ENOENT: #{
|
505
|
-
rescue SignalException =>
|
734
|
+
fout "Error ENOENT: #{err.inspect}"
|
735
|
+
rescue SignalException => err
|
506
736
|
#d 'command SIGTERM triggered by user or system'
|
507
737
|
@execute_aborted_at = Time.now.utc
|
508
738
|
@execute_error_message = 'SIGTERM'
|
509
|
-
@execute_error =
|
739
|
+
@execute_error = err
|
510
740
|
@execute_files[EF_STDERR] += [@execute_error_message]
|
511
|
-
fout "Error ENOENT: #{
|
741
|
+
fout "Error ENOENT: #{err.inspect}"
|
512
742
|
end
|
513
743
|
|
514
744
|
def count_blocks_in_filename
|
515
745
|
fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match]
|
516
746
|
cnt = 0
|
517
|
-
|
747
|
+
cfile.readlines(@options[:filename]).each do |line|
|
518
748
|
cnt += 1 if line.match(fenced_start_and_end_match)
|
519
749
|
end
|
520
750
|
cnt / 2
|
@@ -530,7 +760,7 @@ module MarkdownExec
|
|
530
760
|
|
531
761
|
# :reek:DuplicateMethodCall
|
532
762
|
def exec_block(options, _block_name = '')
|
533
|
-
options =
|
763
|
+
options = calculated_options.merge(options).tap_yaml 'options'
|
534
764
|
update_options options, over: false
|
535
765
|
|
536
766
|
# document and block reports
|
@@ -548,12 +778,16 @@ module MarkdownExec
|
|
548
778
|
list_docs: -> { fout_list files },
|
549
779
|
list_default_env: -> { fout_list list_default_env },
|
550
780
|
list_recent_output: lambda {
|
551
|
-
fout_list list_recent_output(
|
552
|
-
|
781
|
+
fout_list list_recent_output(
|
782
|
+
@options[:saved_stdout_folder],
|
783
|
+
@options[:saved_stdout_glob], @options[:list_count]
|
784
|
+
)
|
553
785
|
},
|
554
786
|
list_recent_scripts: lambda {
|
555
|
-
fout_list list_recent_scripts(
|
556
|
-
|
787
|
+
fout_list list_recent_scripts(
|
788
|
+
options[:saved_script_folder],
|
789
|
+
options[:saved_script_glob], options[:list_count]
|
790
|
+
)
|
557
791
|
},
|
558
792
|
pwd: -> { fout File.expand_path('..', __dir__) },
|
559
793
|
run_last_script: -> { run_last_script },
|
@@ -572,41 +806,43 @@ module MarkdownExec
|
|
572
806
|
# process
|
573
807
|
#
|
574
808
|
@options[:filename] = select_md_file(files)
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
809
|
+
select_approve_and_execute_block({
|
810
|
+
bash: true,
|
811
|
+
struct: true
|
812
|
+
})
|
813
|
+
return unless @options[:output_saved_script_filename]
|
814
|
+
|
815
|
+
fout "saved_filespec: #{@execute_script_filespec}"
|
816
|
+
rescue StandardError => err
|
817
|
+
warn(error = "ERROR ** MarkParse.exec_block(); #{err.inspect}")
|
818
|
+
binding.pry if $tap_enable
|
819
|
+
raise ArgumentError, error
|
580
820
|
end
|
581
821
|
|
582
|
-
|
583
|
-
|
822
|
+
## summarize blocks
|
823
|
+
#
|
824
|
+
def get_block_summary(call_options, fcb)
|
584
825
|
opts = optsmerge call_options
|
585
|
-
return
|
586
|
-
return [summarize_block(headings, block_title).merge({ body: block_body })] unless opts[:bash]
|
587
|
-
|
588
|
-
block_title.tap_inspect name: :block_title
|
589
|
-
call = block_title.scan(Regexp.new(opts[:block_calls_scan]))
|
590
|
-
.map { |scanned| scanned[1..] }
|
591
|
-
&.first.tap_inspect name: :call
|
592
|
-
(titlexcall = call ? block_title.sub("%#{call}", '') : block_title).tap_inspect name: :titlexcall
|
826
|
+
# return fcb.body unless opts[:struct]
|
593
827
|
|
594
|
-
|
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
|
828
|
+
return fcb unless opts[:bash]
|
599
829
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
830
|
+
fcb.call = fcb.title.match(Regexp.new(opts[:block_calls_scan]))&.fetch(1, nil)
|
831
|
+
titlexcall = if fcb.call
|
832
|
+
fcb.title.sub("%#{fcb.call}", '')
|
833
|
+
else
|
834
|
+
fcb.title
|
835
|
+
end
|
836
|
+
bm = option_match_groups(titlexcall, opts[:block_name_match])
|
837
|
+
fcb.stdin = option_match_groups(titlexcall, opts[:block_stdin_scan])
|
838
|
+
fcb.stdout = option_match_groups(titlexcall, opts[:block_stdout_scan])
|
839
|
+
fcb.title = fcb.name = (bm && bm[1] ? bm[:title] : titlexcall)
|
840
|
+
fcb
|
606
841
|
end
|
607
842
|
|
608
843
|
# :reek:DuplicateMethodCall
|
609
844
|
# :reek:LongYieldList
|
845
|
+
# :reek:NestedIterators
|
610
846
|
def iter_blocks_in_file(opts = {})
|
611
847
|
# opts = optsmerge call_options, options_block
|
612
848
|
|
@@ -622,14 +858,15 @@ module MarkdownExec
|
|
622
858
|
|
623
859
|
fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
|
624
860
|
fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
|
625
|
-
|
626
|
-
block_body = nil
|
627
|
-
headings = []
|
861
|
+
fcb = FCB.new
|
628
862
|
in_block = false
|
863
|
+
headings = []
|
629
864
|
|
865
|
+
## get type of messages to select
|
866
|
+
#
|
630
867
|
selected_messages = yield :filter
|
631
868
|
|
632
|
-
|
869
|
+
cfile.readlines(opts[:filename]).each.with_index do |line, _line_num|
|
633
870
|
continue unless line
|
634
871
|
|
635
872
|
if opts[:menu_blocks_with_headings]
|
@@ -644,73 +881,130 @@ module MarkdownExec
|
|
644
881
|
|
645
882
|
if line.match(fenced_start_and_end_match)
|
646
883
|
if in_block
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
884
|
+
# end fcb
|
885
|
+
#
|
886
|
+
fcb.name = fcb.title || ''
|
887
|
+
if fcb.body
|
888
|
+
if fcb.title.nil? || fcb.title.empty?
|
889
|
+
fcb.title = fcb.body.join(' ').gsub(/ +/, ' ')[0..64]
|
890
|
+
end
|
891
|
+
|
892
|
+
if block_given? && selected_messages.include?(:blocks) &&
|
893
|
+
Filter.fcb_select?(opts, fcb)
|
894
|
+
yield :blocks, fcb
|
895
|
+
end
|
653
896
|
end
|
654
897
|
in_block = false
|
655
|
-
block_title = ''
|
656
898
|
else
|
657
|
-
# start
|
899
|
+
# start fcb
|
658
900
|
#
|
659
|
-
lm = line.match(fenced_start_ex)
|
660
|
-
block_allow = false
|
661
|
-
if opts[:bash_only]
|
662
|
-
block_allow = true if lm && (lm[:shell] == 'bash')
|
663
|
-
else
|
664
|
-
block_allow = true
|
665
|
-
block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
|
666
|
-
end
|
667
|
-
|
668
901
|
in_block = true
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
902
|
+
|
903
|
+
fcb_title_groups = line.match(fenced_start_ex).named_captures.sym_keys
|
904
|
+
fcb = FCB.new
|
905
|
+
fcb.headings = headings
|
906
|
+
fcb.name = fcb_title_groups.fetch(:name, '')
|
907
|
+
fcb.shell = fcb_title_groups.fetch(:shell, '')
|
908
|
+
fcb.title = fcb_title_groups.fetch(:name, '')
|
909
|
+
|
910
|
+
# selected fcb
|
911
|
+
#
|
912
|
+
fcb.body = []
|
913
|
+
|
914
|
+
rest = fcb_title_groups.fetch(:rest, '')
|
915
|
+
fcb.reqs = rest.scan(/\+[^\s]+/).map { |req| req[1..-1] }
|
916
|
+
|
917
|
+
fcb.call = rest.match(Regexp.new(opts[:block_calls_scan]))&.to_a&.first
|
918
|
+
fcb.stdin = if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
|
919
|
+
tn.named_captures.sym_keys
|
920
|
+
end
|
921
|
+
fcb.stdout = if (tn = rest.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/))
|
922
|
+
tn.named_captures.sym_keys
|
923
|
+
end
|
673
924
|
end
|
674
|
-
elsif
|
675
|
-
|
925
|
+
elsif in_block && fcb.body
|
926
|
+
dp 'append line to fcb body'
|
927
|
+
fcb.body += [line.chomp]
|
676
928
|
elsif block_given? && selected_messages.include?(:line)
|
677
|
-
|
678
|
-
|
679
|
-
|
929
|
+
dp 'text outside of fcb'
|
930
|
+
fcb = FCB.new
|
931
|
+
fcb.body = [line]
|
932
|
+
yield :line, fcb
|
680
933
|
end
|
681
934
|
end
|
682
935
|
end
|
683
936
|
|
937
|
+
# return body, title if option.struct
|
938
|
+
# return body if not struct
|
939
|
+
#
|
684
940
|
def list_blocks_in_file(call_options = {}, &options_block)
|
685
|
-
opts = optsmerge
|
686
|
-
|
941
|
+
opts = optsmerge(call_options, options_block) #.tap_yaml 'opts'
|
687
942
|
blocks = []
|
688
943
|
if opts[:menu_initial_divider].present?
|
689
|
-
blocks
|
690
|
-
|
691
|
-
|
692
|
-
|
944
|
+
blocks.push FCB.new({
|
945
|
+
# name: '',
|
946
|
+
chrome: true,
|
947
|
+
text: format(
|
948
|
+
opts[:menu_divider_format],
|
949
|
+
opts[:menu_initial_divider]
|
950
|
+
).send(opts[:menu_divider_color].to_sym),
|
951
|
+
disabled: '' # __LINE__.to_s
|
952
|
+
})
|
693
953
|
end
|
694
|
-
|
954
|
+
|
955
|
+
iter_blocks_in_file(opts) do |btype, fcb|
|
956
|
+
# binding.pry
|
695
957
|
case btype
|
696
958
|
when :filter
|
959
|
+
## return type of blocks to select
|
960
|
+
#
|
697
961
|
%i[blocks line]
|
962
|
+
|
698
963
|
when :line
|
699
|
-
|
700
|
-
|
701
|
-
|
964
|
+
## convert line to block
|
965
|
+
#
|
966
|
+
# binding.pry
|
967
|
+
if opts[:menu_divider_match].present? &&
|
968
|
+
(mbody = fcb.body[0].match opts[:menu_divider_match])
|
969
|
+
# binding.pry
|
970
|
+
blocks.push FCB.new(
|
971
|
+
{ chrome: true,
|
972
|
+
disabled: '',
|
973
|
+
text: format(opts[:menu_divider_format],
|
974
|
+
mbody[:name]).send(opts[:menu_divider_color].to_sym) }
|
975
|
+
)
|
976
|
+
elsif opts[:menu_task_match].present? &&
|
977
|
+
(mbody = fcb.body[0].match opts[:menu_task_match])
|
978
|
+
blocks.push FCB.new(
|
979
|
+
{ chrome: true,
|
980
|
+
disabled: '',
|
981
|
+
text: format(opts[:menu_task_format],
|
982
|
+
mbody[:name]).send(opts[:menu_task_color].to_sym) }
|
983
|
+
)
|
984
|
+
else
|
985
|
+
# line not added
|
702
986
|
end
|
703
987
|
when :blocks
|
704
|
-
|
988
|
+
## enhance fcb with block summary
|
989
|
+
#
|
990
|
+
blocks.push get_block_summary(opts, fcb) ### if Filter.fcb_select? opts, fcb
|
705
991
|
end
|
706
992
|
end
|
993
|
+
|
707
994
|
if opts[:menu_divider_format].present? && opts[:menu_final_divider].present?
|
708
|
-
blocks
|
709
|
-
|
710
|
-
|
711
|
-
|
995
|
+
blocks.push FCB.new(
|
996
|
+
{ chrome: true,
|
997
|
+
disabled: '',
|
998
|
+
text: format(opts[:menu_divider_format],
|
999
|
+
opts[:menu_final_divider])
|
1000
|
+
.send(opts[:menu_divider_color].to_sym) }
|
1001
|
+
)
|
712
1002
|
end
|
713
|
-
blocks.
|
1003
|
+
blocks.tap_inspect
|
1004
|
+
rescue StandardError => err
|
1005
|
+
warn(error = "ERROR ** MarkParse.list_blocks_in_file(); #{err.inspect}")
|
1006
|
+
warn(caller[0..4])
|
1007
|
+
raise StandardError, error
|
714
1008
|
end
|
715
1009
|
|
716
1010
|
def list_default_env
|
@@ -741,75 +1035,77 @@ module MarkdownExec
|
|
741
1035
|
specified_folder: options[:path],
|
742
1036
|
default_filename: 'README.md',
|
743
1037
|
default_folder: '.'
|
744
|
-
)
|
1038
|
+
)
|
745
1039
|
end
|
746
1040
|
|
747
1041
|
# :reek:LongParameterList
|
748
1042
|
def list_files_specified(specified_filename: nil, specified_folder: nil,
|
749
1043
|
default_filename: nil, default_folder: nil, filetree: nil)
|
750
1044
|
fn = File.join(if specified_filename&.present?
|
751
|
-
# puts ' LFS 01'
|
752
1045
|
if specified_filename.start_with? '/'
|
753
|
-
# puts ' LFS 02'
|
754
1046
|
[specified_filename]
|
755
1047
|
elsif specified_folder&.present?
|
756
|
-
# puts ' LFS 03'
|
757
1048
|
[specified_folder, specified_filename]
|
758
1049
|
else
|
759
|
-
# puts ' LFS 04'
|
760
1050
|
[default_folder, specified_filename]
|
761
1051
|
end
|
762
1052
|
elsif specified_folder&.present?
|
763
|
-
# puts ' LFS 05'
|
764
1053
|
if filetree
|
765
|
-
# puts ' LFS 06'
|
766
1054
|
[specified_folder, @options[:md_filename_match]]
|
767
1055
|
else
|
768
|
-
# puts ' LFS 07'
|
769
1056
|
[specified_folder, @options[:md_filename_glob]]
|
770
1057
|
end
|
771
1058
|
else
|
772
|
-
# puts ' LFS 08'
|
773
1059
|
[default_folder, default_filename]
|
774
|
-
end)
|
1060
|
+
end)
|
775
1061
|
if filetree
|
776
|
-
filetree.select
|
1062
|
+
filetree.select do |filename|
|
1063
|
+
filename == fn || filename.match(/^#{fn}$/) || filename.match(%r{^#{fn}/.+$})
|
1064
|
+
end
|
777
1065
|
else
|
778
1066
|
Dir.glob(fn)
|
779
|
-
end
|
1067
|
+
end
|
780
1068
|
end
|
781
1069
|
|
782
1070
|
def list_markdown_files_in_path
|
783
|
-
Dir.glob(File.join(@options[:path],
|
1071
|
+
Dir.glob(File.join(@options[:path],
|
1072
|
+
@options[:md_filename_glob]))
|
784
1073
|
end
|
785
1074
|
|
786
|
-
def
|
787
|
-
opts
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
1075
|
+
def blocks_per_opts(blocks, opts)
|
1076
|
+
if opts[:struct]
|
1077
|
+
blocks
|
1078
|
+
else
|
1079
|
+
# blocks.map(&:name)
|
1080
|
+
blocks.map do |block|
|
1081
|
+
block.fetch(:text, nil) || block.fetch(:name, nil)
|
1082
|
+
end
|
1083
|
+
end.compact.reject(&:empty?).tap_inspect
|
794
1084
|
end
|
795
1085
|
|
796
|
-
|
797
|
-
|
798
|
-
|
1086
|
+
## output type (body string or full object) per option struct and bash
|
1087
|
+
#
|
1088
|
+
def list_named_blocks_in_file(call_options = {}, &options_block)
|
1089
|
+
opts = optsmerge call_options, options_block
|
799
1090
|
|
800
|
-
|
801
|
-
|
1091
|
+
blocks = list_blocks_in_file(opts.merge(struct: true)).select do |fcb|
|
1092
|
+
# fcb.fetch(:name, '') != '' && Filter.fcb_select?(opts, fcb)
|
1093
|
+
Filter.fcb_select?(opts.merge(no_chrome: true), fcb)
|
1094
|
+
end
|
1095
|
+
blocks_per_opts(blocks, opts).tap_inspect
|
802
1096
|
end
|
803
1097
|
|
804
1098
|
def make_block_labels(call_options = {})
|
805
1099
|
opts = options.merge(call_options)
|
806
|
-
list_blocks_in_file(opts).map do |
|
1100
|
+
list_blocks_in_file(opts).map do |fcb|
|
807
1101
|
BlockLabel.new(filename: opts[:filename],
|
808
|
-
headings:
|
1102
|
+
headings: fcb.fetch(:headings, []),
|
809
1103
|
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
810
1104
|
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
811
|
-
title:
|
812
|
-
|
1105
|
+
title: fcb[:title],
|
1106
|
+
text: fcb[:text],
|
1107
|
+
body: fcb[:body]).make
|
1108
|
+
end.compact
|
813
1109
|
end
|
814
1110
|
|
815
1111
|
# :reek:DuplicateMethodCall
|
@@ -843,7 +1139,9 @@ module MarkdownExec
|
|
843
1139
|
fout options.sort_by_key.to_yaml
|
844
1140
|
}
|
845
1141
|
when 'val_as_bool'
|
846
|
-
|
1142
|
+
lambda { |value|
|
1143
|
+
value.instance_of?(::String) ? (value.chomp != '0') : value
|
1144
|
+
}
|
847
1145
|
when 'val_as_int'
|
848
1146
|
->(value) { value.to_i }
|
849
1147
|
when 'val_as_str'
|
@@ -862,22 +1160,22 @@ module MarkdownExec
|
|
862
1160
|
end
|
863
1161
|
|
864
1162
|
def menu_for_blocks(menu_options)
|
865
|
-
options =
|
1163
|
+
options = calculated_options.merge menu_options
|
866
1164
|
menu = []
|
867
|
-
iter_blocks_in_file(options) do |btype,
|
1165
|
+
iter_blocks_in_file(options) do |btype, fcb|
|
868
1166
|
case btype
|
869
1167
|
when :filter
|
870
1168
|
%i[blocks line]
|
871
1169
|
when :line
|
872
|
-
if options[:menu_divider_match] &&
|
873
|
-
|
1170
|
+
if options[:menu_divider_match] &&
|
1171
|
+
(mbody = fcb.body[0].match(options[:menu_divider_match]))
|
1172
|
+
menu.push FCB.new({ name: mbody[:name], disabled: '' })
|
874
1173
|
end
|
875
1174
|
when :blocks
|
876
|
-
|
877
|
-
menu += [summ[0][:name]]
|
1175
|
+
menu += [fcb.name]
|
878
1176
|
end
|
879
1177
|
end
|
880
|
-
menu
|
1178
|
+
menu
|
881
1179
|
end
|
882
1180
|
|
883
1181
|
def menu_iter(data = menu_for_optparse, &block)
|
@@ -892,17 +1190,17 @@ module MarkdownExec
|
|
892
1190
|
return unless item[:long_name].present? || item[:short_name].present?
|
893
1191
|
|
894
1192
|
opts.on(*[
|
895
|
-
# long name
|
1193
|
+
# - long name
|
896
1194
|
if item[:long_name].present?
|
897
1195
|
"--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
898
1196
|
end,
|
899
1197
|
|
900
|
-
# short name
|
1198
|
+
# - short name
|
901
1199
|
item[:short_name].present? ? "-#{item[:short_name]}" : nil,
|
902
1200
|
|
903
|
-
# description and default
|
1201
|
+
# - description and default
|
904
1202
|
[item[:description],
|
905
|
-
|
1203
|
+
("[#{value_for_cli item[:default]}]" if item[:default].present?)].compact.join(' '),
|
906
1204
|
|
907
1205
|
# apply proccode, if present, to value
|
908
1206
|
# save value to options hash if option is named
|
@@ -979,6 +1277,10 @@ module MarkdownExec
|
|
979
1277
|
}
|
980
1278
|
end
|
981
1279
|
|
1280
|
+
## tty prompt to select
|
1281
|
+
# insert exit option at head or tail
|
1282
|
+
# return selected option or nil
|
1283
|
+
#
|
982
1284
|
def prompt_with_quit(prompt_text, items, opts = {})
|
983
1285
|
exit_option = '* Exit'
|
984
1286
|
all_items = if @options[:menu_exit_at_top]
|
@@ -1006,7 +1308,8 @@ module MarkdownExec
|
|
1006
1308
|
|
1007
1309
|
## read local configuration file
|
1008
1310
|
#
|
1009
|
-
read_configuration_file! @options,
|
1311
|
+
read_configuration_file! @options,
|
1312
|
+
".#{MarkdownExec::APP_NAME.downcase}.yml"
|
1010
1313
|
|
1011
1314
|
@option_parser = option_parser = OptionParser.new do |opts|
|
1012
1315
|
executable_name = File.basename($PROGRAM_NAME)
|
@@ -1017,28 +1320,35 @@ module MarkdownExec
|
|
1017
1320
|
].join("\n")
|
1018
1321
|
|
1019
1322
|
menu_iter do |item|
|
1020
|
-
item.tap_yaml 'item'
|
1021
1323
|
menu_option_append opts, options, item
|
1022
1324
|
end
|
1023
1325
|
end
|
1024
|
-
option_parser.load # filename defaults to basename of the program
|
1025
|
-
|
1026
|
-
|
1326
|
+
option_parser.load # filename defaults to basename of the program
|
1327
|
+
# without suffix in a directory ~/.options
|
1328
|
+
option_parser.environment # env defaults to the basename of the program
|
1329
|
+
# child_argv = arguments_for_child
|
1330
|
+
rest = option_parser.parse!(arguments_for_mde) # (into: options)
|
1027
1331
|
|
1028
1332
|
begin
|
1029
1333
|
options_finalize rest
|
1030
1334
|
exec_block options, options[:block_name]
|
1031
|
-
rescue FileMissingError =>
|
1032
|
-
puts "File missing: #{
|
1335
|
+
rescue FileMissingError => err
|
1336
|
+
puts "File missing: #{err}"
|
1033
1337
|
end
|
1338
|
+
rescue StandardError => err
|
1339
|
+
warn(error = "ERROR ** MarkParse.run(); #{err.inspect}")
|
1340
|
+
binding.pry if $tap_enable
|
1341
|
+
raise ArgumentError, error
|
1034
1342
|
end
|
1035
1343
|
|
1036
1344
|
def saved_name_split(name)
|
1037
|
-
|
1345
|
+
# rubocop:disable Layout/LineLength
|
1346
|
+
mf = /#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/.match name
|
1347
|
+
# rubocop:enable Layout/LineLength
|
1038
1348
|
return unless mf
|
1039
1349
|
|
1040
|
-
@options[:block_name] = mf[:block]
|
1041
|
-
@options[:filename] = mf[:file].gsub(FNR12, FNR11)
|
1350
|
+
@options[:block_name] = mf[:block]
|
1351
|
+
@options[:filename] = mf[:file].gsub(FNR12, FNR11)
|
1042
1352
|
end
|
1043
1353
|
|
1044
1354
|
def run_last_script
|
@@ -1048,11 +1358,10 @@ module MarkdownExec
|
|
1048
1358
|
|
1049
1359
|
saved_name_split filename
|
1050
1360
|
@options[:save_executed_script] = false
|
1051
|
-
|
1361
|
+
select_approve_and_execute_block({})
|
1052
1362
|
end
|
1053
1363
|
|
1054
1364
|
def save_execution_output
|
1055
|
-
@options.tap_inspect name: :options
|
1056
1365
|
return unless @options[:save_execution_output]
|
1057
1366
|
|
1058
1367
|
@options[:logged_stdout_filename] =
|
@@ -1061,9 +1370,11 @@ module MarkdownExec
|
|
1061
1370
|
prefix: @options[:logged_stdout_filename_prefix],
|
1062
1371
|
time: Time.now.utc).stdout_name
|
1063
1372
|
|
1064
|
-
@options[:logged_stdout_filespec] =
|
1373
|
+
@options[:logged_stdout_filespec] =
|
1374
|
+
File.join @options[:saved_stdout_folder],
|
1375
|
+
@options[:logged_stdout_filename]
|
1065
1376
|
@logged_stdout_filespec = @options[:logged_stdout_filespec]
|
1066
|
-
(dirname = File.dirname(@options[:logged_stdout_filespec]))
|
1377
|
+
(dirname = File.dirname(@options[:logged_stdout_filespec]))
|
1067
1378
|
Dir.mkdir_p dirname
|
1068
1379
|
|
1069
1380
|
ol = ["-STDOUT-\n"]
|
@@ -1076,40 +1387,61 @@ module MarkdownExec
|
|
1076
1387
|
File.write(@options[:logged_stdout_filespec], ol.join)
|
1077
1388
|
end
|
1078
1389
|
|
1079
|
-
def
|
1390
|
+
def select_approve_and_execute_block(call_options, &options_block)
|
1080
1391
|
opts = optsmerge call_options, options_block
|
1081
|
-
blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
|
1082
|
-
mdoc = MDoc.new(blocks_in_file)
|
1083
|
-
|
1392
|
+
blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
|
1393
|
+
mdoc = MDoc.new(blocks_in_file) do |nopts|
|
1394
|
+
opts.merge!(nopts)
|
1395
|
+
end
|
1396
|
+
blocks_menu = mdoc.fcbs_per_options(opts.merge(struct: true))
|
1084
1397
|
|
1085
1398
|
repeat_menu = true && !opts[:block_name].present?
|
1086
1399
|
loop do
|
1087
1400
|
unless opts[:block_name].present?
|
1088
1401
|
pt = (opts[:prompt_select_block]).to_s
|
1089
1402
|
|
1090
|
-
blocks_menu.
|
1091
|
-
next if
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1403
|
+
bm = blocks_menu.map do |fcb|
|
1404
|
+
# next if fcb.fetch(:disabled, false)
|
1405
|
+
# next unless fcb.fetch(:name, '').present?
|
1406
|
+
|
1407
|
+
fcb.merge!(
|
1408
|
+
label: BlockLabel.new(
|
1409
|
+
body: fcb[:body],
|
1410
|
+
filename: opts[:filename],
|
1411
|
+
headings: fcb.fetch(:headings, []),
|
1412
|
+
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
1413
|
+
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
1414
|
+
text: fcb[:text],
|
1415
|
+
title: fcb[:title]
|
1416
|
+
).make
|
1417
|
+
)
|
1418
|
+
|
1419
|
+
fcb.to_h
|
1420
|
+
end.compact
|
1421
|
+
return nil if bm.count.zero?
|
1422
|
+
|
1423
|
+
# binding.pry
|
1424
|
+
|
1425
|
+
sel = prompt_with_quit pt, bm,
|
1426
|
+
per_page: opts[:select_page_height]
|
1103
1427
|
return nil if sel.nil?
|
1104
1428
|
|
1105
|
-
|
1106
|
-
|
1429
|
+
## store selected option
|
1430
|
+
#
|
1431
|
+
label_block = blocks_in_file.select do |fcb|
|
1432
|
+
fcb[:label] == sel
|
1433
|
+
end.fetch(0, nil)
|
1434
|
+
opts[:block_name] = @options[:block_name] = label_block.fetch(:name, '')
|
1107
1435
|
end
|
1108
|
-
|
1436
|
+
approve_and_execute_block opts, mdoc
|
1109
1437
|
break unless repeat_menu
|
1110
1438
|
|
1111
1439
|
opts[:block_name] = ''
|
1112
1440
|
end
|
1441
|
+
rescue StandardError => err
|
1442
|
+
warn(error = "ERROR ** MarkParse.select_approve_and_execute_block(); #{err.inspect}")
|
1443
|
+
binding.pry if $tap_enable
|
1444
|
+
raise ArgumentError, error
|
1113
1445
|
end
|
1114
1446
|
|
1115
1447
|
def select_md_file(files = list_markdown_files_in_path)
|
@@ -1117,7 +1449,8 @@ module MarkdownExec
|
|
1117
1449
|
if (count = files.count) == 1
|
1118
1450
|
files[0]
|
1119
1451
|
elsif count >= 2
|
1120
|
-
prompt_with_quit opts[:prompt_select_md].to_s, files,
|
1452
|
+
prompt_with_quit opts[:prompt_select_md].to_s, files,
|
1453
|
+
per_page: opts[:select_page_height]
|
1121
1454
|
end
|
1122
1455
|
end
|
1123
1456
|
|
@@ -1150,15 +1483,11 @@ module MarkdownExec
|
|
1150
1483
|
|
1151
1484
|
saved_name_split(filename)
|
1152
1485
|
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
end
|
1159
|
-
|
1160
|
-
def summarize_block(headings, title)
|
1161
|
-
{ headings: headings, name: title, title: title }
|
1486
|
+
select_approve_and_execute_block({
|
1487
|
+
bash: true,
|
1488
|
+
save_executed_script: false,
|
1489
|
+
struct: true
|
1490
|
+
})
|
1162
1491
|
end
|
1163
1492
|
|
1164
1493
|
def menu_export(data = menu_for_optparse)
|
@@ -1182,7 +1511,7 @@ module MarkdownExec
|
|
1182
1511
|
else
|
1183
1512
|
@options.merge! opts
|
1184
1513
|
end
|
1185
|
-
@options
|
1514
|
+
@options
|
1186
1515
|
end
|
1187
1516
|
|
1188
1517
|
def write_command_file(call_options, required_blocks)
|