markdown_exec 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek +24 -0
- data/CHANGELOG.md +75 -11
- data/Gemfile +7 -5
- data/Gemfile.lock +19 -1
- data/README.md +25 -8
- data/Rakefile +56 -2
- data/bin/tab_completion.sh +131 -3
- data/bin/tab_completion.sh.erb +85 -0
- data/lib/colorize.rb +64 -0
- data/lib/env.rb +37 -0
- data/lib/markdown_exec/version.rb +3 -2
- data/lib/markdown_exec.rb +915 -368
- data/lib/shared.rb +19 -0
- data/lib/tap.rb +36 -0
- metadata +31 -11
- data/fixtures/bash1.md +0 -12
- data/fixtures/bash2.md +0 -15
- data/fixtures/exclude1.md +0 -6
- data/fixtures/exclude2.md +0 -12
- data/fixtures/exec1.md +0 -8
- data/fixtures/heading1.md +0 -19
- data/fixtures/sample1.md +0 -9
- data/fixtures/title1.md +0 -6
data/lib/markdown_exec.rb
CHANGED
@@ -3,46 +3,38 @@
|
|
3
3
|
|
4
4
|
# encoding=utf-8
|
5
5
|
|
6
|
+
require 'English'
|
7
|
+
require 'clipboard'
|
6
8
|
require 'open3'
|
7
9
|
require 'optparse'
|
8
10
|
require 'tty-prompt'
|
9
11
|
require 'yaml'
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def env_bool(name, default: false)
|
17
|
-
return default if name.nil? || (val = ENV[name]).nil?
|
18
|
-
return false if val.empty? || val == '0'
|
19
|
-
|
20
|
-
true
|
21
|
-
end
|
22
|
-
|
23
|
-
def env_int(name, default: 0)
|
24
|
-
return default if name.nil? || (val = ENV[name]).nil?
|
25
|
-
return default if val.empty?
|
26
|
-
|
27
|
-
val.to_i
|
28
|
-
end
|
29
|
-
|
30
|
-
def env_str(name, default: '')
|
31
|
-
return default if name.nil? || (val = ENV[name]).nil?
|
32
|
-
|
33
|
-
val || default
|
34
|
-
end
|
35
|
-
|
36
|
-
$pdebug = env_bool 'MDE_DEBUG'
|
37
|
-
|
13
|
+
require_relative 'colorize'
|
14
|
+
require_relative 'env'
|
15
|
+
require_relative 'shared'
|
16
|
+
require_relative 'tap'
|
38
17
|
require_relative 'markdown_exec/version'
|
39
18
|
|
19
|
+
include Tap # rubocop:disable Style/MixinUsage
|
20
|
+
|
40
21
|
$stderr.sync = true
|
41
22
|
$stdout.sync = true
|
42
23
|
|
43
24
|
BLOCK_SIZE = 1024
|
44
25
|
|
45
|
-
|
26
|
+
# hash with keys sorted by name
|
27
|
+
#
|
28
|
+
class Hash
|
29
|
+
def sort_by_key
|
30
|
+
keys.sort.to_h { |key| [key, self[key]] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# is the value a non-empty string or a binary?
|
35
|
+
#
|
36
|
+
# :reek:ManualDispatch ### temp
|
37
|
+
class Object
|
46
38
|
def present?
|
47
39
|
case self.class.to_s
|
48
40
|
when 'FalseClass', 'TrueClass'
|
@@ -53,7 +45,9 @@ class Object # rubocop:disable Style/Documentation
|
|
53
45
|
end
|
54
46
|
end
|
55
47
|
|
56
|
-
|
48
|
+
# is value empty?
|
49
|
+
#
|
50
|
+
class String
|
57
51
|
BLANK_RE = /\A[[:space:]]*\z/.freeze
|
58
52
|
def blank?
|
59
53
|
empty? || BLANK_RE.match?(self)
|
@@ -62,51 +56,249 @@ end
|
|
62
56
|
|
63
57
|
public
|
64
58
|
|
65
|
-
#
|
59
|
+
# display_level values
|
60
|
+
DISPLAY_LEVEL_BASE = 0 # required output
|
61
|
+
DISPLAY_LEVEL_ADMIN = 1
|
62
|
+
DISPLAY_LEVEL_DEBUG = 2
|
63
|
+
DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
|
64
|
+
DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
|
65
|
+
|
66
|
+
# @execute_files[ind] = @execute_files[ind] + [block]
|
67
|
+
EF_STDOUT = 0
|
68
|
+
EF_STDERR = 1
|
69
|
+
EF_STDIN = 2
|
70
|
+
|
71
|
+
# execute markdown documents
|
66
72
|
#
|
67
|
-
|
68
|
-
|
73
|
+
module MarkdownExec
|
74
|
+
# :reek:IrresponsibleModule
|
75
|
+
class Error < StandardError; end
|
69
76
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
fn = cvt.fetch(format, cvt[:else])
|
77
|
+
## an imported markdown document
|
78
|
+
#
|
79
|
+
class MDoc
|
80
|
+
def initialize(table)
|
81
|
+
@table = table
|
82
|
+
end
|
77
83
|
|
78
|
-
|
79
|
-
|
84
|
+
def code(block)
|
85
|
+
all = [block[:name]] + recursively_required(block[:reqs])
|
86
|
+
all.reverse.map do |req|
|
87
|
+
get_block_by_name(req).fetch(:body, '')
|
88
|
+
end
|
89
|
+
.flatten(1)
|
90
|
+
.tap_inspect
|
91
|
+
end
|
80
92
|
|
81
|
-
|
82
|
-
|
93
|
+
def get_block_by_name(name, default = {})
|
94
|
+
@table.select { |block| block[:name] == name }.fetch(0, default)
|
95
|
+
end
|
83
96
|
|
84
|
-
|
85
|
-
|
97
|
+
def list_recursively_required_blocks(name)
|
98
|
+
name_block = get_block_by_name(name)
|
99
|
+
raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
|
100
|
+
|
101
|
+
all = [name_block[:name]] + recursively_required(name_block[:reqs])
|
102
|
+
|
103
|
+
# in order of appearance in document
|
104
|
+
@table.select { |block| all.include? block[:name] }
|
105
|
+
.map { |block| block.fetch(:body, '') }
|
106
|
+
.flatten(1)
|
107
|
+
.tap_inspect
|
108
|
+
end
|
109
|
+
|
110
|
+
def option_exclude_blocks(opts)
|
111
|
+
block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
|
112
|
+
if opts[:hide_blocks_by_name]
|
113
|
+
@table.reject { |block| block[:name].match(block_name_excluded_match) }
|
114
|
+
else
|
115
|
+
@table
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def recursively_required(reqs)
|
120
|
+
all = []
|
121
|
+
rem = reqs
|
122
|
+
while rem.count.positive?
|
123
|
+
rem = rem.map do |req|
|
124
|
+
next if all.include? req
|
125
|
+
|
126
|
+
all += [req]
|
127
|
+
get_block_by_name(req).fetch(:reqs, [])
|
128
|
+
end
|
129
|
+
.compact
|
130
|
+
.flatten(1)
|
131
|
+
.tap_inspect(name: 'rem')
|
132
|
+
end
|
133
|
+
all.tap_inspect
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# format option defaults and values
|
138
|
+
#
|
139
|
+
# :reek:TooManyInstanceVariables
|
140
|
+
class BlockLabel
|
141
|
+
def initialize(filename:, headings:, menu_blocks_with_docname:, menu_blocks_with_headings:, title:)
|
142
|
+
@filename = filename
|
143
|
+
@headings = headings
|
144
|
+
@menu_blocks_with_docname = menu_blocks_with_docname
|
145
|
+
@menu_blocks_with_headings = menu_blocks_with_headings
|
146
|
+
@title = title
|
147
|
+
end
|
148
|
+
|
149
|
+
def make
|
150
|
+
([@title] +
|
151
|
+
(if @menu_blocks_with_headings
|
152
|
+
[@headings.compact.join(' # ')]
|
153
|
+
else
|
154
|
+
[]
|
155
|
+
end) +
|
156
|
+
(
|
157
|
+
if @menu_blocks_with_docname
|
158
|
+
[@filename]
|
159
|
+
else
|
160
|
+
[]
|
161
|
+
end
|
162
|
+
)).join(' ')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
FNR11 = '/'
|
167
|
+
FNR12 = ',~'
|
168
|
+
|
169
|
+
# format option defaults and values
|
170
|
+
#
|
171
|
+
class SavedAsset
|
172
|
+
def initialize(filename:, prefix:, time:, blockname:)
|
173
|
+
@filename = filename
|
174
|
+
@prefix = prefix
|
175
|
+
@time = time
|
176
|
+
@blockname = blockname
|
177
|
+
end
|
178
|
+
|
179
|
+
def script_name
|
180
|
+
fne = @filename.gsub(FNR11, FNR12)
|
181
|
+
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), fne, ',', @blockname].join('_')}.sh".tap_inspect
|
182
|
+
end
|
183
|
+
|
184
|
+
def stdout_name
|
185
|
+
"#{[@prefix, @time.strftime('%F-%H-%M-%S'), @filename, @blockname].join('_')}.out.txt".tap_inspect
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# format option defaults and values
|
190
|
+
#
|
191
|
+
class OptionValue
|
192
|
+
def initialize(value)
|
193
|
+
@value = value
|
194
|
+
end
|
195
|
+
|
196
|
+
# as default value in env_str()
|
197
|
+
#
|
198
|
+
def for_hash(default = nil)
|
199
|
+
return default if @value.nil?
|
200
|
+
|
201
|
+
case @value.class.to_s
|
202
|
+
when 'String', 'Integer'
|
203
|
+
@value
|
204
|
+
when 'FalseClass', 'TrueClass'
|
205
|
+
@value ? true : false
|
206
|
+
when @value.empty?
|
207
|
+
default
|
208
|
+
else
|
209
|
+
@value.to_s
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# for output as default value in list_default_yaml()
|
214
|
+
#
|
215
|
+
def for_yaml(default = nil)
|
216
|
+
return default if @value.nil?
|
217
|
+
|
218
|
+
case @value.class.to_s
|
219
|
+
when 'String'
|
220
|
+
"'#{@value}'"
|
221
|
+
when 'Integer'
|
222
|
+
@value
|
223
|
+
when 'FalseClass', 'TrueClass'
|
224
|
+
@value ? true : false
|
225
|
+
when @value.empty?
|
226
|
+
default
|
227
|
+
else
|
228
|
+
@value.to_s
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# a generated list of saved files
|
234
|
+
#
|
235
|
+
class Sfiles
|
236
|
+
def initialize(folder, glob)
|
237
|
+
@folder = folder
|
238
|
+
@glob = glob
|
239
|
+
end
|
240
|
+
|
241
|
+
def list_all
|
242
|
+
Dir.glob(File.join(@folder, @glob)).tap_inspect
|
243
|
+
end
|
244
|
+
|
245
|
+
def most_recent(arr = list_all)
|
246
|
+
return unless arr
|
247
|
+
return if arr.count < 1
|
248
|
+
|
249
|
+
arr.max.tap_inspect
|
250
|
+
end
|
251
|
+
|
252
|
+
def most_recent_list(arr = list_all)
|
253
|
+
return unless arr
|
254
|
+
return if (ac = arr.count) < 1
|
255
|
+
|
256
|
+
arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
|
257
|
+
end
|
258
|
+
end
|
86
259
|
|
87
260
|
##
|
88
261
|
#
|
262
|
+
# :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
|
263
|
+
# :reek:MissingSafeMethod { exclude: [ read_configuration_file! ] }
|
264
|
+
# :reek:TooManyInstanceVariables ### temp
|
265
|
+
# :reek:TooManyMethods ### temp
|
89
266
|
class MarkParse
|
90
|
-
|
267
|
+
attr_reader :options
|
91
268
|
|
92
269
|
def initialize(options = {})
|
93
270
|
@options = options
|
94
271
|
@prompt = TTY::Prompt.new(interrupt: :exit)
|
272
|
+
@execute_aborted_at = nil
|
273
|
+
@execute_completed_at = nil
|
274
|
+
@execute_error = nil
|
275
|
+
@execute_error_message = nil
|
276
|
+
@execute_files = nil
|
277
|
+
@execute_options = nil
|
278
|
+
@execute_script_filespec = nil
|
279
|
+
@execute_started_at = nil
|
280
|
+
@option_parser = nil
|
95
281
|
end
|
96
282
|
|
97
283
|
##
|
98
284
|
# options necessary to start, parse input, defaults for cli options
|
99
285
|
|
100
286
|
def base_options
|
101
|
-
|
102
|
-
|
103
|
-
next unless opt_name.present?
|
104
|
-
|
105
|
-
|
106
|
-
|
287
|
+
menu_iter do |item|
|
288
|
+
# noisy item.tap_inspect name: :item, format: :yaml
|
289
|
+
next unless item[:opt_name].present?
|
290
|
+
|
291
|
+
item_default = item[:default]
|
292
|
+
# noisy item_default.tap_inspect name: :item_default
|
293
|
+
value = if item_default.nil?
|
294
|
+
item_default
|
295
|
+
else
|
296
|
+
env_str(item[:env_var], default: OptionValue.new(item_default).for_hash)
|
297
|
+
end
|
298
|
+
[item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
|
107
299
|
end.compact.to_h.merge(
|
108
300
|
{
|
109
|
-
|
301
|
+
menu_exit_at_top: true,
|
110
302
|
menu_with_exit: true
|
111
303
|
}
|
112
304
|
).tap_inspect format: :yaml
|
@@ -127,78 +319,110 @@ module MarkdownExec
|
|
127
319
|
}
|
128
320
|
end
|
129
321
|
|
130
|
-
|
131
|
-
|
132
|
-
def all_at_eof(files)
|
133
|
-
files.find { |f| !f.eof }.nil?
|
134
|
-
end
|
135
|
-
|
136
|
-
def approve_block(opts, blocks_in_file)
|
137
|
-
required_blocks = list_recursively_required_blocks(blocks_in_file, opts[:block_name])
|
322
|
+
def approve_block(opts, mdoc)
|
323
|
+
required_blocks = mdoc.list_recursively_required_blocks(opts[:block_name])
|
138
324
|
display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
|
139
325
|
|
140
326
|
allow = true
|
141
|
-
|
142
|
-
|
143
|
-
|
327
|
+
if opts[:user_must_approve]
|
328
|
+
loop do
|
329
|
+
(sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
|
330
|
+
menu.default 1
|
331
|
+
# menu.enum '.'
|
332
|
+
# menu.filter true
|
333
|
+
|
334
|
+
menu.choice 'Yes', 1
|
335
|
+
menu.choice 'No', 2
|
336
|
+
menu.choice 'Copy script to clipboard', 3
|
337
|
+
menu.choice 'Save script', 4
|
338
|
+
end).tap_inspect name: :sel
|
339
|
+
allow = (sel == 1)
|
340
|
+
if sel == 3
|
341
|
+
text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
|
342
|
+
Clipboard.copy(text)
|
343
|
+
fout "Clipboard updated: #{required_blocks.count} blocks," /
|
344
|
+
" #{required_blocks.flatten.count} lines," /
|
345
|
+
" #{text.length} characters"
|
346
|
+
end
|
347
|
+
if sel == 4
|
348
|
+
write_command_file(opts.merge(save_executed_script: true), required_blocks)
|
349
|
+
fout "File saved: #{@options[:saved_filespec]}"
|
350
|
+
end
|
351
|
+
break if [1, 2].include? sel
|
352
|
+
end
|
353
|
+
end
|
354
|
+
(opts[:ir_approve] = allow).tap_inspect name: :allow
|
355
|
+
|
356
|
+
selected = mdoc.get_block_by_name opts[:block_name]
|
144
357
|
|
145
358
|
if opts[:ir_approve]
|
146
359
|
write_command_file opts, required_blocks
|
147
360
|
command_execute opts, required_blocks.flatten.join("\n")
|
148
361
|
save_execution_output
|
149
362
|
output_execution_summary
|
363
|
+
output_execution_result
|
150
364
|
end
|
151
365
|
|
152
366
|
selected[:name]
|
153
367
|
end
|
154
368
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
end
|
160
|
-
.flatten(1)
|
161
|
-
.tap_inspect
|
162
|
-
end
|
163
|
-
|
164
|
-
def command_execute(opts, cmd2)
|
369
|
+
# :reek:DuplicateMethodCall
|
370
|
+
# :reek:UncommunicativeVariableName { exclude: [ e ] }
|
371
|
+
# :reek:LongYieldList
|
372
|
+
def command_execute(opts, command)
|
165
373
|
@execute_files = Hash.new([])
|
166
374
|
@execute_options = opts
|
167
375
|
@execute_started_at = Time.now.utc
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
until
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
376
|
+
|
377
|
+
Open3.popen3(@options[:shell], '-c', command) do |stdin, stdout, stderr, exec_thr|
|
378
|
+
# pid = exec_thr.pid # pid of the started process
|
379
|
+
|
380
|
+
Thread.new do
|
381
|
+
until (line = stdout.gets).nil?
|
382
|
+
@execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
|
383
|
+
print line if opts[:output_stdout]
|
384
|
+
yield nil, line, nil, exec_thr if block_given?
|
385
|
+
end
|
386
|
+
rescue IOError
|
387
|
+
# thread killed, do nothing
|
388
|
+
end
|
389
|
+
|
390
|
+
Thread.new do
|
391
|
+
until (line = stderr.gets).nil?
|
392
|
+
@execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
|
393
|
+
print line if opts[:output_stdout]
|
394
|
+
yield nil, nil, line, exec_thr if block_given?
|
395
|
+
end
|
396
|
+
rescue IOError
|
397
|
+
# thread killed, do nothing
|
398
|
+
end
|
399
|
+
|
400
|
+
in_thr = Thread.new do
|
401
|
+
while exec_thr.alive? # reading input until the child process ends
|
402
|
+
stdin.puts(line = $stdin.gets)
|
403
|
+
@execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line]
|
404
|
+
yield line, nil, nil, exec_thr if block_given?
|
190
405
|
end
|
191
|
-
rescue IOError => e
|
192
|
-
fout "IOError: #{e}"
|
193
406
|
end
|
194
|
-
|
407
|
+
|
408
|
+
exec_thr.join
|
409
|
+
in_thr.kill
|
410
|
+
# @return_code = exec_thr.value
|
195
411
|
end
|
412
|
+
@execute_completed_at = Time.now.utc
|
196
413
|
rescue Errno::ENOENT => e
|
197
414
|
# error triggered by missing command in script
|
198
415
|
@execute_aborted_at = Time.now.utc
|
199
416
|
@execute_error_message = e.message
|
200
417
|
@execute_error = e
|
201
|
-
@execute_files[
|
418
|
+
@execute_files[EF_STDERR] += [@execute_error_message]
|
419
|
+
fout "Error ENOENT: #{e.inspect}"
|
420
|
+
rescue SignalException => e
|
421
|
+
# SIGTERM triggered by user or system
|
422
|
+
@execute_aborted_at = Time.now.utc
|
423
|
+
@execute_error_message = 'SIGTERM'
|
424
|
+
@execute_error = e
|
425
|
+
@execute_files[EF_STDERR] += [@execute_error_message]
|
202
426
|
fout "Error ENOENT: #{e.inspect}"
|
203
427
|
end
|
204
428
|
|
@@ -211,10 +435,15 @@ module MarkdownExec
|
|
211
435
|
cnt / 2
|
212
436
|
end
|
213
437
|
|
438
|
+
# :reek:DuplicateMethodCall
|
214
439
|
def display_command(_opts, required_blocks)
|
440
|
+
frame = ' #=#=#'.yellow
|
441
|
+
fout frame
|
215
442
|
required_blocks.each { |cb| fout cb }
|
443
|
+
fout frame
|
216
444
|
end
|
217
445
|
|
446
|
+
# :reek:DuplicateMethodCall
|
218
447
|
def exec_block(options, _block_name = '')
|
219
448
|
options = default_options.merge options
|
220
449
|
update_options options, over: false
|
@@ -239,7 +468,8 @@ module MarkdownExec
|
|
239
468
|
run_last_script: -> { run_last_script },
|
240
469
|
select_recent_output: -> { select_recent_output },
|
241
470
|
select_recent_script: -> { select_recent_script },
|
242
|
-
tab_completions: -> { fout tab_completions }
|
471
|
+
tab_completions: -> { fout tab_completions },
|
472
|
+
menu_export: -> { fout menu_export }
|
243
473
|
}
|
244
474
|
simple_commands.each_key do |key|
|
245
475
|
if @options[key]
|
@@ -273,17 +503,15 @@ module MarkdownExec
|
|
273
503
|
puts data.to_yaml
|
274
504
|
end
|
275
505
|
|
276
|
-
|
277
|
-
|
278
|
-
end
|
279
|
-
|
280
|
-
def get_block_summary(opts, headings, block_title, current)
|
506
|
+
# :reek:LongParameterList
|
507
|
+
def get_block_summary(opts, headings:, block_title:, current:)
|
281
508
|
return [current] unless opts[:struct]
|
282
509
|
|
283
510
|
return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
|
284
511
|
|
285
512
|
bm = block_title.match(Regexp.new(opts[:block_name_match]))
|
286
|
-
reqs = block_title.scan(Regexp.new(opts[:block_required_scan]))
|
513
|
+
reqs = block_title.scan(Regexp.new(opts[:block_required_scan]))
|
514
|
+
.map { |scanned| scanned[1..] }
|
287
515
|
|
288
516
|
if bm && bm[1]
|
289
517
|
[summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
|
@@ -292,6 +520,20 @@ module MarkdownExec
|
|
292
520
|
end
|
293
521
|
end
|
294
522
|
|
523
|
+
def approved_fout?(level)
|
524
|
+
level <= @options[:display_level]
|
525
|
+
end
|
526
|
+
|
527
|
+
# display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
|
528
|
+
#
|
529
|
+
def lout(str, level: DISPLAY_LEVEL_BASE)
|
530
|
+
return unless approved_fout? level
|
531
|
+
|
532
|
+
# fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
|
533
|
+
fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
|
534
|
+
end
|
535
|
+
|
536
|
+
# :reek:DuplicateMethodCall
|
295
537
|
def list_blocks_in_file(call_options = {}, &options_block)
|
296
538
|
opts = optsmerge call_options, options_block
|
297
539
|
|
@@ -315,7 +557,7 @@ module MarkdownExec
|
|
315
557
|
File.readlines(opts[:filename]).each do |line|
|
316
558
|
continue unless line
|
317
559
|
|
318
|
-
if opts[:
|
560
|
+
if opts[:menu_blocks_with_headings]
|
319
561
|
if (lm = line.match(Regexp.new(opts[:heading3_match])))
|
320
562
|
headings = [headings[0], headings[1], lm[:name]]
|
321
563
|
elsif (lm = line.match(Regexp.new(opts[:heading2_match])))
|
@@ -329,7 +571,7 @@ module MarkdownExec
|
|
329
571
|
if in_block
|
330
572
|
if current
|
331
573
|
block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
|
332
|
-
blocks += get_block_summary opts, headings, block_title, current
|
574
|
+
blocks += get_block_summary opts, headings: headings, block_title: block_title, current: current
|
333
575
|
current = nil
|
334
576
|
end
|
335
577
|
in_block = false
|
@@ -338,16 +580,16 @@ module MarkdownExec
|
|
338
580
|
# new block
|
339
581
|
#
|
340
582
|
lm = line.match(fenced_start_ex)
|
341
|
-
|
583
|
+
block_allow = false
|
342
584
|
if opts[:bash_only]
|
343
|
-
|
585
|
+
block_allow = true if lm && (lm[:shell] == 'bash')
|
344
586
|
else
|
345
|
-
|
346
|
-
|
587
|
+
block_allow = true
|
588
|
+
block_allow = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
|
347
589
|
end
|
348
590
|
|
349
591
|
in_block = true
|
350
|
-
if
|
592
|
+
if block_allow && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
|
351
593
|
current = []
|
352
594
|
block_title = (lm && lm[:name])
|
353
595
|
end
|
@@ -360,39 +602,39 @@ module MarkdownExec
|
|
360
602
|
end
|
361
603
|
|
362
604
|
def list_default_env
|
363
|
-
|
364
|
-
|
365
|
-
next unless env_var.present?
|
605
|
+
menu_iter do |item|
|
606
|
+
next unless item[:env_var].present?
|
366
607
|
|
367
608
|
[
|
368
|
-
"#{env_var}=#{value_for_cli default}",
|
369
|
-
description.present? ? description : nil
|
609
|
+
"#{item[:env_var]}=#{value_for_cli item[:default]}",
|
610
|
+
item[:description].present? ? item[:description] : nil
|
370
611
|
].compact.join(' # ')
|
371
612
|
end.compact.sort
|
372
613
|
end
|
373
614
|
|
374
615
|
def list_default_yaml
|
375
|
-
|
376
|
-
|
377
|
-
next unless opt_name.present? && default.present?
|
616
|
+
menu_iter do |item|
|
617
|
+
next unless item[:opt_name].present? && item[:default].present?
|
378
618
|
|
379
619
|
[
|
380
|
-
"#{opt_name}: #{
|
381
|
-
description.present? ? description : nil
|
620
|
+
"#{item[:opt_name]}: #{OptionValue.new(item[:default]).for_yaml}",
|
621
|
+
item[:description].present? ? item[:description] : nil
|
382
622
|
].compact.join(' # ')
|
383
623
|
end.compact.sort
|
384
624
|
end
|
385
625
|
|
386
626
|
def list_files_per_options(options)
|
387
627
|
list_files_specified(
|
388
|
-
options[:filename]&.present? ? options[:filename] : nil,
|
389
|
-
options[:path],
|
390
|
-
'README.md',
|
391
|
-
'.'
|
628
|
+
specified_filename: options[:filename]&.present? ? options[:filename] : nil,
|
629
|
+
specified_folder: options[:path],
|
630
|
+
default_filename: 'README.md',
|
631
|
+
default_folder: '.'
|
392
632
|
).tap_inspect
|
393
633
|
end
|
394
634
|
|
395
|
-
|
635
|
+
# :reek:LongParameterList
|
636
|
+
def list_files_specified(specified_filename: nil, specified_folder: nil,
|
637
|
+
default_filename: nil, default_folder: nil, filetree: nil)
|
396
638
|
fn = File.join(if specified_filename&.present?
|
397
639
|
if specified_folder&.present?
|
398
640
|
[specified_folder, specified_filename]
|
@@ -431,51 +673,14 @@ module MarkdownExec
|
|
431
673
|
end.compact.tap_inspect
|
432
674
|
end
|
433
675
|
|
434
|
-
def list_recursively_required_blocks(table, name)
|
435
|
-
name_block = get_block_by_name(table, name)
|
436
|
-
raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
|
437
|
-
|
438
|
-
all = [name_block[:name]] + recursively_required(table, name_block[:reqs])
|
439
|
-
|
440
|
-
# in order of appearance in document
|
441
|
-
table.select { |block| all.include? block[:name] }
|
442
|
-
.map { |block| block.fetch(:body, '') }
|
443
|
-
.flatten(1)
|
444
|
-
.tap_inspect
|
445
|
-
end
|
446
|
-
|
447
|
-
def most_recent(arr)
|
448
|
-
return unless arr
|
449
|
-
return if arr.count < 1
|
450
|
-
|
451
|
-
arr.max.tap_inspect
|
452
|
-
end
|
453
|
-
|
454
|
-
def most_recent_list(arr)
|
455
|
-
return unless arr
|
456
|
-
return if (ac = arr.count) < 1
|
457
|
-
|
458
|
-
arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
|
459
|
-
end
|
460
|
-
|
461
676
|
def list_recent_output
|
462
|
-
|
463
|
-
|
677
|
+
Sfiles.new(@options[:saved_stdout_folder],
|
678
|
+
@options[:saved_stdout_glob]).most_recent_list
|
464
679
|
end
|
465
680
|
|
466
681
|
def list_recent_scripts
|
467
|
-
|
468
|
-
|
469
|
-
end
|
470
|
-
|
471
|
-
def make_block_label(block, call_options = {})
|
472
|
-
opts = options.merge(call_options)
|
473
|
-
if opts[:mdheadings]
|
474
|
-
heads = block.fetch(:headings, []).compact.join(' # ')
|
475
|
-
"#{block[:title]} [#{heads}] (#{opts[:filename]})"
|
476
|
-
else
|
477
|
-
"#{block[:title]} (#{opts[:filename]})"
|
478
|
-
end
|
682
|
+
Sfiles.new(@options[:saved_script_folder],
|
683
|
+
@options[:saved_script_glob]).most_recent_list
|
479
684
|
end
|
480
685
|
|
481
686
|
def make_block_labels(call_options = {})
|
@@ -483,102 +688,435 @@ module MarkdownExec
|
|
483
688
|
list_blocks_in_file(opts).map do |block|
|
484
689
|
# next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
|
485
690
|
|
486
|
-
|
691
|
+
BlockLabel.new(filename: opts[:filename],
|
692
|
+
headings: block.fetch(:headings, []),
|
693
|
+
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
694
|
+
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
695
|
+
title: block[:title]).make
|
487
696
|
end.compact.tap_inspect
|
488
697
|
end
|
489
698
|
|
490
|
-
|
699
|
+
# :reek:DuplicateMethodCall
|
700
|
+
# :reek:UncommunicativeMethodName ### temp
|
701
|
+
def menu_data1
|
491
702
|
val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
|
492
703
|
val_as_int = ->(value) { value.to_i }
|
493
704
|
val_as_str = ->(value) { value.to_s }
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
705
|
+
# val_true = ->(_value) { true } # for commands, sets option to true
|
706
|
+
menu_options = [
|
707
|
+
{
|
708
|
+
arg_name: 'PATH',
|
709
|
+
default: '.',
|
710
|
+
description: 'Read configuration file',
|
711
|
+
long_name: 'config',
|
712
|
+
proc1: lambda { |value|
|
713
|
+
read_configuration_file! options, value
|
714
|
+
}
|
715
|
+
},
|
716
|
+
{
|
717
|
+
arg_name: 'BOOL',
|
718
|
+
default: false,
|
719
|
+
description: 'Debug output',
|
720
|
+
env_var: 'MDE_DEBUG',
|
721
|
+
long_name: 'debug',
|
722
|
+
short_name: 'd',
|
723
|
+
proc1: lambda { |value|
|
724
|
+
tap_config value.to_i != 0
|
725
|
+
}
|
726
|
+
},
|
727
|
+
{
|
728
|
+
arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
|
729
|
+
default: DISPLAY_LEVEL_DEFAULT,
|
730
|
+
description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
|
731
|
+
env_var: 'MDE_DISPLAY_LEVEL',
|
732
|
+
long_name: 'display-level',
|
733
|
+
opt_name: :display_level,
|
734
|
+
proc1: val_as_int
|
735
|
+
},
|
736
|
+
{
|
737
|
+
arg_name: 'NAME',
|
738
|
+
compreply: false,
|
739
|
+
description: 'Name of block',
|
740
|
+
env_var: 'MDE_BLOCK_NAME',
|
741
|
+
long_name: 'block-name',
|
742
|
+
opt_name: :block_name,
|
743
|
+
short_name: 'f',
|
744
|
+
proc1: val_as_str
|
745
|
+
},
|
746
|
+
{
|
747
|
+
arg_name: 'RELATIVE_PATH',
|
748
|
+
compreply: '.',
|
749
|
+
description: 'Name of document',
|
750
|
+
env_var: 'MDE_FILENAME',
|
751
|
+
long_name: 'filename',
|
752
|
+
opt_name: :filename,
|
753
|
+
short_name: 'f',
|
754
|
+
proc1: val_as_str
|
755
|
+
},
|
756
|
+
{
|
757
|
+
description: 'List blocks',
|
758
|
+
long_name: 'list-blocks',
|
759
|
+
opt_name: :list_blocks,
|
760
|
+
proc1: val_as_bool
|
761
|
+
},
|
762
|
+
{
|
763
|
+
arg_name: 'INT.1-',
|
764
|
+
default: 32,
|
765
|
+
description: 'Max. items to return in list',
|
766
|
+
env_var: 'MDE_LIST_COUNT',
|
767
|
+
long_name: 'list-count',
|
768
|
+
opt_name: :list_count,
|
769
|
+
proc1: val_as_int
|
770
|
+
},
|
771
|
+
{
|
772
|
+
description: 'List default configuration as environment variables',
|
773
|
+
long_name: 'list-default-env',
|
774
|
+
opt_name: :list_default_env
|
775
|
+
},
|
776
|
+
{
|
777
|
+
description: 'List default configuration as YAML',
|
778
|
+
long_name: 'list-default-yaml',
|
779
|
+
opt_name: :list_default_yaml
|
780
|
+
},
|
781
|
+
{
|
782
|
+
description: 'List docs in current folder',
|
783
|
+
long_name: 'list-docs',
|
784
|
+
opt_name: :list_docs,
|
785
|
+
proc1: val_as_bool
|
786
|
+
},
|
787
|
+
{
|
788
|
+
description: 'List recent saved output',
|
789
|
+
long_name: 'list-recent-output',
|
790
|
+
opt_name: :list_recent_output,
|
791
|
+
proc1: val_as_bool
|
792
|
+
},
|
793
|
+
{
|
794
|
+
description: 'List recent saved scripts',
|
795
|
+
long_name: 'list-recent-scripts',
|
796
|
+
opt_name: :list_recent_scripts,
|
797
|
+
proc1: val_as_bool
|
798
|
+
},
|
799
|
+
{
|
800
|
+
arg_name: 'PREFIX',
|
801
|
+
default: MarkdownExec::BIN_NAME,
|
802
|
+
description: 'Name prefix for stdout files',
|
803
|
+
env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
|
804
|
+
long_name: 'logged-stdout-filename-prefix',
|
805
|
+
opt_name: :logged_stdout_filename_prefix,
|
806
|
+
proc1: val_as_str
|
807
|
+
},
|
808
|
+
{
|
809
|
+
arg_name: 'BOOL',
|
810
|
+
default: false,
|
811
|
+
description: 'Display document name in block selection menu',
|
812
|
+
env_var: 'MDE_MENU_BLOCKS_WITH_DOCNAME',
|
813
|
+
long_name: 'menu-blocks-with-docname',
|
814
|
+
opt_name: :menu_blocks_with_docname,
|
815
|
+
proc1: val_as_bool
|
816
|
+
},
|
817
|
+
{
|
818
|
+
arg_name: 'BOOL',
|
819
|
+
default: false,
|
820
|
+
description: 'Display headings (levels 1,2,3) in block selection menu',
|
821
|
+
env_var: 'MDE_MENU_BLOCKS_WITH_HEADINGS',
|
822
|
+
long_name: 'menu-blocks-with-headings',
|
823
|
+
opt_name: :menu_blocks_with_headings,
|
824
|
+
proc1: val_as_bool
|
825
|
+
},
|
826
|
+
{
|
827
|
+
arg_name: 'BOOL',
|
828
|
+
default: false,
|
829
|
+
description: 'Display summary for execution',
|
830
|
+
env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
|
831
|
+
long_name: 'output-execution-summary',
|
832
|
+
opt_name: :output_execution_summary,
|
833
|
+
proc1: val_as_bool
|
834
|
+
},
|
835
|
+
{
|
836
|
+
arg_name: 'BOOL',
|
837
|
+
default: false,
|
838
|
+
description: 'Display script prior to execution',
|
839
|
+
env_var: 'MDE_OUTPUT_SCRIPT',
|
840
|
+
long_name: 'output-script',
|
841
|
+
opt_name: :output_script,
|
842
|
+
proc1: val_as_bool
|
843
|
+
},
|
844
|
+
{
|
845
|
+
arg_name: 'BOOL',
|
846
|
+
default: true,
|
847
|
+
description: 'Display standard output from execution',
|
848
|
+
env_var: 'MDE_OUTPUT_STDOUT',
|
849
|
+
long_name: 'output-stdout',
|
850
|
+
opt_name: :output_stdout,
|
851
|
+
proc1: val_as_bool
|
852
|
+
},
|
853
|
+
{
|
854
|
+
arg_name: 'RELATIVE_PATH',
|
855
|
+
default: '.',
|
856
|
+
description: 'Path to documents',
|
857
|
+
env_var: 'MDE_PATH',
|
858
|
+
long_name: 'path',
|
859
|
+
opt_name: :path,
|
860
|
+
short_name: 'p',
|
861
|
+
proc1: val_as_str
|
862
|
+
},
|
863
|
+
{
|
864
|
+
description: 'Gem home folder',
|
865
|
+
long_name: 'pwd',
|
866
|
+
opt_name: :pwd,
|
867
|
+
proc1: val_as_bool
|
868
|
+
},
|
869
|
+
{
|
870
|
+
description: 'Run most recently saved script',
|
871
|
+
long_name: 'run-last-script',
|
872
|
+
opt_name: :run_last_script,
|
873
|
+
proc1: val_as_bool
|
874
|
+
},
|
875
|
+
{
|
876
|
+
arg_name: 'BOOL',
|
877
|
+
default: false,
|
878
|
+
description: 'Save executed script',
|
879
|
+
env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
|
880
|
+
long_name: 'save-executed-script',
|
881
|
+
opt_name: :save_executed_script,
|
882
|
+
proc1: val_as_bool
|
883
|
+
},
|
884
|
+
{
|
885
|
+
arg_name: 'BOOL',
|
886
|
+
default: false,
|
887
|
+
description: 'Save standard output of the executed script',
|
888
|
+
env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
|
889
|
+
long_name: 'save-execution-output',
|
890
|
+
opt_name: :save_execution_output,
|
891
|
+
proc1: val_as_bool
|
892
|
+
},
|
893
|
+
{
|
894
|
+
arg_name: 'INT',
|
895
|
+
default: 0o755,
|
896
|
+
description: 'chmod for saved scripts',
|
897
|
+
env_var: 'MDE_SAVED_SCRIPT_CHMOD',
|
898
|
+
long_name: 'saved-script-chmod',
|
899
|
+
opt_name: :saved_script_chmod,
|
900
|
+
proc1: val_as_int
|
901
|
+
},
|
902
|
+
{
|
903
|
+
arg_name: 'PREFIX',
|
904
|
+
default: MarkdownExec::BIN_NAME,
|
905
|
+
description: 'Name prefix for saved scripts',
|
906
|
+
env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
|
907
|
+
long_name: 'saved-script-filename-prefix',
|
908
|
+
opt_name: :saved_script_filename_prefix,
|
909
|
+
proc1: val_as_str
|
910
|
+
},
|
911
|
+
{
|
912
|
+
arg_name: 'RELATIVE_PATH',
|
913
|
+
default: 'logs',
|
914
|
+
description: 'Saved script folder',
|
915
|
+
env_var: 'MDE_SAVED_SCRIPT_FOLDER',
|
916
|
+
long_name: 'saved-script-folder',
|
917
|
+
opt_name: :saved_script_folder,
|
918
|
+
proc1: val_as_str
|
919
|
+
},
|
920
|
+
{
|
921
|
+
arg_name: 'GLOB',
|
922
|
+
default: 'mde_*.sh',
|
923
|
+
description: 'Glob matching saved scripts',
|
924
|
+
env_var: 'MDE_SAVED_SCRIPT_GLOB',
|
925
|
+
long_name: 'saved-script-glob',
|
926
|
+
opt_name: :saved_script_glob,
|
927
|
+
proc1: val_as_str
|
928
|
+
},
|
929
|
+
{
|
930
|
+
arg_name: 'RELATIVE_PATH',
|
931
|
+
default: 'logs',
|
932
|
+
description: 'Saved stdout folder',
|
933
|
+
env_var: 'MDE_SAVED_STDOUT_FOLDER',
|
934
|
+
long_name: 'saved-stdout-folder',
|
935
|
+
opt_name: :saved_stdout_folder,
|
936
|
+
proc1: val_as_str
|
937
|
+
},
|
938
|
+
{
|
939
|
+
arg_name: 'GLOB',
|
940
|
+
default: 'mde_*.out.txt',
|
941
|
+
description: 'Glob matching saved outputs',
|
942
|
+
env_var: 'MDE_SAVED_STDOUT_GLOB',
|
943
|
+
long_name: 'saved-stdout-glob',
|
944
|
+
opt_name: :saved_stdout_glob,
|
945
|
+
proc1: val_as_str
|
946
|
+
},
|
947
|
+
{
|
948
|
+
description: 'Select and execute a recently saved output',
|
949
|
+
long_name: 'select-recent-output',
|
950
|
+
opt_name: :select_recent_output,
|
951
|
+
proc1: val_as_bool
|
952
|
+
},
|
953
|
+
{
|
954
|
+
description: 'Select and execute a recently saved script',
|
955
|
+
long_name: 'select-recent-script',
|
956
|
+
opt_name: :select_recent_script,
|
957
|
+
proc1: val_as_bool
|
958
|
+
},
|
959
|
+
{
|
960
|
+
description: 'YAML export of menu',
|
961
|
+
long_name: 'menu-export',
|
962
|
+
opt_name: :menu_export,
|
963
|
+
proc1: val_as_bool
|
964
|
+
},
|
965
|
+
{
|
966
|
+
description: 'List tab completions',
|
967
|
+
long_name: 'tab-completions',
|
968
|
+
opt_name: :tab_completions,
|
969
|
+
proc1: val_as_bool
|
970
|
+
},
|
971
|
+
{
|
972
|
+
arg_name: 'BOOL',
|
973
|
+
default: true,
|
974
|
+
description: 'Pause for user to approve script',
|
975
|
+
env_var: 'MDE_USER_MUST_APPROVE',
|
976
|
+
long_name: 'user-must-approve',
|
977
|
+
opt_name: :user_must_approve,
|
978
|
+
proc1: val_as_bool
|
979
|
+
},
|
980
|
+
{
|
981
|
+
description: 'Show current configuration values',
|
982
|
+
short_name: '0',
|
983
|
+
proc1: lambda { |_|
|
984
|
+
options_finalize options
|
985
|
+
fout options.sort_by_key.to_yaml
|
986
|
+
}
|
987
|
+
},
|
988
|
+
{
|
989
|
+
description: 'App help',
|
990
|
+
long_name: 'help',
|
991
|
+
short_name: 'h',
|
992
|
+
proc1: lambda { |_|
|
993
|
+
fout menu_help
|
994
|
+
exit
|
995
|
+
}
|
996
|
+
},
|
997
|
+
{
|
998
|
+
description: "Print the gem's version",
|
999
|
+
long_name: 'version',
|
1000
|
+
short_name: 'v',
|
1001
|
+
proc1: lambda { |_|
|
1002
|
+
fout MarkdownExec::VERSION
|
1003
|
+
exit
|
1004
|
+
}
|
1005
|
+
},
|
1006
|
+
{
|
1007
|
+
description: 'Exit app',
|
1008
|
+
long_name: 'exit',
|
1009
|
+
short_name: 'x',
|
1010
|
+
proc1: ->(_) { exit }
|
1011
|
+
},
|
1012
|
+
{
|
1013
|
+
default: '^\(.*\)$',
|
1014
|
+
description: 'Pattern for blocks to hide from user-selection',
|
1015
|
+
env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
|
1016
|
+
opt_name: :block_name_excluded_match,
|
1017
|
+
proc1: val_as_str
|
1018
|
+
},
|
1019
|
+
{
|
1020
|
+
default: ':(?<title>\S+)( |$)',
|
1021
|
+
env_var: 'MDE_BLOCK_NAME_MATCH',
|
1022
|
+
opt_name: :block_name_match,
|
1023
|
+
proc1: val_as_str
|
1024
|
+
},
|
1025
|
+
{
|
1026
|
+
default: '\+\S+',
|
1027
|
+
env_var: 'MDE_BLOCK_REQUIRED_SCAN',
|
1028
|
+
opt_name: :block_required_scan,
|
1029
|
+
proc1: val_as_str
|
1030
|
+
},
|
1031
|
+
{
|
1032
|
+
default: '> ',
|
1033
|
+
env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
|
1034
|
+
opt_name: :display_level_xbase_prefix,
|
1035
|
+
proc1: val_as_str
|
1036
|
+
},
|
1037
|
+
{
|
1038
|
+
default: '^`{3,}',
|
1039
|
+
env_var: 'MDE_FENCED_START_AND_END_MATCH',
|
1040
|
+
opt_name: :fenced_start_and_end_match,
|
1041
|
+
proc1: val_as_str
|
1042
|
+
},
|
1043
|
+
{
|
1044
|
+
default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
|
1045
|
+
env_var: 'MDE_FENCED_START_EX_MATCH',
|
1046
|
+
opt_name: :fenced_start_ex_match,
|
1047
|
+
proc1: val_as_str
|
1048
|
+
},
|
1049
|
+
{
|
1050
|
+
default: '^# *(?<name>[^#]*?) *$',
|
1051
|
+
env_var: 'MDE_HEADING1_MATCH',
|
1052
|
+
opt_name: :heading1_match,
|
1053
|
+
proc1: val_as_str
|
1054
|
+
},
|
1055
|
+
{
|
1056
|
+
default: '^## *(?<name>[^#]*?) *$',
|
1057
|
+
env_var: 'MDE_HEADING2_MATCH',
|
1058
|
+
opt_name: :heading2_match,
|
1059
|
+
proc1: val_as_str
|
1060
|
+
},
|
1061
|
+
{
|
1062
|
+
default: '^### *(?<name>.+?) *$',
|
1063
|
+
env_var: 'MDE_HEADING3_MATCH',
|
1064
|
+
opt_name: :heading3_match,
|
1065
|
+
proc1: val_as_str
|
1066
|
+
},
|
1067
|
+
{
|
1068
|
+
default: '*.[Mm][Dd]',
|
1069
|
+
env_var: 'MDE_MD_FILENAME_GLOB',
|
1070
|
+
opt_name: :md_filename_glob,
|
1071
|
+
proc1: val_as_str
|
1072
|
+
},
|
1073
|
+
{
|
1074
|
+
default: '.+\\.md',
|
1075
|
+
env_var: 'MDE_MD_FILENAME_MATCH',
|
1076
|
+
opt_name: :md_filename_match,
|
1077
|
+
proc1: val_as_str
|
1078
|
+
},
|
1079
|
+
{
|
1080
|
+
description: 'Options for viewing saved output file',
|
1081
|
+
env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
|
1082
|
+
opt_name: :output_viewer_options,
|
1083
|
+
proc1: val_as_str
|
1084
|
+
},
|
1085
|
+
{
|
1086
|
+
default: 24,
|
1087
|
+
description: 'Maximum # of rows in select list',
|
1088
|
+
env_var: 'MDE_SELECT_PAGE_HEIGHT',
|
1089
|
+
opt_name: :select_page_height,
|
1090
|
+
proc1: val_as_int
|
1091
|
+
},
|
1092
|
+
{
|
1093
|
+
default: '#!/usr/bin/env',
|
1094
|
+
description: 'Shebang for saved scripts',
|
1095
|
+
env_var: 'MDE_SHEBANG',
|
1096
|
+
opt_name: :shebang,
|
1097
|
+
proc1: val_as_str
|
1098
|
+
},
|
1099
|
+
{
|
1100
|
+
default: 'bash',
|
1101
|
+
description: 'Shell for launched scripts',
|
1102
|
+
env_var: 'MDE_SHELL',
|
1103
|
+
opt_name: :shell,
|
1104
|
+
proc1: val_as_str
|
1105
|
+
}
|
564
1106
|
]
|
1107
|
+
# commands first, options second
|
1108
|
+
(menu_options.reject { |option| option[:arg_name] }) +
|
1109
|
+
(menu_options.select { |option| option[:arg_name] })
|
1110
|
+
end
|
565
1111
|
|
566
|
-
|
1112
|
+
def menu_iter(data = menu_data1, &block)
|
1113
|
+
data.map(&block)
|
567
1114
|
end
|
568
1115
|
|
569
1116
|
def menu_help
|
570
1117
|
@option_parser.help
|
571
1118
|
end
|
572
1119
|
|
573
|
-
def option_exclude_blocks(opts, blocks)
|
574
|
-
block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
|
575
|
-
if opts[:hide_blocks_by_name]
|
576
|
-
blocks.reject { |block| block[:name].match(block_name_excluded_match) }
|
577
|
-
else
|
578
|
-
blocks
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
1120
|
## post-parse options configuration
|
583
1121
|
#
|
584
1122
|
def options_finalize(rest)
|
@@ -600,6 +1138,7 @@ module MarkdownExec
|
|
600
1138
|
@options[:block_name] = block_name if block_name.present?
|
601
1139
|
end
|
602
1140
|
|
1141
|
+
# :reek:ControlParameter
|
603
1142
|
def optsmerge(call_options = {}, options_block = nil)
|
604
1143
|
class_call_options = @options.merge(call_options || {})
|
605
1144
|
if options_block
|
@@ -609,6 +1148,24 @@ module MarkdownExec
|
|
609
1148
|
end.tap_inspect
|
610
1149
|
end
|
611
1150
|
|
1151
|
+
def output_execution_result
|
1152
|
+
oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN],
|
1153
|
+
['Command',
|
1154
|
+
[MarkdownExec::BIN_NAME,
|
1155
|
+
@options[:filename],
|
1156
|
+
@options[:block_name]].join(' '),
|
1157
|
+
DISPLAY_LEVEL_ADMIN]]
|
1158
|
+
|
1159
|
+
[['Script', :saved_filespec],
|
1160
|
+
['StdOut', :logged_stdout_filespec]].each do |label, name|
|
1161
|
+
oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name]
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
oq.map do |label, value, level|
|
1165
|
+
lout ["#{label}:".yellow, value.to_s].join(' '), level: level
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
|
612
1169
|
def output_execution_summary
|
613
1170
|
return unless @options[:output_execution_summary]
|
614
1171
|
|
@@ -626,12 +1183,16 @@ module MarkdownExec
|
|
626
1183
|
|
627
1184
|
def prompt_with_quit(prompt_text, items, opts = {})
|
628
1185
|
exit_option = '* Exit'
|
629
|
-
|
630
|
-
|
631
|
-
|
1186
|
+
all_items = if @options[:menu_exit_at_top]
|
1187
|
+
(@options[:menu_with_exit] ? [exit_option] : []) + items
|
1188
|
+
else
|
1189
|
+
items + (@options[:menu_with_exit] ? [exit_option] : [])
|
1190
|
+
end
|
1191
|
+
sel = @prompt.select(prompt_text, all_items, opts.merge(filter: true))
|
632
1192
|
sel == exit_option ? nil : sel
|
633
1193
|
end
|
634
1194
|
|
1195
|
+
# :reek:UtilityFunction ### temp
|
635
1196
|
def read_configuration_file!(options, configuration_path)
|
636
1197
|
return unless File.exist?(configuration_path)
|
637
1198
|
|
@@ -641,23 +1202,7 @@ module MarkdownExec
|
|
641
1202
|
# rubocop:enable Security/YAMLLoad
|
642
1203
|
end
|
643
1204
|
|
644
|
-
|
645
|
-
all = []
|
646
|
-
rem = reqs
|
647
|
-
while rem.count.positive?
|
648
|
-
rem = rem.map do |req|
|
649
|
-
next if all.include? req
|
650
|
-
|
651
|
-
all += [req]
|
652
|
-
get_block_by_name(table, req).fetch(:reqs, [])
|
653
|
-
end
|
654
|
-
.compact
|
655
|
-
.flatten(1)
|
656
|
-
.tap_inspect(name: 'rem')
|
657
|
-
end
|
658
|
-
all.tap_inspect
|
659
|
-
end
|
660
|
-
|
1205
|
+
# :reek:NestedIterators
|
661
1206
|
def run
|
662
1207
|
## default configuration
|
663
1208
|
#
|
@@ -675,19 +1220,19 @@ module MarkdownExec
|
|
675
1220
|
"Usage: #{executable_name} [(path | filename [block_name])] [options]"
|
676
1221
|
].join("\n")
|
677
1222
|
|
678
|
-
|
679
|
-
|
680
|
-
next unless long_name.present? || short_name.present?
|
1223
|
+
menu_iter do |item|
|
1224
|
+
next unless item[:long_name].present? || item[:short_name].present?
|
681
1225
|
|
682
|
-
opts.on(*[if long_name.present?
|
683
|
-
"--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
|
1226
|
+
opts.on(*[if item[:long_name].present?
|
1227
|
+
"--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
|
684
1228
|
end,
|
685
|
-
short_name.present? ? "-#{short_name}" : nil,
|
686
|
-
[description,
|
687
|
-
default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
|
1229
|
+
item[:short_name].present? ? "-#{item[:short_name]}" : nil,
|
1230
|
+
[item[:description],
|
1231
|
+
item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
|
688
1232
|
lambda { |value|
|
689
|
-
ret = proc1.call(value)
|
690
|
-
|
1233
|
+
# ret = item[:proc1].call(value)
|
1234
|
+
ret = item[:proc1] ? item[:proc1].call(value) : value
|
1235
|
+
options[item[:opt_name]] = ret if item[:opt_name]
|
691
1236
|
ret
|
692
1237
|
}].compact)
|
693
1238
|
end
|
@@ -701,15 +1246,6 @@ module MarkdownExec
|
|
701
1246
|
exec_block options, options[:block_name]
|
702
1247
|
end
|
703
1248
|
|
704
|
-
FNR11 = '/'
|
705
|
-
FNR12 = ',;'
|
706
|
-
|
707
|
-
def saved_name_make(opts)
|
708
|
-
fne = opts[:filename].gsub(FNR11, FNR12)
|
709
|
-
"#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
|
710
|
-
',', opts[:block_name]].join('_')}.sh"
|
711
|
-
end
|
712
|
-
|
713
1249
|
def saved_name_split(name)
|
714
1250
|
mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
|
715
1251
|
return unless mf
|
@@ -719,58 +1255,91 @@ module MarkdownExec
|
|
719
1255
|
end
|
720
1256
|
|
721
1257
|
def run_last_script
|
722
|
-
filename =
|
723
|
-
|
1258
|
+
filename = Sfiles.new(@options[:saved_script_folder],
|
1259
|
+
@options[:saved_script_glob]).most_recent
|
724
1260
|
return unless filename
|
725
1261
|
|
726
|
-
filename.tap_inspect name: filename
|
727
1262
|
saved_name_split filename
|
728
1263
|
@options[:save_executed_script] = false
|
729
1264
|
select_and_approve_block
|
730
1265
|
end
|
731
1266
|
|
732
1267
|
def save_execution_output
|
1268
|
+
@options.tap_inspect name: :options
|
733
1269
|
return unless @options[:save_execution_output]
|
734
1270
|
|
735
|
-
fne = File.basename(@options[:filename], '.*')
|
736
|
-
|
737
1271
|
@options[:logged_stdout_filename] =
|
738
|
-
|
739
|
-
|
1272
|
+
SavedAsset.new(blockname: @options[:block_name],
|
1273
|
+
filename: File.basename(@options[:filename], '.*'),
|
1274
|
+
prefix: @options[:logged_stdout_filename_prefix],
|
1275
|
+
time: Time.now.utc).stdout_name
|
1276
|
+
|
740
1277
|
@options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
|
741
1278
|
@logged_stdout_filespec = @options[:logged_stdout_filespec]
|
742
|
-
dirname = File.dirname(@options[:logged_stdout_filespec])
|
1279
|
+
(dirname = File.dirname(@options[:logged_stdout_filespec])).tap_inspect name: :dirname
|
743
1280
|
Dir.mkdir dirname unless File.exist?(dirname)
|
744
|
-
|
1281
|
+
|
1282
|
+
ol = ["-STDOUT-\n"]
|
1283
|
+
ol += @execute_files&.fetch(EF_STDOUT, [])
|
1284
|
+
ol += ["\n-STDERR-\n"]
|
1285
|
+
ol += @execute_files&.fetch(EF_STDERR, [])
|
1286
|
+
ol += ["\n-STDIN-\n"]
|
1287
|
+
ol += @execute_files&.fetch(EF_STDIN, [])
|
1288
|
+
ol += ["\n"]
|
1289
|
+
File.write(@options[:logged_stdout_filespec], ol.join)
|
745
1290
|
end
|
746
1291
|
|
747
1292
|
def select_and_approve_block(call_options = {}, &options_block)
|
748
1293
|
opts = optsmerge call_options, options_block
|
749
1294
|
blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
|
1295
|
+
mdoc = MDoc.new(blocks_in_file)
|
750
1296
|
|
751
|
-
|
752
|
-
pt = (opts[:prompt_select_block]).to_s
|
753
|
-
blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
|
754
|
-
block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
|
1297
|
+
repeat_menu = true && !opts[:block_name].present?
|
755
1298
|
|
756
|
-
|
1299
|
+
loop do
|
1300
|
+
unless opts[:block_name].present?
|
1301
|
+
pt = (opts[:prompt_select_block]).to_s
|
757
1302
|
|
758
|
-
|
759
|
-
|
1303
|
+
blocks_in_file.each do |block|
|
1304
|
+
block.merge! label:
|
1305
|
+
BlockLabel.new(filename: opts[:filename],
|
1306
|
+
headings: block.fetch(:headings, []),
|
1307
|
+
menu_blocks_with_docname: opts[:menu_blocks_with_docname],
|
1308
|
+
menu_blocks_with_headings: opts[:menu_blocks_with_headings],
|
1309
|
+
title: block[:title]).make
|
1310
|
+
end
|
760
1311
|
|
761
|
-
|
762
|
-
|
763
|
-
|
1312
|
+
block_labels = mdoc.option_exclude_blocks(opts).map { |block| block[:label] }
|
1313
|
+
|
1314
|
+
return nil if block_labels.count.zero?
|
1315
|
+
|
1316
|
+
sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
|
1317
|
+
return nil if sel.nil?
|
1318
|
+
|
1319
|
+
# if sel.nil?
|
1320
|
+
# repeat_menu = false
|
1321
|
+
# break
|
1322
|
+
# end
|
1323
|
+
|
1324
|
+
label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
|
1325
|
+
opts[:block_name] = @options[:block_name] = label_block[:name]
|
1326
|
+
|
1327
|
+
end
|
1328
|
+
# if repeat_menu
|
1329
|
+
approve_block opts, mdoc
|
1330
|
+
# end
|
1331
|
+
|
1332
|
+
break unless repeat_menu
|
764
1333
|
|
765
|
-
|
1334
|
+
opts[:block_name] = ''
|
1335
|
+
end
|
766
1336
|
end
|
767
1337
|
|
768
|
-
def select_md_file(
|
1338
|
+
def select_md_file(files = list_markdown_files_in_path)
|
769
1339
|
opts = options
|
770
|
-
|
771
|
-
if files.count == 1
|
1340
|
+
if (count = files.count) == 1
|
772
1341
|
files[0]
|
773
|
-
elsif
|
1342
|
+
elsif count >= 2
|
774
1343
|
prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
|
775
1344
|
end
|
776
1345
|
end
|
@@ -796,20 +1365,25 @@ module MarkdownExec
|
|
796
1365
|
)
|
797
1366
|
end
|
798
1367
|
|
799
|
-
def sorted_keys(hash1)
|
800
|
-
hash1.keys.sort.to_h { |k| [k, hash1[k]] }
|
801
|
-
end
|
802
|
-
|
803
1368
|
def summarize_block(headings, title)
|
804
1369
|
{ headings: headings, name: title, title: title }
|
805
1370
|
end
|
806
1371
|
|
807
|
-
def
|
1372
|
+
def menu_export(data = menu_data1)
|
808
1373
|
data.map do |item|
|
809
|
-
|
1374
|
+
item.delete(:proc1)
|
1375
|
+
item
|
1376
|
+
end.to_yaml
|
1377
|
+
end
|
1378
|
+
|
1379
|
+
def tab_completions(data = menu_data1)
|
1380
|
+
data.map do |item|
|
1381
|
+
"--#{item[:long_name]}" if item[:long_name]
|
810
1382
|
end.compact
|
811
1383
|
end
|
812
1384
|
|
1385
|
+
# :reek:BooleanParameter
|
1386
|
+
# :reek:ControlParameter
|
813
1387
|
def update_options(opts = {}, over: true)
|
814
1388
|
if over
|
815
1389
|
@options = @options.merge opts
|
@@ -819,64 +1393,37 @@ module MarkdownExec
|
|
819
1393
|
@options.tap_inspect format: :yaml
|
820
1394
|
end
|
821
1395
|
|
822
|
-
def
|
823
|
-
|
824
|
-
when 'String'
|
825
|
-
"'#{value}'"
|
826
|
-
when 'FalseClass', 'TrueClass'
|
827
|
-
value ? '1' : '0'
|
828
|
-
when 'Integer'
|
829
|
-
value
|
830
|
-
else
|
831
|
-
value.to_s
|
832
|
-
end
|
833
|
-
end
|
834
|
-
|
835
|
-
def value_for_hash(value, default = nil)
|
836
|
-
return default if value.nil?
|
837
|
-
|
838
|
-
case value.class.to_s
|
839
|
-
when 'String', 'Integer', 'FalseClass', 'TrueClass'
|
840
|
-
value
|
841
|
-
when value.empty?
|
842
|
-
default
|
843
|
-
else
|
844
|
-
value.to_s
|
845
|
-
end
|
846
|
-
end
|
1396
|
+
def write_command_file(call_options, required_blocks)
|
1397
|
+
return unless call_options[:save_executed_script]
|
847
1398
|
|
848
|
-
|
849
|
-
|
1399
|
+
time_now = Time.now.utc
|
1400
|
+
opts = optsmerge call_options
|
1401
|
+
opts[:saved_script_filename] =
|
1402
|
+
SavedAsset.new(blockname: opts[:block_name],
|
1403
|
+
filename: opts[:filename],
|
1404
|
+
prefix: opts[:saved_script_filename_prefix],
|
1405
|
+
time: time_now).script_name
|
850
1406
|
|
851
|
-
case value.class.to_s
|
852
|
-
when 'String'
|
853
|
-
"'#{value}'"
|
854
|
-
when 'Integer'
|
855
|
-
value
|
856
|
-
when 'FalseClass', 'TrueClass'
|
857
|
-
value ? true : false
|
858
|
-
when value.empty?
|
859
|
-
default
|
860
|
-
else
|
861
|
-
value.to_s
|
862
|
-
end
|
863
|
-
end
|
864
|
-
|
865
|
-
def write_command_file(opts, required_blocks)
|
866
|
-
return unless opts[:save_executed_script]
|
867
|
-
|
868
|
-
opts[:saved_script_filename] = saved_name_make(opts)
|
869
1407
|
@execute_script_filespec =
|
870
1408
|
@options[:saved_filespec] =
|
871
1409
|
File.join opts[:saved_script_folder], opts[:saved_script_filename]
|
872
1410
|
|
873
1411
|
dirname = File.dirname(@options[:saved_filespec])
|
874
1412
|
Dir.mkdir dirname unless File.exist?(dirname)
|
875
|
-
|
1413
|
+
(shebang = if @options[:shebang]&.present?
|
1414
|
+
"#{@options[:shebang]} #{@options[:shell]}\n"
|
1415
|
+
else
|
1416
|
+
''
|
1417
|
+
end
|
1418
|
+
).tap_inspect name: :shebang
|
1419
|
+
File.write(@options[:saved_filespec], shebang +
|
876
1420
|
"# file_name: #{opts[:filename]}\n" \
|
877
1421
|
"# block_name: #{opts[:block_name]}\n" \
|
878
|
-
"# time: #{
|
1422
|
+
"# time: #{time_now}\n" \
|
879
1423
|
"#{required_blocks.flatten.join("\n")}\n")
|
1424
|
+
return if @options[:saved_script_chmod].zero?
|
1425
|
+
|
1426
|
+
File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
|
880
1427
|
end
|
881
1428
|
end
|
882
1429
|
end
|