markdown_exec 0.2.4 → 0.2.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2336bb2091df5bf018181e832fb9c5e7a5b7ee542b6f71cde38a3afb3aa88ab4
4
- data.tar.gz: '072654942ddf5897784c71724a7f9fa6fb5bea9b395d54ca18a3f3fd9b02c5af'
3
+ metadata.gz: 95d097cfcf93e09ba3bf487989923d4be03dd9fde0521854ea0660850b157d01
4
+ data.tar.gz: 22bc0e8bc8cd35d0d1dd52b6275c1609fb4cb95d69bd125064f0ca16f2f931f7
5
5
  SHA512:
6
- metadata.gz: 2f28a18aea943a7fec92b16d232b8c3722a30f6960fbd32bf84e95762b4ac322f0fb53f75563118a15136da72e49f16c580a8c7fbf05d2001857e068403834ef
7
- data.tar.gz: 8bb762715d790e37906d61cf8834e86dfe7e4f9611b8f7477da508d2ff658d496c6e1e0c89fc42e9e5f2cf937e29dae3cddf062cabda867f3a5bb1466986f2c3
6
+ metadata.gz: 591c09a221c0f5a34f8523d99d8e416bb022219fee38a7a05e3541600b03132e5ae3cc6aee02048eda2170a408d9e86090dedadff7c16f1f79ea98469826c27a
7
+ data.tar.gz: 74a76b6a6b10b165f8e79e59d25db78bcba173d730fd1a696e73c2012794bcfedd7c1b648cabe6b15d4d0a70995e5303b02c58d0b16c35a03ffbfea0b4efc5f4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [ToDo]
4
+
5
+ - pipe stdin to script
6
+ - yes/no/write/clipboard/record/edit/history
7
+ - present timestamp, result of last exec for each command
8
+ - user settings
9
+ - hidden w , w/o () in names
10
+ - fix regexp in pathnames
11
+ - tab completion from md file
12
+ - read file once to allow for tempdoc stream
13
+ - include md or blocks file
14
+
15
+ - tree display
16
+
17
+ - mde options in md file or included file
18
+
19
+ - include blocks from local md file
20
+
21
+ - chmod a+x logged script
22
+
23
+ - add shebang to saved script
24
+
25
+ ## [0.2.5] - 2022-04
26
+
27
+ ### Added
28
+
29
+ - Command `--list-default-env` to show default configuration as environment variables.
30
+ - Command `--list-default-yaml` to show default configuration as YAML.
31
+ - Option to exit program when selecting files or blocks.
32
+
33
+ ### Changed
34
+
35
+ - Composition of menu to facilitate reports.
36
+ - List default values in menu help.
3
37
 
4
38
  ## [0.2.4] - 2022-04-01
5
39
 
@@ -11,7 +45,7 @@
11
45
 
12
46
  | YAML Name | Environment Variable | Option Name | Default | Purpose |
13
47
  | :--- | :--- | :--- | :--- | :--- |
14
- | list_count | MDE_LIST_COUNT | `--list_count` | `16` | Max. items to return in list |
48
+ | list_count | MDE_LIST_COUNT | `--list-count` | `16` | Max. items to return in list |
15
49
  | logged_stdout_filename_prefix | MDE_LOGGED_STDOUT_FILENAME_PREFIX | | `mde` | Name prefix for stdout files |
16
50
  | save_execution_output | MDE_SAVE_EXECUTION_OUTPUT | `--save-execution-output` | False | Save standard output of the executed script |
17
51
  | saved_script_filename_prefix | MDE_SAVED_SCRIPT_FILENAME_PREFIX | | `mde` | Name prefix for saved scripts |
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- markdown_exec (0.2.4)
4
+ markdown_exec (0.2.5)
5
5
  open3 (~> 0.1.1)
6
6
  optparse (~> 0.1.1)
7
7
  tty-prompt (~> 0.23.1)
data/README.md CHANGED
@@ -1,30 +1,35 @@
1
1
  # MarkdownExec
2
2
 
3
- Interactively select and execute fenced code blocks in markdown files. Build complex scripts by naming and requiring blocks.
3
+ Interactively select and execute fenced code blocks in markdown files. Build complex scripts by naming and requiring blocks. Log resulting scripts and output. Re-run scripts.
4
4
 
5
- * Code blocks may be named.
5
+ * Code blocks may be named. Named blocks can be required by other blocks.
6
6
 
