markdown_exec 0.2.6 → 1.1.1

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,15 @@
3
3
 
4
4
  # encoding=utf-8
5
5
 
6
+ require 'English'
7
+ require 'clipboard'
8
+ require 'mrdialog'
6
9
  require 'open3'
7
10
  require 'optparse'
8
11
  require 'tty-prompt'
9
12
  require 'yaml'
10
13
 
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
-
14
+ require_relative 'shared'
38
15
  require_relative 'markdown_exec/version'
39
16
 
40
17
  $stderr.sync = true
@@ -62,27 +39,17 @@ end
62
39
 
63
40
  public
64
41
 
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
42
+ # display_level values
43
+ DISPLAY_LEVEL_BASE = 0 # required output
44
+ DISPLAY_LEVEL_ADMIN = 1
45
+ DISPLAY_LEVEL_DEBUG = 2
46
+ DISPLAY_LEVEL_DEFAULT = DISPLAY_LEVEL_ADMIN
47
+ DISPLAY_LEVEL_MAX = DISPLAY_LEVEL_DEBUG
48
+
49
+ # @execute_files[ind] = @execute_files[ind] + [block]
50
+ EF_STDOUT = 0
51
+ EF_STDERR = 1
52
+ EF_STDIN = 2
86
53
 
87
54
  module MarkdownExec
88
55
  class Error < StandardError; end
@@ -101,14 +68,22 @@ module MarkdownExec
101
68
  # options necessary to start, parse input, defaults for cli options
102
69
 
103
70
  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))]
71
+ menu_iter do |item|
72
+ item.tap_inspect name: :item, format: :yaml
73
+ next unless item[:opt_name].present?
74
+
75
+ item_default = item[:default]
76
+ item_default.tap_inspect name: :item_default
77
+ value = if item_default.nil?
78
+ item_default
79
+ else
80
+ env_str(item[:env_var], default: value_for_hash(item_default))
81
+ end
82
+ [item[:opt_name], item[:proc1] ? item[:proc1].call(value) : value]
109
83
  end.compact.to_h.merge(
110
84
  {
111
85
  mdheadings: true, # use headings (levels 1,2,3) in block lable
86
+ menu_exit_at_top: true,
112
87
  menu_with_exit: true
113
88
  }
114
89
  ).tap_inspect format: :yaml
@@ -123,6 +98,7 @@ module MarkdownExec
123
98
  prompt_approve_block: 'Process?',
124
99
  prompt_select_block: 'Choose a block:',
125
100
  prompt_select_md: 'Choose a file:',
101
+ prompt_select_output: 'Choose a file:',
126
102
  saved_script_filename: nil, # calculated
127
103
  struct: true # allow get_block_summary()
128
104
  }
@@ -139,13 +115,43 @@ module MarkdownExec
139
115
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
140
116
 
141
117
  allow = true
142
- allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve]
143
- opts[:ir_approve] = allow
118
+ if opts[:user_must_approve]
119
+ loop do
120
+ # (sel = @prompt.select(opts[:prompt_approve_block], %w(Yes No Copy_script_to_clipboard Save_script), cycle: true)).tap_inspect name: :sel
121
+ (sel = @prompt.select(opts[:prompt_approve_block], filter: true) do |menu|
122
+ menu.default 1
123
+ # menu.enum '.'
124
+ # menu.filter true
125
+
126
+ menu.choice 'Yes', 1
127
+ menu.choice 'No', 2
128
+ menu.choice 'Copy script to clipboard', 3
129
+ menu.choice 'Save script', 4
130
+ end).tap_inspect name: :sel
131
+ allow = (sel == 1)
132
+ if sel == 3
133
+ text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
134
+ Clipboard.copy(text)
135
+ fout "Clipboard updated: #{required_blocks.count} blocks, #{required_blocks.flatten.count} lines, #{text.length} characters"
136
+ end
137
+ if sel == 4
138
+ # opts[:saved_script_filename] = saved_name_make(opts)
139
+ write_command_file(opts.merge(save_executed_script: true), required_blocks)
140
+ fout "File saved: #{@options[:saved_filespec]}"
141
+ end
142
+ break if [1, 2].include? sel
143
+ end
144
+ end
145
+ (opts[:ir_approve] = allow).tap_inspect name: :allow
146
+
144
147
  selected = get_block_by_name blocks_in_file, opts[:block_name]
145
148
 
146
149
  if opts[:ir_approve]
147
- write_command_file(opts, required_blocks) if opts[:save_executed_script]
150
+ write_command_file opts, required_blocks
148
151
  command_execute opts, required_blocks.flatten.join("\n")
152
+ save_execution_output
153
+ output_execution_summary
154
+ output_execution_result
149
155
  end
150
156
 
151
157
  selected[:name]
@@ -164,40 +170,49 @@ module MarkdownExec
164
170
  @execute_files = Hash.new([])
165
171
  @execute_options = opts
166
172
  @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
173
+
174
+ Open3.popen3(@options[:shell], '-c', cmd2) do |stdin, stdout, stderr, exec_thr|
175
+ # pid = exec_thr.pid # pid of the started process
176
+
177
+ t1 = Thread.new do
178
+ until (line = stdout.gets).nil?
179
+ @execute_files[EF_STDOUT] = @execute_files[EF_STDOUT] + [line]
180
+ print line if opts[:output_stdout]
181
+ yield nil, line, nil, exec_thr if block_given?
182
+ end
183
+ rescue IOError => e
184
+ # thread killed, do nothing
185
+ end
186
+
187
+ t2 = Thread.new do
188
+ until (line = stderr.gets).nil?
189
+ @execute_files[EF_STDERR] = @execute_files[EF_STDERR] + [line]
190
+ print line if opts[:output_stdout]
191
+ yield nil, nil, line, exec_thr if block_given?
189
192
  end
190
193
  rescue IOError => e
191
- fout "IOError: #{e}"
194
+ # thread killed, do nothing
192
195
  end
