markdown_exec 1.3.1 → 1.3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -4
- data/CHANGELOG.md +45 -1
- data/Gemfile +8 -5
- data/Gemfile.lock +125 -29
- data/README.md +5 -80
- data/Rakefile +108 -24
- 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 +8 -3
- data/lib/env_opts.rb +242 -0
- data/lib/environment_opt_parse.rb +28 -19
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +593 -281
- data/lib/menu.yml +70 -4
- data/lib/object_present.rb +44 -1
- data/lib/rspec_helpers.rb +10 -0
- data/lib/tap.rb +81 -17
- metadata +5 -3
- data/lib/globfiles.rb +0 -40
data/lib/markdown_exec.rb
CHANGED
@@ -6,16 +6,15 @@
|
|
6
6
|
require 'English'
|
7
7
|
require 'clipboard'
|
8
8
|
require 'open3'
|
9
|
-
|
9
|
+
require 'optparse'
|
10
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?
|
@@ -478,7 +708,7 @@ module MarkdownExec
|
|
478
708
|
yield line, nil, nil, exec_thr if block_given?
|
479
709
|
end
|
480
710
|
#d 'exec_thr now dead'
|
481
|
-
rescue
|
711
|
+
rescue StandardError
|
482
712
|
#d 'stdin error, thread killed, do nothing'
|
483
713
|
end
|
484
714
|
|
@@ -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]
|
826
|
+
# return fcb.body unless opts[:struct]
|
587
827
|
|
588
|
-
|
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
|
828
|
+
return fcb unless opts[:bash]
|
593
829
|
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
stdout: stdout })].tap_yaml
|
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,77 +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
|
-
|
794
|
-
|
795
|
-
end.compact.tap_inspect
|
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
|
796
1084
|
end
|
797
1085
|
|
798
|
-
|
799
|
-
|
800
|
-
|
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
|
801
1090
|
|
802
|
-
|
803
|
-
|
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
|
804
1096
|
end
|
805
1097
|
|
806
1098
|
def make_block_labels(call_options = {})
|
807
1099
|
opts = options.merge(call_options)
|
808
|
-
list_blocks_in_file(opts).map do |
|
1100
|
+
list_blocks_in_file(opts).map do |fcb|
|
809
1101
|
BlockLabel.new(filename: opts[:filename],
|
810
|
-
headings:
|
1102
|
+
headings: fcb.fetch(:headings, []),
|
811
1103
|
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
812
1104
|
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
813
|
-
title:
|
814
|
-
|
1105
|
+
title: fcb[:title],
|
1106
|
+
text: fcb[:text],
|
1107
|
+
body: fcb[:body]).make
|
1108
|
+
end.compact
|
815
1109
|
end
|
816
1110
|
|
817
1111
|
# :reek:DuplicateMethodCall
|
@@ -845,7 +1139,9 @@ module MarkdownExec
|
|
845
1139
|
fout options.sort_by_key.to_yaml
|
846
1140
|
}
|
847
1141
|
when 'val_as_bool'
|
848
|
-
|
1142
|
+
lambda { |value|
|
1143
|
+
value.instance_of?(::String) ? (value.chomp != '0') : value
|
1144
|
+
}
|
849
1145
|
when 'val_as_int'
|
850
1146
|
->(value) { value.to_i }
|
851
1147
|
when 'val_as_str'
|
@@ -864,22 +1160,22 @@ module MarkdownExec
|
|
864
1160
|
end
|
865
1161
|
|
866
1162
|
def menu_for_blocks(menu_options)
|
867
|
-
options =
|
1163
|
+
options = calculated_options.merge menu_options
|
868
1164
|
menu = []
|
869
|
-
iter_blocks_in_file(options) do |btype,
|
1165
|
+
iter_blocks_in_file(options) do |btype, fcb|
|
870
1166
|
case btype
|
871
1167
|
when :filter
|
872
1168
|
%i[blocks line]
|
873
1169
|
when :line
|
874
|
-
if options[:menu_divider_match] &&
|
875
|
-
|
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: '' })
|
876
1173
|
end
|
877
1174
|
when :blocks
|
878
|
-
|
879
|
-
menu += [summ[0][:name]]
|
1175
|
+
menu += [fcb.name]
|
880
1176
|
end
|
881
1177
|
end
|
882
|
-
menu
|
1178
|
+
menu
|
883
1179
|
end
|
884
1180
|
|
885
1181
|
def menu_iter(data = menu_for_optparse, &block)
|
@@ -894,17 +1190,17 @@ module MarkdownExec
|
|
894
1190
|
return unless item[:long_name].present? || item[:short_name].present?
|
895
1191
|
|
896
1192
|
opts.on(*[
|
897
|
-
# long name
|
1193
|
+
# - long name
|
898
1194
|
if item[:long_name].present?
|
899
1195
|
"--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
900
1196
|
end,
|
901
1197
|
|
902
|
-
# short name
|
1198
|
+
# - short name
|
903
1199
|
item[:short_name].present? ? "-#{item[:short_name]}" : nil,
|
904
1200
|
|
905
|
-
# description and default
|
1201
|
+
# - description and default
|
906
1202
|
[item[:description],
|
907
|
-
|
1203
|
+
("[#{value_for_cli item[:default]}]" if item[:default].present?)].compact.join(' '),
|
908
1204
|
|
909
1205
|
# apply proccode, if present, to value
|
910
1206
|
# save value to options hash if option is named
|
@@ -981,6 +1277,10 @@ module MarkdownExec
|
|
981
1277
|
}
|
982
1278
|
end
|
983
1279
|
|
1280
|
+
## tty prompt to select
|
1281
|
+
# insert exit option at head or tail
|
1282
|
+
# return selected option or nil
|
1283
|
+
#
|
984
1284
|
def prompt_with_quit(prompt_text, items, opts = {})
|
985
1285
|
exit_option = '* Exit'
|
986
1286
|
all_items = if @options[:menu_exit_at_top]
|
@@ -1002,28 +1302,14 @@ module MarkdownExec
|
|
1002
1302
|
|
1003
1303
|
# :reek:NestedIterators
|
1004
1304
|
def run
|
1005
|
-
# eop = EnvironmentOptParse.new(
|
1006
|
-
# menu: File.join(File.expand_path(__dir__), 'menu.yml'),
|
1007
|
-
# options: {
|
1008
|
-
# menu_exit_at_top: true,
|
1009
|
-
# menu_with_exit: true
|
1010
|
-
# }
|
1011
|
-
# ).tap_yaml '** eop'
|
1012
|
-
# # eop = EnvironmentOptParse.new(menu: 'lib/menu.yml', options: ".#{MarkdownExec::APP_NAME.downcase}.yml", version: MarkdownExec::VERSION).tap_yaml '** eop'
|
1013
|
-
# eop.options.tap_inspect 'eop.options'
|
1014
|
-
# eop.remainder.tap_inspect 'eop.remainder'
|
1015
|
-
|
1016
|
-
# exec_block eop.options, eop.options[:block_name]
|
1017
|
-
|
1018
|
-
# return
|
1019
|
-
|
1020
1305
|
## default configuration
|
1021
1306
|
#
|
1022
1307
|
@options = base_options
|
1023
1308
|
|
1024
1309
|
## read local configuration file
|
1025
1310
|
#
|
1026
|
-
read_configuration_file! @options,
|
1311
|
+
read_configuration_file! @options,
|
1312
|
+
".#{MarkdownExec::APP_NAME.downcase}.yml"
|
1027
1313
|
|
1028
1314
|
@option_parser = option_parser = OptionParser.new do |opts|
|
1029
1315
|
executable_name = File.basename($PROGRAM_NAME)
|
@@ -1034,28 +1320,35 @@ module MarkdownExec
|
|
1034
1320
|
].join("\n")
|
1035
1321
|
|
1036
1322
|
menu_iter do |item|
|
1037
|
-
item.tap_yaml 'item'
|
1038
1323
|
menu_option_append opts, options, item
|
1039
1324
|
end
|
1040
1325
|
end
|
1041
|
-
option_parser.load # filename defaults to basename of the program
|
1042
|
-
|
1043
|
-
|
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)
|
1044
1331
|
|
1045
1332
|
begin
|
1046
1333
|
options_finalize rest
|
1047
1334
|
exec_block options, options[:block_name]
|
1048
|
-
rescue FileMissingError =>
|
1049
|
-
puts "File missing: #{
|
1335
|
+
rescue FileMissingError => err
|
1336
|
+
puts "File missing: #{err}"
|
1050
1337
|
end
|
1338
|
+
rescue StandardError => err
|
1339
|
+
warn(error = "ERROR ** MarkParse.run(); #{err.inspect}")
|
1340
|
+
binding.pry if $tap_enable
|
1341
|
+
raise ArgumentError, error
|
1051
1342
|
end
|
1052
1343
|
|
1053
1344
|
def saved_name_split(name)
|
1054
|
-
|
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
|
1055
1348
|
return unless mf
|
1056
1349
|
|
1057
|
-
@options[:block_name] = mf[:block]
|
1058
|
-
@options[:filename] = mf[:file].gsub(FNR12, FNR11)
|
1350
|
+
@options[:block_name] = mf[:block]
|
1351
|
+
@options[:filename] = mf[:file].gsub(FNR12, FNR11)
|
1059
1352
|
end
|
1060
1353
|
|
1061
1354
|
def run_last_script
|
@@ -1065,11 +1358,10 @@ module MarkdownExec
|
|
1065
1358
|
|
1066
1359
|
saved_name_split filename
|
1067
1360
|
@options[:save_executed_script] = false
|
1068
|
-
|
1361
|
+
select_approve_and_execute_block({})
|
1069
1362
|
end
|
1070
1363
|
|
1071
1364
|
def save_execution_output
|
1072
|
-
@options.tap_inspect name: :options
|
1073
1365
|
return unless @options[:save_execution_output]
|
1074
1366
|
|
1075
1367
|
@options[:logged_stdout_filename] =
|
@@ -1078,10 +1370,12 @@ module MarkdownExec
|
|
1078
1370
|
prefix: @options[:logged_stdout_filename_prefix],
|
1079
1371
|
time: Time.now.utc).stdout_name
|
1080
1372
|
|
1081
|
-
@options[:logged_stdout_filespec] =
|
1373
|
+
@options[:logged_stdout_filespec] =
|
1374
|
+
File.join @options[:saved_stdout_folder],
|
1375
|
+
@options[:logged_stdout_filename]
|
1082
1376
|
@logged_stdout_filespec = @options[:logged_stdout_filespec]
|
1083
|
-
(dirname = File.dirname(@options[:logged_stdout_filespec]))
|
1084
|
-
Dir.
|
1377
|
+
(dirname = File.dirname(@options[:logged_stdout_filespec]))
|
1378
|
+
Dir.mkdir_p dirname
|
1085
1379
|
|
1086
1380
|
ol = ["-STDOUT-\n"]
|
1087
1381
|
ol += @execute_files&.fetch(EF_STDOUT, [])
|
@@ -1093,40 +1387,61 @@ module MarkdownExec
|
|
1093
1387
|
File.write(@options[:logged_stdout_filespec], ol.join)
|
1094
1388
|
end
|
1095
1389
|
|
1096
|
-
def
|
1390
|
+
def select_approve_and_execute_block(call_options, &options_block)
|
1097
1391
|
opts = optsmerge call_options, options_block
|
1098
|
-
blocks_in_file = list_blocks_in_file(opts.merge(struct: true)).tap_inspect
|
1099
|
-
mdoc = MDoc.new(blocks_in_file)
|
1100
|
-
|
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))
|
1101
1397
|
|
1102
1398
|
repeat_menu = true && !opts[:block_name].present?
|
1103
1399
|
loop do
|
1104
1400
|
unless opts[:block_name].present?
|
1105
1401
|
pt = (opts[:prompt_select_block]).to_s
|
1106
1402
|
|
1107
|
-
blocks_menu.
|
1108
|
-
next if
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
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]
|
1120
1427
|
return nil if sel.nil?
|
1121
1428
|
|
1122
|
-
|
1123
|
-
|
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, '')
|
1124
1435
|
end
|
1125
|
-
|
1436
|
+
approve_and_execute_block opts, mdoc
|
1126
1437
|
break unless repeat_menu
|
1127
1438
|
|
1128
1439
|
opts[:block_name] = ''
|
1129
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
|
1130
1445
|
end
|
1131
1446
|
|
1132
1447
|
def select_md_file(files = list_markdown_files_in_path)
|
@@ -1134,7 +1449,8 @@ module MarkdownExec
|
|
1134
1449
|
if (count = files.count) == 1
|
1135
1450
|
files[0]
|
1136
1451
|
elsif count >= 2
|
1137
|
-
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]
|
1138
1454
|
end
|
1139
1455
|
end
|
1140
1456
|
|
@@ -1167,15 +1483,11 @@ module MarkdownExec
|
|
1167
1483
|
|
1168
1484
|
saved_name_split(filename)
|
1169
1485
|
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
end
|
1176
|
-
|
1177
|
-
def summarize_block(headings, title)
|
1178
|
-
{ headings: headings, name: title, title: title }
|
1486
|
+
select_approve_and_execute_block({
|
1487
|
+
bash: true,
|
1488
|
+
save_executed_script: false,
|
1489
|
+
struct: true
|
1490
|
+
})
|
1179
1491
|
end
|
1180
1492
|
|
1181
1493
|
def menu_export(data = menu_for_optparse)
|
@@ -1199,7 +1511,7 @@ module MarkdownExec
|
|
1199
1511
|
else
|
1200
1512
|
@options.merge! opts
|
1201
1513
|
end
|
1202
|
-
@options
|
1514
|
+
@options
|
1203
1515
|
end
|
1204
1516
|
|
1205
1517
|
def write_command_file(call_options, required_blocks)
|
@@ -1218,7 +1530,7 @@ module MarkdownExec
|
|
1218
1530
|
File.join opts[:saved_script_folder], opts[:saved_script_filename]
|
1219
1531
|
|
1220
1532
|
dirname = File.dirname(@options[:saved_filespec])
|
1221
|
-
Dir.
|
1533
|
+
Dir.mkdir_p dirname
|
1222
1534
|
(shebang = if @options[:shebang]&.present?
|
1223
1535
|
"#{@options[:shebang]} #{@options[:shell]}\n"
|
1224
1536
|
else
|