markdown_exec 0.2.5 → 1.1.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.
data/lib/markdown_exec.rb CHANGED
@@ -3,38 +3,14 @@
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
- # 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
+ require_relative 'shared'
38
14
  require_relative 'markdown_exec/version'
39
15
 
40
16
  $stderr.sync = true
@@ -62,27 +38,17 @@ end
62
38
 
63
39
  public
64
40
 
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
41
+ # display_level values
42
+ DISPLAY_LEVEL_BASE = 0 # required output
43
+ DISPLAY_LEVEL_ADMIN = 1
44
+ DISPLAY_LEVEL_DEBUG = 2
45
+ DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
46
+ DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
47
+
48
+ # @execute_files[ind] = @execute_files[ind] + [block]
49
+ EF_STDOUT = 0
50
+ EF_STDERR = 1
51
+ EF_STDIN = 2
86
52
 
87
53
  module MarkdownExec
88
54
  class Error < StandardError; end
@@ -101,14 +67,22 @@ module MarkdownExec
101
67
  # options necessary to start, parse input, defaults for cli options
102
68
 
103
69
  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))]
70
+ menu_iter do |item|
71
+ item.tap_inspect name: :item, format: :yaml
72
+ next unless item[:opt_name].present?
73
+
74
+ item_default = item[:default]
75
+ item_default.tap_inspect name: :item_default
76
+ value = if item_default.nil?
77
+ item_default
78
+ else
79
+ env_str(item[:env_var], default: value_for_hash(item_default))
80
+ end
81
+ [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
109
82
  end.compact.to_h.merge(
110
83
  {
111
84
  mdheadings: true, # use headings (levels 1,2,3) in block lable
85
+ menu_exit_at_top: true,
112
86
  menu_with_exit: true
113
87
  }
114
88
  ).tap_inspect format: :yaml
@@ -123,6 +97,7 @@ module MarkdownExec
123
97
  prompt_approve_block: 'Process?',
124
98
  prompt_select_block: 'Choose a block:',
125
99
  prompt_select_md: 'Choose a file:',
100
+ prompt_select_output: 'Choose a file:',
126
101
  saved_script_filename: nil, # calculated
127
102
  struct: true # allow get_block_summary()
128
103
  }
@@ -139,13 +114,43 @@ module MarkdownExec
139
114
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
140
115
 
141
116
  allow = true
142
- allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve]
143
- opts[:ir_approve] = allow
117
+ if opts[:user_must_approve]
118
+ loop do
119
+ # (sel = @prompt.select(opts[:prompt_approve_block], %w(Yes No Copy_script_to_clipboard Save_script), cycle: true)).tap_inspect name: :sel
120
+ (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
121
+ menu.default 1
122
+ # menu.enum '.'
123
+ # menu.filter true
124
+
125
+ menu.choice 'Yes', 1
126
+ menu.choice 'No', 2
127
+ menu.choice 'Copy script to clipboard', 3
128
+ menu.choice 'Save script', 4
129
+ end).tap_inspect name: :sel
130
+ allow = (sel == 1)
131
+ if sel == 3
132
+ text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
133
+ Clipboard.copy(text)
134
+ fout "Clipboard updated: #{required_blocks.count} blocks, #{required_blocks.flatten.count} lines, #{text.length} characters"
135
+ end
136
+ if sel == 4
137
+ # opts[:saved_script_filename] = saved_name_make(opts)
138
+ write_command_file(opts.merge(save_executed_script: true), required_blocks)
139
+ fout "File saved: #{@options[:saved_filespec]}"
140
+ end
141
+ break if [1, 2].include? sel
142
+ end
143
+ end
144
+ (opts[:ir_approve] = allow).tap_inspect name: :allow
145
+
144
146
  selected = get_block_by_name blocks_in_file, opts[:block_name]
145
147
 
146
148
  if opts[:ir_approve]
147
- write_command_file(opts, required_blocks) if opts[:save_executed_script]
149
+ write_command_file opts, required_blocks
148
150
  command_execute opts, required_blocks.flatten.join("\n")
151
+ save_execution_output
152
+ output_execution_summary
153
+ output_execution_result
149
154
  end
150
155
 
151
156
  selected[:name]
@@ -164,40 +169,45 @@ module MarkdownExec
164
169
  @execute_files = Hash.new([])
165
170
  @execute_options = opts
166
171
  @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
172
+
173
+ Open3.popen3(@options[:shell], '-c', cmd2) do |stdin, stdout, stderr, exec_thr|
174
+ # pid = exec_thr.pid # pid of the started process
175
+
176
+ t1 = Thread.new do
177
+ until (line = stdout.gets).nil?
178
+ @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
179
+ print line if opts[:output_stdout]
180
+ yield nil, line, nil, exec_thr if block_given?
181
+ end
182
+ end
183
+
184
+ t2 = Thread.new do
185
+ until (line = stderr.gets).nil?
186
+ @execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
187
+ print line if opts[:output_stdout]
188
+ yield nil, nil, line, exec_thr if block_given?
189
+ end
190
+ end
191
+
192
+ in_thr = Thread.new do
193
+ while exec_thr.alive? # reading input until the child process ends
194
+ stdin.puts(line = $stdin.gets)
195
+ @execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line]
196
+ yield line, nil, nil, exec_thr if block_given?
189
197
  end
190
- rescue IOError => e
191
- fout "IOError: #{e}"
192
198
  end
193
- @execute_completed_at = Time.now.utc
199
+
200
+ exec_thr.join
201
+ in_thr.kill
202
+ # @return_code = exec_thr.value
194
203
  end
204
+ @execute_completed_at = Time.now.utc
195
205
  rescue Errno::ENOENT => e
196
206
  # error triggered by missing command in script
197
207
  @execute_aborted_at = Time.now.utc
198
208
  @execute_error_message = e.message
199
209
  @execute_error = e
200
- @execute_files[1] = e.message
210
+ @execute_files[EF_STDERR] += [e.message]
201
211
  fout "Error ENOENT: #{e.inspect}"
202
212
  end
203
213
 
@@ -211,7 +221,9 @@ module MarkdownExec
211
221
  end
212
222
 
213
223
  def display_command(_opts, required_blocks)
224
+ fout ' #=#=#'.yellow
214
225
  required_blocks.each { |cb| fout cb }
226
+ fout ' #=#=#'.yellow
215
227
  end
216
228
 
217
229
  def exec_block(options, _block_name = '')
@@ -221,41 +233,31 @@ module MarkdownExec
221
233
  # document and block reports
222
234
  #
223
235
  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
236
 
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
237
+ simple_commands = {
238
+ doc_glob: -> { fout options[:md_filename_glob] },
239
+ list_blocks: lambda do
240
+ fout_list (files.map do |file|
241
+ make_block_labels(filename: file, struct: true)
242
+ end).flatten(1)
243
+ end,
244
+ list_default_yaml: -> { fout_list list_default_yaml },
245
+ list_docs: -> { fout_list files },
246
+ list_default_env: -> { fout_list list_default_env },
247
+ list_recent_output: -> { fout_list list_recent_output },
248
+ list_recent_scripts: -> { fout_list list_recent_scripts },
249
+ pwd: -> { fout File.expand_path('..', __dir__) },
250
+ run_last_script: -> { run_last_script },
251
+ select_recent_output: -> { select_recent_output },
252
+ select_recent_script: -> { select_recent_script },
253
+ tab_completions: -> { fout tab_completions },
254
+ menu_export: -> { fout menu_export }
255
+ }
256
+ simple_commands.each_key do |key|
257
+ if @options[key]
258
+ simple_commands[key].call
259
+ return # rubocop:disable Lint/NonLocalExitFromIterator
260
+ end
259
261
  end
260
262
 
261
263
  # process
@@ -266,8 +268,6 @@ module MarkdownExec
266
268
  struct: true
267
269
  )
268
270
  fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
269
- save_execution_output
270
- output_execution_summary
271
271
  end
272
272
 
273
273
  # standard output; not for debug
@@ -304,6 +304,19 @@ module MarkdownExec
304
304
  end
305
305
  end
306
306
 
307
+ def approved_fout?(level)
308
+ level <= @options[:display_level]
309
+ end
310
+
311
+ # display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
312
+ #
313
+ def lout(str, level: DISPLAY_LEVEL_BASE)
314
+ return unless approved_fout? level
315
+
316
+ # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
317
+ fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
318
+ end
319
+
307
320
  def list_blocks_in_file(call_options = {}, &options_block)
308
321
  opts = optsmerge call_options, options_block
309
322
 
@@ -312,6 +325,11 @@ module MarkdownExec
312
325
  exit 1
313
326
  end
314
327
 
328
+ unless File.exist? opts[:filename]
329
+ fout 'Document is missing.'
330
+ exit 1
331
+ end
332
+
315
333
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
316
334
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
317
335
  block_title = ''
@@ -367,25 +385,23 @@ module MarkdownExec
367
385
  end
368
386
 
369
387
  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?
388
+ menu_iter do |item|
389
+ next unless item[:env_var].present?
373
390
 
374
391
  [
375
- "#{env_var}=#{value_for_cli default}",
376
- description.present? ? description : nil
392
+ "#{item[:env_var]}=#{value_for_cli item[:default]}",
393
+ item[:description].present? ? item[:description] : nil
377
394
  ].compact.join(' # ')
378
395
  end.compact.sort
379
396
  end
380
397
 
381
398
  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?
399
+ menu_iter do |item|
400
+ next unless item[:opt_name].present? && item[:default].present?
385
401
 
386
402
  [
387
- "#{opt_name}: #{value_for_yaml default}",
388
- description.present? ? description : nil
403
+ "#{item[:opt_name]}: #{value_for_yaml item[:default]}",
404
+ item[:description].present? ? item[:description] : nil
389
405
  ].compact.join(' # ')
390
406
  end.compact.sort
391
407
  end
@@ -451,9 +467,28 @@ module MarkdownExec
451
467
  .tap_inspect
452
468
  end
453
469
 
470
+ def most_recent(arr)
471
+ return unless arr
472
+ return if arr.count < 1
473
+
474
+ arr.max.tap_inspect
475
+ end
476
+
477
+ def most_recent_list(arr)
478
+ return unless arr
479
+ return if (ac = arr.count) < 1
480
+
481
+ arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
482
+ end
483
+
484
+ def list_recent_output
485
+ most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
486
+ @options[:saved_stdout_glob]))).tap_inspect
487
+ end
488
+
454
489
  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
490
+ most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
491
+ @options[:saved_script_glob]))).tap_inspect
457
492
  end
458
493
 
459
494
  def make_block_label(block, call_options = {})
@@ -475,86 +510,400 @@ module MarkdownExec
475
510
  end.compact.tap_inspect
476
511
  end
477
512
 
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],
513
+ def menu_data1
514
+ val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
515
+ val_as_int = ->(value) { value.to_i }
516
+ val_as_str = ->(value) { value.to_s }
517
+ # val_true = ->(_value) { true } # for commands, sets option to true
518
+ set1 = [
519
+ {
520
+ arg_name: 'PATH',
521
+ default: '.',
522
+ description: 'Read configuration file',
523
+ long_name: 'config',
524
+ proc1: lambda { |value|
525
+ read_configuration_file! options, value
526
+ }
527
+ },
528
+ {
529
+ arg_name: 'BOOL',
530
+ default: false,
531
+ description: 'Debug output',
532
+ env_var: 'MDE_DEBUG',
533
+ long_name: 'debug',
534
+ short_name: 'd',
535
+ proc1: lambda { |value|
536
+ $pdebug = value.to_i != 0
537
+ }
538
+ },
539
+ {
540
+ arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
541
+ default: DISPLAY_LEVEL_DEFAULT,
542
+ description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
543
+ env_var: 'MDE_DISPLAY_LEVEL',
544
+ long_name: 'display-level',
545
+ opt_name: :display_level,
546
+ proc1: val_as_int
547
+ },
548
+ {
549
+ arg_name: 'NAME',
550
+ compreply: false,
551
+ description: 'Name of block',
552
+ env_var: 'MDE_BLOCK_NAME',
553
+ long_name: 'block-name',
554
+ opt_name: :block_name,
555
+ short_name: 'f',
556
+ proc1: val_as_str
557
+ },
558
+ {
559
+ arg_name: 'RELATIVE_PATH',
560
+ compreply: '.',
561
+ description: 'Name of document',
562
+ env_var: 'MDE_FILENAME',
563
+ long_name: 'filename',
564
+ opt_name: :filename,
565
+ short_name: 'f',
566
+ proc1: val_as_str
567
+ },
568
+ {
569
+ description: 'List blocks',
570
+ long_name: 'list-blocks',
571
+ opt_name: :list_blocks,
572
+ proc1: val_as_bool
573
+ },
574
+ {
575
+ arg_name: 'INT.1-',
576
+ default: 32,
577
+ description: 'Max. items to return in list',
578
+ env_var: 'MDE_LIST_COUNT',
579
+ long_name: 'list-count',
580
+ opt_name: :list_count,
581
+ proc1: val_as_int
582
+ },
583
+ {
584
+ description: 'List default configuration as environment variables',
585
+ long_name: 'list-default-env',
586
+ opt_name: :list_default_env
587
+ },
588
+ {
589
+ description: 'List default configuration as YAML',
590
+ long_name: 'list-default-yaml',
591
+ opt_name: :list_default_yaml
592
+ },
593
+ {
594
+ description: 'List docs in current folder',
595
+ long_name: 'list-docs',
596
+ opt_name: :list_docs,
597
+ proc1: val_as_bool
598
+ },
599
+ {
600
+ description: 'List recent saved output',
601
+ long_name: 'list-recent-output',
602
+ opt_name: :list_recent_output,
603
+ proc1: val_as_bool
604
+ },
605
+ {
606
+ description: 'List recent saved scripts',
607
+ long_name: 'list-recent-scripts',
608
+ opt_name: :list_recent_scripts,
609
+ proc1: val_as_bool
610
+ },
611
+ {
612
+ arg_name: 'PREFIX',
613
+ default: MarkdownExec::BIN_NAME,
614
+ description: 'Name prefix for stdout files',
615
+ env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
616
+ long_name: 'logged-stdout-filename-prefix',
617
+ opt_name: :logged_stdout_filename_prefix,
618
+ proc1: val_as_str
619
+ },
620
+ {
621
+ arg_name: 'BOOL',
622
+ default: false,
623
+ description: 'Display summary for execution',
624
+ env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
625
+ long_name: 'output-execution-summary',
626
+ opt_name: :output_execution_summary,
627
+ proc1: val_as_bool
628
+ },
629
+ {
630
+ arg_name: 'BOOL',
631
+ default: false,
632
+ description: 'Display script prior to execution',
633
+ env_var: 'MDE_OUTPUT_SCRIPT',
634
+ long_name: 'output-script',
635
+ opt_name: :output_script,
636
+ proc1: val_as_bool
637
+ },
638
+ {
639
+ arg_name: 'BOOL',
640
+ default: true,
641
+ description: 'Display standard output from execution',
642
+ env_var: 'MDE_OUTPUT_STDOUT',
643
+ long_name: 'output-stdout',
644
+ opt_name: :output_stdout,
645
+ proc1: val_as_bool
646
+ },
647
+ {
648
+ arg_name: 'RELATIVE_PATH',
649
+ default: '.',
650
+ description: 'Path to documents',
651
+ env_var: 'MDE_PATH',
652
+ long_name: 'path',
653
+ opt_name: :path,
654
+ short_name: 'p',
655
+ proc1: val_as_str
656
+ },
657
+ {
658
+ description: 'Gem home folder',
659
+ long_name: 'pwd',
660
+ opt_name: :pwd,
661
+ proc1: val_as_bool
662
+ },
663
+ {
664
+ description: 'Run most recently saved script',
665
+ long_name: 'run-last-script',
666
+ opt_name: :run_last_script,
667
+ proc1: val_as_bool
668
+ },
669
+ {
670
+ arg_name: 'BOOL',
671
+ default: false,
672
+ description: 'Save executed script',
673
+ env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
674
+ long_name: 'save-executed-script',
675
+ opt_name: :save_executed_script,
676
+ proc1: val_as_bool
677
+ },
678
+ {
679
+ arg_name: 'BOOL',
680
+ default: false,
681
+ description: 'Save standard output of the executed script',
682
+ env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
683
+ long_name: 'save-execution-output',
684
+ opt_name: :save_execution_output,
685
+ proc1: val_as_bool
686
+ },
687
+ {
688
+ arg_name: 'INT',
689
+ default: 0o755,
690
+ description: 'chmod for saved scripts',
691
+ env_var: 'MDE_SAVED_SCRIPT_CHMOD',
692
+ long_name: 'saved-script-chmod',
693
+ opt_name: :saved_script_chmod,
694
+ proc1: val_as_int
695
+ },
696
+ {
697
+ arg_name: 'PREFIX',
698
+ default: MarkdownExec::BIN_NAME,
699
+ description: 'Name prefix for saved scripts',
700
+ env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
701
+ long_name: 'saved-script-filename-prefix',
702
+ opt_name: :saved_script_filename_prefix,
703
+ proc1: val_as_str
704
+ },
705
+ {
706
+ arg_name: 'RELATIVE_PATH',
707
+ default: 'logs',
708
+ description: 'Saved script folder',
709
+ env_var: 'MDE_SAVED_SCRIPT_FOLDER',
710
+ long_name: 'saved-script-folder',
711
+ opt_name: :saved_script_folder,
712
+ proc1: val_as_str
713
+ },
714
+ {
715
+ arg_name: 'GLOB',
716
+ default: 'mde_*.sh',
717
+ description: 'Glob matching saved scripts',
718
+ env_var: 'MDE_SAVED_SCRIPT_GLOB',
719
+ long_name: 'saved-script-glob',
720
+ opt_name: :saved_script_glob,
721
+ proc1: val_as_str
722
+ },
723
+ {
724
+ arg_name: 'RELATIVE_PATH',
725
+ default: 'logs',
726
+ description: 'Saved stdout folder',
727
+ env_var: 'MDE_SAVED_STDOUT_FOLDER',
728
+ long_name: 'saved-stdout-folder',
729
+ opt_name: :saved_stdout_folder,
730
+ proc1: val_as_str
731
+ },
732
+ {
733
+ arg_name: 'GLOB',
734
+ default: 'mde_*.out.txt',
735
+ description: 'Glob matching saved outputs',
736
+ env_var: 'MDE_SAVED_STDOUT_GLOB',
737
+ long_name: 'saved-stdout-glob',
738
+ opt_name: :saved_stdout_glob,
739
+ proc1: val_as_str
740
+ },
741
+ {
742
+ description: 'Select and execute a recently saved output',
743
+ long_name: 'select-recent-output',
744
+ opt_name: :select_recent_output,
745
+ proc1: val_as_bool
746
+ },
747
+ {
748
+ description: 'Select and execute a recently saved script',
749
+ long_name: 'select-recent-script',
750
+ opt_name: :select_recent_script,
751
+ proc1: val_as_bool
752
+ },
753
+ {
754
+ description: 'YAML export of menu',
755
+ long_name: 'menu-export',
756
+ opt_name: :menu_export,
757
+ proc1: val_as_bool
758
+ },
759
+ {
760
+ description: 'List tab completions',
761
+ long_name: 'tab-completions',
762
+ opt_name: :tab_completions,
763
+ proc1: val_as_bool
764
+ },
765
+ {
766
+ arg_name: 'BOOL',
767
+ default: true,
768
+ description: 'Pause for user to approve script',
769
+ env_var: 'MDE_USER_MUST_APPROVE',
770
+ long_name: 'user-must-approve',
771
+ opt_name: :user_must_approve,
772
+ proc1: val_as_bool
773
+ },
774
+ {
775
+ description: 'Show current configuration values',
776
+ short_name: '0',
777
+ proc1: lambda { |_|
778
+ options_finalize options
779
+ fout sorted_keys(options).to_yaml
780
+ }
781
+ },
782
+ {
783
+ description: 'App help',
784
+ long_name: 'help',
785
+ short_name: 'h',
786
+ proc1: lambda { |_|
787
+ fout menu_help
788
+ exit
789
+ }
790
+ },
791
+ {
792
+ description: "Print the gem's version",
793
+ long_name: 'version',
794
+ short_name: 'v',
795
+ proc1: lambda { |_|
796
+ fout MarkdownExec::VERSION
797
+ exit
798
+ }
799
+ },
800
+ {
801
+ description: 'Exit app',
802
+ long_name: 'exit',
803
+ short_name: 'x',
804
+ proc1: ->(_) { exit }
805
+ },
806
+ {
807
+ default: '^\(.*\)$',
808
+ description: 'Pattern for blocks to hide from user-selection',
809
+ env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
810
+ opt_name: :block_name_excluded_match,
811
+ proc1: val_as_str
812
+ },
813
+ {
814
+ default: ':(?<title>\S+)( |$)',
815
+ env_var: 'MDE_BLOCK_NAME_MATCH',
816
+ opt_name: :block_name_match,
817
+ proc1: val_as_str
818
+ },
819
+ {
820
+ default: '\+\S+',
821
+ env_var: 'MDE_BLOCK_REQUIRED_SCAN',
822
+ opt_name: :block_required_scan,
823
+ proc1: val_as_str
824
+ },
825
+ {
826
+ default: '> ',
827
+ env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
828
+ opt_name: :display_level_xbase_prefix,
829
+ proc1: val_as_str
830
+ },
831
+ {
832
+ default: '^`{3,}',
833
+ env_var: 'MDE_FENCED_START_AND_END_MATCH',
834
+ opt_name: :fenced_start_and_end_match,
835
+ proc1: val_as_str
836
+ },
837
+ {
838
+ default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
839
+ env_var: 'MDE_FENCED_START_EX_MATCH',
840
+ opt_name: :fenced_start_ex_match,
841
+ proc1: val_as_str
842
+ },
843
+ {
844
+ default: '^# *(?<name>[^#]*?) *$',
845
+ env_var: 'MDE_HEADING1_MATCH',
846
+ opt_name: :heading1_match,
847
+ proc1: val_as_str
848
+ },
849
+ {
850
+ default: '^## *(?<name>[^#]*?) *$',
851
+ env_var: 'MDE_HEADING2_MATCH',
852
+ opt_name: :heading2_match,
853
+ proc1: val_as_str
854
+ },
855
+ {
856
+ default: '^### *(?<name>.+?) *$',
857
+ env_var: 'MDE_HEADING3_MATCH',
858
+ opt_name: :heading3_match,
859
+ proc1: val_as_str
860
+ },
861
+ {
862
+ default: '*.[Mm][Dd]',
863
+ env_var: 'MDE_MD_FILENAME_GLOB',
864
+ opt_name: :md_filename_glob,
865
+ proc1: val_as_str
866
+ },
867
+ {
868
+ default: '.+\\.md',
869
+ env_var: 'MDE_MD_FILENAME_MATCH',
870
+ opt_name: :md_filename_match,
871
+ proc1: val_as_str
872
+ },
873
+ {
874
+ description: 'Options for viewing saved output file',
875
+ env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
876
+ opt_name: :output_viewer_options,
877
+ proc1: val_as_str
878
+ },
879
+ {
880
+ default: 24,
881
+ description: 'Maximum # of rows in select list',
882
+ env_var: 'MDE_SELECT_PAGE_HEIGHT',
883
+ opt_name: :select_page_height,
884
+ proc1: val_as_int
885
+ },
886
+ {
887
+ default: '#!/usr/bin/env',
888
+ description: 'Shebang for saved scripts',
889
+ env_var: 'MDE_SHEBANG',
890
+ opt_name: :shebang,
891
+ proc1: val_as_str
892
+ },
893
+ {
894
+ default: 'bash',
895
+ description: 'Shell for launched scripts',
896
+ env_var: 'MDE_SHELL',
897
+ opt_name: :shell,
898
+ proc1: val_as_str
899
+ }
555
900
  ]