7
- * Named blocks can be required by other blocks.
7
+ * The user-selected code block, and all required blocks, are arranged into a script in the order they appear in the markdown file. The script can be presented for approval prior to execution.
8
8
 
9
- * The user-selected code block, and all required blocks, are arranged in the order they appear in the markdown file.
9
+ * Executed scripts can be saved. Saved scripts can be listed, selected, and executed.
10
10
 
11
- * The code is presented for approval prior to execution.
11
+ * Output from executed scripts can be saved.
12
12
 
13
13
  ## Screenshots
14
14
 
15
15
  ### Select a file
16
+
16
17
  ![Select a file](/assets/select_a_file.png)
17
18
 
18
19
  ### Select a block
20
+
19
21
  ![Select a block](/assets/select_a_block.png)
20
22
 
21
23
  ### Approve code
24
+
22
25
  ![Approve code](/assets/approve_code.png)
23
26
 
24
27
  ### Output
28
+
25
29
  ![Output of execution](/assets/output_of_execution.png)
26
30
 
27
31
  ### Example blocks
32
+
28
33
  ![Example blocks](/assets/example_blocks.png)
29
34
 
30
35
  ## Installation
@@ -34,39 +39,111 @@ Install:
34
39
 
35
40
  ## Usage
36
41
 
37
- ### `mde --help`
42
+ ### Help
43
+
44
+ #### `mde --help`
45
+
38
46
  Displays help information.
39
47
 
40
- ### `mde`
48
+ ### Basic
49
+
50
+ #### `mde`
51
+
41
52
  Process `README.md` file in the current folder. Displays all the blocks in the file and allows you to select using [up], [down], and [return]. Press [ctrl]-c to abort selection.
42
53
 
43
- ### `mde my.md` or `mde -f my.md`
54
+ #### `mde my.md` or `mde -f my.md`
55
+
44
56
  Select a block to execute from `my.md`.
45
57
 
46
- ### `mde .` or `mde -p .`
58
+ #### `mde .` or `mde -p .`
47
59
 
48
60
  Select a markdown file in the current folder. Select a block to execute from that file.
49
61
 
50
- ### `mde --list-blocks`
62
+ ### Report documents and blocks
63
+
64
+ #### `mde --list-blocks`
65
+
51
66
  List all blocks in the all the markdown documents in the current folder.
52
67
 
53
- ### `mde --list-docs`
68
+ #### `mde --list-docs`
69
+
54
70
  List all markdown documents in the current folder.
55
71
 
72
+ ### Configuration
73
+
74
+ #### `mde --list-default-env` or `mde --list-default-yaml`
75
+
76
+ List default values that can be set in configuration file, environment, and command line.
77
+
78
+ #### `mde -0`
79
+
80
+ Show current configuation values that will be applied to the current run. Does not interrupt processing.
81
+
82
+ ### Save scripts
83
+
84
+ #### `mde --save-executed-script 1`
85
+
86
+ Save executed script in saved script folder.
87
+
88
+ #### `mde --list-recent-scripts`
89
+
90
+ List recent saved scripts in saved script folder.
91
+
92
+ #### `mde --select-recent-script`
93
+
94
+ Select and execute a recently saved script in saved script folder.
95
+
96
+ ### Save output
97
+
98
+ #### `mde --save-execution-output 1`
99
+
100
+ Save execution output in saved output folder.
101
+
56
102
  ## Behavior
103
+
57
104
  * If no file and no folder are specified, blocks within `./README.md` are presented.
58
105
  * If a file is specified, its blocks are presented.
59
106
  * If a folder is specified, its files are presented. When a file is selected, its blocks are presented.
60
107
 
61
108
  ## Configuration
62
- While starting up, reads the YAML configuration file `.mde.yml` in the current folder if it exists.
63
109
 
64
- e.g. Use to set the default file for the current folder.
110
+ ### Environment Variables
111
+
112
+ When executed, `mde` reads the current environment.
113
+ * Configuration in current and children shells, e.g. `export MDE_SAVE_EXECUTED_SCRIPT=1`.
114
+ * Configuration for the current command, e.g. `MDE_SAVE_EXECUTED_SCRIPT=1 mde`.
115
+
116
+ ### Configuration Files
117
+
118
+ * Configuration in all shells, e.g. environment variables set in your user's `~/.bashrc` or `~/.bash_profile` files.
119
+ * Configuration in the optional file `.mde.yml` in the current folder. .e.g. `save_executed_script: true`
120
+ * Configuration in a YAML file and read while parsing the inputs, e.g. `--config my_path/my_file.yml`
65
121
 
