markdown_exec 1.3.7 → 1.3.8

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: f4a8eee857b22f6ce92a03144f7eb68a2c8298e8888f0b538ff2bfb710aeb790
4
- data.tar.gz: 651842b64a4ec92610d8f4ea00e002ffeaba952838d9142eb7da845edd2c7594
3
+ metadata.gz: 1f61a046f9f071edb4e123fd5ad1972de408c4a40c97d923322e416f643f4ffb
4
+ data.tar.gz: 8d00be273b75ac093809cbc168e585d36da49a26fa0bdde6ee242d06a94c3caf
5
5
  SHA512:
6
- metadata.gz: afc863af78e1b75eec616e855625d1d5e8cae8a750bc51377c7110a8ed630d82a215242b2a09f8a49fdd549be7b57e96567c3b6bc384b61313feffa404600516
7
- data.tar.gz: 41cd17fff38d663fd9b052496ec1f37d40ac8df2929d28f2ffd2e0b7fee5c002a982f4ed01618e82aa4c51dc71b85032d3772d8667727f63ec75a7795ccfe1dc
6
+ metadata.gz: 054de96356ebb625db55bccb693e498abebf01ab7eeb44b28f6391c3fec3ca87b72970916cbc3ba4dca9088905206b47a33213c9ded81fa67458c47827ba7068
7
+ data.tar.gz: 642a9e511a013c4485eb187e4be1d22a6516686b8815de1c6b1c230790269612357e9635d0b7b078dbc8f334b1e2d2e06980af71330f3bf8c1a273861bf902f4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.8] - 2023-10-20
4
+
5
+ ### Added
6
+
7
+ - Options for hidden, included, and wrapped blocks
8
+
9
+ ## [1.3.7] - 2023-10-16
10
+
11
+ ### Changed
12
+
13
+ - Fix invocation of SavedAsset class
14
+
3
15
  ## [1.3.6] - 2023-10-15
4
16
 
5
17
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- markdown_exec (1.3.7)
4
+ markdown_exec (1.3.8)
5
5
  clipboard (~> 1.3.6)
6
6
  open3 (~> 0.1.1)
7
7
  optparse (~> 0.1.1)
data/Rakefile CHANGED
@@ -81,6 +81,7 @@ task :minitest do
81
81
  './lib/cached_nested_file_reader.rb',
82
82
  './lib/fcb.rb',
83
83
  './lib/filter.rb',
84
+ './lib/markdown_block_manager.rb',
84
85
  './lib/mdoc.rb',
85
86
  './lib/object_present.rb',
86
87
  './lib/option_value.rb',
@@ -13,7 +13,7 @@ __filedirs_all()
13
13
  }
14
14
 
15
15
  _mde_echo_version() {
16
- echo "1.3.7"
16
+ echo "1.3.8"
17
17
  }
18
18
 