901
+ # commands first, options second
902
+ (set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
903
+ end
556
904
 
557
- summary_head + summary_body + summary_tail + env_vars
905
+ def menu_iter(data = menu_data1, &block)
906
+ data.map(&block)
558
907
  end
559
908
 
560
909
  def menu_help
@@ -587,7 +936,8 @@ module MarkdownExec
587
936
 
588
937
  ## position 1: block name (optional)
589
938
  #
590
- @options[:block_name] = rest.fetch(1, nil)
939
+ block_name = rest.fetch(1, nil)
940
+ @options[:block_name] = block_name if block_name.present?
591
941
  end
592
942
 
593
943
  def optsmerge(call_options = {}, options_block = nil)
@@ -599,6 +949,24 @@ module MarkdownExec
599
949
  end.tap_inspect
600
950
  end
601
951
 
952
+ def output_execution_result
953
+ oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN],
954
+ ['Command',
955
+ [MarkdownExec::BIN_NAME,
956
+ @options[:filename],
957
+ @options[:block_name]].join(' '),
958
+ DISPLAY_LEVEL_ADMIN]]
959
+
960
+ [['Script', :saved_filespec],
961
+ ['StdOut', :logged_stdout_filespec]].each do |label, name|
962
+ oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name]
963
+ end
964
+
965
+ oq.map do |label, value, level|
966
+ lout ["#{label}:".yellow, value.to_s].join(' '), level: level
967
+ end
968
+ end
969
+
602
970
  def output_execution_summary
