markdown_exec 0.2.1 → 0.2.5
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 +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
|