66
- * `filename: CHANGELOG.md` sets the file to open.
67
- * `folder: documents` sets the folder to search for default or specified files.
122
+ ### Program Arguments
123
+
124
+ * Configuration in command options, e.g. `mde --save-executed-script 1`
125
+
126
+ ## Representing boolean values
127
+
128
+ Boolean values expressed as strings are interpreted as:
129
+ | String | Boolean |
130
+ | :---: | :---: |
131
+ | *empty string* | False |
132
+ | `0` | False |
133
+ | `1` | True |
134
+ | *anything else* | True |
135
+
136
+ E.g. `opt1=1` will set option `opt1` to True.
137
+
138
+ Boolean options configured with environment variables:
139
+ - Set to `1` or non-empty value to save executed scripts; empty or `0` to disable saving.
140
+ e.g. `export MDE_SAVE_EXECUTED_SCRIPT=1`
141
+ e.g. `export MDE_SAVE_EXECUTED_SCRIPT=`
142
+ - Specify variable on command line.
143
+ e.g. `MDE_SAVE_EXECUTED_SCRIPT=1 mde`
68
144
 
69
145
  # Example blocks
146
+
70
147
  When prompted, select either the `awake` or `asleep` block.
71
148
 
72
149
  ``` :(day)
@@ -89,6 +166,17 @@ export ACTIVITY=asleep
89
166
  echo "$TIME -> $ACTIVITY"
90
167
  ```
91
168
 
169
+ ``` :missing_command
170
+ fail
171
+ ```
172
+
173
+ ``` :exit_value
174
+ echo "a"
175
+ echo "b"
176
+ echo "c" >>/dev/stderr
177
+ grep nx Gemfile
178
+ ```
179
+
92
180
  # License
93
181
 
94
182
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -2,7 +2,7 @@
2
2
 
3
3
  module MarkdownExec
4
4
  APP_NAME = 'MDE'
5
- APP_DESC = 'Markdown block executor'
5
+ APP_DESC = 'Markdown Executor'
6
6
  GEM_NAME = 'markdown_exec'
7
- VERSION = '0.2.4'
7
+ VERSION = '0.2.5'
8
8
  end
data/lib/markdown_exec.rb CHANGED
@@ -14,21 +14,23 @@ require 'yaml'
14
14
  # else true
15
15
 
16
16
  def env_bool(name, default: false)
17
- return default if (val = ENV[name]).nil?
17
+ return default if name.nil? || (val = ENV[name]).nil?
18
18
  return false if val.empty? || val == '0'
19
19
 
20
20
  true
21
21
  end
22
22
 
23
23
  def env_int(name, default: 0)
24
- return default if (val = ENV[name]).nil?
24
+ return default if name.nil? || (val = ENV[name]).nil?
25
25
  return default if val.empty?
26
26
 
27
27
  val.to_i
28
28
  end
29
29
 
30
30
  def env_str(name, default: '')
31
- ENV[name] || default
31
+ return default if name.nil? || (val = ENV[name]).nil?
32
+
33
+ val || default
32
34
  end
33
35
 
34
36
  $pdebug = env_bool 'MDE_DEBUG'
@@ -42,7 +44,12 @@ BLOCK_SIZE = 1024
42
44
 
43
45
  class Object # rubocop:disable Style/Documentation
44
46
  def present?
45
- self && !blank?
47
+ case self.class.to_s
48
+ when 'FalseClass', 'TrueClass'
49
+ true
50
+ else
51
+ self && (!respond_to?(:blank?) || !blank?)
52
+ end
46
53
  end
47
54
  end
48
55
 
@@ -94,44 +101,17 @@ module MarkdownExec
94
101
  # options necessary to start, parse input, defaults for cli options
95
102
 
96
103
  def base_options