603
971
  return unless @options[:output_execution_summary]
604
972
 
@@ -616,9 +984,12 @@ module MarkdownExec
616
984
 
617
985
  def prompt_with_quit(prompt_text, items, opts = {})
618
986
  exit_option = '* Exit'
619
- sel = @prompt.select prompt_text,
620
- items + (@options[:menu_with_exit] ? [exit_option] : []),
621
- opts
987
+ all_items = if @options[:menu_exit_at_top]
988
+ (@options[:menu_with_exit] ? [exit_option] : []) + items
989
+ else
990
+ items + (@options[:menu_with_exit] ? [exit_option] : [])
991
+ end
992
+ sel = @prompt.select(prompt_text, all_items, opts.merge(filter: true))
622
993
  sel == exit_option ? nil : sel
623
994
  end
624
995
 
@@ -662,22 +1033,22 @@ module MarkdownExec
662
1033
  opts.banner = [
663
1034
  "#{MarkdownExec::APP_NAME}" \
664
1035
  " - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
665
- "Usage: #{executable_name} [path] [filename] [options]"
1036
+ "Usage: #{executable_name} [(path | filename [block_name])] [options]"
666
1037
  ].join("\n")
667
1038
 
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?
1039
+ menu_iter do |item|
1040
+ next unless item[:long_name].present? || item[:short_name].present?
671
1041
 