193
- @execute_completed_at = Time.now.utc
196
+
197
+ in_thr = Thread.new do
198
+ while exec_thr.alive? # reading input until the child process ends
199
+ stdin.puts(line = $stdin.gets)
200
+ @execute_files[EF_STDIN] = @execute_files[EF_STDIN] + [line]
201
+ yield line, nil, nil, exec_thr if block_given?
202
+ end
203
+ end
204
+
205
+ exec_thr.join
206
+ in_thr.kill
207
+ # @return_code = exec_thr.value
194
208
  end
209
+ @execute_completed_at = Time.now.utc
195
210
  rescue Errno::ENOENT => e
196
211
  # error triggered by missing command in script
197
212
  @execute_aborted_at = Time.now.utc
198
213
  @execute_error_message = e.message
199
214
  @execute_error = e
200
- @execute_files[1] = e.message
215
+ @execute_files[EF_STDERR] += [e.message]
201
216
  fout "Error ENOENT: #{e.inspect}"
202
217
  end
203
218
 
@@ -211,7 +226,9 @@ module MarkdownExec
211
226
  end
212
227
 
213
228
  def display_command(_opts, required_blocks)
229
+ fout ' #=#=#'.yellow
214
230
  required_blocks.each { |cb| fout cb }
231
+ fout ' #=#=#'.yellow
215
232
  end
216
233
 
217
234
  def exec_block(options, _block_name = '')
@@ -221,41 +238,143 @@ module MarkdownExec
221
238
  # document and block reports
222
239
  #
223
240
  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
241
 
256
- if @options[:select_recent_script]
257
- select_recent_script
258
- return
242
+ simple_commands = {
243
+ doc_glob: -> { fout options[:md_filename_glob] },
244
+ list_blocks: lambda do
245
+ fout_list (files.map do |file|
246
+ make_block_labels(filename: file, struct: true)
247
+ end).flatten(1)
248
+ end,
249
+ list_default_yaml: -> { fout_list list_default_yaml },
250
+ list_docs: -> { fout_list files },
251
+ list_default_env: -> { fout_list list_default_env },
252
+ list_recent_output: -> { fout_list list_recent_output },
253
+ list_recent_scripts: -> { fout_list list_recent_scripts },
254
+ pwd: -> { fout File.expand_path('..', __dir__) },
255
+ pwd3: lambda {
256
+ text = 'A B C'
257
+ items = []
258
+ Struct.new('BuildListData', :tag, :item, :status)
259
+ data = Struct::BuildListData.new
260
+
261
+ data.tag = '1'
262
+ data.item = 'Item number 1'
263
+ data.status = true
264
+ items.push(data.to_a)
265
+
266
+ data = Struct::BuildListData.new
267
+ data.tag = '2'
268
+ data.item = 'Item number 2'
269
+ data.status = false
270
+ items.push(data.to_a)
271
+
272
+ data = Struct::BuildListData.new
273
+ data.tag = '3'
274
+ data.item = 'Item number 3'
275
+ data.status = false
276
+ items.push(data.to_a)
277
+
278
+ data = Struct::BuildListData.new
279
+ data.tag = '4'
280
+ data.item = 'Item number 4'
281
+ data.status = true
282
+ items.push(data.to_a)
283
+
284
+ data = Struct::BuildListData.new
285
+ data.tag = '5'
286
+ data.item = 'Item number 5'
287
+ data.status = false
288
+ items.push(data.to_a)
289
+
290
+ data = Struct::BuildListData.new
291
+ data.tag = '6'
292
+ data.item = 'Item number 6'
293
+ data.status = true
294
+ items.push(data.to_a)
295
+
296
+ dialog = MRDialog.new
297
+ dialog.clear = true
298
+ dialog.shadow = false
299
+ dialog.title = 'BUILDLIST'
300
+ # dialog.logger = Logger.new(ENV["HOME"] + "/dialog_" + ME + ".log")
301
+
302
+ height = 0
303
+ width = 0
304
+ listheight = 0
305
+
306
+ selected_items = dialog.buildlist(text, items, height, width, listheight)
307
+ exit_code = dialog.exit_code
308
+ puts "Exit code: #{exit_code}"
309
+ puts 'Selecetd tags:'
310
+ selected_items.each do |item|
311
+ puts " '#{item}'"
312
+ end
313
+ },
314
+ pwd1: lambda {
315
+ dialog = MRDialog.new
316
+ dialog.clear = true
317
+ dialog.title = 'YES/NO BOX'
318
+ puts "yesno: #{dialog.yesno('ABC', 0, 0)}"
319
+ },
320
+ pwd2: lambda {
321
+ dialog = MRDialog.new
322
+ # dialog.logger = Logger.new(ENV["HOME"] + "/dialog_" + ME + ".log")
323
+ dialog.clear = true
324
+ dialog.title = 'MENU BOX'
325
+ text = 'textextst'
326
+ items = []
327
+ menu_data = Struct.new(:tag, :item)
328
+ data = menu_data.new
329
+ data.tag = 'Linux'
330
+ data.item = 'The Great Unix Clone for 386/486'
331
+ items.push(data.to_a)
332
+
333
+ data = menu_data.new
334
+ data.tag = 'NetBSD'
335
+ data.item = 'Another free Unix Clone for 386/486'
336
+ items.push(data.to_a)
337
+
338
+ data = menu_data.new
339
+ data.tag = 'OS/2'
340
+ data.item = 'IBM OS/2'
341
+ items.push(data.to_a)
342
+
343
+ data = menu_data.new
344
+ data.tag = 'WIN NT'
345
+ data.item = 'Microsoft Windows NT'
346
+ items.push(data.to_a)
347
+
348
+ data = menu_data.new
349
+ data.tag = 'PCDOS'
350
+ data.item = 'IBM PC DOS'
351
+ items.push(data.to_a)
352
+
353
+ data = menu_data.new
354
+ data.tag = 'MSDOS'
355
+ data.item = 'Microsoft DOS'
356
+ items.push(data.to_a)
357
+
358
+ height = 0
359
+ width = 0
360
+ menu_height = 4
361
+
362
+ selected_item = dialog.menu(text, items, height, width, menu_height)
363
+
364
+ puts "Selected item: #{selected_item}"
365
+ },
366
+ # pwd: -> { fout `dialog --yesno "ABC" 99 99` },
367
+ run_last_script: -> { run_last_script },
368
+ select_recent_output: -> { select_recent_output },
369
+ select_recent_script: -> { select_recent_script },
370
+ tab_completions: -> { fout tab_completions },
371
+ menu_export: -> { fout menu_export }
372
+ }
373
+ simple_commands.each_key do |key|
374
+ if @options[key]
375
+ simple_commands[key].call
376
+ return # rubocop:disable Lint/NonLocalExitFromIterator
377
+ end
259
378
  end
