markdown_exec 0.2.6 → 1.1.1

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,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