markdown_exec 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/markdown_exec.rb CHANGED
@@ -3,38 +3,14 @@
3
3
 
4
4
  # encoding=utf-8
5
5
 
6
+ require 'English'
7
+ require 'clipboard'
6
8
  require 'open3'
7
9
  require 'optparse'
8
10
  require 'tty-prompt'
9
11
  require 'yaml'
10
12
 
11
- ##
12
- # default if nil
13
- # false if empty or '0'
14
- # else true
15
-
16
- def env_bool(name, default: false)
17
- return default if name.nil? || (val = ENV[name]).nil?
18
- return false if val.empty? || val == '0'
19
-
20
- true
21
- end
22
-
23
- def env_int(name, default: 0)
24
- return default if name.nil? || (val = ENV[name]).nil?
25
- return default if val.empty?
26
-
27
- val.to_i
28
- end
29
-
30
- def env_str(name, default: '')
31
- return default if name.nil? || (val = ENV[name]).nil?
32
-
33
- val || default
34
- end
35
-
36
- $pdebug = env_bool 'MDE_DEBUG'
37
-
13
+ require_relative 'shared'
38
14
  require_relative 'markdown_exec/version'
39
15
 
40
16
  $stderr.sync = true
@@ -62,24 +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
- cvt = {
71
- json: :to_json,
72
- string: :to_s,
73
- yaml: :to_yaml,
74
- else: :inspect
75
- }
76
- fn = cvt.fetch(format, cvt[:else])
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
77
47
 
78
- puts "-> #{caller[0].scan(/in `?(\S+)'$/)[0][0]}()" \
79
- " #{name}: #{method(fn).call}"
80
-
81
- self
82
- end
48
+ # @execute_files[ind] = @execute_files[ind] + [block]
49
+ EF_STDOUT = 0
50
+ EF_STDERR = 1
51
+ EF_STDIN = 2
83
52
 
84
53
  module MarkdownExec
85
54
  class Error < StandardError; end
@@ -98,15 +67,22 @@ module MarkdownExec
98
67
  # options necessary to start, parse input, defaults for cli options
99
68
 
100
69
  def base_options
101
- menu_data
102
- .map do |_long_name, _short_name, env_var, _arg_name, _description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
103
- next unless opt_name.present?
104
-
105
- value = env_str(env_var, default: value_for_hash(default))
106
- [opt_name, proc1 ? proc1.call(value) : value]
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]
107
82
  end.compact.to_h.merge(
108
83
  {
109
84
  mdheadings: true, # use headings (levels 1,2,3) in block lable
85
+ menu_exit_at_top: true,
110
86
  menu_with_exit: true
111
87
  }
112
88
  ).tap_inspect format: :yaml
@@ -138,8 +114,35 @@ module MarkdownExec
138
114
  display_command(opts, required_blocks) if opts[:output_script] || opts[:user_must_approve]
139
115
 
140
116
  allow = true
141
- allow = @prompt.yes? opts[:prompt_approve_block] if opts[:user_must_approve]
142
- 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
+
143
146
  selected = get_block_by_name blocks_in_file, opts[:block_name]
144
147
 
145
148
  if opts[:ir_approve]
@@ -147,6 +150,7 @@ module MarkdownExec
147
150
  command_execute opts, required_blocks.flatten.join("\n")
148
151
  save_execution_output
149
152
  output_execution_summary
153
+ output_execution_result
150
154
  end
151
155
 
152
156
  selected[:name]
@@ -165,40 +169,45 @@ module MarkdownExec
165
169
  @execute_files = Hash.new([])
166
170
  @execute_options = opts
167
171
  @execute_started_at = Time.now.utc
168
- Open3.popen3(cmd2) do |stdin, stdout, stderr|
169
- stdin.close_write
170
- begin
171
- files = [stdout, stderr]
172
-
173
- until all_at_eof(files)
174
- ready = IO.select(files)
175
-
176
- next unless ready
177
-
178
- # readable = ready[0]
179
- # # writable = ready[1]
180
- # # exceptions = ready[2]
181
- ready.each.with_index do |readable, ind|
182
- readable.each do |f|
183
- block = f.read_nonblock(BLOCK_SIZE)
184
- @execute_files[ind] = @execute_files[ind] + [block]
185
- print block if opts[:output_stdout]
186
- rescue EOFError #=> e
187
- # do nothing at EOF
188
- end
189
- 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?
190
181
  end
191
- rescue IOError => e
192
- fout "IOError: #{e}"
193
182
  end
194
- @execute_completed_at = Time.now.utc
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?
197
+ end
198
+ end
199
+
200
+ exec_thr.join
201
+ in_thr.kill
202
+ # @return_code = exec_thr.value
195
203
  end