260
379
 
261
380
  # process
@@ -266,8 +385,6 @@ module MarkdownExec
266
385
  struct: true
267
386
  )
268
387
  fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
269
- save_execution_output
270
- output_execution_summary
271
388
  end
272
389
 
273
390
  # standard output; not for debug
@@ -304,6 +421,19 @@ module MarkdownExec
304
421
  end
305
422
  end
306
423
 
424
+ def approved_fout?(level)
425
+ level <= @options[:display_level]
426
+ end
427
+
428
+ # display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
429
+ #
430
+ def lout(str, level: DISPLAY_LEVEL_BASE)
431
+ return unless approved_fout? level
432
+
433
+ # fout level == DISPLAY_LEVEL_BASE ? str : DISPLAY_LEVEL_XBASE_PREFIX + str
434
+ fout level == DISPLAY_LEVEL_BASE ? str : @options[:display_level_xbase_prefix] + str
435
+ end
436
+
307
437
  def list_blocks_in_file(call_options = {}, &options_block)
308
438
  opts = optsmerge call_options, options_block
309
439
 
@@ -312,6 +442,11 @@ module MarkdownExec
312
442
  exit 1
313
443
  end
314
444
 
445
+ unless File.exist? opts[:filename]
446
+ fout 'Document is missing.'
447
+ exit 1
448
+ end
449
+
315
450
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
316
451
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
317
452
  block_title = ''
@@ -367,25 +502,23 @@ module MarkdownExec
367
502
  end
368
503
 
369
504
  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?
505
+ menu_iter do |item|
506
+ next unless item[:env_var].present?
373
507
 
374
508
  [
375
- "#{env_var}=#{value_for_cli default}",
376
- description.present? ? description : nil
509
+ "#{item[:env_var]}=#{value_for_cli item[:default]}",
510
+ item[:description].present? ? item[:description] : nil
377
511
  ].compact.join(' # ')
378
512
  end.compact.sort
379
513
  end
380
514
 
381
515
  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?
516
+ menu_iter do |item|
517
+ next unless item[:opt_name].present? && item[:default].present?
385
518
 
386
519
  [
387
- "#{opt_name}: #{value_for_yaml default}",
388
- description.present? ? description : nil
520
+ "#{item[:opt_name]}: #{value_for_yaml item[:default]}",
521
+ item[:description].present? ? item[:description] : nil
389
522
  ].compact.join(' # ')
390
523
  end.compact.sort
391
524
  end
@@ -451,9 +584,28 @@ module MarkdownExec
451
584
  .tap_inspect
452
585
  end
453
586
 
587
+ def most_recent(arr)
588
+ return unless arr
589
+ return if arr.count < 1
590
+
591
+ arr.max.tap_inspect
592
+ end
593
+
594
+ def most_recent_list(arr)
595
+ return unless arr
596
+ return if (ac = arr.count) < 1
597
+
598
+ arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
599
+ end
600
+
601
+ def list_recent_output
602
+ most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
603
+ @options[:saved_stdout_glob]))).tap_inspect
604
+ end
605
+
454
606
  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
607
+ most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
608
+ @options[:saved_script_glob]))).tap_inspect
457
609
  end
458
610
 
459
611
  def make_block_label(block, call_options = {})
@@ -475,76 +627,400 @@ module MarkdownExec
475
627
  end.compact.tap_inspect
476
628
  end
477
629
 
478
- def menu_data
479
- val_as_bool = ->(value) { value.to_i != 0 }
630
+ def menu_data1
631
+ val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
480
632
  val_as_int = ->(value) { value.to_i }
481
633
  val_as_str = ->(value) { value.to_s }