97
- {
98
- # commands
99
- list_blocks: false, # command
100
- list_docs: false, # command
101
- list_recent_scripts: false, # command
102
- run_last_script: false, # command
103
- select_recent_script: false, # command
104
-
105
- # command options
106
- filename: env_str('MDE_FILENAME', default: nil), # option Filename to open
107
- list_count: 16,
108
- logged_stdout_filename_prefix: 'mde',
109
- output_execution_summary: env_bool('MDE_OUTPUT_EXECUTION_SUMMARY', default: false), # option
110
- output_script: env_bool('MDE_OUTPUT_SCRIPT', default: false), # option
111
- output_stdout: env_bool('MDE_OUTPUT_STDOUT', default: true), # option
112
- path: env_str('MDE_PATH', default: nil), # option Folder to search for files
113
- save_executed_script: env_bool('MDE_SAVE_EXECUTED_SCRIPT', default: false), # option
114
- save_execution_output: env_bool('MDE_SAVE_EXECUTION_OUTPUT', default: false), # option
115
- saved_script_filename_prefix: 'mde',
116
- saved_script_folder: env_str('MDE_SAVED_SCRIPT_FOLDER', default: 'logs'), # option
117
- saved_script_glob: 'mde_*.sh',
118
- saved_stdout_folder: env_str('MDE_SAVED_STDOUT_FOLDER', default: 'logs'), # option
119
- user_must_approve: env_bool('MDE_USER_MUST_APPROVE', default: true), # option Pause for user to approve script
120
-
121
- # configuration options
122
- block_name_excluded_match: env_str('MDE_BLOCK_NAME_EXCLUDED_MATCH', default: '^\(.+\)$'),
123
- block_name_match: env_str('MDE_BLOCK_NAME_MATCH', default: ':(?<title>\S+)( |$)'),
124
- block_required_scan: env_str('MDE_BLOCK_REQUIRED_SCAN', default: '\+\S+'),
125
- fenced_start_and_end_match: env_str('MDE_FENCED_START_AND_END_MATCH', default: '^`{3,}'),
126
- fenced_start_ex_match: env_str('MDE_FENCED_START_EX_MATCH', default: '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$'),
127
- heading1_match: env_str('MDE_HEADING1_MATCH', default: '^# *(?<name>[^#]*?) *$'),
128
- heading2_match: env_str('MDE_HEADING2_MATCH', default: '^## *(?<name>[^#]*?) *$'),
129
- heading3_match: env_str('MDE_HEADING3_MATCH', default: '^### *(?<name>.+?) *$'),
130
- md_filename_glob: env_str('MDE_MD_FILENAME_GLOB', default: '*.[Mm][Dd]'),
131
- md_filename_match: env_str('MDE_MD_FILENAME_MATCH', default: '.+\\.md'),
132
- mdheadings: true, # use headings (levels 1,2,3) in block lable
133
- select_page_height: env_int('MDE_SELECT_PAGE_HEIGHT', default: 12)
134
- }
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))]
109
+ end.compact.to_h.merge(
110
+ {
111
+ mdheadings: true, # use headings (levels 1,2,3) in block lable
112
+ menu_with_exit: true
113
+ }
114
+ ).tap_inspect format: :yaml
135
115
  end
136
116
 
137
117
  def default_options
@@ -248,11 +228,21 @@ module MarkdownExec
248
228
  return
249
229
  end
250
230
 
231
+ if @options[:list_default_yaml]
232
+ fout_list list_default_yaml
233
+ return
234
+ end
235
+
251
236
  if @options[:list_docs]
252
237
  fout_list files
253
238
  return
254
239
  end
255
240
 
241
+ if @options[:list_default_env]
242
+ fout_list list_default_env
243
+ return
244
+ end
245
+
256
246
  if @options[:list_recent_scripts]
257
247
  fout_list list_recent_scripts
258
248
  return
@@ -376,14 +366,37 @@ module MarkdownExec
376
366
  blocks.tap_inspect
377
367
  end
378
368
 
369
+ 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?
373
+
374
+ [
375
+ "#{env_var}=#{value_for_cli default}",
376
+ description.present? ? description : nil
377
+ ].compact.join(' # ')
378
+ end.compact.sort
379
+ end
380
+
381
+ 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?
385
+
386
+ [
387
+ "#{opt_name}: #{value_for_yaml default}",
388
+ description.present? ? description : nil
389
+ ].compact.join(' # ')
390
+ end.compact.sort
391
+ end
392
+
379
393
  def list_files_per_options(options)
380
- default_filename = 'README.md'
381
- default_folder = '.'
382
- if options[:filename]&.present?
383
- list_files_specified(options[:filename], options[:path], default_filename, default_folder)
384
- else
385
- list_files_specified(nil, options[:path], default_filename, default_folder)
386
- end.tap_inspect
394
+ list_files_specified(
395
+ options[:filename]&.present? ? options[:filename] : nil,
396
+ options[:path],
397
+ 'README.md',
398
+ '.'
399
+ ).tap_inspect
387
400
  end
