markdown_exec 0.2.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2336bb2091df5bf018181e832fb9c5e7a5b7ee542b6f71cde38a3afb3aa88ab4
4
- data.tar.gz: '072654942ddf5897784c71724a7f9fa6fb5bea9b395d54ca18a3f3fd9b02c5af'
3
+ metadata.gz: 9b40d575297112f5a4bb934475b84251aeeab8f43de5dae963c8d3b1711c3edc
4
+ data.tar.gz: 9e3d3273dc3e3a0deb326fe30e3ff70b15e73f4fd9cea06c42f099f184903fad
5
5
  SHA512:
6
- metadata.gz: 2f28a18aea943a7fec92b16d232b8c3722a30f6960fbd32bf84e95762b4ac322f0fb53f75563118a15136da72e49f16c580a8c7fbf05d2001857e068403834ef
7
- data.tar.gz: 8bb762715d790e37906d61cf8834e86dfe7e4f9611b8f7477da508d2ff658d496c6e1e0c89fc42e9e5f2cf937e29dae3cddf062cabda867f3a5bb1466986f2c3
6
+ metadata.gz: 65654b5af78419591c41e3b50e0697b503ddf429c83312311335dd1b16d0d645c98b4e0c19d6c69446c42823861ca8f384788aeb4c1b5664804e4feecc39a45a
7
+ data.tar.gz: 866360b40733e8925c173582f3c59799e241bf48f52faffa4bfb03cd4c1a11c93f5b27305344b01c4b44a1518ef1c49961c7fbfbdb713638ed8261c8d87401c0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## [ToDo]
4
+
5
+ - pipe stdin to script
6
+ - present timestamp, result of last exec for each command
7
+ - user settings
8
+ - hidden w , w/o () in names
9
+ - fix regexp in pathnames
10
+ - tab completion from md file
11
+ - read file once to allow for tempdoc stream
12
+ - include md or blocks file
13
+
14
+ - tree display
15
+
16
+ - mde options in md file or included file
17
+
18
+ - include blocks from local md file
19
+
20
+ - chmod a+x logged script
21
+
22
+ - add shebang to saved script
23
+
24
+ - yes/no/write/clipboard/record/edit/history
25
+
26
+ - list, view saved output
27
+
28
+ ## [1.0.0] - 2022-04-26
29
+
30
+ ### Added
31
+
32
+ - Command `--pwd` to print the gem's home folder.
33
+ - Command `--select-recent-output` to select and open a recent output log file.
34
+ e.g. `MDE_OUTPUT_VIEWER_OPTIONS="-a '/Applications/Sublime Text.app'" mde --select-recent-output`
35
+ - Command `--tab-completions` to list the application options.
36
+ - Tab completion script for Bash shell.
37
+
38
+ ### Changed
39
+
40
+ - File names for saved scripts.
41
+ - Hide blocks with empty names, e.g. `:()`.
42
+
43
+ ## [0.2.6] - 2022-04-07
44
+
45
+ ### Changed
46
+
47
+ - Default values for command line options.
48
+
49
+ ## [0.2.5] - 2022-04-03
50
+
51
+ ### Added
52
+
53
+ - Command `--list-default-env` to show default configuration as environment variables.
54
+ - Command `--list-default-yaml` to show default configuration as YAML.
55
+ - Option to exit program when selecting files or blocks.
56
+
57
+ ### Changed
58
+
59
+ - Composition of menu to facilitate reports.
60
+ - List default values in menu help.
3
61
 
4
62
  ## [0.2.4] - 2022-04-01
5
63
 
@@ -11,7 +69,7 @@
11
69
 
12
70
  | YAML Name | Environment Variable | Option Name | Default | Purpose |
13
71
  | :--- | :--- | :--- | :--- | :--- |
14
- | list_count | MDE_LIST_COUNT | `--list_count` | `16` | Max. items to return in list |
72
+ | list_count | MDE_LIST_COUNT | `--list-count` | `16` | Max. items to return in list |
15
73
  | logged_stdout_filename_prefix | MDE_LOGGED_STDOUT_FILENAME_PREFIX | | `mde` | Name prefix for stdout files |
