markdown_exec 0.2.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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