388
401
 
389
402
  def list_files_specified(specified_filename, specified_folder, default_filename, default_folder, filetree = nil)
@@ -462,6 +475,92 @@ module MarkdownExec
462
475
  end.compact.tap_inspect
463
476
  end
464
477
 
478
+ def menu_data
479
+ proc_self = ->(value) { value }
480
+ proc_to_i = ->(value) { value.to_i != 0 }
481
+ proc_true = ->(_) { true }
482
+
483
+ summary_head = [
484
+ ['config', nil, nil, 'PATH', 'Read configuration file',
485
+ nil, '.', ->(value) { read_configuration_file! options, value }],
486
+ ['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output',
487
+ nil, false, ->(value) { $pdebug = value.to_i != 0 }]
488
+ ]
489
+
490
+ summary_body = [
491
+ ['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document',
492
+ :filename, nil, proc_self],
493
+ ['list-blocks', nil, nil, nil, 'List blocks',
494
+ :list_blocks, nil, proc_true],
495
+ ['list-default-env', nil, nil, nil, 'List default configuration as environment variables',
496
+ :list_default_env, nil, proc_true],
497
+ ['list-default-yaml', nil, nil, nil, 'List default configuration as YAML',
498
+ :list_default_yaml, nil, proc_true],
499
+ ['list-docs', nil, nil, nil, 'List docs in current folder',
500
+ :list_docs, nil, proc_true],
501
+ ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts',
502
+ :list_recent_scripts, nil, proc_true],
503
+ ['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution',
504
+ :output_execution_summary, false, proc_to_i],
505
+ ['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script prior to execution',
506
+ :output_script, false, proc_to_i],
507
+ ['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution',
508
+ :output_stdout, true, proc_to_i],
509
+ ['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents',
510
+ :path, nil, proc_self],
511
+ ['run-last-script', nil, nil, nil, 'Run most recently saved script',
512
+ :run_last_script, nil, proc_true],
513
+ ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script',
514
+ :select_recent_script, nil, proc_true],
515
+ ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save execution output',
516
+ :save_execution_output, false, proc_to_i],
517
+ ['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script',
518
+ :save_executed_script, false, proc_to_i],
519
+ ['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder',
520
+ :saved_script_folder, 'logs', proc_self],
521
+ ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder',
522
+ :saved_stdout_folder, 'logs', proc_self],
523
+ ['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause for user to approve script',
524
+ :user_must_approve, true, proc_to_i]
525
+ ]
526
+
527
+ # rubocop:disable Style/Semicolon
528
+ summary_tail = [
529
+ [nil, '0', nil, nil, 'Show current configuration values',
530
+ nil, nil, ->(_) { options_finalize options; fout sorted_keys(options).to_yaml }],
531
+ ['help', 'h', nil, nil, 'App help',
532
+ nil, nil, ->(_) { fout menu_help; exit }],
533
+ ['version', 'v', nil, nil, 'App version',
534
+ nil, nil, ->(_) { fout MarkdownExec::VERSION; exit }],
535
+ ['exit', 'x', nil, nil, 'Exit app',
536
+ nil, nil, ->(_) { exit }]
537
+ ]
538
+ # rubocop:enable Style/Semicolon
539
+
540
+ env_vars = [
541
+ [nil, nil, 'MDE_BLOCK_NAME_EXCLUDED_MATCH', nil, 'Pattern for blocks to hide from user-selection',
542
+ :block_name_excluded_match, '^\(.+\)$', nil],
543
+ [nil, nil, 'MDE_BLOCK_NAME_MATCH', nil, '', :block_name_match, ':(?<title>\S+)( |$)', nil],
544
+ [nil, nil, 'MDE_BLOCK_REQUIRED_SCAN', nil, '', :block_required_scan, '\+\S+', nil],
545
+ [nil, nil, 'MDE_FENCED_START_AND_END_MATCH', nil, '', :fenced_start_and_end_match, '^`{3,}', nil],
546
+ [nil, nil, 'MDE_FENCED_START_EX_MATCH', nil, '', :fenced_start_ex_match,
547
+ '^`{3,}(?<shell>[^`\s]*) *(?<name>.*)$', nil],
548
+ [nil, nil, 'MDE_HEADING1_MATCH', nil, '', :heading1_match, '^# *(?<name>[^#]*?) *$', nil],
549
+ [nil, nil, 'MDE_HEADING2_MATCH', nil, '', :heading2_match, '^## *(?<name>[^#]*?) *$', nil],
550
+ [nil, nil, 'MDE_HEADING3_MATCH', nil, '', :heading3_match, '^### *(?<name>.+?) *$', nil],
551
+ [nil, nil, 'MDE_MD_FILENAME_GLOB', nil, '', :md_filename_glob, '*.[Mm][Dd]', nil],
552
+ [nil, nil, 'MDE_MD_FILENAME_MATCH', nil, '', :md_filename_match, '.+\\.md', nil],
553
+ [nil, nil, 'MDE_SELECT_PAGE_HEIGHT', nil, '', :select_page_height, 12, nil]
554
+ # [nil, nil, 'MDE_', nil, '', nil, '', nil],
555
+ ]
556
+
557
+ summary_head + summary_body + summary_tail + env_vars
558
+ end
559
+
560
+ def menu_help
561
+ @option_parser.help
562
+ end
563
+
465
564
  def option_exclude_blocks(opts, blocks)