204
+ @execute_completed_at = Time.now.utc
196
205
  rescue Errno::ENOENT => e
197
206
  # error triggered by missing command in script
198
207
  @execute_aborted_at = Time.now.utc
199
208
  @execute_error_message = e.message
200
209
  @execute_error = e
201
- @execute_files[1] = e.message
210
+ @execute_files[EF_STDERR] += [e.message]
202
211
  fout "Error ENOENT: #{e.inspect}"
203
212
  end
204
213
 
@@ -212,7 +221,9 @@ module MarkdownExec
212
221
  end
213
222
 
214
223
  def display_command(_opts, required_blocks)
224
+ fout ' #=#=#'.yellow
215
225
  required_blocks.each { |cb| fout cb }
226
+ fout ' #=#=#'.yellow
216
227
  end
217
228
 
218
229
  def exec_block(options, _block_name = '')
@@ -239,7 +250,8 @@ module MarkdownExec
239
250
  run_last_script: -> { run_last_script },
240
251
  select_recent_output: -> { select_recent_output },
241
252
  select_recent_script: -> { select_recent_script },
242
- tab_completions: -> { fout tab_completions }
253
+ tab_completions: -> { fout tab_completions },
254
+ menu_export: -> { fout menu_export }
243
255
  }
244
256
  simple_commands.each_key do |key|
245
257
  if @options[key]
@@ -292,6 +304,19 @@ module MarkdownExec
292
304
  end
293
305
  end
294
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
+
295
320
  def list_blocks_in_file(call_options = {}, &options_block)
296
321
  opts = optsmerge call_options, options_block
297
322
 
@@ -360,25 +385,23 @@ module MarkdownExec
360
385
  end
361
386
 
362
387
  def list_default_env
363
- menu_data
364
- .map do |_long_name, _short_name, env_var, _arg_name, description, _opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
365
- next unless env_var.present?
388
+ menu_iter do |item|
389
+ next unless item[:env_var].present?
366
390
 
367
391
  [
368
- "#{env_var}=#{value_for_cli default}",
369
- description.present? ? description : nil
392
+ "#{item[:env_var]}=#{value_for_cli item[:default]}",
393
+ item[:description].present? ? item[:description] : nil
370
394
  ].compact.join(' # ')
371
395
  end.compact.sort
372
396
  end
373
397
 
374
398
  def list_default_yaml
375
- menu_data
376
- .map do |_long_name, _short_name, _env_var, _arg_name, description, opt_name, default, _proc1| # rubocop:disable Metrics/ParameterLists
377
- next unless opt_name.present? && default.present?
399
+ menu_iter do |item|
400
+ next unless item[:opt_name].present? && item[:default].present?
378
401
 
379
402
  [
380
- "#{opt_name}: #{value_for_yaml default}",
381
- description.present? ? description : nil
403
+ "#{item[:opt_name]}: #{value_for_yaml item[:default]}",
404
+ item[:description].present? ? item[:description] : nil
382
405
  ].compact.join(' # ')
383
406
  end.compact.sort
384
407
  end
@@ -487,83 +510,400 @@ module MarkdownExec
487
510
  end.compact.tap_inspect
488
511
  end
489
512
 
490
- def menu_data
513
+ def menu_data1
491
514
  val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
492
515
  val_as_int = ->(value) { value.to_i }
493
516
  val_as_str = ->(value) { value.to_s }