16
74
  | save_execution_output | MDE_SAVE_EXECUTION_OUTPUT | `--save-execution-output` | False | Save standard output of the executed script |
17
75
  | 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 (1.0.0)
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,135 @@ 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 my.md myblock`
59
+
60
+ Execute the block named `myblock` from `my.md`.
61
+
62
+ #### `mde .` or `mde -p .`
47
63
 
48
64
  Select a markdown file in the current folder. Select a block to execute from that file.
49
65
 
50
- ### `mde --list-blocks`
66
+ ### Report documents and blocks
67
+
68
+ #### `mde --list-blocks`
69
+
51
70
  List all blocks in the all the markdown documents in the current folder.
52
71
 
53
- ### `mde --list-docs`
72
+ #### `mde --list-docs`
73
+
54
74
  List all markdown documents in the current folder.
55
75
 
76
+ ### Configuration
77
+
78
+ #### `mde --list-default-env` or `mde --list-default-yaml`
79
+
80
+ List default values that can be set in configuration file, environment, and command line.
81
+
82
+ #### `mde -0`
83
+
84
+ Show current configuation values that will be applied to the current run. Does not interrupt processing.
85
+
86
+ ### Save scripts
87
+
88
+ #### `mde --save-executed-script 1`
89
+
90
+ Save executed script in saved script folder.
91
+
92
+ #### `mde --list-recent-scripts`
93
+
94
+ List recent saved scripts in saved script folder.
95
+
96
+ #### `mde --select-recent-script`
97
+
98
+ Select and execute a recently saved script in saved script folder.
99
+
100
+ ### Save output
101
+
102
+ #### `mde --save-execution-output 1`
103
+
104
+ Save execution output in saved output folder.
105
+
56
106
  ## Behavior
107
+
57
108
  * If no file and no folder are specified, blocks within `./README.md` are presented.
58
109
  * If a file is specified, its blocks are presented.
59
110
  * If a folder is specified, its files are presented. When a file is selected, its blocks are presented.
60
111
 
61
112
  ## Configuration
62
- While starting up, reads the YAML configuration file `.mde.yml` in the current folder if it exists.
63
113
 
64
- e.g. Use to set the default file for the current folder.
114
+ ### Environment Variables
115
+
116
+ When executed, `mde` reads the current environment.
117
+ * Configuration in current and children shells, e.g. `export MDE_SAVE_EXECUTED_SCRIPT=1`.
118
+ * Configuration for the current command, e.g. `MDE_SAVE_EXECUTED_SCRIPT=1 mde`.
119
+
120
+ ### Configuration Files
121
+
122
+ * Configuration in all shells, e.g. environment variables set in your user's `~/.bashrc` or `~/.bash_profile` files.
123
+ * Configuration in the optional file `.mde.yml` in the current folder. .e.g. `save_executed_script: true`
124
+ * Configuration in a YAML file and read while parsing the inputs, e.g. `--config my_path/my_file.yml`
125
+
126
+ ### Program Arguments
127
+
128
+ * Configuration in command options, e.g. `mde --save-executed-script 1`
129
+
130
+ ## Representing boolean values
131
+
132
+ Boolean values expressed as strings are interpreted as:
133
+ | String | Boolean |
134
+ | :---: | :---: |
135
+ | *empty string* | False |
136
+ | `0` | False |
137
+ | `1` | True |
138
+ | *anything else* | True |
139
+
140
+ E.g. `opt1=1` will set option `opt1` to True.
141
+
142
+ Boolean options configured with environment variables:
143
+ - Set to `1` or non-empty value to save executed scripts; empty or `0` to disable saving.
144
+ e.g. `export MDE_SAVE_EXECUTED_SCRIPT=1`
145
+ e.g. `export MDE_SAVE_EXECUTED_SCRIPT=`
146
+ - Specify variable on command line.
147
+ e.g. `MDE_SAVE_EXECUTED_SCRIPT=1 mde`
148
+
149
+ ## Tab Completion
150
+
151
+ ### Install tab completion
152
+
153
+ Append a command to load the completion script to your shell configuration file:
154
+
155
+ ```bash :()
156
+ echo "source $(mde --pwd)/bin/tab_completion.sh" >> ~/.bash_profile
157
+ ```
158
+
159
+ ### Example Completions
160
+
161
+ Type tab at end of any of the following commands to see the options.
162
+ - `mde `
163
+ - `mde -`
164
+ - `mde --`
165
+ - `mde --o`
166
+ - `mde --filename my.md -`
167
+ - `mde --filename my.md --`
65
168
 
66
- * `filename: CHANGELOG.md` sets the file to open.
67
- * `folder: documents` sets the folder to search for default or specified files.
169
+ ## Example Blocks
68
170
 
69
- # Example blocks
70
171
  When prompted, select either the `awake` or `asleep` block.
71
172
 
72
173
  ``` :(day)
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+
3
+ __filedirs()
4
+ {
5
+ local IFS=$'\n'
6
+ COMPREPLY=( $(compgen -o plusdirs -f -- "${cur}") )
7
+ }
8
+
9
+ _mde() {
10
+ local cur prev opts
11
+ cur="${COMP_WORDS[COMP_CWORD]}"
12
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
13
+ opts="$(mde --tab-completions)"
14
+
15
+ if [[ ${cur} == -* ]] ; then
16
+ COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
17
+ return 0
18
+ fi
19
+
20
+ __filedirs
21
+ }
22
+
23
+ complete -o filenames -F _mde mde
data/fixtures/exclude2.md CHANGED
@@ -7,3 +7,6 @@ b
7
7
  ``` :three
8
8
  c
9
9
  ```