466
565
  block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
467
566
  if opts[:hide_blocks_by_name]
@@ -471,6 +570,26 @@ module MarkdownExec
471
570
  end
472
571
  end
473
572
 
573
+ ## post-parse options configuration
574
+ #
575
+ def options_finalize(rest)
576
+ ## position 0: file or folder (optional)
577
+ #
578
+ if (pos = rest.fetch(0, nil))&.present?
579
+ if Dir.exist?(pos)
580
+ @options[:path] = pos
581
+ elsif File.exist?(pos)
582
+ @options[:filename] = pos
583
+ else
584
+ raise "Invalid parameter: #{pos}"
585
+ end
586
+ end
587
+
588
+ ## position 1: block name (optional)
589
+ #
590
+ @options[:block_name] = rest.fetch(1, nil)
591
+ end
592
+
474
593
  def optsmerge(call_options = {}, options_block = nil)
475
594
  class_call_options = @options.merge(call_options || {})
476
595
  if options_block
@@ -495,6 +614,14 @@ module MarkdownExec
495
614
  }
496
615
  end
497
616
 
617
+ def prompt_with_quit(prompt_text, items, opts = {})
618
+ exit_option = '* Exit'
619
+ sel = @prompt.select prompt_text,
620
+ items + (@options[:menu_with_exit] ? [exit_option] : []),
621
+ opts
622
+ sel == exit_option ? nil : sel
623
+ end
624
+
498
625
  def read_configuration_file!(options, configuration_path)
499
626
  return unless File.exist?(configuration_path)
500
627
 
@@ -526,19 +653,11 @@ module MarkdownExec
526
653
  #
527
654
  @options = base_options
528
655
 
529
- ## post-parse options configuration
530
- #
531
- options_finalize = ->(_options) {}
532
-
533
- proc_self = ->(value) { value }
534
- proc_to_i = ->(value) { value.to_i != 0 }
535
- proc_true = ->(_) { true }
536
-
537
- # read local configuration file
656
+ ## read local configuration file
538
657
  #
539
658
  read_configuration_file! @options, ".#{MarkdownExec::APP_NAME.downcase}.yml"
540
659
 
541
- option_parser = OptionParser.new do |opts|
660
+ @option_parser = option_parser = OptionParser.new do |opts|
542
661
  executable_name = File.basename($PROGRAM_NAME)
543
662
  opts.banner = [
544
663
  "#{MarkdownExec::APP_NAME}" \
@@ -546,67 +665,16 @@ module MarkdownExec
546
665
  "Usage: #{executable_name} [path] [filename] [options]"
547
666
  ].join("\n")
548
667
 