672
- opts.on(*[if long_name.present?
673
- "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
1042
+ opts.on(*[if item[:long_name].present?
1043
+ "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
674
1044
  end,
675
- short_name.present? ? "-#{short_name}" : nil,
676
- [description,
677
- default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
1045
+ item[:short_name].present? ? "-#{item[:short_name]}" : nil,
1046
+ [item[:description],
1047
+ item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
678
1048
  lambda { |value|
679
- ret = proc1.call(value)
680
- options[opt_name] = ret if opt_name
1049
+ # ret = item[:proc1].call(value)
1050
+ ret = item[:proc1] ? item[:proc1].call(value) : value
1051
+ options[item[:opt_name]] = ret if item[:opt_name]
681
1052
  ret
682
1053
  }].compact)
683
1054
  end
@@ -691,24 +1062,39 @@ module MarkdownExec
691
1062
  exec_block options, options[:block_name]
692
1063
  end
693
1064
 
1065
+ FNR11 = '/'
1066
+ FNR12 = ',~'
1067
+
1068
+ def saved_name_make(opts)
1069
+ fne = opts[:filename].gsub(FNR11, FNR12)
1070
+ "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
1071
+ ',', opts[:block_name]].join('_')}.sh"
1072
+ end
1073
+
1074
+ def saved_name_split(name)
1075
+ mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
1076
+ return unless mf
1077
+
1078
+ @options[:block_name] = mf[:block].tap_inspect name: :options_block_name
1079
+ @options[:filename] = mf[:file].gsub(FNR12, FNR11).tap_inspect name: :options_filename
1080
+ end
1081
+
694
1082
  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/)
1083
+ filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
1084
+ @options[:saved_script_glob]))
1085
+ return unless filename
699
1086
 
700
- @options[:block_name] = mf[:block]
701
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
1087
+ filename.tap_inspect name: filename
1088
+ saved_name_split filename
702
1089
  @options[:save_executed_script] = false
703
1090
  select_and_approve_block
704
- save_execution_output
705
- output_execution_summary
706
1091
  end
707
1092
 
708
1093
  def save_execution_output
709
1094
  return unless @options[:save_execution_output]
710
1095
 
711
1096
  fne = File.basename(@options[:filename], '.*')
1097
+
712
1098
  @options[:logged_stdout_filename] =
713
1099
  "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
714
1100
  @options[:block_name]].join('_')}.out.txt"
@@ -716,34 +1102,51 @@ module MarkdownExec
716
1102
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
717
1103
  dirname = File.dirname(@options[:logged_stdout_filespec])
718
1104
  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, ''))
1105
+
1106
+ # File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, ''))
1107
+ ol = ["-STDOUT-\n"]
1108
+ ol += @execute_files&.fetch(EF_STDOUT, [])
1109
+ ol += ["-STDERR-\n"].tap_inspect name: :ol3
1110
+ ol += @execute_files&.fetch(EF_STDERR, [])
1111
+ ol += ["-STDIN-\n"]
1112
+ ol += @execute_files&.fetch(EF_STDIN, [])
1113
+ File.write(@options[:logged_stdout_filespec], ol.join)
726
1114
  end
727
1115
 
728
1116
  def select_and_approve_block(call_options = {}, &options_block)
729
1117
  opts = optsmerge call_options, options_block
730
1118
  blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
731
1119
 
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] }
1120
+ loop1 = true && !opts[:block_name].present?
736
1121
 