10
+ ``` :()
11
+ d
12
+ ```
@@ -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 = '1.0.0'
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
 
@@ -60,16 +67,13 @@ public
60
67
  def tap_inspect(format: nil, name: 'return')
61
68
  return self unless $pdebug
62
69
 
63
- fn = case format
64
- when :json
65
- :to_json
66
- when :string
67
- :to_s
68
- when :yaml
69
- :to_yaml
70
- else
71
- :inspect
72
- end
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])
73
77
 
74
78
  puts "-> #{caller[0].scan(/in `?(\S+)'$/)[0][0]}()" \
75
79
  " #{name}: #{method(fn).call}"
@@ -94,44 +98,18 @@ module MarkdownExec
94
98
  # options necessary to start, parse input, defaults for cli options
95
99
 
96
100
  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
- }
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]
107
+ end.compact.to_h.merge(
108
+ {
109
+ mdheadings: true, # use headings (levels 1,2,3) in block lable
110
+ menu_with_exit: true
111
+ }
112
+ ).tap_inspect format: :yaml
135
113
  end
136
114
 
137
115
  def default_options
@@ -143,6 +121,7 @@ module MarkdownExec
143
121
  prompt_approve_block: 'Process?',
144
122
  prompt_select_block: 'Choose a block:',
145
123
  prompt_select_md: 'Choose a file:',
124
+ prompt_select_output: 'Choose a file:',
146
125
  saved_script_filename: nil, # calculated
147
126
  struct: true # allow get_block_summary()
148
127
  }
@@ -164,8 +143,10 @@ module MarkdownExec
164
143
  selected = get_block_by_name blocks_in_file, opts[:block_name]
165
144
 
166
145
  if opts[:ir_approve]
167
- write_command_file(opts, required_blocks) if opts[:save_executed_script]
146
+ write_command_file opts, required_blocks
168
147
  command_execute opts, required_blocks.flatten.join("\n")
148
+ save_execution_output
149
+ output_execution_summary
169
150
  end
170
151
 
171
152
  selected[:name]
@@ -241,31 +222,30 @@ module MarkdownExec
241
222
  # document and block reports
242
223
  #
243
224
  files = list_files_per_options(options)