549
- summary_head = [
550
- ['config', nil, nil, 'PATH', 'Read configuration file',
551
- nil, ->(value) { read_configuration_file! options, value }],
552
- ['debug', 'd', 'MDE_DEBUG', 'BOOL', 'Debug output',
553
- nil, ->(value) { $pdebug = value.to_i != 0 }]
554
- ]
555
-
556
- summary_body = [
557
- ['filename', 'f', 'MDE_FILENAME', 'RELATIVE', 'Name of document',
558
- :filename, proc_self],
559
- ['list-blocks', nil, nil, nil, 'List blocks',
560
- :list_blocks, proc_true],
561
- ['list-docs', nil, nil, nil, 'List docs in current folder',
562
- :list_docs, proc_true],
563
- ['list-recent-scripts', nil, nil, nil, 'List recent saved scripts',
564
- :list_recent_scripts, proc_true],
565
- ['output-execution-summary', nil, 'MDE_OUTPUT_EXECUTION_SUMMARY', 'BOOL', 'Display summary for execution',
566
- :output_execution_summary, proc_to_i],
567
- ['output-script', nil, 'MDE_OUTPUT_SCRIPT', 'BOOL', 'Display script',
568
- :output_script, proc_to_i],
569
- ['output-stdout', nil, 'MDE_OUTPUT_STDOUT', 'BOOL', 'Display standard output from execution',
570
- :output_stdout, proc_to_i],
571
- ['path', 'p', 'MDE_PATH', 'PATH', 'Path to documents',
572
- :path, proc_self],
573
- ['run-last-script', nil, nil, nil, 'Run most recently saved script',
574
- :run_last_script, proc_true],
575
- ['select-recent-script', nil, nil, nil, 'Select and execute a recently saved script',
576
- :select_recent_script, proc_true],
577
- ['save-execution-output', nil, 'MDE_SAVE_EXECUTION_OUTPUT', 'BOOL', 'Save execution output',
578
- :save_execution_output, proc_to_i],
579
- ['save-executed-script', nil, 'MDE_SAVE_EXECUTED_SCRIPT', 'BOOL', 'Save executed script',
580
- :save_executed_script, proc_to_i],
581
- ['saved-script-folder', nil, 'MDE_SAVED_SCRIPT_FOLDER', 'SPEC', 'Saved script folder',
582
- :saved_script_folder, proc_self],
583
- ['saved-stdout-folder', nil, 'MDE_SAVED_STDOUT_FOLDER', 'SPEC', 'Saved stdout folder',
584
- :saved_stdout_folder, proc_self],
585
- ['user-must-approve', nil, 'MDE_USER_MUST_APPROVE', 'BOOL', 'Pause to approve execution',
586
- :user_must_approve, proc_to_i]
587
- ]
588
-
589
- # rubocop:disable Style/Semicolon
590
- summary_tail = [
591
- [nil, '0', nil, nil, 'Show configuration',
592
- nil, ->(_) { options_finalize.call options; fout sorted_keys(options).to_yaml }],
593
- ['help', 'h', nil, nil, 'App help',
594
- nil, ->(_) { fout option_parser.help; exit }],
595
- ['version', 'v', nil, nil, 'App version',
596
- nil, ->(_) { fout MarkdownExec::VERSION; exit }],
597
- ['exit', 'x', nil, nil, 'Exit app',
598
- nil, ->(_) { exit }]
599
- ]
600
- # rubocop:enable Style/Semicolon
601
-
602
- (summary_head + summary_body + summary_tail)
603
- .map do |long_name, short_name, env_var, arg_name, description, opt_name, proc1| # rubocop:disable Metrics/ParameterLists
668
+ menu_data
669
+ .map do |long_name, short_name, _env_var, arg_name, description, opt_name, default, proc1| # rubocop:disable Metrics/ParameterLists
670
+ next unless long_name.present? || short_name.present?
671
+
604
672
  opts.on(*[if long_name.present?
605
673
  "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
606
674
  end,
607
675
  short_name.present? ? "-#{short_name}" : nil,
608
676
  [description,
609
- env_var.present? ? "env: #{env_var}" : nil].compact.join(' - '),
677
+ default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
610
678
  lambda { |value|
611
679
  ret = proc1.call(value)
612
680
  options[opt_name] = ret if opt_name
@@ -618,27 +686,9 @@ module MarkdownExec
618
686
  option_parser.environment # env defaults to the basename of the program.
619
687
  rest = option_parser.parse! # (into: options)
620
688
 
621
- ## finalize configuration
622
- #
623
- options_finalize.call options
624
-
625
- ## position 0: file or folder (optional)
626
- #
627
- if (pos = rest.fetch(0, nil))&.present?
628
- if Dir.exist?(pos)
629
- options[:path] = pos
630
- elsif File.exist?(pos)
631
- options[:filename] = pos
632
- else
633
- raise "Invalid parameter: #{pos}"
634
- end
635
- end
689
+ options_finalize rest
636
690
 
637
- ## position 1: block name (optional)
638
- #
639
- block_name = rest.fetch(1, nil)
640
-
641
- exec_block options, block_name
691
+ exec_block options, options[:block_name]
642
692
  end
643
693
 
644
694
  def run_last_script
@@ -667,6 +717,12 @@ module MarkdownExec
667
717
  dirname = File.dirname(@options[:logged_stdout_filespec])
668
718
  Dir.mkdir dirname unless File.exist?(dirname)
669
719
  File.write(@options[:logged_stdout_filespec], @execute_files&.fetch(0, ''))
720
+ # @options[:logged_stderr_filename] =
721
+ # "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
722
+ # @options[:block_name]].join('_')}.err.txt"
723
+ # @options[:logged_stderr_filespec] = File.join @options[:saved_stdout_folder], @options[:logged_stderr_filename]
724
+ # @logged_stderr_filespec = @options[:logged_stderr_filespec]
725
+ # File.write(@options[:logged_stderr_filespec], @execute_files&.fetch(1, ''))
670
726
  end
671
727
 
672
728
  def select_and_approve_block(call_options = {}, &options_block)
@@ -680,7 +736,9 @@ module MarkdownExec
680
736
 
681
737
  return nil if block_labels.count.zero?
682
738
 
683
- sel = @prompt.select pt, block_labels, per_page: opts[:select_page_height]
739
+ sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
740
+ return nil if sel.nil?
741
+
684
742
  label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
685
743
  opts[:block_name] = @options[:block_name] = label_block[:name]
686
744
  end
@@ -694,13 +752,15 @@ module MarkdownExec
694
752
  if files.count == 1
695
753
  files[0]
696
754
  elsif files.count >= 2
697
- @prompt.select opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
755
+ prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
698
756
  end
699
757
  end
700
758
 
701
759
  def select_recent_script
702
- filename = @prompt.select @options[:prompt_select_md].to_s, list_recent_scripts,
703
- per_page: @options[:select_page_height]
760
+ filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
761
+ per_page: @options[:select_page_height]
762
+ return if filename.nil?
763
+
704
764
  mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
705
765
 
706
766
  @options[:block_name] = mf[:block]
@@ -728,7 +788,50 @@ module MarkdownExec
728
788
  else
729
789
  @options.merge! opts
730
790
  end
731
- @options
791
+ @options.tap_inspect format: :yaml
792
+ end
793
+
794
+ def value_for_cli(value)
795
+ case value.class.to_s
796
+ when 'String'
797
+ "'#{value}'"
798
+ when 'FalseClass', 'TrueClass'
799
+ value ? '1' : '0'
800
+ when 'Integer'
801
+ value
802
+ else
803
+ value.to_s
804
+ end
805
+ end
806
+
807
+ def value_for_hash(value, default = nil)
808
+ return default if value.nil?
809
+
810
+ case value.class.to_s
811
+ when 'String', 'Integer', 'FalseClass', 'TrueClass'
812
+ value
813
+ when value.empty?
814
+ default
815
+ else
816
+ value.to_s
817
+ end
818
+ end
819
+
820
+ def value_for_yaml(value)
821
+ return default if value.nil?
822
+
823
+ case value.class.to_s
824
+ when 'String'
825
+ "'#{value}'"
826
+ when 'Integer'
827
+ value
828
+ when 'FalseClass', 'TrueClass'
829
+ value ? true : false
830
+ when value.empty?
831
+ default
832
+ else
833
+ value.to_s
834
+ end
732
835
  end
733
836
 
734
837
  def write_command_file(opts, required_blocks)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdown_exec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fareed Stevenson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-02 00:00:00.000000000 Z
11
+ date: 2022-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: open3
@@ -67,7 +67,8 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.2.0
69
69
  description: Interactively select and execute fenced code blocks in markdown files.
70
- Build complex scripts by naming and requiring blocks.
70
+ Build complex scripts by naming and requiring blocks. Log resulting scripts and
71
+ output. Re-run scripts.
71
72
  email:
72
73
  - fareed@phomento.com
73
74
  executables:
@@ -127,5 +128,5 @@ requirements: []
127
128
  rubygems_version: 3.2.32
128
129
  signing_key:
129
130
  specification_version: 4
130
- summary: Execute shell blocks in markdown files.
131
+ summary: Interactively select and execute fenced code blocks in markdown files.
131
132
  test_files: []