494
-
495
- summary_head = [
496
- ['config', nil, nil, 'PATH', 'Read configuration file', nil, '.', lambda { |value|
497
- read_configuration_file! options, value
498
- }],
499
- ['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output', nil, false, ->(value) { $pdebug = value.to_i != 0 }]
500
- ]
501
-
502
- # rubocop:disable Layout/LineLength
503
- summary_body = [
504
- ['block-name', 'f', 'MDE_BLOCK_NAME', 'RELATIVE', 'Name of block', :block_name, nil, val_as_str],
505
- ['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document', :filename, nil, val_as_str],
506
- ['list-blocks', nil, nil, nil, 'List blocks', :list_blocks, false, val_as_bool],
507
- ['list-count', nil, 'MDE_LIST_COUNT', 'NUM', 'Max. items to return in list', :list_count, 16, val_as_int],
508
- ['list-default-env', nil, nil, nil, 'List default configuration as environment variables', :list_default_env, false, val_as_bool],
509
- ['list-default-yaml', nil, nil, nil, 'List default configuration as YAML', :list_default_yaml, false, val_as_bool],
510
- ['list-docs', nil, nil, nil, 'List docs in current folder', :list_docs, false, val_as_bool],
511
- ['list-recent-output', nil, nil, nil, 'List recent saved output', :list_recent_output, false, val_as_bool],
512
- ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts', :list_recent_scripts, false, val_as_bool],
513
- ['logged-stdout-filename-prefix', nil, 'MDE_LOGGED_STDOUT_FILENAME_PREFIX', 'NAME', 'Name prefix for stdout files', :logged_stdout_filename_prefix, 'mde', val_as_str],
514
- ['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution', :output_execution_summary, false, val_as_bool],
515
- ['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script prior to execution', :output_script, false, val_as_bool],
516
- ['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution', :output_stdout, true, val_as_bool],
517
- ['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents', :path, nil, val_as_str],
518
- ['pwd', nil, nil, nil, 'Gem home folder', :pwd, false, val_as_bool],
519
- ['run-last-script', nil, nil, nil, 'Run most recently saved script', :run_last_script, false, val_as_bool],
520
- ['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script', :save_executed_script, false, val_as_bool],
521
- ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save standard output of the executed script', :save_execution_output, false, val_as_bool],
522
- ['saved-script-filename-prefix', nil, 'MDE_SAVED_SCRIPT_FILENAME_PREFIX', 'NAME', 'Name prefix for saved scripts', :saved_script_filename_prefix, 'mde', val_as_str],
523
- ['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder', :saved_script_folder, 'logs', val_as_str],
524
- ['saved-script-glob', nil, 'MDE_SAVED_SCRIPT_GLOB', 'SPEC', 'Glob matching saved scripts', :saved_script_glob, 'mde_*.sh', val_as_str],
525
- ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder', :saved_stdout_folder, 'logs', val_as_str],
526
- ['saved-stdout-glob', nil, 'MDE_SAVED_STDOUT_GLOB', 'SPEC', 'Glob matching saved outputs', :saved_stdout_glob, 'mde_*.out.txt', val_as_str],
527
- ['select-recent-output', nil, nil, nil, 'Select and execute a recently saved output', :select_recent_output, false, val_as_bool],
528
- ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script', :select_recent_script, false, val_as_bool],
529
- ['tab-completions', nil, nil, nil, 'List tab completions', :tab_completions, false, val_as_bool],
530
- ['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause for user to approve script', :user_must_approve, true, val_as_bool]
531
- ]
532
- # rubocop:enable Layout/LineLength
533
-
534
- # rubocop:disable Style/Semicolon
535
- summary_tail = [
536
- [nil, '0', nil, nil, 'Show current configuration values',
537
- nil, nil, ->(_) { options_finalize options; fout sorted_keys(options).to_yaml }],
538
- ['help', 'h', nil, nil, 'App help',
539
- nil, nil, ->(_) { fout menu_help; exit }],
540
- ['version', 'v', nil, nil, "Print the gem's version",
541
- nil, nil, ->(_) { fout MarkdownExec::VERSION; exit }],
542
- ['exit', 'x', nil, nil, 'Exit app',
543
- nil, nil, ->(_) { exit }]
544
- ]
545
- # rubocop:enable Style/Semicolon
546
-
547
- env_vars = [
548
- [nil, nil, 'MDE_BLOCK_NAME_EXCLUDED_MATCH', nil, 'Pattern for blocks to hide from user-selection',
549
- :block_name_excluded_match, '^\(.*\)$', val_as_str],
550
- [nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', val_as_str],
551
- [nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', val_as_str],
552
- [nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', val_as_str],
553
- [nil, nil, 'MDE_FENCED_START_EX_MATCH', nil, '', :fenced_start_ex_match,
554
- '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$', val_as_str],
555
- [nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', val_as_str],
556
- [nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', val_as_str],
557
- [nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', val_as_str],
558
- [nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', val_as_str],
559
- [nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', val_as_str],
560
- [nil, nil, 'MDE_OUTPUT_VIEWER_OPTIONS', nil, 'Options for viewing saved output file', :output_viewer_options,
561
- '', val_as_str],
562
- [nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, val_as_int]
563
- # [nil, nil, 'MDE_', nil, '', nil, '', nil],
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
+ }
564
900
  ]
901
+ # commands first, options second
902
+ (set1.reject { |v1| v1[:arg_name] }) + (set1.select { |v1| v1[:arg_name] })
903
+ end
565
904
 
566
- summary_head + summary_body + summary_tail + env_vars
905
+ def menu_iter(data = menu_data1, &block)
906
+ data.map(&block)
567
907
  end
568
908
 
569
909
  def menu_help
@@ -609,6 +949,24 @@ module MarkdownExec
609
949
  end.tap_inspect
610
950
  end
611
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
+
612
970
  def output_execution_summary
613
971
  return unless @options[:output_execution_summary]
614
972
 
@@ -626,9 +984,12 @@ module MarkdownExec
626
984
 
627
985
  def prompt_with_quit(prompt_text, items, opts = {})
628
986
  exit_option = '* Exit'
629
- sel = @prompt.select prompt_text,
630
- items + (@options[:menu_with_exit] ? [exit_option] : []),
631
- 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))
632
993
  sel == exit_option ? nil : sel
633
994
  end
634
995
 
@@ -675,19 +1036,19 @@ module MarkdownExec
675
1036
  "Usage: #{executable_name} [(path | filename [block_name])] [options]"
676
1037
  ].join("\n")
677
1038
 
678
- menu_data
679
- .map do |long_name, short_name, _env_var, arg_name, description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
680
- next unless long_name.present? || short_name.present?
1039
+ menu_iter do |item|
1040
+ next unless item[:long_name].present? || item[:short_name].present?
681
1041
 
682
- opts.on(*[if long_name.present?
683
- "--#{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]}" : ''}"
684
1044
  end,
685
- short_name.present? ? "-#{short_name}" : nil,
686
- [description,
687
- 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(' '),
688
1048
  lambda { |value|
689
- ret = proc1.call(value)
690
- 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]
691
1052
  ret
692
1053
  }].compact)
693
1054
  end
@@ -702,7 +1063,7 @@ module MarkdownExec
702
1063
  end
703
1064
 
704
1065
  FNR11 = '/'
705
- FNR12 = ',;'
1066
+ FNR12 = ',~'
706
1067
 
707
1068
  def saved_name_make(opts)
708
1069
  fne = opts[:filename].gsub(FNR11, FNR12)
@@ -741,28 +1102,51 @@ module MarkdownExec
741
1102
  @logged_stdout_filespec = @options[:logged_stdout_filespec]
742
1103
  dirname = File.dirname(@options[:logged_stdout_filespec])
743
1104
  Dir.mkdir dirname unless File.exist?(dirname)
744
- File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(0, ''))
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)
745
1114
  end
746
1115
 
747
1116
  def select_and_approve_block(call_options = {}, &options_block)
748
1117
  opts = optsmerge call_options, options_block
749
1118
  blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
750
1119
 
751
- unless opts[:block_name].present?
752
- pt = (opts[:prompt_select_block]).to_s
753
- blocks_in_file.each { |block| block.merge! label: make_block_label(block, opts) }
754
- block_labels = option_exclude_blocks(opts, blocks_in_file).map { |block| block[:label] }
1120
+ loop1 = true && !opts[:block_name].present?
755
1121
 
756
- 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] }
757
1127
 
758
- sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
759
- return nil if sel.nil?
1128
+ return nil if block_labels.count.zero?
760
1129
 
761
- label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
762
- opts[:block_name] = @options[:block_name] = label_block[:name]
763
- end
1130
+ sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
1131
+ return nil if sel.nil?
764
1132
 
765
- 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
766
1150
  end
767
1151
 
768
1152
  def select_md_file(files_ = nil)
@@ -804,9 +1188,16 @@ module MarkdownExec
804
1188
  { headings: headings, name: title, title: title }
805
1189
  end
806
1190
 
807
- def tab_completions(data = menu_data)
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)
808
1199
  data.map do |item|
809
- "--#{item[0]}" if item[0]
1200
+ "--#{item[:long_name]}" if item[:long_name]
810
1201
  end.compact
811
1202
  end
812
1203
 
@@ -819,19 +1210,6 @@ module MarkdownExec
819
1210
  @options.tap_inspect format: :yaml
820
1211
  end
821
1212
 
822
- def value_for_cli(value)
823
- case value.class.to_s
824
- when 'String'
825
- "'#{value}'"
826
- when 'FalseClass', 'TrueClass'
827
- value ? '1' : '0'
828
- when 'Integer'
829
- value
830
- else
831
- value.to_s
832
- end
833
- end
834
-
835
1213
  def value_for_hash(value, default = nil)
836
1214
  return default if value.nil?
837
1215
 
@@ -872,11 +1250,24 @@ module MarkdownExec
872
1250
 
873
1251
  dirname = File.dirname(@options[:saved_filespec])
874
1252
  Dir.mkdir dirname unless File.exist?(dirname)
875
- 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 +
876
1260
  "# file_name: #{opts[:filename]}\n" \
877
1261
  "# block_name: #{opts[:block_name]}\n" \
878
1262
  "# time: #{Time.now.utc}\n" \
879
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
880
1271
  end
881
1272
  end
882
1273
  end