markdown_exec 1.3.7 → 1.3.8

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