737
- return nil if block_labels.count.zero?
1122
+ loop do
1123
+ unless opts[:block_name].present?
1124
+ pt = (opts[:prompt_select_block]).to_s
1125
+ blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
1126
+ block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
738
1127
 
739
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
740
- return nil if sel.nil?
1128
+ return nil if block_labels.count.zero?
741
1129
 
742
- label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
743
- opts[:block_name] = @options[:block_name] = label_block[:name]
744
- end
1130
+ sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
1131
+ return nil if sel.nil?
745
1132
 
746
- approve_block opts, blocks_in_file
1133
+ # if sel.nil?
1134
+ # loop1 = false
1135
+ # break
1136
+ # end
1137
+
1138
+ label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1139
+ opts[:block_name] = @options[:block_name] = label_block[:name]
1140
+
1141
+ end
1142
+ # if loop1
1143
+ approve_block opts, blocks_in_file
1144
+ # end
1145
+
1146
+ break unless loop1
1147
+
1148
+ opts[:block_name] = ''
1149
+ end
747
1150
  end
748
1151
 
749
1152
  def select_md_file(files_ = nil)
@@ -756,22 +1159,25 @@ module MarkdownExec
756
1159
  end
757
1160
  end
758
1161
 
1162
+ def select_recent_output
1163
+ filename = prompt_with_quit @options[:prompt_select_output].to_s, list_recent_output,
1164
+ per_page: @options[:select_page_height]
1165
+ return unless filename.present?
1166
+
1167
+ `open #{filename} #{options[:output_viewer_options]}`
1168
+ end
1169
+
759
1170
  def select_recent_script
760
1171
  filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
761
1172
  per_page: @options[:select_page_height]
762
1173
  return if filename.nil?
763
1174
 
764
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
765
-
766
- @options[:block_name] = mf[:block]
767
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
1175
+ saved_name_split filename
768
1176
  select_and_approve_block(
769
1177
  bash: true,
770
1178
  save_executed_script: false,
771
1179
  struct: true
772
1180
  )
773
- save_execution_output
774
- output_execution_summary
775
1181
  end
776
1182
 
777
1183
  def sorted_keys(hash1)
@@ -782,6 +1188,19 @@ module MarkdownExec
782
1188
  { headings: headings, name: title, title: title }
783
1189
  end
784
1190
 
1191
+ def menu_export(data = menu_data1)
1192
+ data.map do |item|
1193
+ item.delete(:proc1)
1194
+ item
1195
+ end.to_yaml
1196
+ end
1197
+
1198
+ def tab_completions(data = menu_data1)
1199
+ data.map do |item|
1200
+ "--#{item[:long_name]}" if item[:long_name]
1201
+ end.compact
1202
+ end
1203
+
785
1204
  def update_options(opts = {}, over: true)
786
1205
  if over
787
1206
  @options = @options.merge opts
@@ -791,19 +1210,6 @@ module MarkdownExec
791
1210
  @options.tap_inspect format: :yaml
792
1211
  end
793
1212
 
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
806
-
807
1213
  def value_for_hash(value, default = nil)
808
1214
  return default if value.nil?
809
1215
 
@@ -835,19 +1241,33 @@ module MarkdownExec
835
1241
  end
836
1242
 
837
1243
  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]
1244
+ return unless opts[:save_executed_script]
1245
+
1246
+ opts[:saved_script_filename] = saved_name_make(opts)
1247
+ @execute_script_filespec =
1248
+ @options[:saved_filespec] =
1249
+ File.join opts[:saved_script_folder], opts[:saved_script_filename]
1250
+
844
1251
  dirname = File.dirname(@options[:saved_filespec])
845
1252
  Dir.mkdir dirname unless File.exist?(dirname)
846
- File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \
1253
+ (shebang = if @options[:shebang]&.present?
1254
+ "#{@options[:shebang]} #{@options[:shell]}\n"
1255
+ else
1256
+ ''
1257
+ end
1258
+ ).tap_inspect name: :shebang
1259
+ File.write(@options[:saved_filespec], shebang +
847
1260
  "# file_name: #{opts[:filename]}\n" \
848
1261
  "# block_name: #{opts[:block_name]}\n" \
849
1262
  "# time: #{Time.now.utc}\n" \
850
1263
  "#{required_blocks.flatten.join("\n")}\n")
1264
+
1265
+ @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1266
+ return if @options[:saved_script_chmod].zero?
1267
+
1268
+ @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1269
+ File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
1270
+ @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
851
1271
  end
852
1272
  end
853
1273
  end