482
- val_true = ->(_) { true }
483
-
484
- summary_head = [
485
- ['config', nil, nil, 'PATH', 'Read configuration file', nil, '.', lambda { |value|
486
- read_configuration_file! options, value
487
- }],
488
- ['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output', nil, false, ->(value) { $pdebug = value.to_i != 0 }]
489
- ]
490
-
491
- # rubocop:disable Layout/LineLength
492
- summary_body = [
493
- ['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document', :filename, nil, val_as_str],
494
- ['list-blocks', nil, nil, nil, 'List blocks', :list_blocks, nil, val_true],
495
- ['list-count', nil, 'MDE_LIST_COUNT', 'NUM', 'Max. items to return in list', :list_count, 16, val_as_int],
496
- ['list-default-env', nil, nil, nil, 'List default configuration as environment variables', :list_default_env, nil, val_true],
497
- ['list-default-yaml', nil, nil, nil, 'List default configuration as YAML', :list_default_yaml, nil, val_true],
498
- ['list-docs', nil, nil, nil, 'List docs in current folder', :list_docs, nil, val_true],
499
- ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts', :list_recent_scripts, nil, val_true],
500
- ['logged-stdout-filename-prefix', nil, 'MDE_LOGGED_STDOUT_FILENAME_PREFIX', 'NAME', 'Name prefix for stdout files', :logged_stdout_filename_prefix, 'mde', val_as_str],
501
- ['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution', :output_execution_summary, false, val_as_bool],
502
- ['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script prior to execution', :output_script, false, val_as_bool],
503
- ['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution', :output_stdout, true, val_as_bool],
504
- ['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents', :path, nil, val_as_str],
505
- ['run-last-script', nil, nil, nil, 'Run most recently saved script', :run_last_script, nil, val_true],
506
- ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script', :select_recent_script, nil, val_true],
507
- ['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script', :save_executed_script, false, val_as_bool],
508
- ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save standard output of the executed script', :save_execution_output, false, val_as_bool],
509
- ['saved-script-filename-prefix', nil, 'MDE_SAVED_SCRIPT_FILENAME_PREFIX', 'NAME', 'Name prefix for saved scripts', :saved_script_filename_prefix, 'mde', val_as_str],
510
- ['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder', :saved_script_folder, 'logs', val_as_str],
511
- ['saved-script-glob', nil, 'MDE_SAVED_SCRIPT_GLOB', 'SPEC', 'Glob matching saved scripts', :saved_script_glob, 'mde_*.sh', val_as_str],
512
- ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder', :saved_stdout_folder, 'logs', val_as_str],
513
- ['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause for user to approve script', :user_must_approve, true, val_as_bool]
514
- ]
515
- # rubocop:enable Layout/LineLength
516
-
517
- # rubocop:disable Style/Semicolon
518
- summary_tail = [
519
- [nil, '0', nil, nil, 'Show current configuration values',
520
- nil, nil, ->(_) { options_finalize options; fout sorted_keys(options).to_yaml }],
521
- ['help', 'h', nil, nil, 'App help',
522
- nil, nil, ->(_) { fout menu_help; exit }],
523
- ['version', 'v', nil, nil, 'App version',
524
- nil, nil, ->(_) { fout MarkdownExec::VERSION; exit }],
525
- ['exit', 'x', nil, nil, 'Exit app',
526
- nil, nil, ->(_) { exit }]
527
- ]
528
- # rubocop:enable Style/Semicolon
529
-
530
- env_vars = [
531
- [nil, nil, 'MDE_BLOCK_NAME_EXCLUDED_MATCH', nil, 'Pattern for blocks to hide from user-selection',
532
- :block_name_excluded_match, '^\(.+\)$', nil],
533
- [nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', nil],
534
- [nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', nil],
535
- [nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', nil],
536
- [nil, nil, 'MDE_FENCED_START_EX_MATCH', nil, '', :fenced_start_ex_match,
537
- '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$', nil],
538
- [nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', nil],
539
- [nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', nil],
540
- [nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', nil],
541
- [nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', nil],
542
- [nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', nil],
543
- [nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, nil]
544
- # [nil, nil, 'MDE_', nil, '', nil, '', nil],
634
+ # val_true = ->(_value) { true } # for commands, sets option to true
635
+ set1 = [
636
+ {
637
+ arg_name: 'PATH',
638
+ default: '.',
639
+ description: 'Read configuration file',
640
+ long_name: 'config',
641
+ proc1: lambda { |value|
642
+ read_configuration_file! options, value
643
+ }
644
+ },
645
+ {
646
+ arg_name: 'BOOL',
647
+ default: false,
648
+ description: 'Debug output',
649
+ env_var: 'MDE_DEBUG',
650
+ long_name: 'debug',
651
+ short_name: 'd',
652
+ proc1: lambda { |value|
653
+ $pdebug = value.to_i != 0
654
+ }
655
+ },
656
+ {
657
+ arg_name: "INT.#{DISPLAY_LEVEL_BASE}-#{DISPLAY_LEVEL_MAX}",
658
+ default: DISPLAY_LEVEL_DEFAULT,
659
+ description: "Output display level (#{DISPLAY_LEVEL_BASE} to #{DISPLAY_LEVEL_MAX})",
660
+ env_var: 'MDE_DISPLAY_LEVEL',
661
+ long_name: 'display-level',
662
+ opt_name: :display_level,
663
+ proc1: val_as_int
664
+ },
665
+ {
666
+ arg_name: 'NAME',
667
+ compreply: false,
668
+ description: 'Name of block',
669
+ env_var: 'MDE_BLOCK_NAME',
670
+ long_name: 'block-name',
671
+ opt_name: :block_name,
672
+ short_name: 'f',
673
+ proc1: val_as_str
674
+ },
675
+ {
676
+ arg_name: 'RELATIVE_PATH',
677
+ compreply: '.',
678
+ description: 'Name of document',
679
+ env_var: 'MDE_FILENAME',
680
+ long_name: 'filename',
681
+ opt_name: :filename,
682
+ short_name: 'f',
683
+ proc1: val_as_str
684
+ },
685
+ {
686
+ description: 'List blocks',
687
+ long_name: 'list-blocks',
688
+ opt_name: :list_blocks,
689
+ proc1: val_as_bool
690
+ },
691
+ {
692
+ arg_name: 'INT.1-',
693
+ default: 32,
694
+ description: 'Max. items to return in list',
695
+ env_var: 'MDE_LIST_COUNT',
696
+ long_name: 'list-count',
697
+ opt_name: :list_count,
698
+ proc1: val_as_int
699
+ },
700
+ {
701
+ description: 'List default configuration as environment variables',
702
+ long_name: 'list-default-env',
703
+ opt_name: :list_default_env
704
+ },
705
+ {
706
+ description: 'List default configuration as YAML',
707
+ long_name: 'list-default-yaml',
708
+ opt_name: :list_default_yaml
709
+ },
710
+ {
711
+ description: 'List docs in current folder',
712
+ long_name: 'list-docs',
713
+ opt_name: :list_docs,
714
+ proc1: val_as_bool
715
+ },
716
+ {
717
+ description: 'List recent saved output',
718
+ long_name: 'list-recent-output',
719
+ opt_name: :list_recent_output,
720
+ proc1: val_as_bool
721
+ },
722
+ {
723
+ description: 'List recent saved scripts',
724
+ long_name: 'list-recent-scripts',
725
+ opt_name: :list_recent_scripts,
726
+ proc1: val_as_bool
727
+ },
728
+ {
729
+ arg_name: 'PREFIX',
730
+ default: MarkdownExec::BIN_NAME,
731
+ description: 'Name prefix for stdout files',
732
+ env_var: 'MDE_LOGGED_STDOUT_FILENAME_PREFIX',
733
+ long_name: 'logged-stdout-filename-prefix',
734
+ opt_name: :logged_stdout_filename_prefix,
735
+ proc1: val_as_str
736
+ },
737
+ {
738
+ arg_name: 'BOOL',
739
+ default: false,
740
+ description: 'Display summary for execution',
741
+ env_var: 'MDE_OUTPUT_EXECUTION_SUMMARY',
742
+ long_name: 'output-execution-summary',
743
+ opt_name: :output_execution_summary,
744
+ proc1: val_as_bool
745
+ },
746
+ {
747
+ arg_name: 'BOOL',
748
+ default: false,
749
+ description: 'Display script prior to execution',
750
+ env_var: 'MDE_OUTPUT_SCRIPT',
751
+ long_name: 'output-script',
752
+ opt_name: :output_script,
753
+ proc1: val_as_bool
754
+ },
755
+ {
756
+ arg_name: 'BOOL',
757
+ default: true,
758
+ description: 'Display standard output from execution',
759
+ env_var: 'MDE_OUTPUT_STDOUT',
760
+ long_name: 'output-stdout',
761
+ opt_name: :output_stdout,
762
+ proc1: val_as_bool
763
+ },
764
+ {
765
+ arg_name: 'RELATIVE_PATH',
766
+ default: '.',
767
+ description: 'Path to documents',
768
+ env_var: 'MDE_PATH',
769
+ long_name: 'path',
770
+ opt_name: :path,
771
+ short_name: 'p',
772
+ proc1: val_as_str
773
+ },
774
+ {
775
+ description: 'Gem home folder',
776
+ long_name: 'pwd',
777
+ opt_name: :pwd,
778
+ proc1: val_as_bool
779
+ },
780
+ {
781
+ description: 'Run most recently saved script',
782
+ long_name: 'run-last-script',
783
+ opt_name: :run_last_script,
784
+ proc1: val_as_bool
785
+ },
786
+ {
787
+ arg_name: 'BOOL',
788
+ default: false,
789
+ description: 'Save executed script',
790
+ env_var: 'MDE_SAVE_EXECUTED_SCRIPT',
791
+ long_name: 'save-executed-script',
792
+ opt_name: :save_executed_script,
793
+ proc1: val_as_bool
794
+ },
795
+ {
796
+ arg_name: 'BOOL',
797
+ default: false,
798
+ description: 'Save standard output of the executed script',
799
+ env_var: 'MDE_SAVE_EXECUTION_OUTPUT',
800
+ long_name: 'save-execution-output',
801
+ opt_name: :save_execution_output,
802
+ proc1: val_as_bool
803
+ },
804
+ {
805
+ arg_name: 'INT',
806
+ default: 0o755,
807
+ description: 'chmod for saved scripts',
808
+ env_var: 'MDE_SAVED_SCRIPT_CHMOD',
809
+ long_name: 'saved-script-chmod',
810
+ opt_name: :saved_script_chmod,
811
+ proc1: val_as_int
812
+ },
813
+ {
814
+ arg_name: 'PREFIX',
815
+ default: MarkdownExec::BIN_NAME,
816
+ description: 'Name prefix for saved scripts',
817
+ env_var: 'MDE_SAVED_SCRIPT_FILENAME_PREFIX',
818
+ long_name: 'saved-script-filename-prefix',
819
+ opt_name: :saved_script_filename_prefix,
820
+ proc1: val_as_str
821
+ },
822
+ {
823
+ arg_name: 'RELATIVE_PATH',
824
+ default: 'logs',
825
+ description: 'Saved script folder',
826
+ env_var: 'MDE_SAVED_SCRIPT_FOLDER',
827
+ long_name: 'saved-script-folder',
828
+ opt_name: :saved_script_folder,
829
+ proc1: val_as_str
830
+ },
831
+ {
832
+ arg_name: 'GLOB',
833
+ default: 'mde_*.sh',
834
+ description: 'Glob matching saved scripts',
835
+ env_var: 'MDE_SAVED_SCRIPT_GLOB',
836
+ long_name: 'saved-script-glob',
837
+ opt_name: :saved_script_glob,
838
+ proc1: val_as_str
839
+ },
840
+ {
841
+ arg_name: 'RELATIVE_PATH',
842
+ default: 'logs',
843
+ description: 'Saved stdout folder',
844
+ env_var: 'MDE_SAVED_STDOUT_FOLDER',
845
+ long_name: 'saved-stdout-folder',
846
+ opt_name: :saved_stdout_folder,
847
+ proc1: val_as_str
848
+ },
849
+ {
850
+ arg_name: 'GLOB',
851
+ default: 'mde_*.out.txt',
852
+ description: 'Glob matching saved outputs',
853
+ env_var: 'MDE_SAVED_STDOUT_GLOB',
854
+ long_name: 'saved-stdout-glob',
855
+ opt_name: :saved_stdout_glob,
856
+ proc1: val_as_str
857
+ },
858
+ {
859
+ description: 'Select and execute a recently saved output',
860
+ long_name: 'select-recent-output',
861
+ opt_name: :select_recent_output,
862
+ proc1: val_as_bool
863
+ },
864
+ {
865
+ description: 'Select and execute a recently saved script',
866
+ long_name: 'select-recent-script',
867
+ opt_name: :select_recent_script,
868
+ proc1: val_as_bool
869
+ },
870
+ {
871
+ description: 'YAML export of menu',
872
+ long_name: 'menu-export',
873
+ opt_name: :menu_export,
874
+ proc1: val_as_bool
875
+ },
876
+ {
877
+ description: 'List tab completions',
878
+ long_name: 'tab-completions',
879
+ opt_name: :tab_completions,
880
+ proc1: val_as_bool
881
+ },
882
+ {
883
+ arg_name: 'BOOL',
884
+ default: true,
885
+ description: 'Pause for user to approve script',
886
+ env_var: 'MDE_USER_MUST_APPROVE',
887
+ long_name: 'user-must-approve',
888
+ opt_name: :user_must_approve,
889
+ proc1: val_as_bool
890
+ },
891
+ {
892
+ description: 'Show current configuration values',
893
+ short_name: '0',
894
+ proc1: lambda { |_|
895
+ options_finalize options
896
+ fout sorted_keys(options).to_yaml
897
+ }
898
+ },
899
+ {
900
+ description: 'App help',
901
+ long_name: 'help',
902
+ short_name: 'h',
903
+ proc1: lambda { |_|
904
+ fout menu_help
905
+ exit
906
+ }
907
+ },
908
+ {
909
+ description: "Print the gem's version",
910
+ long_name: 'version',
911
+ short_name: 'v',
912
+ proc1: lambda { |_|
913
+ fout MarkdownExec::VERSION
914
+ exit
915
+ }
916
+ },
917
+ {
918
+ description: 'Exit app',
919
+ long_name: 'exit',
920
+ short_name: 'x',
921
+ proc1: ->(_) { exit }
922
+ },
923
+ {
924
+ default: '^\(.*\)$',
925
+ description: 'Pattern for blocks to hide from user-selection',
926
+ env_var: 'MDE_BLOCK_NAME_EXCLUDED_MATCH',
927
+ opt_name: :block_name_excluded_match,
928
+ proc1: val_as_str
929
+ },
930
+ {
931
+ default: ':(?<title>\S+)( |$)',
932
+ env_var: 'MDE_BLOCK_NAME_MATCH',
933
+ opt_name: :block_name_match,
934
+ proc1: val_as_str
935
+ },
936
+ {
937
+ default: '\+\S+',
938
+ env_var: 'MDE_BLOCK_REQUIRED_SCAN',
939
+ opt_name: :block_required_scan,
940
+ proc1: val_as_str
941
+ },
942
+ {
943
+ default: '> ',
944
+ env_var: 'MDE_DISPLAY_LEVEL_XBASE_PREFIX',
945
+ opt_name: :display_level_xbase_prefix,
946
+ proc1: val_as_str
947
+ },
948
+ {
949
+ default: '^`{3,}',
950
+ env_var: 'MDE_FENCED_START_AND_END_MATCH',
951
+ opt_name: :fenced_start_and_end_match,
952
+ proc1: val_as_str
953
+ },
954
+ {
955
+ default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$',
956
+ env_var: 'MDE_FENCED_START_EX_MATCH',
957
+ opt_name: :fenced_start_ex_match,
958
+ proc1: val_as_str
959
+ },
960
+ {
961
+ default: '^# *(?<name>[^#]*?) *$',
962
+ env_var: 'MDE_HEADING1_MATCH',
963
+ opt_name: :heading1_match,
964
+ proc1: val_as_str
965
+ },
966
+ {
967
+ default: '^## *(?<name>[^#]*?) *$',
968
+ env_var: 'MDE_HEADING2_MATCH',
969
+ opt_name: :heading2_match,
970
+ proc1: val_as_str
971
+ },
972
+ {
973
+ default: '^### *(?<name>.+?) *$',
974
+ env_var: 'MDE_HEADING3_MATCH',
975
+ opt_name: :heading3_match,
976
+ proc1: val_as_str
977
+ },
978
+ {
979
+ default: '*.[Mm][Dd]',
980
+ env_var: 'MDE_MD_FILENAME_GLOB',
981
+ opt_name: :md_filename_glob,
982
+ proc1: val_as_str
983
+ },
984
+ {
985
+ default: '.+\\.md',
986
+ env_var: 'MDE_MD_FILENAME_MATCH',
987
+ opt_name: :md_filename_match,
988
+ proc1: val_as_str
989
+ },
990
+ {
991
+ description: 'Options for viewing saved output file',
992
+ env_var: 'MDE_OUTPUT_VIEWER_OPTIONS',
993
+ opt_name: :output_viewer_options,
994
+ proc1: val_as_str
995
+ },
996
+ {
997
+ default: 24,
998
+ description: 'Maximum # of rows in select list',
999
+ env_var: 'MDE_SELECT_PAGE_HEIGHT',
1000
+ opt_name: :select_page_height,
1001
+ proc1: val_as_int
1002
+ },
1003
+ {
1004
+ default: '#!/usr/bin/env',
1005
+ description: 'Shebang for saved scripts',
1006
+ env_var: 'MDE_SHEBANG',
1007
+ opt_name: :shebang,
1008
+ proc1: val_as_str
1009
+ },
1010
+ {
1011
+ default: 'bash',
1012
+ description: 'Shell for launched scripts',
1013
+ env_var: 'MDE_SHELL',
1014
+ opt_name: :shell,
1015
+ proc1: val_as_str
1016
+ }
545
1017
  ]
1018
+ # commands first, options second
1019
+ (set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
1020
+ end
546
1021
 
547
- summary_head + summary_body + summary_tail + env_vars
1022
+ def menu_iter(data = menu_data1, &block)
1023
+ data.map(&block)
548
1024
  end
549
1025
 
550
1026
  def menu_help
@@ -577,7 +1053,8 @@ module MarkdownExec
577
1053
 
578
1054
  ## position 1: block name (optional)
579
1055
  #
580
- @options[:block_name] = rest.fetch(1, nil)
1056
+ block_name = rest.fetch(1, nil)
1057
+ @options[:block_name] = block_name if block_name.present?
581
1058
  end
582
1059
 
583
1060
  def optsmerge(call_options = {}, options_block = nil)
@@ -589,6 +1066,24 @@ module MarkdownExec
589
1066
  end.tap_inspect
590
1067
  end
591
1068
 
1069
+ def output_execution_result
1070
+ oq = [['Block', @options[:block_name], DISPLAY_LEVEL_ADMIN],
1071
+ ['Command',
1072
+ [MarkdownExec::BIN_NAME,
1073
+ @options[:filename],
1074
+ @options[:block_name]].join(' '),
1075
+ DISPLAY_LEVEL_ADMIN]]
1076
+
1077
+ [['Script', :saved_filespec],
1078
+ ['StdOut', :logged_stdout_filespec]].each do |label, name|
1079
+ oq << [label, @options[name], DISPLAY_LEVEL_ADMIN] if @options[name]
1080
+ end
1081
+
1082
+ oq.map do |label, value, level|
1083
+ lout ["#{label}:".yellow, value.to_s].join(' '), level: level
1084
+ end
1085
+ end
1086
+
592
1087
  def output_execution_summary
593
1088
  return unless @options[:output_execution_summary]
594
1089
 
@@ -606,9 +1101,12 @@ module MarkdownExec
606
1101
 
607
1102
  def prompt_with_quit(prompt_text, items, opts = {})
608
1103
  exit_option = '* Exit'
609
- sel = @prompt.select prompt_text,
610
- items + (@options[:menu_with_exit] ? [exit_option] : []),
611
- opts
1104
+ all_items = if @options[:menu_exit_at_top]
1105
+ (@options[:menu_with_exit] ? [exit_option] : []) + items
1106
+ else
1107
+ items + (@options[:menu_with_exit] ? [exit_option] : [])
1108
+ end
1109
+ sel = @prompt.select(prompt_text, all_items, opts.merge(filter: true))
612
1110
  sel == exit_option ? nil : sel
613
1111
  end
614
1112
 
@@ -652,22 +1150,22 @@ module MarkdownExec
652
1150
  opts.banner = [
653
1151
  "#{MarkdownExec::APP_NAME}" \
654
1152
  " - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
655
- "Usage: #{executable_name} [path] [filename] [options]"
1153
+ "Usage: #{executable_name} [(path | filename [block_name])] [options]"
656
1154
  ].join("\n")
657
1155
 
658
- menu_data
659
- .map do |long_name, short_name, _env_var, arg_name, description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
660
- next unless long_name.present? || short_name.present?
1156
+ menu_iter do |item|
1157
+ next unless item[:long_name].present? || item[:short_name].present?
661
1158
 
662
- opts.on(*[if long_name.present?
663
- "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
1159
+ opts.on(*[if item[:long_name].present?
1160
+ "--#{item[:long_name]}#{item[:arg_name].present? ? " #{item[:arg_name]}" : ''}"
664
1161
  end,
665
- short_name.present? ? "-#{short_name}" : nil,
666
- [description,
667
- default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
1162
+ item[:short_name].present? ? "-#{item[:short_name]}" : nil,
1163
+ [item[:description],
1164
+ item[:default].present? ? "[#{value_for_cli item[:default]}]" : nil].compact.join(' '),
668
1165
  lambda { |value|
669
- ret = proc1.call(value)
670
- options[opt_name] = ret if opt_name
1166
+ # ret = item[:proc1].call(value)
1167
+ ret = item[:proc1] ? item[:proc1].call(value) : value
1168
+ options[item[:opt_name]] = ret if item[:opt_name]
671
1169
  ret
672
1170
  }].compact)
673
1171
  end
@@ -681,24 +1179,39 @@ module MarkdownExec
681
1179
  exec_block options, options[:block_name]
682
1180
  end
683
1181
 
1182
+ FNR11 = '/'
1183
+ FNR12 = ',~'
1184
+
1185
+ def saved_name_make(opts)
1186
+ fne = opts[:filename].gsub(FNR11, FNR12)
1187
+ "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
1188
+ ',', opts[:block_name]].join('_')}.sh"
1189
+ end
1190
+
1191
+ def saved_name_split(name)
1192
+ mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
1193
+ return unless mf
1194
+
1195
+ @options[:block_name] = mf[:block].tap_inspect name: :options_block_name
1196
+ @options[:filename] = mf[:file].gsub(FNR12, FNR11).tap_inspect name: :options_filename
1197
+ end
1198
+
684
1199
  def run_last_script
685
- filename = Dir.glob(File.join(@options[:saved_script_folder],
686
- @options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].last
687
- filename.tap_inspect name: filename
688
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
1200
+ filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
1201
+ @options[:saved_script_glob]))
1202
+ return unless filename
689
1203
 
690
- @options[:block_name] = mf[:block]
691
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
1204
+ filename.tap_inspect name: filename
1205
+ saved_name_split filename
692
1206
  @options[:save_executed_script] = false
693
1207
  select_and_approve_block
694
- save_execution_output
695
- output_execution_summary
696
1208
  end
697
1209
 
698
1210
  def save_execution_output
699
1211
  return unless @options[:save_execution_output]
700
1212
 
701
1213
  fne = File.basename(@options[:filename], '.*')
1214
+
702
1215
  @options[:logged_stdout_filename] =
703
1216
  "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
704
1217
  @options[:block_name]].join('_')}.out.txt"
@@ -706,34 +1219,51 @@ module MarkdownExec
706
1219
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
707
1220
  dirname = File.dirname(@options[:logged_stdout_filespec])
708
1221
  Dir.mkdir dirname unless File.exist?(dirname)
709
- File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(0, ''))
710
- # @options[:logged_stderr_filename] =
711
- # "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
712
- # @options[:block_name]].join('_')}.err.txt"
713
- # @options[:logged_stderr_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stderr_filename]
714
- # @logged_stderr_filespec = @options[:logged_stderr_filespec]
715
- # File.write(@options[:logged_stderr_filespec], @execute_files&.fetch(1, ''))
1222
+
1223
+ # File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(EF_STDOUT, ''))
1224
+ ol = ["-STDOUT-\n"]
1225
+ ol += @execute_files&.fetch(EF_STDOUT, [])
1226
+ ol += ["-STDERR-\n"].tap_inspect name: :ol3
1227
+ ol += @execute_files&.fetch(EF_STDERR, [])
1228
+ ol += ["-STDIN-\n"]
1229
+ ol += @execute_files&.fetch(EF_STDIN, [])
1230
+ File.write(@options[:logged_stdout_filespec], ol.join)
716
1231
  end
717
1232
 
718
1233
  def select_and_approve_block(call_options = {}, &options_block)
719
1234
  opts = optsmerge call_options, options_block
720
1235
  blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
721
1236
 
722
- unless opts[:block_name].present?
723
- pt = (opts[:prompt_select_block]).to_s
724
- blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
725
- block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
1237
+ loop1 = true && !opts[:block_name].present?
726
1238
 
727
- return nil if block_labels.count.zero?
1239
+ loop do
1240
+ unless opts[:block_name].present?
1241
+ pt = (opts[:prompt_select_block]).to_s
1242
+ blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
1243
+ block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
728
1244
 
729
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
730
- return nil if sel.nil?
1245
+ return nil if block_labels.count.zero?
731
1246
 
732
- label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
733
- opts[:block_name] = @options[:block_name] = label_block[:name]
734
- end
1247
+ sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
1248
+ return nil if sel.nil?
735
1249
 
736
- approve_block opts, blocks_in_file
1250
+ # if sel.nil?
1251
+ # loop1 = false
1252
+ # break
1253
+ # end
1254
+
1255
+ label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
1256
+ opts[:block_name] = @options[:block_name] = label_block[:name]
1257
+
1258
+ end
1259
+ # if loop1
1260
+ approve_block opts, blocks_in_file
1261
+ # end
1262
+
1263
+ break unless loop1
1264
+
1265
+ opts[:block_name] = ''
1266
+ end
737
1267
  end
738
1268
 
739
1269
  def select_md_file(files_ = nil)
@@ -746,22 +1276,25 @@ module MarkdownExec
746
1276
  end
747
1277
  end
748
1278
 
1279
+ def select_recent_output
1280
+ filename = prompt_with_quit @options[:prompt_select_output].to_s, list_recent_output,
1281
+ per_page: @options[:select_page_height]
1282
+ return unless filename.present?
1283
+
1284
+ `open #{filename} #{options[:output_viewer_options]}`
1285
+ end
1286
+
749
1287
  def select_recent_script
750
1288
  filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
751
1289
  per_page: @options[:select_page_height]
752
1290
  return if filename.nil?
753
1291
 
754
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
755
-
756
- @options[:block_name] = mf[:block]
757
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
1292
+ saved_name_split filename
758
1293
  select_and_approve_block(
759
1294
  bash: true,
760
1295
  save_executed_script: false,
761
1296
  struct: true
762
1297
  )
763
- save_execution_output
764
- output_execution_summary
765
1298
  end
766
1299
 
767
1300
  def sorted_keys(hash1)
@@ -772,6 +1305,19 @@ module MarkdownExec
772
1305
  { headings: headings, name: title, title: title }
773
1306
  end
774
1307
 
1308
+ def menu_export(data = menu_data1)
1309
+ data.map do |item|
1310
+ item.delete(:proc1)
1311
+ item
1312
+ end.to_yaml
1313
+ end
1314
+
1315
+ def tab_completions(data = menu_data1)
1316
+ data.map do |item|
1317
+ "--#{item[:long_name]}" if item[:long_name]
1318
+ end.compact
1319
+ end
1320
+
775
1321
  def update_options(opts = {}, over: true)
776
1322
  if over
777
1323
  @options = @options.merge opts
@@ -781,19 +1327,6 @@ module MarkdownExec
781
1327
  @options.tap_inspect format: :yaml
782
1328
  end
783
1329
 
784
- def value_for_cli(value)
785
- case value.class.to_s
786
- when 'String'
787
- "'#{value}'"
788
- when 'FalseClass', 'TrueClass'
789
- value ? '1' : '0'
790
- when 'Integer'
791
- value
792
- else
793
- value.to_s
794
- end
795
- end
796
-
797
1330
  def value_for_hash(value, default = nil)
798
1331
  return default if value.nil?
799
1332
 
@@ -825,19 +1358,33 @@ module MarkdownExec
825
1358
  end
826
1359
 
827
1360
  def write_command_file(opts, required_blocks)
828
- fne = File.basename(opts[:filename], '.*')
829
- opts[:saved_script_filename] =
830
- "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
831
- opts[:block_name]].join('_')}.sh"
832
- @options[:saved_filespec] = File.join opts[:saved_script_folder], opts[:saved_script_filename]
833
- @execute_script_filespec = @options[:saved_filespec]
1361
+ return unless opts[:save_executed_script]
1362
+
1363
+ opts[:saved_script_filename] = saved_name_make(opts)
1364
+ @execute_script_filespec =
1365
+ @options[:saved_filespec] =
1366
+ File.join opts[:saved_script_folder], opts[:saved_script_filename]
1367
+
834
1368
  dirname = File.dirname(@options[:saved_filespec])
835
1369
  Dir.mkdir dirname unless File.exist?(dirname)
836
- File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \
1370
+ (shebang = if @options[:shebang]&.present?
1371
+ "#{@options[:shebang]} #{@options[:shell]}\n"
1372
+ else
1373
+ ''
1374
+ end
1375
+ ).tap_inspect name: :shebang
1376
+ File.write(@options[:saved_filespec], shebang +
837
1377
  "# file_name: #{opts[:filename]}\n" \
838
1378
  "# block_name: #{opts[:block_name]}\n" \
839
1379
  "# time: #{Time.now.utc}\n" \
840
1380
  "#{required_blocks.flatten.join("\n")}\n")
1381
+
1382
+ @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1383
+ return if @options[:saved_script_chmod].zero?
1384
+
1385
+ @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
1386
+ File.chmod @options[:saved_script_chmod], @options[:saved_filespec]
1387
+ @options[:saved_script_chmod].tap_inspect name: :@options_saved_script_chmod
841
1388
  end
842
1389
  end
843
1390
  end