markdown_exec 0.2.1 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -0
- data/CHANGELOG.md +132 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +7 -1
- data/README.md +117 -31
- data/Rakefile +18 -6
- data/assets/approve_code.png +0 -0
- data/assets/example_blocks.png +0 -0
- data/assets/output_of_execution.png +0 -0
- data/assets/select_a_block.png +0 -0
- data/assets/select_a_file.png +0 -0
- data/fixtures/bash1.md +12 -0
- data/fixtures/bash2.md +15 -0
- data/fixtures/exclude1.md +6 -0
- data/fixtures/exclude2.md +9 -0
- data/fixtures/exec1.md +8 -0
- data/fixtures/heading1.md +19 -0
- data/fixtures/sample1.md +9 -0
- data/fixtures/title1.md +6 -0
- data/lib/markdown_exec/version.rb +2 -2
- data/lib/markdown_exec.rb +613 -268
- metadata +21 -11
- data/assets/approve.png +0 -0
- data/assets/blocks.png +0 -0
- data/assets/executed.png +0 -0
- data/assets/select.png +0 -0
- data/assets/select_file.png +0 -0
data/lib/markdown_exec.rb
CHANGED
@@ -3,24 +3,53 @@
|
|
3
3
|
|
4
4
|
# encoding=utf-8
|
5
5
|
|
6
|
-
$pdebug = !(ENV['MARKDOWN_EXEC_DEBUG'] || '').empty?
|
7
|
-
|
8
6
|
require 'open3'
|
9
7
|
require 'optparse'
|
10
8
|
require 'tty-prompt'
|
11
9
|
require 'yaml'
|
12
10
|
|
11
|
+
##
|
12
|
+
# default if nil
|
13
|
+
# false if empty or '0'
|
14
|
+
# else true
|
15
|
+
|
16
|
+
def env_bool(name, default: false)
|
17
|
+
return default if name.nil? || (val = ENV[name]).nil?
|
18
|
+
return false if val.empty? || val == '0'
|
19
|
+
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def env_int(name, default: 0)
|
24
|
+
return default if name.nil? || (val = ENV[name]).nil?
|
25
|
+
return default if val.empty?
|
26
|
+
|
27
|
+
val.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def env_str(name, default: '')
|
31
|
+
return default if name.nil? || (val = ENV[name]).nil?
|
32
|
+
|
33
|
+
val || default
|
34
|
+
end
|
35
|
+
|
36
|
+
$pdebug = env_bool 'MDE_DEBUG'
|
37
|
+
|
13
38
|
require_relative 'markdown_exec/version'
|
14
39
|
|
15
40
|
$stderr.sync = true
|
16
41
|
$stdout.sync = true
|
17
42
|
|
18
43
|
BLOCK_SIZE = 1024
|
19
|
-
SELECT_PAGE_HEIGHT = 12
|
20
44
|
|
21
45
|
class Object # rubocop:disable Style/Documentation
|
22
46
|
def present?
|
23
|
-
self
|
47
|
+
case self.class.to_s
|
48
|
+
when 'FalseClass', 'TrueClass'
|
49
|
+
true
|
50
|
+
else
|
51
|
+
self && (!respond_to?(:blank?) || !blank?)
|
52
|
+
end
|
24
53
|
end
|
25
54
|
end
|
26
55
|
|
@@ -31,6 +60,30 @@ class String # rubocop:disable Style/Documentation
|
|
31
60
|
end
|
32
61
|
end
|
33
62
|
|
63
|
+
public
|
64
|
+
|
65
|
+
# debug output
|
66
|
+
#
|
67
|
+
def tap_inspect(format: nil, name: 'return')
|
68
|
+
return self unless $pdebug
|
69
|
+
|
70
|
+
fn = case format
|
71
|
+
when :json
|
72
|
+
:to_json
|
73
|
+
when :string
|
74
|
+
:to_s
|
75
|
+
when :yaml
|
76
|
+
:to_yaml
|
77
|
+
else
|
78
|
+
:inspect
|
79
|
+
end
|
80
|
+
|
81
|
+
puts "-> #{caller[0].scan(/in `?(\S+)'$/)[0][0]}()" \
|
82
|
+
" #{name}: #{method(fn).call}"
|
83
|
+
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
34
87
|
module MarkdownExec
|
35
88
|
class Error < StandardError; end
|
36
89
|
|
@@ -41,6 +94,38 @@ module MarkdownExec
|
|
41
94
|
|
42
95
|
def initialize(options = {})
|
43
96
|
@options = options
|
97
|
+
@prompt = TTY::Prompt.new(interrupt: :exit)
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# options necessary to start, parse input, defaults for cli options
|
102
|
+
|
103
|
+
def base_options
|
104
|
+
menu_data
|
105
|
+
.map do |_long_name, _short_name, env_var, _arg_name, _description, opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
|
106
|
+
next unless opt_name.present?
|
107
|
+
|
108
|
+
[opt_name, env_bool(env_var, default: value_for_hash(default))]
|
109
|
+
end.compact.to_h.merge(
|
110
|
+
{
|
111
|
+
mdheadings: true, # use headings (levels 1,2,3) in block lable
|
112
|
+
menu_with_exit: true
|
113
|
+
}
|
114
|
+
).tap_inspect format: :yaml
|
115
|
+
end
|
116
|
+
|
117
|
+
def default_options
|
118
|
+
{
|
119
|
+
bash: true, # bash block parsing in get_block_summary()
|
120
|
+
exclude_expect_blocks: true,
|
121
|
+
hide_blocks_by_name: true,
|
122
|
+
output_saved_script_filename: false,
|
123
|
+
prompt_approve_block: 'Process?',
|
124
|
+
prompt_select_block: 'Choose a block:',
|
125
|
+
prompt_select_md: 'Choose a file:',
|
126
|
+
saved_script_filename: nil, # calculated
|
127
|
+
struct: true # allow get_block_summary()
|
128
|
+
}
|
44
129
|
end
|
45
130
|
|
46
131
|
# Returns true if all files are EOF
|
@@ -49,16 +134,155 @@ module MarkdownExec
|
|
49
134
|
files.find { |f| !f.eof }.nil?
|
50
135
|
end
|
51
136
|
|
137
|
+
def approve_block(opts, blocks_in_file)
|
138
|
+
required_blocks = list_recursively_required_blocks(blocks_in_file, opts[:block_name])
|
139
|
+
display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
|
140
|
+
|
141
|
+
allow = true
|
142
|
+
allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve]
|
143
|
+
opts[:ir_approve] = allow
|
144
|
+
selected = get_block_by_name blocks_in_file, opts[:block_name]
|
145
|
+
|
146
|
+
if opts[:ir_approve]
|
147
|
+
write_command_file(opts, required_blocks) if opts[:save_executed_script]
|
148
|
+
command_execute opts, required_blocks.flatten.join("\n")
|
149
|
+
end
|
150
|
+
|
151
|
+
selected[:name]
|
152
|
+
end
|
153
|
+
|
154
|
+
def code(table, block)
|
155
|
+
all = [block[:name]] + recursively_required(table, block[:reqs])
|
156
|
+
all.reverse.map do |req|
|
157
|
+
get_block_by_name(table, req).fetch(:body, '')
|
158
|
+
end
|
159
|
+
.flatten(1)
|
160
|
+
.tap_inspect
|
161
|
+
end
|
162
|
+
|
163
|
+
def command_execute(opts, cmd2)
|
164
|
+
@execute_files = Hash.new([])
|
165
|
+
@execute_options = opts
|
166
|
+
@execute_started_at = Time.now.utc
|
167
|
+
Open3.popen3(cmd2) do |stdin, stdout, stderr|
|
168
|
+
stdin.close_write
|
169
|
+
begin
|
170
|
+
files = [stdout, stderr]
|
171
|
+
|
172
|
+
until all_at_eof(files)
|
173
|
+
ready = IO.select(files)
|
174
|
+
|
175
|
+
next unless ready
|
176
|
+
|
177
|
+
# readable = ready[0]
|
178
|
+
# # writable = ready[1]
|
179
|
+
# # exceptions = ready[2]
|
180
|
+
ready.each.with_index do |readable, ind|
|
181
|
+
readable.each do |f|
|
182
|
+
block = f.read_nonblock(BLOCK_SIZE)
|
183
|
+
@execute_files[ind] = @execute_files[ind] + [block]
|
184
|
+
print block if opts[:output_stdout]
|
185
|
+
rescue EOFError #=> e
|
186
|
+
# do nothing at EOF
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
rescue IOError => e
|
191
|
+
fout "IOError: #{e}"
|
192
|
+
end
|
193
|
+
@execute_completed_at = Time.now.utc
|
194
|
+
end
|
195
|
+
rescue Errno::ENOENT => e
|
196
|
+
# error triggered by missing command in script
|
197
|
+
@execute_aborted_at = Time.now.utc
|
198
|
+
@execute_error_message = e.message
|
199
|
+
@execute_error = e
|
200
|
+
@execute_files[1] = e.message
|
201
|
+
fout "Error ENOENT: #{e.inspect}"
|
202
|
+
end
|
203
|
+
|
52
204
|
def count_blocks_in_filename
|
205
|
+
fenced_start_and_end_match = Regexp.new @options[:fenced_start_and_end_match]
|
53
206
|
cnt = 0
|
54
|
-
File.readlines(options[:filename]).each do |line|
|
55
|
-
cnt += 1 if line.match(
|
207
|
+
File.readlines(@options[:filename]).each do |line|
|
208
|
+
cnt += 1 if line.match(fenced_start_and_end_match)
|
56
209
|
end
|
57
210
|
cnt / 2
|
58
211
|
end
|
59
212
|
|
213
|
+
def display_command(_opts, required_blocks)
|
214
|
+
required_blocks.each { |cb| fout cb }
|
215
|
+
end
|
216
|
+
|
217
|
+
def exec_block(options, _block_name = '')
|
218
|
+
options = default_options.merge options
|
219
|
+
update_options options, over: false
|
220
|
+
|
221
|
+
# document and block reports
|
222
|
+
#
|
223
|
+
files = list_files_per_options(options)
|
224
|
+
if @options[:list_blocks]
|
225
|
+
fout_list (files.map do |file|
|
226
|
+
make_block_labels(filename: file, struct: true)
|
227
|
+
end).flatten(1)
|
228
|
+
return
|
229
|
+
end
|
230
|
+
|
231
|
+
if @options[:list_default_yaml]
|
232
|
+
fout_list list_default_yaml
|
233
|
+
return
|
234
|
+
end
|
235
|
+
|
236
|
+
if @options[:list_docs]
|
237
|
+
fout_list files
|
238
|
+
return
|
239
|
+
end
|
240
|
+
|
241
|
+
if @options[:list_default_env]
|
242
|
+
fout_list list_default_env
|
243
|
+
return
|
244
|
+
end
|
245
|
+
|
246
|
+
if @options[:list_recent_scripts]
|
247
|
+
fout_list list_recent_scripts
|
248
|
+
return
|
249
|
+
end
|
250
|
+
|
251
|
+
if @options[:run_last_script]
|
252
|
+
run_last_script
|
253
|
+
return
|
254
|
+
end
|
255
|
+
|
256
|
+
if @options[:select_recent_script]
|
257
|
+
select_recent_script
|
258
|
+
return
|
259
|
+
end
|
260
|
+
|
261
|
+
# process
|
262
|
+
#
|
263
|
+
@options[:filename] = select_md_file(files)
|
264
|
+
select_and_approve_block(
|
265
|
+
bash: true,
|
266
|
+
struct: true
|
267
|
+
)
|
268
|
+
fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
|
269
|
+
save_execution_output
|
270
|
+
output_execution_summary
|
271
|
+
end
|
272
|
+
|
273
|
+
# standard output; not for debug
|
274
|
+
#
|
60
275
|
def fout(str)
|
61
|
-
puts str
|
276
|
+
puts str
|
277
|
+
end
|
278
|
+
|
279
|
+
def fout_list(str)
|
280
|
+
puts str
|
281
|
+
end
|
282
|
+
|
283
|
+
def fout_section(name, data)
|
284
|
+
puts "# #{name}"
|
285
|
+
puts data.to_yaml
|
62
286
|
end
|
63
287
|
|
64
288
|
def get_block_by_name(table, name, default = {})
|
@@ -70,11 +294,11 @@ module MarkdownExec
|
|
70
294
|
|
71
295
|
return [summarize_block(headings, block_title).merge({ body: current })] unless opts[:bash]
|
72
296
|
|
73
|
-
bm = block_title.match(
|
74
|
-
reqs = block_title.scan(
|
297
|
+
bm = block_title.match(Regexp.new(opts[:block_name_match]))
|
298
|
+
reqs = block_title.scan(Regexp.new(opts[:block_required_scan])).map { |s| s[1..] }
|
75
299
|
|
76
300
|
if bm && bm[1]
|
77
|
-
[summarize_block(headings, bm[
|
301
|
+
[summarize_block(headings, bm[:title]).merge({ body: current, reqs: reqs })]
|
78
302
|
else
|
79
303
|
[summarize_block(headings, block_title).merge({ body: current, reqs: reqs })]
|
80
304
|
end
|
@@ -88,136 +312,148 @@ module MarkdownExec
|
|
88
312
|
exit 1
|
89
313
|
end
|
90
314
|
|
315
|
+
fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
|
316
|
+
fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
|
317
|
+
block_title = ''
|
91
318
|
blocks = []
|
92
319
|
current = nil
|
93
|
-
in_block = false
|
94
|
-
block_title = ''
|
95
|
-
|
96
320
|
headings = []
|
321
|
+
in_block = false
|
97
322
|
File.readlines(opts[:filename]).each do |line|
|
98
323
|
continue unless line
|
99
324
|
|
100
325
|
if opts[:mdheadings]
|
101
|
-
if (lm = line.match(
|
102
|
-
headings = [headings[0], headings[1], lm[
|
103
|
-
elsif (lm = line.match(
|
104
|
-
headings = [headings[0], lm[
|
105
|
-
elsif (lm = line.match(
|
106
|
-
headings = [lm[
|
326
|
+
if (lm = line.match(Regexp.new(opts[:heading3_match])))
|
327
|
+
headings = [headings[0], headings[1], lm[:name]]
|
328
|
+
elsif (lm = line.match(Regexp.new(opts[:heading2_match])))
|
329
|
+
headings = [headings[0], lm[:name]]
|
330
|
+
elsif (lm = line.match(Regexp.new(opts[:heading1_match])))
|
331
|
+
headings = [lm[:name]]
|
107
332
|
end
|
108
333
|
end
|
109
334
|
|
110
|
-
if line.match(
|
335
|
+
if line.match(fenced_start_and_end_match)
|
111
336
|
if in_block
|
112
337
|
if current
|
113
|
-
|
114
338
|
block_title = current.join(' ').gsub(/ +/, ' ')[0..64] if block_title.nil? || block_title.empty?
|
115
|
-
|
116
339
|
blocks += get_block_summary opts, headings, block_title, current
|
117
340
|
current = nil
|
118
341
|
end
|
119
342
|
in_block = false
|
120
343
|
block_title = ''
|
121
344
|
else
|
122
|
-
|
345
|
+
# new block
|
123
346
|
#
|
124
|
-
|
125
|
-
lm = line.match(/^`{3,}([^`\s]*) *(.*)$/)
|
347
|
+
lm = line.match(fenced_start_ex)
|
126
348
|
do1 = false
|
127
349
|
if opts[:bash_only]
|
128
|
-
do1 = true if lm && (lm[
|
350
|
+
do1 = true if lm && (lm[:shell] == 'bash')
|
129
351
|
else
|
130
352
|
do1 = true
|
131
|
-
do1 = !(lm && (lm[
|
132
|
-
|
133
|
-
# if do1 && opts[:exclude_matching_block_names]
|
134
|
-
# puts " MW a4"
|
135
|
-
# puts " MW a4 #{(lm[2].match %r{^:\(.+\)$})}"
|
136
|
-
# do1 = !(lm && (lm[2].match %r{^:\(.+\)$}))
|
137
|
-
# end
|
353
|
+
do1 = !(lm && (lm[:shell] == 'expect')) if opts[:exclude_expect_blocks]
|
138
354
|
end
|
139
355
|
|
140
356
|
in_block = true
|
141
|
-
if do1 && (!opts[:title_match] || (lm && lm[
|
357
|
+
if do1 && (!opts[:title_match] || (lm && lm[:name] && lm[:name].match(opts[:title_match])))
|
142
358
|
current = []
|
143
|
-
block_title = (lm && lm[
|
359
|
+
block_title = (lm && lm[:name])
|
144
360
|
end
|
145
361
|
end
|
146
362
|
elsif current
|
147
363
|
current += [line.chomp]
|
148
364
|
end
|
149
365
|
end
|
150
|
-
blocks.
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
366
|
+
blocks.tap_inspect
|
367
|
+
end
|
368
|
+
|
369
|
+
def list_default_env
|
370
|
+
menu_data
|
371
|
+
.map do |_long_name, _short_name, env_var, _arg_name, description, _opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
|
372
|
+
next unless env_var.present?
|
373
|
+
|
374
|
+
[
|
375
|
+
"#{env_var}=#{value_for_cli default}",
|
376
|
+
description.present? ? description : nil
|
377
|
+
].compact.join(' # ')
|
378
|
+
end.compact.sort
|
379
|
+
end
|
380
|
+
|
381
|
+
def list_default_yaml
|
382
|
+
menu_data
|
383
|
+
.map do |_long_name, _short_name, _env_var, _arg_name, description, opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
|
384
|
+
next unless opt_name.present? && default.present?
|
385
|
+
|
386
|
+
[
|
387
|
+
"#{opt_name}: #{value_for_yaml default}",
|
388
|
+
description.present? ? description : nil
|
389
|
+
].compact.join(' # ')
|
390
|
+
end.compact.sort
|
155
391
|
end
|
156
392
|
|
157
393
|
def list_files_per_options(options)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
394
|
+
list_files_specified(
|
395
|
+
options[:filename]&.present? ? options[:filename] : nil,
|
396
|
+
options[:path],
|
397
|
+
'README.md',
|
398
|
+
'.'
|
399
|
+
).tap_inspect
|
165
400
|
end
|
166
401
|
|
167
402
|
def list_files_specified(specified_filename, specified_folder, default_filename, default_folder, filetree = nil)
|
168
|
-
fn = if specified_filename&.present?
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
403
|
+
fn = File.join(if specified_filename&.present?
|
404
|
+
if specified_folder&.present?
|
405
|
+
[specified_folder, specified_filename]
|
406
|
+
elsif specified_filename.start_with? '/'
|
407
|
+
[specified_filename]
|
408
|
+
else
|
409
|
+
[default_folder, specified_filename]
|
410
|
+
end
|
411
|
+
elsif specified_folder&.present?
|
412
|
+
if filetree
|
413
|
+
[specified_folder, @options[:md_filename_match]]
|
414
|
+
else
|
415
|
+
[specified_folder, @options[:md_filename_glob]]
|
416
|
+
end
|
417
|
+
else
|
418
|
+
[default_folder, default_filename]
|
419
|
+
end)
|
183
420
|
if filetree
|
184
421
|
filetree.select { |filename| filename == fn || filename.match(/^#{fn}$/) || filename.match(%r{^#{fn}/.+$}) }
|
185
422
|
else
|
186
423
|
Dir.glob(fn)
|
187
|
-
end.
|
424
|
+
end.tap_inspect
|
188
425
|
end
|
189
426
|
|
190
|
-
def
|
191
|
-
Dir.glob(File.join(options[:
|
427
|
+
def list_markdown_files_in_path
|
428
|
+
Dir.glob(File.join(@options[:path], @options[:md_filename_glob])).tap_inspect
|
192
429
|
end
|
193
430
|
|
194
431
|
def list_named_blocks_in_file(call_options = {}, &options_block)
|
195
432
|
opts = optsmerge call_options, options_block
|
433
|
+
block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
|
196
434
|
list_blocks_in_file(opts).map do |block|
|
197
|
-
next if opts[:
|
435
|
+
next if opts[:hide_blocks_by_name] && block[:name].match(block_name_excluded_match)
|
198
436
|
|
199
437
|
block
|
200
|
-
end.compact.
|
201
|
-
end
|
202
|
-
|
203
|
-
def code(table, block)
|
204
|
-
all = [block[:name]] + recursively_required(table, block[:reqs])
|
205
|
-
all.reverse.map do |req|
|
206
|
-
get_block_by_name(table, req).fetch(:body, '')
|
207
|
-
end
|
208
|
-
.flatten(1)
|
209
|
-
.tap { |ret| puts "code() ret: #{ret.inspect}" if $pdebug }
|
438
|
+
end.compact.tap_inspect
|
210
439
|
end
|
211
440
|
|
212
441
|
def list_recursively_required_blocks(table, name)
|
213
442
|
name_block = get_block_by_name(table, name)
|
443
|
+
raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
|
444
|
+
|
214
445
|
all = [name_block[:name]] + recursively_required(table, name_block[:reqs])
|
215
446
|
|
216
447
|
# in order of appearance in document
|
217
448
|
table.select { |block| all.include? block[:name] }
|
218
449
|
.map { |block| block.fetch(:body, '') }
|
219
450
|
.flatten(1)
|
220
|
-
.
|
451
|
+
.tap_inspect
|
452
|
+
end
|
453
|
+
|
454
|
+
def list_recent_scripts
|
455
|
+
Dir.glob(File.join(@options[:saved_script_folder],
|
456
|
+
@options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].reverse.tap_inspect
|
221
457
|
end
|
222
458
|
|
223
459
|
def make_block_label(block, call_options = {})
|
@@ -233,37 +469,166 @@ module MarkdownExec
|
|
233
469
|
def make_block_labels(call_options = {})
|
234
470
|
opts = options.merge(call_options)
|
235
471
|
list_blocks_in_file(opts).map do |block|
|
236
|
-
# next if opts[:
|
472
|
+
# next if opts[:hide_blocks_by_name] && block[:name].match(%r{^:\(.+\)$})
|
237
473
|
|
238
474
|
make_block_label block, opts
|
239
|
-
end.compact.
|
475
|
+
end.compact.tap_inspect
|
476
|
+
end
|
477
|
+
|
478
|
+
def menu_data
|
479
|
+
proc_self = ->(value) { value }
|
480
|
+
proc_to_i = ->(value) { value.to_i != 0 }
|
481
|
+
proc_true = ->(_) { true }
|
482
|
+
|
483
|
+
summary_head = [
|
484
|
+
['config', nil, nil, 'PATH', 'Read configuration file',
|
485
|
+
nil, '.', ->(value) { read_configuration_file! options, value }],
|
486
|
+
['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output',
|
487
|
+
nil, false, ->(value) { $pdebug = value.to_i != 0 }]
|
488
|
+
]
|
489
|
+
|
490
|
+
summary_body = [
|
491
|
+
['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document',
|
492
|
+
:filename, nil, proc_self],
|
493
|
+
['list-blocks', nil, nil, nil, 'List blocks',
|
494
|
+
:list_blocks, nil, proc_true],
|
495
|
+
['list-default-env', nil, nil, nil, 'List default configuration as environment variables',
|
496
|
+
:list_default_env, nil, proc_true],
|
497
|
+
['list-default-yaml', nil, nil, nil, 'List default configuration as YAML',
|
498
|
+
:list_default_yaml, nil, proc_true],
|
499
|
+
['list-docs', nil, nil, nil, 'List docs in current folder',
|
500
|
+
:list_docs, nil, proc_true],
|
501
|
+
['list-recent-scripts', nil, nil, nil, 'List recent saved scripts',
|
502
|
+
:list_recent_scripts, nil, proc_true],
|
503
|
+
['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution',
|
504
|
+
:output_execution_summary, false, proc_to_i],
|
505
|
+
['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script prior to execution',
|
506
|
+
:output_script, false, proc_to_i],
|
507
|
+
['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution',
|
508
|
+
:output_stdout, true, proc_to_i],
|
509
|
+
['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents',
|
510
|
+
:path, nil, proc_self],
|
511
|
+
['run-last-script', nil, nil, nil, 'Run most recently saved script',
|
512
|
+
:run_last_script, nil, proc_true],
|
513
|
+
['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script',
|
514
|
+
:select_recent_script, nil, proc_true],
|
515
|
+
['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save execution output',
|
516
|
+
:save_execution_output, false, proc_to_i],
|
517
|
+
['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script',
|
518
|
+
:save_executed_script, false, proc_to_i],
|
519
|
+
['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder',
|
520
|
+
:saved_script_folder, 'logs', proc_self],
|
521
|
+
['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder',
|
522
|
+
:saved_stdout_folder, 'logs', proc_self],
|
523
|
+
['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause for user to approve script',
|
524
|
+
:user_must_approve, true, proc_to_i]
|
525
|
+
]
|
526
|
+
|
527
|
+
# rubocop:disable Style/Semicolon
|
528
|
+
summary_tail = [
|
529
|
+
[nil, '0', nil, nil, 'Show current configuration values',
|
530
|
+
nil, nil, ->(_) { options_finalize options; fout sorted_keys(options).to_yaml }],
|
531
|
+
['help', 'h', nil, nil, 'App help',
|
532
|
+
nil, nil, ->(_) { fout menu_help; exit }],
|
533
|
+
['version', 'v', nil, nil, 'App version',
|
534
|
+
nil, nil, ->(_) { fout MarkdownExec::VERSION; exit }],
|
535
|
+
['exit', 'x', nil, nil, 'Exit app',
|
536
|
+
nil, nil, ->(_) { exit }]
|
537
|
+
]
|
538
|
+
# rubocop:enable Style/Semicolon
|
539
|
+
|
540
|
+
env_vars = [
|
541
|
+
[nil, nil, 'MDE_BLOCK_NAME_EXCLUDED_MATCH', nil, 'Pattern for blocks to hide from user-selection',
|
542
|
+
:block_name_excluded_match, '^\(.+\)$', nil],
|
543
|
+
[nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', nil],
|
544
|
+
[nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', nil],
|
545
|
+
[nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', nil],
|
546
|
+
[nil, nil, 'MDE_FENCED_START_EX_MATCH', nil, '', :fenced_start_ex_match,
|
547
|
+
'^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$', nil],
|
548
|
+
[nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', nil],
|
549
|
+
[nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', nil],
|
550
|
+
[nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', nil],
|
551
|
+
[nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', nil],
|
552
|
+
[nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', nil],
|
553
|
+
[nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, nil]
|
554
|
+
# [nil, nil, 'MDE_', nil, '', nil, '', nil],
|
555
|
+
]
|
556
|
+
|
557
|
+
summary_head + summary_body + summary_tail + env_vars
|
558
|
+
end
|
559
|
+
|
560
|
+
def menu_help
|
561
|
+
@option_parser.help
|
240
562
|
end
|
241
563
|
|
242
564
|
def option_exclude_blocks(opts, blocks)
|
243
|
-
|
244
|
-
|
565
|
+
block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
|
566
|
+
if opts[:hide_blocks_by_name]
|
567
|
+
blocks.reject { |block| block[:name].match(block_name_excluded_match) }
|
245
568
|
else
|
246
569
|
blocks
|
247
570
|
end
|
248
571
|
end
|
249
572
|
|
573
|
+
## post-parse options configuration
|
574
|
+
#
|
575
|
+
def options_finalize(rest)
|
576
|
+
## position 0: file or folder (optional)
|
577
|
+
#
|
578
|
+
if (pos = rest.fetch(0, nil))&.present?
|
579
|
+
if Dir.exist?(pos)
|
580
|
+
@options[:path] = pos
|
581
|
+
elsif File.exist?(pos)
|
582
|
+
@options[:filename] = pos
|
583
|
+
else
|
584
|
+
raise "Invalid parameter: #{pos}"
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
## position 1: block name (optional)
|
589
|
+
#
|
590
|
+
@options[:block_name] = rest.fetch(1, nil)
|
591
|
+
end
|
592
|
+
|
250
593
|
def optsmerge(call_options = {}, options_block = nil)
|
251
|
-
class_call_options = options.merge(call_options || {})
|
594
|
+
class_call_options = @options.merge(call_options || {})
|
252
595
|
if options_block
|
253
596
|
options_block.call class_call_options
|
254
597
|
else
|
255
598
|
class_call_options
|
256
|
-
end.
|
599
|
+
end.tap_inspect
|
600
|
+
end
|
601
|
+
|
602
|
+
def output_execution_summary
|
603
|
+
return unless @options[:output_execution_summary]
|
604
|
+
|
605
|
+
fout_section 'summary', {
|
606
|
+
execute_aborted_at: @execute_aborted_at,
|
607
|
+
execute_completed_at: @execute_completed_at,
|
608
|
+
execute_error: @execute_error,
|
609
|
+
execute_error_message: @execute_error_message,
|
610
|
+
execute_files: @execute_files,
|
611
|
+
execute_options: @execute_options,
|
612
|
+
execute_started_at: @execute_started_at,
|
613
|
+
execute_script_filespec: @execute_script_filespec
|
614
|
+
}
|
615
|
+
end
|
616
|
+
|
617
|
+
def prompt_with_quit(prompt_text, items, opts = {})
|
618
|
+
exit_option = '* Exit'
|
619
|
+
sel = @prompt.select prompt_text,
|
620
|
+
items + (@options[:menu_with_exit] ? [exit_option] : []),
|
621
|
+
opts
|
622
|
+
sel == exit_option ? nil : sel
|
257
623
|
end
|
258
624
|
|
259
625
|
def read_configuration_file!(options, configuration_path)
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
options
|
626
|
+
return unless File.exist?(configuration_path)
|
627
|
+
|
628
|
+
# rubocop:disable Security/YAMLLoad
|
629
|
+
options.merge!((YAML.load(File.open(configuration_path)) || {})
|
630
|
+
.transform_keys(&:to_sym))
|
631
|
+
# rubocop:enable Security/YAMLLoad
|
267
632
|
end
|
268
633
|
|
269
634
|
def recursively_required(table, reqs)
|
@@ -278,231 +643,211 @@ module MarkdownExec
|
|
278
643
|
end
|
279
644
|
.compact
|
280
645
|
.flatten(1)
|
281
|
-
.
|
646
|
+
.tap_inspect(name: 'rem')
|
282
647
|
end
|
283
|
-
all.
|
648
|
+
all.tap_inspect
|
284
649
|
end
|
285
650
|
|
286
651
|
def run
|
287
652
|
## default configuration
|
288
653
|
#
|
289
|
-
options =
|
290
|
-
mdheadings: true,
|
291
|
-
list_blocks: false,
|
292
|
-
list_docs: false
|
293
|
-
}
|
654
|
+
@options = base_options
|
294
655
|
|
295
|
-
##
|
656
|
+
## read local configuration file
|
296
657
|
#
|
297
|
-
|
658
|
+
read_configuration_file! @options, ".#{MarkdownExec::APP_NAME.downcase}.yml"
|
298
659
|
|
299
|
-
|
300
|
-
#
|
301
|
-
read_configuration_file! options, ".#{MarkdownExec::APP_NAME.downcase}.yml"
|
302
|
-
|
303
|
-
option_parser = OptionParser.new do |opts|
|
660
|
+
@option_parser = option_parser = OptionParser.new do |opts|
|
304
661
|
executable_name = File.basename($PROGRAM_NAME)
|
305
662
|
opts.banner = [
|
306
|
-
"#{MarkdownExec::APP_NAME}
|
307
|
-
"
|
663
|
+
"#{MarkdownExec::APP_NAME}" \
|
664
|
+
" - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
|
665
|
+
"Usage: #{executable_name} [path] [filename] [options]"
|
308
666
|
].join("\n")
|
309
667
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
opts.on('--list-blocks', 'List blocks') do |_value|
|
327
|
-
options[:list_blocks] = true
|
328
|
-
end
|
329
|
-
|
330
|
-
opts.on('--list-docs', 'List docs in current folder') do |_value|
|
331
|
-
options[:list_docs] = true
|
332
|
-
end
|
333
|
-
|
334
|
-
## menu bottom: items appear in order added
|
335
|
-
#
|
336
|
-
opts.on_tail('-h', '--help', 'App help') do |_value|
|
337
|
-
fout option_parser.help
|
338
|
-
exit
|
339
|
-
end
|
340
|
-
|
341
|
-
opts.on_tail('-v', '--version', 'App version') do |_value|
|
342
|
-
fout MarkdownExec::VERSION
|
343
|
-
exit
|
344
|
-
end
|
345
|
-
|
346
|
-
opts.on_tail('-x', '--exit', 'Exit app') do |_value|
|
347
|
-
exit
|
348
|
-
end
|
349
|
-
|
350
|
-
opts.on_tail('-0', 'Show configuration') do |_v|
|
351
|
-
options_finalize.call options
|
352
|
-
fout options.to_yaml
|
668
|
+
menu_data
|
669
|
+
.map do |long_name, short_name, _env_var, arg_name, description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
|
670
|
+
next unless long_name.present? || short_name.present?
|
671
|
+
|
672
|
+
opts.on(*[if long_name.present?
|
673
|
+
"--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
|
674
|
+
end,
|
675
|
+
short_name.present? ? "-#{short_name}" : nil,
|
676
|
+
[description,
|
677
|
+
default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
|
678
|
+
lambda { |value|
|
679
|
+
ret = proc1.call(value)
|
680
|
+
options[opt_name] = ret if opt_name
|
681
|
+
ret
|
682
|
+
}].compact)
|
353
683
|
end
|
354
684
|
end
|
355
685
|
option_parser.load # filename defaults to basename of the program without suffix in a directory ~/.options
|
356
686
|
option_parser.environment # env defaults to the basename of the program.
|
357
687
|
rest = option_parser.parse! # (into: options)
|
358
|
-
options_finalize.call options
|
359
688
|
|
360
|
-
|
361
|
-
if Dir.exist?(rest[0])
|
362
|
-
options[:folder] = rest[0]
|
363
|
-
elsif File.exist?(rest[0])
|
364
|
-
options[:filename] = rest[0]
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
## process
|
369
|
-
#
|
370
|
-
options.merge!(
|
371
|
-
{
|
372
|
-
approve: true,
|
373
|
-
bash: true,
|
374
|
-
display: true,
|
375
|
-
exclude_expect_blocks: true,
|
376
|
-
exclude_matching_block_names: true,
|
377
|
-
execute: true,
|
378
|
-
prompt: 'Execute',
|
379
|
-
struct: true
|
380
|
-
}
|
381
|
-
)
|
382
|
-
mp = MarkParse.new options
|
689
|
+
options_finalize rest
|
383
690
|
|
384
|
-
|
385
|
-
|
386
|
-
if options[:list_docs]
|
387
|
-
fout mp.list_files_per_options options
|
388
|
-
return
|
389
|
-
end
|
691
|
+
exec_block options, options[:block_name]
|
692
|
+
end
|
390
693
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
694
|
+
def run_last_script
|
695
|
+
filename = Dir.glob(File.join(@options[:saved_script_folder],
|
696
|
+
@options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].last
|
697
|
+
filename.tap_inspect name: filename
|
698
|
+
mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
|
699
|
+
|
700
|
+
@options[:block_name] = mf[:block]
|
701
|
+
@options[:filename] = "#{mf[:file]}.md" ### other extensions
|
702
|
+
@options[:save_executed_script] = false
|
703
|
+
select_and_approve_block
|
704
|
+
save_execution_output
|
705
|
+
output_execution_summary
|
706
|
+
end
|
397
707
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
708
|
+
def save_execution_output
|
709
|
+
return unless @options[:save_execution_output]
|
710
|
+
|
711
|
+
fne = File.basename(@options[:filename], '.*')
|
712
|
+
@options[:logged_stdout_filename] =
|
713
|
+
"#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
|
714
|
+
@options[:block_name]].join('_')}.out.txt"
|
715
|
+
@options[:logged_stdout_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stdout_filename]
|
716
|
+
@logged_stdout_filespec = @options[:logged_stdout_filespec]
|
717
|
+
dirname = File.dirname(@options[:logged_stdout_filespec])
|
718
|
+
Dir.mkdir dirname unless File.exist?(dirname)
|
719
|
+
File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(0, ''))
|
720
|
+
# @options[:logged_stderr_filename] =
|
721
|
+
# "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
|
722
|
+
# @options[:block_name]].join('_')}.err.txt"
|
723
|
+
# @options[:logged_stderr_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stderr_filename]
|
724
|
+
# @logged_stderr_filespec = @options[:logged_stderr_filespec]
|
725
|
+
# File.write(@options[:logged_stderr_filespec], @execute_files&.fetch(1, ''))
|
403
726
|
end
|
404
727
|
|
405
|
-
def
|
728
|
+
def select_and_approve_block(call_options = {}, &options_block)
|
406
729
|
opts = optsmerge call_options, options_block
|
730
|
+
blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
|
407
731
|
|
408
|
-
|
732
|
+
unless opts[:block_name].present?
|
733
|
+
pt = (opts[:prompt_select_block]).to_s
|
734
|
+
blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
|
735
|
+
block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
|
409
736
|
|
410
|
-
|
411
|
-
pt = "#{opts.fetch(:prompt, nil) || 'Pick one'}:"
|
737
|
+
return nil if block_labels.count.zero?
|
412
738
|
|
413
|
-
|
414
|
-
|
415
|
-
# block
|
416
|
-
# end.compact.tap { |ret| puts "list_blocks_in_file() ret: #{ret.inspect}" if $pdebug }
|
739
|
+
sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
|
740
|
+
return nil if sel.nil?
|
417
741
|
|
418
|
-
|
419
|
-
|
420
|
-
block_labels = option_exclude_blocks(opts, blocks).map { |block| block[:label] }
|
421
|
-
|
422
|
-
if opts[:preview_options]
|
423
|
-
select_per_page = 3
|
424
|
-
block_labels.each do |bn|
|
425
|
-
fout " - #{bn}"
|
426
|
-
end
|
427
|
-
else
|
428
|
-
select_per_page = SELECT_PAGE_HEIGHT
|
742
|
+
label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
|
743
|
+
opts[:block_name] = @options[:block_name] = label_block[:name]
|
429
744
|
end
|
430
745
|
|
431
|
-
|
432
|
-
|
433
|
-
sel = prompt.select(pt, block_labels, per_page: select_per_page)
|
434
|
-
|
435
|
-
label_block = blocks.select { |block| block[:label] == sel }.fetch(0, nil)
|
436
|
-
sel = label_block[:name]
|
437
|
-
|
438
|
-
cbs = list_recursively_required_blocks(blocks, sel)
|
439
|
-
|
440
|
-
## display code blocks for approval
|
441
|
-
#
|
442
|
-
cbs.each { |cb| fout cb } if opts[:display] || opts[:approve]
|
443
|
-
|
444
|
-
allow = true
|
445
|
-
allow = prompt.yes? 'Process?' if opts[:approve]
|
746
|
+
approve_block opts, blocks_in_file
|
747
|
+
end
|
446
748
|
|
447
|
-
|
448
|
-
|
749
|
+
def select_md_file(files_ = nil)
|
750
|
+
opts = options
|
751
|
+
files = files_ || list_markdown_files_in_path
|
752
|
+
if files.count == 1
|
753
|
+
files[0]
|
754
|
+
elsif files.count >= 2
|
755
|
+
prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
|
756
|
+
end
|
757
|
+
end
|
449
758
|
|
450
|
-
|
759
|
+
def select_recent_script
|
760
|
+
filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
|
761
|
+
per_page: @options[:select_page_height]
|
762
|
+
return if filename.nil?
|
451
763
|
|
452
|
-
|
453
|
-
stdin.close_write
|
454
|
-
begin
|
455
|
-
files = [stdout, stderr]
|
764
|
+
mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
|
456
765
|
|
457
|
-
|
458
|
-
|
766
|
+
@options[:block_name] = mf[:block]
|
767
|
+
@options[:filename] = "#{mf[:file]}.md" ### other extensions
|
768
|
+
select_and_approve_block(
|
769
|
+
bash: true,
|
770
|
+
save_executed_script: false,
|
771
|
+
struct: true
|
772
|
+
)
|
773
|
+
save_execution_output
|
774
|
+
output_execution_summary
|
775
|
+
end
|
459
776
|
|
460
|
-
|
777
|
+
def sorted_keys(hash1)
|
778
|
+
hash1.keys.sort.to_h { |k| [k, hash1[k]] }
|
779
|
+
end
|
461
780
|
|
462
|
-
|
463
|
-
|
464
|
-
|
781
|
+
def summarize_block(headings, title)
|
782
|
+
{ headings: headings, name: title, title: title }
|
783
|
+
end
|
465
784
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
end
|
472
|
-
rescue IOError => e
|
473
|
-
fout "IOError: #{e}"
|
474
|
-
end
|
475
|
-
end
|
785
|
+
def update_options(opts = {}, over: true)
|
786
|
+
if over
|
787
|
+
@options = @options.merge opts
|
788
|
+
else
|
789
|
+
@options.merge! opts
|
476
790
|
end
|
477
|
-
|
478
|
-
selected[:name]
|
791
|
+
@options.tap_inspect format: :yaml
|
479
792
|
end
|
480
793
|
|
481
|
-
def
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
794
|
+
def value_for_cli(value)
|
795
|
+
case value.class.to_s
|
796
|
+
when 'String'
|
797
|
+
"'#{value}'"
|
798
|
+
when 'FalseClass', 'TrueClass'
|
799
|
+
value ? '1' : '0'
|
800
|
+
when 'Integer'
|
801
|
+
value
|
802
|
+
else
|
803
|
+
value.to_s
|
804
|
+
end
|
805
|
+
end
|
487
806
|
|
488
|
-
|
489
|
-
|
490
|
-
files.each do |file|
|
491
|
-
fout " - #{file}"
|
492
|
-
end
|
493
|
-
else
|
494
|
-
select_per_page = SELECT_PAGE_HEIGHT
|
495
|
-
end
|
807
|
+
def value_for_hash(value, default = nil)
|
808
|
+
return default if value.nil?
|
496
809
|
|
497
|
-
|
498
|
-
|
810
|
+
case value.class.to_s
|
811
|
+
when 'String', 'Integer', 'FalseClass', 'TrueClass'
|
812
|
+
value
|
813
|
+
when value.empty?
|
814
|
+
default
|
815
|
+
else
|
816
|
+
value.to_s
|
499
817
|
end
|
818
|
+
end
|
500
819
|
|
501
|
-
|
820
|
+
def value_for_yaml(value)
|
821
|
+
return default if value.nil?
|
822
|
+
|
823
|
+
case value.class.to_s
|
824
|
+
when 'String'
|
825
|
+
"'#{value}'"
|
826
|
+
when 'Integer'
|
827
|
+
value
|
828
|
+
when 'FalseClass', 'TrueClass'
|
829
|
+
value ? true : false
|
830
|
+
when value.empty?
|
831
|
+
default
|
832
|
+
else
|
833
|
+
value.to_s
|
834
|
+
end
|
502
835
|
end
|
503
836
|
|
504
|
-
def
|
505
|
-
|
837
|
+
def write_command_file(opts, required_blocks)
|
838
|
+
fne = File.basename(opts[:filename], '.*')
|
839
|
+
opts[:saved_script_filename] =
|
840
|
+
"#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
|
841
|
+
opts[:block_name]].join('_')}.sh"
|
842
|
+
@options[:saved_filespec] = File.join opts[:saved_script_folder], opts[:saved_script_filename]
|
843
|
+
@execute_script_filespec = @options[:saved_filespec]
|
844
|
+
dirname = File.dirname(@options[:saved_filespec])
|
845
|
+
Dir.mkdir dirname unless File.exist?(dirname)
|
846
|
+
File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \
|
847
|
+
"# file_name: #{opts[:filename]}\n" \
|
848
|
+
"# block_name: #{opts[:block_name]}\n" \
|
849
|
+
"# time: #{Time.now.utc}\n" \
|
850
|
+
"#{required_blocks.flatten.join("\n")}\n")
|
506
851
|
end
|
507
852
|
end
|
508
853
|
end
|