19
19
  _mde() {
@@ -138,4 +138,4 @@ _mde() {
138
138
 
139
139
  complete -o filenames -o nospace -F _mde mde
140
140
  # _mde_echo_version
141
- # echo "Updated: 2023-10-16 16:49:28 UTC"
141
+ # echo "Updated: 2023-10-20 23:59:34 UTC"
data/lib/filter.rb CHANGED
@@ -6,13 +6,22 @@
6
6
  module MarkdownExec
7
7
  # Filter
8
8
  #
9
- # The Filter class provides utilities to determine the inclusion of fenced code blocks (FCB)
10
- # based on a set of provided options. The primary function, `fcb_select?`, checks
11
- # various properties of an FCB and decides whether to include or exclude it.
9
+ # The Filter class provides utilities to determine the inclusion of
10
+ # fenced code blocks (FCB) based on a set of provided options. The
11
+ # primary function, `fcb_select?`, checks various properties of an
12
+ # FCB and decides whether to include or exclude it.
12
13
  #
13
14
  # :reek:UtilityFunction
14
-
15
15
  class Filter
16
+ # Determines whether to include or exclude a fenced code block
17
+ # (FCB) based on the provided options.
18
+ #
19
+ # @param options [Hash] The options used for filtering FCBs.
20
+ # @param fcb [Hash] The fenced code block to be evaluated.
21
+ # @return [Boolean] True if the FCB should be included; false if
22
+ # it should be excluded.
23
+ # @raise [StandardError] If an error occurs during the evaluation.
24
+ #
16
25
  def self.fcb_select?(options, fcb)
17
26
  filters = {
18
27
  name_default: true,
@@ -21,7 +30,9 @@ module MarkdownExec
21
30
  shell_default: true,
22
31
  shell_exclude: nil,
23
32
  shell_select: nil,
24
- hidden_name: nil
33
+ hidden_name: nil,
34
+ include_name: nil,
35
+ wrap_name: nil
25
36
  }
26
37
 
27
38
  name = fcb.fetch(:name, '')
@@ -37,6 +48,14 @@ module MarkdownExec
37
48
  raise err
38
49
  end
39
50
 
51
+ # Applies name-based filters to determine whether to include or
52
+ # exclude a fenced code block (FCB)
53
+ # based on the block's name and provided options.
54
+ #
55
+ # @param options [Hash] The options used for filtering FCBs.
56
+ # @param filters [Hash] The filter settings to be updated.
57
+ # @param name [String] The name of the fenced code block.
58
+ #
40
59
  def self.apply_name_filters(options, filters, name)
41
60
  if name.present? && options[:block_name]
42
61
  if name =~ /#{options[:block_name]}/
@@ -59,6 +78,14 @@ module MarkdownExec
59
78
  filters[:name_exclude] = !!(name =~ /#{options[:exclude_by_name_regex]}/)
60
79
  end
61
80
 
81
+ # Applies shell-based filters to determine whether to include or
82
+ # exclude a fenced code block (FCB)
83
+ # based on the block's shell type and provided options.
84
+ #
85
+ # @param options [Hash] The options used for filtering FCBs.
86
+ # @param filters [Hash] The filter settings to be updated.
87
+ # @param shell [String] The shell type of the fenced code block.
88
+ #
62
89
  def self.apply_shell_filters(options, filters, shell)
63
90
  filters[:shell_expect] = shell == 'expect'
64
91
 
@@ -71,34 +98,58 @@ module MarkdownExec
71
98
  filters[:shell_exclude] = !!(shell =~ /#{options[:exclude_by_shell_regex]}/)
72
99
  end
73
100
 
101
+ # Applies additional filters to determine whether to include or
102
+ # exclude a fenced code block (FCB)
103
+ # based on various criteria and provided options.
104
+ #
105
+ # @param options [Hash] The options used for filtering FCBs.
106
+ # @param filters [Hash] The filter settings to be updated.
107
+ # @param fcb [Hash] The fenced code block to be evaluated.
108
+ #
74
109
  def self.apply_other_filters(options, filters, fcb)
75
110
  name = fcb.fetch(:name, '')
76
111
  shell = fcb.fetch(:shell, '')
77
112
  filters[:fcb_chrome] = fcb.fetch(:chrome, false)
78
113
 
79
- if name.present? && options[:hide_blocks_by_name] &&
80
- options[:block_name_hidden_match].present?
81
- filters[:hidden_name] = !!(name =~ /#{options[:block_name_hidden_match]}/)
82
- end
83
-
84
- if shell.present? && options[:hide_blocks_by_shell] &&
85
- options[:block_shell_hidden_match].present?
86
- !!(shell =~ /#{options[:block_shell_hidden_match]}/)
114
+ if name.present? && options[:hide_blocks_by_name]
115
+ filters[:hidden_name] =
116
+ !!(options[:block_name_hidden_match].present? &&
117
+ name =~ /#{options[:block_name_hidden_match]}/)
87
118
  end
119
+ filters[:include_name] =
120
+ !!(options[:block_name_include_match].present? &&
121
+ name =~ /#{options[:block_name_include_match]}/)
122
+ filters[:wrap_name] =
123
+ !!(options[:block_name_wrapper_match].present? &&
124
+ name =~ /#{options[:block_name_wrapper_match]}/)
88
125
 
89
126
  return unless options[:bash_only]
90
127
 
91
128
  filters[:shell_default] = (shell == 'bash')
92
129
  end
93
130
 
131
+ # Evaluates the filter settings to make a final decision on
132
+ # whether to include or exclude a fenced
133
+ # code block (FCB) based on the provided options.
134
+ #
135
+ # @param options [Hash] The options used for filtering FCBs.
136
+ # @param filters [Hash] The filter settings to be evaluated.
137
+ # @return [Boolean] True if the FCB should be included; false
138
+ # if it should be excluded.
139
+ #
94
140
  def self.evaluate_filters(options, filters)
95
- if options[:no_chrome] && filters[:fcb_chrome]
141
+ if options[:no_chrome] && filters[:fcb_chrome] == true
96
142
  false
97
- elsif options[:exclude_expect_blocks] && filters[:shell_expect]
143
+ elsif options[:exclude_expect_blocks] && filters[:shell_expect] == true
98
144
  false
99
145
  elsif filters[:hidden_name] == true
146
+ false
147
+ elsif filters[:include_name] == true
100
148
  true
101
- elsif filters[:name_exclude] == true || filters[:shell_exclude] == true || filters[:name_select] == false || filters[:shell_select] == false
149
+ elsif filters[:wrap_name] == true
150
+ true
151
+ elsif filters[:name_exclude] == true || filters[:shell_exclude] == true ||
152
+ filters[:name_select] == false || filters[:shell_select] == false
102
153
  false
103
154
  elsif filters[:name_select] == true || filters[:shell_select] == true
104
155
  true
@@ -112,70 +163,91 @@ module MarkdownExec
112
163
  end
113
164
 
114
165
  if $PROGRAM_NAME == __FILE__
166
+ require 'bundler/setup'
167
+ Bundler.require(:default)
168
+
115
169
  require 'minitest/autorun'
116
170
 
117
- require_relative 'tap'
118
- include Tap
171
+ module MarkdownExec
172
+ class FilterTest < Minitest::Test
173
+ def setup
174
+ @options = {}
175
+ @fcb = {}
176
+ end
119
177
 
120
- class FilterTest < Minitest::Test
121
- def test_no_chrome_condition
122
- options = { no_chrome: true }
123
- fcb = { chrome: true }
124
- refute MarkdownExec::Filter.fcb_select?(options, fcb)
125
- end
178
+ # Tests for fcb_select? method
179
+ def test_no_chrome_condition
180
+ @options[:no_chrome] = true
181
+ @fcb[:chrome] = true
182
+ refute Filter.fcb_select?(@options, @fcb)
183
+ end
126
184
 
127
- def test_exclude_expect_blocks_condition
128
- options = { exclude_expect_blocks: true }
129
- fcb = { shell: 'expect' }
130
- refute MarkdownExec::Filter.fcb_select?(options, fcb)
131
- end
185
+ def test_exclude_expect_blocks_condition
186
+ @options[:exclude_expect_blocks] = true
187
+ @fcb[:shell] = 'expect'
188
+ refute Filter.fcb_select?(@options, @fcb)
189
+ end
132
190
 
133
- def test_hidden_name_condition
134
- options = { hide_blocks_by_name: true, block_name_hidden_match: 'hidden' }
135
- fcb = { name: 'hidden_block' }
136
- assert MarkdownExec::Filter.fcb_select?(options, fcb)
137
- end
191
+ def test_hidden_name_condition
192
+ @options[:hide_blocks_by_name] = true
193
+ @options[:block_name_hidden_match] = 'hidden'
194
+ @fcb[:name] = 'hidden_block'
195
+ refute Filter.fcb_select?(@options, @fcb)
196
+ end
138
197
 
139
- def test_name_exclude_condition
140
- options = { block_name: 'test' }
141
- fcb = { name: 'sample' }
142
- refute MarkdownExec::Filter.fcb_select?(options, fcb)
143
- end
198
+ def test_include_name_condition
199
+ @options[:hide_blocks_by_name] = true
200
+ @options[:block_name_indlude_match] = 'include'
201
+ @fcb[:name] = 'include_block'
202
+ assert Filter.fcb_select?(@options, @fcb)
203
+ end
144
204
 
145
- def test_shell_exclude_condition
146
- options = { exclude_by_shell_regex: 'exclude_this' }
147
- fcb = { shell: 'exclude_this_shell' }
148
- refute MarkdownExec::Filter.fcb_select?(options, fcb)
149
- end
205
+ def test_wrap_name_condition
206
+ @options[:hide_blocks_by_name] = true
207
+ @options[:block_name_wrapper_match] = 'wrap'
208
+ @fcb[:name] = 'wrap_block'
209
+ assert Filter.fcb_select?(@options, @fcb)
210
+ end
150
211
 
151
- def test_name_select_condition
152
- options = { select_by_name_regex: 'select' }
153
- fcb = { name: 'select_this' }
154
- assert MarkdownExec::Filter.fcb_select?(options, fcb)
155
- end
212
+ def test_name_exclude_condition
213
+ @options[:block_name] = 'test'
214
+ @fcb[:name] = 'sample'
215
+ refute Filter.fcb_select?(@options, @fcb)
216
+ end
156
217
 
157
- def test_shell_select_condition
158
- options = { select_by_shell_regex: 'select_this' }
159
- fcb = { shell: 'select_this_shell' }
160
- assert MarkdownExec::Filter.fcb_select?(options, fcb)
161
- end
218
+ def test_shell_exclude_condition
219
+ @options[:exclude_by_shell_regex] = 'exclude_this'
220
+ @fcb[:shell] = 'exclude_this_shell'
221
+ refute Filter.fcb_select?(@options, @fcb)
222
+ end
162
223
 
163
- def test_bash_only_condition_true
164
- options = { bash_only: true }
165
- fcb = { shell: 'bash' }
166
- assert MarkdownExec::Filter.fcb_select?(options, fcb)
167
- end
224
+ def test_name_select_condition
225
+ @options[:select_by_name_regex] = 'select'
226
+ @fcb[:name] = 'select_this'
227
+ assert Filter.fcb_select?(@options, @fcb)
228
+ end
168
229
 
169
- def test_bash_only_condition_false
170
- options = { bash_only: true }
171
- fcb = { shell: 'zsh' }
172
- refute MarkdownExec::Filter.fcb_select?(options, fcb)
173
- end
230
+ def test_shell_select_condition
231
+ @options[:select_by_shell_regex] = 'select_this'
232
+ @fcb[:shell] = 'select_this_shell'
233
+ assert Filter.fcb_select?(@options, @fcb)
234
+ end
235
+
236
+ def test_bash_only_condition_true
237
+ @options[:bash_only] = true
238
+ @fcb[:shell] = 'bash'
239
+ assert Filter.fcb_select?(@options, @fcb)
240
+ end
174
241
 
175
- def test_default_case
176
- options = {}
177
- fcb = {}
178
- assert MarkdownExec::Filter.fcb_select?(options, fcb)
242
+ def test_bash_only_condition_false
243
+ @options[:bash_only] = true
244
+ @fcb[:shell] = 'zsh'
245
+ refute Filter.fcb_select?(@options, @fcb)
246
+ end
247
+
248
+ def test_default_case
249
+ assert Filter.fcb_select?(@options, @fcb)
250
+ end
179
251
  end
180
252
  end
181
253
  end
@@ -16,115 +16,25 @@ module MarkdownExec
16
16
  class MarkdownBlockManager
17
17
  attr_reader :block_table
18
18
 
19
+ # initialize
20
+ #
21
+ # Initializes an instance of MarkdownBlockManager with the given block table.
22
+ #
23
+ # @param block_table [Array<Hash>] An array of hashes representing markdown blocks.
19
24
  def initialize(block_table)
20
25
  @block_table = block_table
21
26
  end
22
27
 
23
- def collect_required_code(name)
24
- gather_required_blocks(name)
25
- .map do |block|
26
- process_block_code(block)
27
- end.flatten(1)
28
- end
29
-
28
+ # get_block
29
+ #
30
+ # Retrieves a code block by its name.
31
+ #
32
+ # @param name [String] The name of the code block to retrieve.
33
+ # @param default [Hash] The default value to return if the code block is not found.
34
+ # @return [Hash] The code block as a hash or the default value if not found.
30
35
  def get_block(name, default = {})
31
36
  @block_table.select { |block| block.fetch(:name, '') == name }.fetch(0, default)
32
37
  end
33
-
34
- def gather_required_blocks(name)
35
- named_block = get_block(name)
36
- if named_block.nil? || named_block.keys.empty?
37
- raise "Named code block `#{name}` not found."
38
- end
39
-
40
- all_blocks = [named_block.fetch(:name, '')] + required_blocks(named_block[:reqs])
41
- @block_table.select { |block| all_blocks.include? block.fetch(:name, '') }
42
- .map do |block|
43
- process_block_references(block)
44
- end.flatten(1)
45
- end
46
-
47
- def hide_block_given_options(opts, block)
48
- (opts[:hide_blocks_by_name] &&
49
- block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
50
- (block[:name]&.present? || block[:label]&.present?)
51
- )
52
- end
53
-
54
- def blocks_per_options(opts = {})
55
- filtered_blocks = @block_table.select do |block_group|
56
- Filter.block_selected? opts, block_group
57
- end
58
- if opts[:hide_blocks_by_name]
59
- filtered_blocks.reject { |block| hide_block_given_options opts, block }
60
- else
61
- filtered_blocks
62
- end.map do |block|
63
- block
64
- end
65
- end
66
-
67
- def required_blocks(reqs)
68
- return [] unless reqs
69
-
70
- remaining = reqs
71
- collected = []
72
- while remaining.count.positive?
73
- remaining = remaining.map do |req|
74
- next if collected.include? req
75
-
76
- collected += [req]
77
- get_block(req).fetch(:reqs, [])
78
- end
79
- .compact
80
- .flatten(1)
81
- end
82
- collected
83
- end
84
-
85
- # Helper method to process block code
86
- def process_block_code(block)
87
- body = block[:body].join("\n")
88
-
89
- if block[:cann]
90
- xcall = block[:cann][1..-2]
91
- mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
92
- mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
93
-
94
- yqcmd = if mstdin[:type]
95
- "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
96
- else
97
- "yq e '#{body}' '#{mstdin[:name]}'"
98
- end
99
- if mstdout[:type]
100
- "export #{mstdout[:name]}=$(#{yqcmd})"
101
- else
102
- "#{yqcmd} > '#{mstdout[:name]}'"
103
- end
104
- elsif block[:stdout]
105
- stdout = block[:stdout]
106
- body = block[:body].join("\n")
107
- if stdout[:type]
108
- %(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
109
- else
110
- "cat > '#{stdout[:name]}' <<\"EOF\"\n" \
111
- "#{body}\n" \
112
- "EOF\n"
113
- end
114
- else
115
- block[:body]
116
- end
117
- end
118
-
119
- # Helper method to process block references
120
- def process_block_references(block)
121
- if (call = block[:call])
122
- [get_block("[#{call.match(/^%\((\S+) |\)/)[1]}]")
123
- .merge({ cann: call })]
124
- else
125
- []
126
- end + [block]
127
- end
128
38
  end
129
39
  end
130
40
 
@@ -134,62 +44,21 @@ if $PROGRAM_NAME == __FILE__
134
44
 
135
45
  require 'minitest/autorun'
136
46
 
137
- require_relative 'tap'
138
- include Tap
139
-
140
- module MarkdownExec
141
- class TestMDoc < Minitest::Test
142
- def setup
143
- @table = [
144
- { name: 'block1', body: ['code for block1'], reqs: ['block2'] },
145
- { name: 'block2', body: ['code for block2'], reqs: nil },
146
- { name: 'block3', body: ['code for block3'], reqs: ['block1'] }
147
- ]
148
- @doc = MDoc.new(@table)
149
- end
150
-
151
- def test_collect_recursively_required_code
152
- result = @doc.collect_recursively_required_code('block1')
153
- expected_result = @table[0][:body] + @table[1][:body]
154
- assert_equal expected_result, result
155
- end
156
-
157
- def test_get_block_by_name
158
- result = @doc.get_block_by_name('block1')
159
- assert_equal @table[0], result
160
-
161
- result_missing = @doc.get_block_by_name('missing_block')
162
- assert_equal({}, result_missing)
163
- end
164
-
165
- def test_get_required_blocks
166
- result = @doc.get_required_blocks('block3')
167
- expected_result = [@table[0], @table[1], @table[2]]
168
- assert_equal expected_result, result
169
-
170
- assert_raises(RuntimeError) { @doc.get_required_blocks('missing_block') }
171
- end
172
-
173
- def test_hide_menu_block_per_options
174
- opts = { hide_blocks_by_name: true, block_name_hidden_match: 'block1' }
175
- block = { name: 'block1' }
176
- result = @doc.hide_menu_block_per_options(opts, block)
177
- assert result # this should be true based on the given logic
178
- end
179
-
180
- def test_fcbs_per_options
181
- opts = { hide_blocks_by_name: true, block_name_hidden_match: 'block1' }
182
- result = @doc.fcbs_per_options(opts)
183
- assert_equal [@table[1], @table[2]], result
184
- end
185
-
186
- def test_recursively_required
187
- result = @doc.recursively_required(['block3'])
188
- assert_equal %w[block3 block1 block2], result
47
+ class MarkdownBlockManagerTest < Minitest::Test
48
+ def setup
49
+ @block_table = [
50
+ { name: 'block1', reqs: ['block2'], body: ['code1'] },
51
+ { name: 'block2', body: ['code2'] },
52
+ { name: 'block3', body: ['code3'] }
53
+ ]
54
+ @manager = MarkdownExec::MarkdownBlockManager.new(@block_table)
55
+ end
189
56
 
190
- result_no_reqs = @doc.recursively_required(nil)
191
- assert_equal [], result_no_reqs
192
- end
57
+ # Test the get_block method
58
+ def test_get_block
59
+ block = @manager.get_block('block2')
60
+ expected_block = { name: 'block2', body: ['code2'] }
61
+ assert_equal expected_block, block
193
62
  end
194
63
  end
195
64
  end
@@ -7,5 +7,5 @@ module MarkdownExec
7
7
  BIN_NAME = 'mde'
8
8
  GEM_NAME = 'markdown_exec'
9
9
  TAP_DEBUG = 'MDE_DEBUG'
10
- VERSION = '1.3.7'
10
+ VERSION = '1.3.8'
11
11
  end
data/lib/markdown_exec.rb CHANGED
@@ -199,26 +199,40 @@ module MarkdownExec
199
199
  }
200
200
  end
201
201
 
202
+ # Execute a code block after approval and provide user interaction options.
203
+ #
204
+ # This method displays required code blocks, asks for user approval, and
205
+ # executes the code block if approved. It also allows users to copy the
206
+ # code to the clipboard or save it to a file.
207
+ #
208
+ # @param opts [Hash] Options hash containing configuration settings.
209
+ # @param mdoc [YourMDocClass] An instance of the MDoc class.
210
+ # @return [String] The name of the executed code block.
202
211
  def approve_and_execute_block(opts, mdoc)
212
+ # Collect required code blocks based on the provided options.
203
213
  required_blocks = mdoc.collect_recursively_required_code(opts[:block_name])
214
+ # Display required code blocks if requested or required approval.
204
215
  if opts[:output_script] || opts[:user_must_approve]
205
216
  display_required_code(opts,
206
217
  required_blocks)
207
218
  end
208
219
 
209
220
  allow = true
221
+ # If user approval is required, prompt the user for approval.
210
222
  if opts[:user_must_approve]
211
223
  loop do
212
- (sel = @prompt.select(opts[:prompt_approve_block],
213
- filter: true) do |menu|
214
- menu.default 1
215
- menu.choice opts[:prompt_yes], 1
216
- menu.choice opts[:prompt_no], 2
217
- menu.choice opts[:prompt_script_to_clipboard], 3
218
- menu.choice opts[:prompt_save_script], 4
219
- end)
224
+ # Present a selection menu for user approval.
225
+ sel = @prompt.select(opts[:prompt_approve_block],
226
+ filter: true) do |menu|
227
+ menu.default 1
228
+ menu.choice opts[:prompt_yes], 1
229
+ menu.choice opts[:prompt_no], 2
230
+ menu.choice opts[:prompt_script_to_clipboard], 3
231
+ menu.choice opts[:prompt_save_script], 4
232
+ end
220
233
  allow = (sel == 1)
221
234
  if sel == 3
235
+ # Copy the code to the clipboard.
222
236
  text = required_blocks.flatten.join($INPUT_RECORD_SEPARATOR)
223
237
  Clipboard.copy(text)
224
238
  fout "Clipboard updated: #{required_blocks.count} blocks," /
@@ -226,6 +240,7 @@ module MarkdownExec
226
240
  " #{text.length} characters"
227
241
  end
228
242
  if sel == 4
243
+ # Save the code to a file.
229
244
  write_command_file(opts.merge(save_executed_script: true),
230
245
  required_blocks)
231
246
  fout "File saved: #{@options[:saved_filespec]}"
@@ -233,13 +248,16 @@ module MarkdownExec
233
248
  break if [1, 2].include? sel
234
249
  end
235
250
  end
236
- (opts[:ir_approve] = allow)
237
251
 
238
- selected = mdoc.get_block_by_name opts[:block_name]
252
+ opts[:ir_approve] = allow
239
253
 
254
+ # Get the selected code block by name.
255
+ selected = mdoc.get_block_by_name(opts[:block_name])
256
+
257
+ # If approved, write the code to a file, execute it, and provide output.
240
258
  if opts[:ir_approve]
241
- write_command_file opts, required_blocks
242
- command_execute opts, required_blocks.flatten.join("\n")
259
+ write_command_file(opts, required_blocks)
260
+ command_execute(opts, required_blocks.flatten.join("\n"))
243
261
  save_execution_output
244
262
  output_execution_summary
245
263
  output_execution_result
@@ -473,7 +491,8 @@ module MarkdownExec
473
491
  fcb.title = fcb.body.join(' ').gsub(/ +/, ' ')[0..64]
474
492
  end
475
493
 
476
- if block_given? && selected_messages.include?(:blocks) &&
494
+ if block_given? &&
495
+ selected_messages.include?(:blocks) &&
477
496
  Filter.fcb_select?(opts, fcb)
478
497
  yield :blocks, fcb
479
498
  end
@@ -496,8 +515,10 @@ module MarkdownExec
496
515
  fcb.body = []
497
516
 
498
517
  rest = fcb_title_groups.fetch(:rest, '')
499
- fcb.reqs = rest.scan(/\+[^\s]+/).map { |req| req[1..-1] }
500
-
518
+ fcb.reqs, fcb.wraps =
519
+ split_array(rest.scan(/\+[^\s]+/).map { |req| req[1..-1] }) do |name|
520
+ !name.match(Regexp.new(opts[:block_name_wrapper_match]))
521
+ end
501
522
  fcb.call = rest.match(Regexp.new(opts[:block_calls_scan]))&.to_a&.first
502
523
  fcb.stdin = if (tn = rest.match(/<(?<type>\$)?(?<name>[A-Za-z_-]\S+)/))
503
524
  tn.named_captures.sym_keys
@@ -518,6 +539,27 @@ module MarkdownExec
518
539
  end
519
540
  end
520
541
 
542
+ def split_array(arr)
543
+ true_list = []
544
+ false_list = []
545
+
546
+ arr.each do |element|
547
+ if yield(element)
548
+ true_list << element
549
+ else
550
+ false_list << element
551
+ end
552
+ end
553
+
554
+ [true_list, false_list]
555
+ end
556
+
557
+ # # Example usage:
558
+ # array = [1, 2, 3, 4, 5]
559
+ # result = split_array(array) { |num| num.even? }
560
+ # puts "True List: #{result[0]}" # Output: True List: [2, 4]
561
+ # puts "False List: #{result[1]}" # Output: False List: [1, 3, 5]
562
+
521
563
  # return body, title if option.struct
522
564
  # return body if not struct
523
565
  #
@@ -984,8 +1026,16 @@ module MarkdownExec
984
1026
  File.write(@options[:logged_stdout_filespec], ol.join)
985
1027
  end
986
1028
 
1029
+ # Select and execute a code block from a Markdown document.
1030
+ #
1031
+ # This method allows the user to interactively select a code block from a
1032
+ # Markdown document, obtain approval, and execute the chosen block of code.
1033
+ #
1034
+ # @param call_options [Hash] Initial options for the method.
1035
+ # @param options_block [Block] Block of options to be merged with call_options.
1036
+ # @return [Nil] Returns nil if no code block is selected or an error occurs.
987
1037
  def select_approve_and_execute_block(call_options, &options_block)
988
- opts = optsmerge call_options, options_block
1038
+ opts = optsmerge(call_options, options_block)
989
1039
  blocks_in_file = list_blocks_in_file(opts.merge(struct: true))
990
1040
  mdoc = MDoc.new(blocks_in_file) do |nopts|
991
1041
  opts.merge!(nopts)
@@ -995,7 +1045,7 @@ module MarkdownExec
995
1045
  repeat_menu = true && !opts[:block_name].present?
996
1046
  loop do
997
1047
  unless opts[:block_name].present?
998
- pt = (opts[:prompt_select_block]).to_s
1048
+ pt = opts[:prompt_select_block].to_s
999
1049
 
1000
1050
  bm = blocks_menu.map do |fcb|
1001
1051
  # next if fcb.fetch(:disabled, false)
@@ -1017,8 +1067,7 @@ module MarkdownExec
1017
1067
  end.compact
1018
1068
  return nil if bm.count.zero?
1019
1069
 
1020
- sel = prompt_with_quit pt, bm,
1021
- per_page: opts[:select_page_height]
1070
+ sel = prompt_with_quit(pt, bm, per_page: opts[:select_page_height])
1022
1071
  return nil if sel.nil?
1023
1072
 
1024
1073
  ## store selected option
@@ -1028,7 +1077,7 @@ module MarkdownExec
1028
1077
  end.fetch(0, nil)
1029
1078
  opts[:block_name] = @options[:block_name] = label_block.fetch(:name, '')
1030
1079
  end
1031
- approve_and_execute_block opts, mdoc
1080
+ approve_and_execute_block(opts, mdoc)
1032
1081
  break unless repeat_menu
1033
1082
 
1034
1083
  opts[:block_name] = ''
data/lib/mdoc.rb CHANGED
@@ -16,15 +16,47 @@ module MarkdownExec
16
16
  class MDoc
17
17
  attr_reader :table
18
18
 
19
- # convert block name to fcb_parse
19
+ # Initializes an instance of MDoc with the given table of markdown sections.
20
+ #
21
+ # @param table [Array<Hash>] An array of hashes representing markdown sections.
20
22
  #
21
23
  def initialize(table)
22
24
  @table = table
23
25
  end
24
26
 
27
+ # Retrieves code blocks that are required by a specified code block.
28
+ #
29
+ # @param name [String] The name of the code block to start the retrieval from.
30
+ # @return [Array<Hash>] An array of code blocks required by the specified code block.
31
+ #
32
+ def collect_recursively_required_blocks(name)
33
+ name_block = get_block_by_name(name)
34
+ raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
35
+
36
+ all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
37
+
38
+ # in order of appearance in document
39
+ # insert function blocks
40
+ @table.select { |fcb| all.include? fcb.fetch(:name, '') }
41
+ .map do |fcb|
42
+ if (call = fcb[:call])
43
+ [get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
44
+ .merge({ cann: call })]
45
+ else
46
+ []
47
+ end + [fcb]
48
+ end.flatten(1)
49
+ end
50
+
51
+ # Collects recursively required code blocks and returns them as an array of strings.
52
+ #
53
+ # @param name [String] The name of the code block to start the collection from.
54
+ # @return [Array<String>] An array of strings containing the collected code blocks.
55
+ #
25
56
  def collect_recursively_required_code(name)
26
- get_required_blocks(name)
27
- .map do |fcb|
57
+ collect_wrapped_blocks(
58
+ collect_recursively_required_blocks(name)
59
+ ).map do |fcb|
28
60
  body = fcb[:body].join("\n")
29
61
 
30
62
  if fcb[:cann]
@@ -58,43 +90,37 @@ module MarkdownExec
58
90
  end.flatten(1)
59
91
  end
60
92
 
61
- def get_block_by_name(name, default = {})
62
- @table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
63
- end
64
-
65
- def get_required_blocks(name)
66
- name_block = get_block_by_name(name)
67
- raise "Named code block `#{name}` not found." if name_block.nil? || name_block.keys.empty?
68
-
69
- all = [name_block.fetch(:name, '')] + recursively_required(name_block[:reqs])
70
-
71
- # in order of appearance in document
72
- # insert function blocks
73
- @table.select { |fcb| all.include? fcb.fetch(:name, '') }
74
- .map do |fcb|
75
- if (call = fcb[:call])
76
- [get_block_by_name("[#{call.match(/^%\((\S+) |\)/)[1]}]")
77
- .merge({ cann: call })]
78
- else
79
- []
80
- end + [fcb]
81
- end.flatten(1)
82
- end
83
-
84
- # :reek:UtilityFunction
85
- def hide_menu_block_per_options(opts, block)
86
- (opts[:hide_blocks_by_name] &&
87
- block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
88
- (block[:name]&.present? || block[:label]&.present?)
89
- )
93
+ # Retrieves code blocks that are wrapped
94
+ # wraps are applied from left to right
95
+ # e.g. w1 w2 => w1-before w2-before w1 w2 w2-after w1-after
96
+ #
97
+ # @return [Array<Hash>] An array of code blocks required by the specified code blocks.
98
+ #
99
+ def collect_wrapped_blocks(blocks)
100
+ blocks.map do |block|
101
+ (block[:wraps] || []).map do |wrap|
102
+ wrap_before = wrap.sub('}', '-before}') ### hardcoded wrap name
103
+ @table.select { |fcb| [wrap_before, wrap].include? fcb[:name] }
104
+ end.flatten(1) +
105
+ [block] +
106
+ (block[:wraps] || []).reverse.map do |wrap|
107
+ wrap_after = wrap.sub('}', '-after}') ### hardcoded wrap name
108
+ @table.select { |fcb| fcb[:name] == wrap_after }
109
+ end.flatten(1)
110
+ end.flatten(1).compact
90
111
  end
91
112
 
113
+ # Retrieves code blocks based on the provided options.
114
+ #
115
+ # @param opts [Hash] The options used for filtering code blocks.
116
+ # @return [Array<Hash>] An array of code blocks that match the options.
117
+ #
92
118
  def fcbs_per_options(opts = {})
93
119
  options = opts.merge(block_name_hidden_match: nil)
94
120
  selrows = @table.select do |fcb_title_groups|
95
121
  Filter.fcb_select? options, fcb_title_groups
96
122
  end
97
-
123
+ # pp selrows; binding.pry
98
124
  ### hide rows correctly
99
125
 
100
126
  if opts[:hide_blocks_by_name]
@@ -107,6 +133,40 @@ module MarkdownExec
107
133
  end
108
134
  end
109
135
 
136
+ # Retrieves a code block by its name.
137
+ #
138
+ # @param name [String] The name of the code block to retrieve.
139
+ # @param default [Hash] The default value to return if the code block is not found.
140
+ # @return [Hash] The code block as a hash or the default value if not found.
141
+ #
142
+ def get_block_by_name(name, default = {})
143
+ @table.select { |fcb| fcb.fetch(:name, '') == name }.fetch(0, default)
144
+ end
145
+
146
+ # Checks if a code block should be hidden based on the given options.
147
+ #
148
+ # @param opts [Hash] The options used for hiding code blocks.
149
+ # @param block [Hash] The code block to check for hiding.
150
+ # @return [Boolean] True if the code block should be hidden; false otherwise.
151
+ #
152
+ # :reek:UtilityFunction
153
+ def hide_menu_block_per_options(opts, block)
154
+ (opts[:hide_blocks_by_name] &&
155
+ ((opts[:block_name_hidden_match]&.present? &&
156
+ block[:name]&.match(Regexp.new(opts[:block_name_hidden_match]))) ||
157
+ (opts[:block_name_include_match]&.present? &&
158
+ block[:name]&.match(Regexp.new(opts[:block_name_include_match]))) ||
159
+ (opts[:block_name_wrapper_match]&.present? &&
160
+ block[:name]&.match(Regexp.new(opts[:block_name_wrapper_match])))) &&
161
+ (block[:name]&.present? || block[:label]&.present?)
162
+ )
163
+ end
164
+
165
+ # Recursively fetches required code blocks for a given list of requirements.
166
+ #
167
+ # @param reqs [Array<String>] An array of requirements to start the recursion from.
168
+ # @return [Array<String>] An array of recursively required code block names.
169
+ #
110
170
  def recursively_required(reqs)
111
171
  return [] unless reqs
112
172
 
@@ -128,14 +188,11 @@ module MarkdownExec
128
188
  end
129
189
 
130
190
  if $PROGRAM_NAME == __FILE__
131
- # require 'bundler/setup'
132
- # Bundler.require(:default)
191
+ require 'bundler/setup'
192
+ Bundler.require(:default)
133
193
 
134
194
  require 'minitest/autorun'
135
195
 
136
- require_relative 'tap'
137
- include Tap
138
-
139
196
  module MarkdownExec
140
197
  class TestMDoc < Minitest::Test
141
198
  def setup
@@ -161,12 +218,14 @@ if $PROGRAM_NAME == __FILE__
161
218
  assert_equal({}, result_missing)
162
219
  end
163
220
 
164
- def test_get_required_blocks
165
- result = @doc.get_required_blocks('block3')
221
+ def test_collect_recursively_required_blocks
222
+ result = @doc.collect_recursively_required_blocks('block3')
166
223
  expected_result = [@table[0], @table[1], @table[2]]
167
224
  assert_equal expected_result, result
168
225
 
169
- assert_raises(RuntimeError) { @doc.get_required_blocks('missing_block') }
226
+ assert_raises(RuntimeError) do
227
+ @doc.collect_recursively_required_blocks('missing_block')
228
+ end
170
229
  end
171
230
 
172
231
  def test_hide_menu_block_per_options
@@ -190,5 +249,58 @@ if $PROGRAM_NAME == __FILE__
190
249
  assert_equal [], result_no_reqs
191
250
  end
192
251
  end
252
+
253
+ class TestMDoc2 < Minitest::Test
254
+ # Mocking the @table object for testing
255
+ def setup
256
+ @table = [
257
+ { name: '{wrap1}' },
258
+ { name: '{wrap2-before}' },
259
+ { name: '{wrap2}' },
260
+ { name: '{wrap2-after}' },
261
+ { name: '{wrap3-before}' },
262
+ { name: '{wrap3}' },
263
+ { name: '{wrap3-after}' }
264
+ ]
265
+ @mdoc = MDoc.new(@table)
266
+ end
267
+
268
+ def test_collect_wrapped_blocks
269
+ # Test case 1: blocks with wraps
270
+ assert_equal(%w[{wrap1} a],
271
+ @mdoc.collect_wrapped_blocks(
272
+ [{ name: 'a',
273
+ wraps: ['{wrap1}'] }]
274
+ ).map do |block|
275
+ block[:name]
276
+ end)
277
+
278
+ assert_equal(%w[{wrap2-before} {wrap2} b {wrap2-after}],
279
+ @mdoc.collect_wrapped_blocks(
280
+ [{ name: 'b',
281
+ wraps: ['{wrap2}'] }]
282
+ ).map do |block|
283
+ block[:name]
284
+ end)
285
+
286
+ assert_equal(%w[{wrap2-before} {wrap2} {wrap3-before} {wrap3} c {wrap3-after} {wrap2-after}],
287
+ @mdoc.collect_wrapped_blocks(
288
+ [{ name: 'c',
289
+ wraps: %w[{wrap2} {wrap3}] }]
290
+ ).map { |block| block[:name] })
291
+
292
+ # Test case 2: blocks with no wraps
293
+ blocks = @mdoc.collect_wrapped_blocks([])
294
+ assert_empty blocks
295
+
296
+ # Test case 3: blocks with missing wraps
297
+ assert_equal(
298
+ %w[block4],
299
+ @mdoc.collect_wrapped_blocks([{ name: 'block4', wraps: ['wrap4'] }]).map do |block|
300
+ block[:name]
301
+ end
302
+ )
303
+ end
304
+ end
193
305
  end
194
306
  end
data/lib/menu.src.yml CHANGED
@@ -266,15 +266,25 @@
266
266
  :env_var: MDE_SELECT_BY_SHELL_REGEX
267
267
  :opt_name: select_by_shell_regex
268
268
  :procname: val_as_str
269
- - :default: "^[\\(\\[].*[\\)\\]]$"
269
+ - :default: "^-.+-$"
270
270
  :description: Pattern for blocks to hide from user-selection
271
271
  :env_var: MDE_BLOCK_NAME_HIDDEN_MATCH
272
272
  :opt_name: block_name_hidden_match
273
273
  :procname: val_as_str
274
+ - :default: "^[\\(\\[].*[\\)\\]]$"
275
+ :description: Pattern for blocks to hide from user-selection
276
+ :env_var: MDE_BLOCK_NAME_INCLUDE_MATCH
277
+ :opt_name: block_name_include_match
278
+ :procname: val_as_str
274
279
  - :default: ":(?<title>\\S+)( |$)"
275
280
  :env_var: MDE_BLOCK_NAME_MATCH
276
281
  :opt_name: block_name_match
277
282
  :procname: val_as_str
283
+ - :default: "^{.+}$"
284
+ :description: Pattern for block names to use as wrappers
285
+ :env_var: MDE_BLOCK_NAME_WRAPPER_MATCH
286
+ :opt_name: block_name_wrapper_match
287
+ :procname: val_as_str
278
288
  - :default: "%\\([^\\)]+\\)"
279
289
  :env_var: MDE_BLOCK_CALLS_SCAN
280
290
  :opt_name: block_calls_scan
@@ -357,7 +367,7 @@
357
367
  :env_var: MDE_MENU_INITIAL_DIVIDER
358
368
  :opt_name: menu_initial_divider
359
369
  :procname: val_as_str
360
- - :default: "[%{status}] %{name}"
370
+ - :default: "%{name} [%{status}]"
361
371
  :description: format for menu tasks and demarcations
362
372
  :env_var: MDE_MENU_TASK_FORMAT
363
373
  :opt_name: menu_task_format
data/lib/menu.yml CHANGED
@@ -1,4 +1,4 @@
1
- # MDE - Markdown Executor (1.3.7)
1
+ # MDE - Markdown Executor (1.3.8)
2
2
  ---
3
3
  - :arg_name: NAME
4
4
  :compreply: false
@@ -267,15 +267,25 @@
267
267
  :env_var: MDE_SELECT_BY_SHELL_REGEX
268
268
  :opt_name: select_by_shell_regex
269
269
  :procname: val_as_str
270
- - :default: "^[\\(\\[].*[\\)\\]]$"
270
+ - :default: "^-.+-$"
271
271
  :description: Pattern for blocks to hide from user-selection
272
272
  :env_var: MDE_BLOCK_NAME_HIDDEN_MATCH
273
273
  :opt_name: block_name_hidden_match
274
274
  :procname: val_as_str
275
+ - :default: "^[\\(\\[].*[\\)\\]]$"
276
+ :description: Pattern for blocks to hide from user-selection
277
+ :env_var: MDE_BLOCK_NAME_INCLUDE_MATCH
278
+ :opt_name: block_name_include_match
279
+ :procname: val_as_str
275
280
  - :default: ":(?<title>\\S+)( |$)"
276
281
  :env_var: MDE_BLOCK_NAME_MATCH
277
282
  :opt_name: block_name_match
278
283
  :procname: val_as_str
284
+ - :default: "^{.+}$"
285
+ :description: Pattern for block names to use as wrappers
286
+ :env_var: MDE_BLOCK_NAME_WRAPPER_MATCH
287
+ :opt_name: block_name_wrapper_match
288
+ :procname: val_as_str
279
289
  - :default: "%\\([^\\)]+\\)"
280
290
  :env_var: MDE_BLOCK_CALLS_SCAN
281
291
  :opt_name: block_calls_scan
@@ -358,7 +368,7 @@
358
368
  :env_var: MDE_MENU_INITIAL_DIVIDER
359
369
  :opt_name: menu_initial_divider
360
370
  :procname: val_as_str
361
- - :default: "[%{status}] %{name}"
371
+ - :default: "%{name} [%{status}]"
362
372
  :description: format for menu tasks and demarcations
363
373
  :env_var: MDE_MENU_TASK_FORMAT
364
374
  :opt_name: menu_task_format
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: 1.3.7
4
+ version: 1.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fareed Stevenson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-16 00:00:00.000000000 Z
11
+ date: 2023-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard