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 +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +1 -0
- data/bin/tab_completion.sh +2 -2
- data/lib/filter.rb +140 -68
- data/lib/markdown_block_manager.rb +26 -157
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +69 -20
- data/lib/mdoc.rb +153 -41
- data/lib/menu.src.yml +12 -2
- data/lib/menu.yml +13 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f61a046f9f071edb4e123fd5ad1972de408c4a40c97d923322e416f643f4ffb
|
4
|
+
data.tar.gz: 8d00be273b75ac093809cbc168e585d36da49a26fa0bdde6ee242d06a94c3caf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 054de96356ebb625db55bccb693e498abebf01ab7eeb44b28f6391c3fec3ca87b72970916cbc3ba4dca9088905206b47a33213c9ded81fa67458c47827ba7068
|
7
|
+
data.tar.gz: 642a9e511a013c4485eb187e4be1d22a6516686b8815de1c6b1c230790269612357e9635d0b7b078dbc8f334b1e2d2e06980af71330f3bf8c1a273861bf902f4
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
data/bin/tab_completion.sh
CHANGED
@@ -13,7 +13,7 @@ __filedirs_all()
|
|
13
13
|
}
|
14
14
|
|
15
15
|
_mde_echo_version() {
|
16
|
-
echo "1.3.
|
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-
|
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
|
10
|
-
# based on a set of provided options. The
|
11
|
-
# various properties of an
|
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
|
-
|
81
|
-
|
82
|
-
|
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[:
|
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
|
-
|
118
|
-
|
171
|
+
module MarkdownExec
|
172
|
+
class FilterTest < Minitest::Test
|
173
|
+
def setup
|
174
|
+
@options = {}
|
175
|
+
@fcb = {}
|
176
|
+
end
|
119
177
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
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
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
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
|
242
|
-
command_execute
|
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? &&
|
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
|
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
|
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 =
|
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
|
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
|
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
|
-
#
|
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
|
-
|
27
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
132
|
-
|
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
|
165
|
-
result = @doc.
|
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)
|
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: "
|
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.
|
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: "
|
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.
|
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-
|
11
|
+
date: 2023-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clipboard
|