244
- if @options[:list_blocks]
245
- fout_list (files.map do |file|
246
- make_block_labels(filename: file, struct: true)
247
- end).flatten(1)
248
- return
249
- end
250
225
 
251
- if @options[:list_docs]
252
- fout_list files
253
- return
254
- end
255
-
256
- if @options[:list_recent_scripts]
257
- fout_list list_recent_scripts
258
- return
259
- end
260
-
261
- if @options[:run_last_script]
262
- run_last_script
263
- return
264
- end
265
-
266
- if @options[:select_recent_script]
267
- select_recent_script
268
- return
226
+ simple_commands = {
227
+ doc_glob: -> { fout options[:md_filename_glob] },
228
+ list_blocks: lambda do
229
+ fout_list (files.map do |file|
230
+ make_block_labels(filename: file, struct: true)
231
+ end).flatten(1)
232
+ end,
233
+ list_default_yaml: -> { fout_list list_default_yaml },
234
+ list_docs: -> { fout_list files },
235
+ list_default_env: -> { fout_list list_default_env },
236
+ list_recent_output: -> { fout_list list_recent_output },
237
+ list_recent_scripts: -> { fout_list list_recent_scripts },
238
+ pwd: -> { fout File.expand_path('..', __dir__) },
239
+ run_last_script: -> { run_last_script },
240
+ select_recent_output: -> { select_recent_output },
241
+ select_recent_script: -> { select_recent_script },
242
+ tab_completions: -> { fout tab_completions }
243
+ }
244
+ simple_commands.each_key do |key|
245
+ if @options[key]
246
+ simple_commands[key].call
247
+ return # rubocop:disable Lint/NonLocalExitFromIterator
248
+ end
269
249
  end
270
250
 
271
251
  # process
@@ -276,8 +256,6 @@ module MarkdownExec
276
256
  struct: true
277
257
  )
278
258
  fout "saved_filespec: #{@execute_script_filespec}" if @options[:output_saved_script_filename]
279
- save_execution_output
280
- output_execution_summary
281
259
  end
282
260
 
283
261
  # standard output; not for debug
@@ -322,6 +300,11 @@ module MarkdownExec
322
300
  exit 1
323
301
  end
324
302
 
303
+ unless File.exist? opts[:filename]
304
+ fout 'Document is missing.'
305
+ exit 1
306
+ end
307
+
325
308
  fenced_start_and_end_match = Regexp.new opts[:fenced_start_and_end_match]
326
309
  fenced_start_ex = Regexp.new opts[:fenced_start_ex_match]
327
310
  block_title = ''
@@ -376,14 +359,37 @@ module MarkdownExec
376
359
  blocks.tap_inspect
377
360
  end
378
361
 
362
+ 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?
366
+
367
+ [
368
+ "#{env_var}=#{value_for_cli default}",
369
+ description.present? ? description : nil
370
+ ].compact.join(' # ')
371
+ end.compact.sort
372
+ end
373
+
374
+ 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?
378
+
379
+ [
380
+ "#{opt_name}: #{value_for_yaml default}",
381
+ description.present? ? description : nil
382
+ ].compact.join(' # ')
383
+ end.compact.sort
384
+ end
385
+
379
386
  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
387
+ list_files_specified(
388
+ options[:filename]&.present? ? options[:filename] : nil,
389
+ options[:path],
390
+ 'README.md',
391
+ '.'
392
+ ).tap_inspect
387
393
  end
388
394
 
389
395
  def list_files_specified(specified_filename, specified_folder, default_filename, default_folder, filetree = nil)
@@ -438,9 +444,28 @@ module MarkdownExec
438
444
  .tap_inspect
439
445
  end
440
446
 
447
+ def most_recent(arr)
448
+ return unless arr
449
+ return if arr.count < 1
450
+
451
+ arr.max.tap_inspect
452
+ end
453
+
454
+ def most_recent_list(arr)
455
+ return unless arr
456
+ return if (ac = arr.count) < 1
457
+
458
+ arr.sort[-[ac, options[:list_count]].min..].reverse.tap_inspect
459
+ end
460
+
461
+ def list_recent_output
462
+ most_recent_list(Dir.glob(File.join(@options[:saved_stdout_folder],
463
+ @options[:saved_stdout_glob]))).tap_inspect
464
+ end
465
+
441
466
  def list_recent_scripts
442
- Dir.glob(File.join(@options[:saved_script_folder],
443
- @options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].reverse.tap_inspect
467
+ most_recent_list(Dir.glob(File.join(@options[:saved_script_folder],
468
+ @options[:saved_script_glob]))).tap_inspect
444
469
  end
445
470
 
446
471
  def make_block_label(block, call_options = {})
@@ -462,6 +487,89 @@ module MarkdownExec
462
487
  end.compact.tap_inspect
463
488
  end
464
489
 
490
+ def menu_data
491
+ val_as_bool = ->(value) { value.class.to_s == 'String' ? (value.chomp != '0') : value }
492
+ val_as_int = ->(value) { value.to_i }
493
+ 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],
564
+ ]
565
+
566
+ summary_head + summary_body + summary_tail + env_vars
567
+ end
568
+
569
+ def menu_help
570
+ @option_parser.help
571
+ end
572
+
465
573
  def option_exclude_blocks(opts, blocks)
466
574
  block_name_excluded_match = Regexp.new opts[:block_name_excluded_match]
467
575
  if opts[:hide_blocks_by_name]
@@ -471,6 +579,27 @@ module MarkdownExec
471
579
  end
472
580
  end
473
581
 
582
+ ## post-parse options configuration
583
+ #
584
+ def options_finalize(rest)
585
+ ## position 0: file or folder (optional)
586
+ #
587
+ if (pos = rest.fetch(0, nil))&.present?
588
+ if Dir.exist?(pos)
589
+ @options[:path] = pos
590
+ elsif File.exist?(pos)
591
+ @options[:filename] = pos
592
+ else
593
+ raise "Invalid parameter: #{pos}"
594
+ end
595
+ end
596
+
597
+ ## position 1: block name (optional)
598
+ #
599
+ block_name = rest.fetch(1, nil)
600
+ @options[:block_name] = block_name if block_name.present?
601
+ end
602
+
474
603
  def optsmerge(call_options = {}, options_block = nil)
475
604
  class_call_options = @options.merge(call_options || {})
476
605
  if options_block
@@ -495,6 +624,14 @@ module MarkdownExec
495
624
  }
496
625
  end
497
626
 
627
+ def prompt_with_quit(prompt_text, items, opts = {})
628
+ exit_option = '* Exit'
629
+ sel = @prompt.select prompt_text,
630
+ items + (@options[:menu_with_exit] ? [exit_option] : []),
631
+ opts
632
+ sel == exit_option ? nil : sel
633
+ end
634
+
498
635
  def read_configuration_file!(options, configuration_path)
499
636
  return unless File.exist?(configuration_path)
500
637
 
@@ -526,87 +663,28 @@ module MarkdownExec
526
663
  #
527
664
  @options = base_options
528
665
 
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
666
+ ## read local configuration file
538
667
  #
539
668
  read_configuration_file! @options, ".#{MarkdownExec::APP_NAME.downcase}.yml"
540
669
 
541
- option_parser = OptionParser.new do |opts|
670
+ @option_parser = option_parser = OptionParser.new do |opts|
542
671
  executable_name = File.basename($PROGRAM_NAME)
543
672
  opts.banner = [
544
673
  "#{MarkdownExec::APP_NAME}" \
545
674
  " - #{MarkdownExec::APP_DESC} (#{MarkdownExec::VERSION})",
546
- "Usage: #{executable_name} [path] [filename] [options]"
675
+ "Usage: #{executable_name} [(path | filename [block_name])] [options]"
547
676
  ].join("\n")
548
677
 
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
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?
681
+
604
682
  opts.on(*[if long_name.present?
605
683
  "--#{long_name}#{arg_name.present? ? " #{arg_name}" : ''}"
606
684
  end,
607
685
  short_name.present? ? "-#{short_name}" : nil,
608
686
  [description,
609
- env_var.present? ? "env: #{env_var}" : nil].compact.join(' - '),
687
+ default.present? ? "[#{value_for_cli default}]" : nil].compact.join(' '),
610
688
  lambda { |value|
611
689
  ret = proc1.call(value)
612
690
  options[opt_name] = ret if opt_name
@@ -618,47 +696,44 @@ module MarkdownExec
618
696
  option_parser.environment # env defaults to the basename of the program.
619
697
  rest = option_parser.parse! # (into: options)
620
698
 
621
- ## finalize configuration
622
- #
623
- options_finalize.call options
699
+ options_finalize rest
624
700
 
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
701
+ exec_block options, options[:block_name]
702
+ end
636
703
 
637
- ## position 1: block name (optional)
638
- #
639
- block_name = rest.fetch(1, nil)
704
+ FNR11 = '/'
705
+ FNR12 = ',;'
706
+
707
+ def saved_name_make(opts)
708
+ fne = opts[:filename].gsub(FNR11, FNR12)
709
+ "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
710
+ ',', opts[:block_name]].join('_')}.sh"
711
+ end
712
+
713
+ def saved_name_split(name)
714
+ mf = name.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/)
715
+ return unless mf
640
716
 
641
- exec_block options, block_name
717
+ @options[:block_name] = mf[:block].tap_inspect name: :options_block_name
718
+ @options[:filename] = mf[:file].gsub(FNR12, FNR11).tap_inspect name: :options_filename
642
719
  end
643
720
 
644
721
  def run_last_script
645
- filename = Dir.glob(File.join(@options[:saved_script_folder],
646
- @options[:saved_script_glob])).sort[0..(options[:list_count] - 1)].last
647
- filename.tap_inspect name: filename
648
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
722
+ filename = most_recent Dir.glob(File.join(@options[:saved_script_folder],
723
+ @options[:saved_script_glob]))
724
+ return unless filename
649
725
 
650
- @options[:block_name] = mf[:block]
651
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
726
+ filename.tap_inspect name: filename
727
+ saved_name_split filename
652
728
  @options[:save_executed_script] = false
653
729
  select_and_approve_block
654
- save_execution_output
655
- output_execution_summary
656
730
  end
657
731
 
658
732
  def save_execution_output
659
733
  return unless @options[:save_execution_output]
660
734
 
661
735
  fne = File.basename(@options[:filename], '.*')
736
+
662
737
  @options[:logged_stdout_filename] =
663
738
  "#{[@options[:logged_stdout_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
664
739
  @options[:block_name]].join('_')}.out.txt"
@@ -680,7 +755,9 @@ module MarkdownExec
680
755
 
681
756
  return nil if block_labels.count.zero?
682
757
 
683
- sel = @prompt.select pt, block_labels, per_page: opts[:select_page_height]
758
+ sel = prompt_with_quit pt, block_labels, per_page: opts[:select_page_height]
759
+ return nil if sel.nil?
760
+
684
761
  label_block = blocks_in_file.select { |block| block[:label] == sel }.fetch(0, nil)
685
762
  opts[:block_name] = @options[:block_name] = label_block[:name]
686
763
  end
@@ -694,24 +771,29 @@ module MarkdownExec
694
771
  if files.count == 1
695
772
  files[0]
696
773
  elsif files.count >= 2
697
- @prompt.select opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
774
+ prompt_with_quit opts[:prompt_select_md].to_s, files, per_page: opts[:select_page_height]
698
775
  end
699
776
  end
700
777
 
778
+ def select_recent_output
779
+ filename = prompt_with_quit @options[:prompt_select_output].to_s, list_recent_output,
780
+ per_page: @options[:select_page_height]
781
+ return unless filename.present?
782
+
783
+ `open #{filename} #{options[:output_viewer_options]}`
784
+ end
785
+
701
786
  def select_recent_script
702
- filename = @prompt.select @options[:prompt_select_md].to_s, list_recent_scripts,
703
- per_page: @options[:select_page_height]
704
- mf = filename.match(/#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_(?<block>.+)\.sh/)
787
+ filename = prompt_with_quit @options[:prompt_select_md].to_s, list_recent_scripts,
788
+ per_page: @options[:select_page_height]
789
+ return if filename.nil?
705
790
 
706
- @options[:block_name] = mf[:block]
707
- @options[:filename] = "#{mf[:file]}.md" ### other extensions
791
+ saved_name_split filename
708
792
  select_and_approve_block(
709
793
  bash: true,
710
794
  save_executed_script: false,
711
795
  struct: true
712
796
  )
713
- save_execution_output
714
- output_execution_summary
715
797
  end
716
798
 
717
799
  def sorted_keys(hash1)
@@ -722,22 +804,72 @@ module MarkdownExec
722
804
  { headings: headings, name: title, title: title }
723
805
  end
724
806
 
807
+ def tab_completions(data = menu_data)
808
+ data.map do |item|
809
+ "--#{item[0]}" if item[0]
810
+ end.compact
811
+ end
812
+
725
813
  def update_options(opts = {}, over: true)
726
814
  if over
727
815
  @options = @options.merge opts
728
816
  else
729
817
  @options.merge! opts
730
818
  end
731
- @options
819
+ @options.tap_inspect format: :yaml
820
+ end
821
+
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
+ def value_for_hash(value, default = nil)
836
+ return default if value.nil?
837
+
838
+ case value.class.to_s
839
+ when 'String', 'Integer', 'FalseClass', 'TrueClass'
840
+ value
841
+ when value.empty?
842
+ default
843
+ else
844
+ value.to_s
845
+ end
846
+ end
847
+
848
+ def value_for_yaml(value)
849
+ return default if value.nil?
850
+
851
+ case value.class.to_s
852
+ when 'String'
853
+ "'#{value}'"
854
+ when 'Integer'
855
+ value
856
+ when 'FalseClass', 'TrueClass'
857
+ value ? true : false
858
+ when value.empty?
859
+ default
860
+ else
861
+ value.to_s
862
+ end
732
863
  end
733
864
 
734
865
  def write_command_file(opts, required_blocks)
735
- fne = File.basename(opts[:filename], '.*')
736
- opts[:saved_script_filename] =
737
- "#{[opts[:saved_script_filename_prefix], Time.now.utc.strftime('%F-%H-%M-%S'), fne,
738
- opts[:block_name]].join('_')}.sh"
739
- @options[:saved_filespec] = File.join opts[:saved_script_folder], opts[:saved_script_filename]
740
- @execute_script_filespec = @options[:saved_filespec]
866
+ return unless opts[:save_executed_script]
867
+
868
+ opts[:saved_script_filename] = saved_name_make(opts)
869
+ @execute_script_filespec =
870
+ @options[:saved_filespec] =
871
+ File.join opts[:saved_script_folder], opts[:saved_script_filename]
872
+
741
873
  dirname = File.dirname(@options[:saved_filespec])
742
874
  Dir.mkdir dirname unless File.exist?(dirname)
743
875
  File.write(@options[:saved_filespec], "#!/usr/bin/env bash\n" \
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: 1.0.0
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-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: open3
@@ -67,11 +67,13 @@ 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:
74
75
  - mde
76
+ - tab_completion.sh
75
77
  extensions: []
76
78
  extra_rdoc_files: []
77
79
  files:
@@ -91,6 +93,7 @@ files:
91
93
  - bin/console
92
94
  - bin/mde
93
95
  - bin/setup
96
+ - bin/tab_completion.sh
94
97
  - fixtures/bash1.md
95
98
  - fixtures/bash2.md
96
99
  - fixtures/exclude1.md
@@ -127,5 +130,5 @@ requirements: []
127
130
  rubygems_version: 3.2.32
128
131
  signing_key:
129
132
  specification_version: 4
130
- summary: Execute shell blocks in markdown files.
133
+ summary: Interactively select and execute fenced code blocks in markdown files.
131
134
  test_files: []