markdown_exec 3.5.1 → 3.5.2
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/.ai-agent-instructions +54 -0
- data/.cursorrules +198 -0
- data/.rubocop.wide.yml +5 -0
- data/.rubocop.yml +7 -2
- data/CHANGELOG.md +12 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +2 -0
- data/ai-principles.md +516 -0
- data/architecture-decisions.md +190 -0
- data/bats/block-hide.bats +1 -1
- data/bats/block-type-bash.bats +5 -5
- data/bats/block-type-link.bats +1 -1
- data/bats/block-type-opts.bats +3 -3
- data/bats/block-type-port.bats +2 -2
- data/bats/block-type-shell-require-ux.bats +2 -2
- data/bats/block-type-ux-allowed.bats +4 -4
- data/bats/block-type-ux-auto.bats +1 -1
- data/bats/block-type-ux-chained.bats +1 -1
- data/bats/block-type-ux-default.bats +1 -1
- data/bats/block-type-ux-echo-hash-transform.bats +1 -1
- data/bats/block-type-ux-echo-hash.bats +2 -2
- data/bats/block-type-ux-echo.bats +3 -3
- data/bats/block-type-ux-exec-hash-transform.bats +1 -1
- data/bats/block-type-ux-exec-hash.bats +2 -2
- data/bats/block-type-ux-exec.bats +1 -1
- data/bats/block-type-ux-force.bats +1 -1
- data/bats/block-type-ux-formats.bats +1 -1
- data/bats/block-type-ux-hidden.bats +1 -1
- data/bats/block-type-ux-invalid.bats +1 -1
- data/bats/block-type-ux-readonly.bats +1 -1
- data/bats/block-type-ux-require-chained.bats +2 -2
- data/bats/block-type-ux-require-context.bats +2 -2
- data/bats/block-type-ux-require.bats +2 -2
- data/bats/block-type-ux-required-variables.bats +1 -1
- data/bats/block-type-ux-row-format.bats +1 -1
- data/bats/block-type-ux-sources.bats +4 -4
- data/bats/block-type-ux-transform.bats +1 -1
- data/bats/block-type-vars.bats +3 -3
- data/bats/border.bats +1 -1
- data/bats/cli.bats +11 -11
- data/bats/command-substitution-options.bats +2 -2
- data/bats/command-substitution.bats +1 -1
- data/bats/document-shell.bats +1 -1
- data/bats/history.bats +5 -5
- data/bats/import-conflict.bats +1 -1
- data/bats/import-directive-line-continuation.bats +1 -1
- data/bats/import-directive-parameter-symbols.bats +1 -1
- data/bats/import-duplicates.bats +6 -6
- data/bats/import-parameter-symbols.bats +1 -1
- data/bats/import-with-text-substitution.bats +1 -1
- data/bats/import.bats +3 -3
- data/bats/indented-block-type-vars.bats +1 -1
- data/bats/indented-multi-line-output.bats +1 -1
- data/bats/line-decor-dynamic.bats +1 -1
- data/bats/line-wrapping.bats +1 -1
- data/bats/load-vars-state-demo.bats +4 -4
- data/bats/markup.bats +4 -4
- data/bats/mde.bats +4 -4
- data/bats/option-expansion.bats +1 -1
- data/bats/options-collapse.bats +4 -4
- data/bats/options.bats +47 -17
- data/bats/plain.bats +1 -1
- data/bats/publish.bats +2 -2
- data/bats/table-column-truncate.bats +1 -1
- data/bats/table.bats +2 -2
- data/bats/variable-expansion-multiline.bats +1 -1
- data/bats/variable-expansion.bats +6 -6
- data/conversation-template.md +611 -0
- data/docs/block-execution-modes.md +177 -0
- data/docs/block-filtering.md +252 -0
- data/docs/block-naming-patterns.md +210 -0
- data/docs/block-scanning-patterns.md +248 -0
- data/docs/cli-reference.md +370 -0
- data/docs/dev/block-hide.md +1 -1
- data/docs/dev/block-type-ux-transform.md +5 -4
- data/docs/dev/print_bytes.md +3 -0
- data/docs/dev/shebang.md +6 -0
- data/docs/docker-testing.md +5 -0
- data/docs/execution-control.md +384 -0
- data/docs/getting-started.md +209 -0
- data/docs/import-options.md +391 -0
- data/docs/tab-completion.md +7 -0
- data/docs/ux-blocks.md +376 -0
- data/examples/linked1.md +8 -1
- data/implementation-decisions.md +212 -0
- data/lib/cached_nested_file_reader.rb +138 -1
- data/lib/command_result.rb +27 -6
- data/lib/executed_shell_command.rb +512 -0
- data/lib/filter.rb +7 -7
- data/lib/hash_delegator.rb +403 -350
- data/lib/link_history.rb +22 -11
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/mdoc.rb +103 -44
- data/lib/menu.src.yml +110 -83
- data/lib/menu.yml +149 -83
- data/lib/transformed_shell_command.rb +449 -0
- data/lib/wl.rb +15 -0
- data/lib/ww.rb +16 -5
- data/requirements.md +111 -0
- data/semantic-tokens.md +132 -0
- data/tasks.md +69 -0
- metadata +26 -4
- data/docs/ux-blocks-examples.md +0 -120
- data/docs/ux-blocks-init-act.md +0 -100
data/lib/link_history.rb
CHANGED
|
@@ -14,12 +14,15 @@ module MarkdownExec
|
|
|
14
14
|
# Initialize the LinkState with keyword arguments for each attribute.
|
|
15
15
|
# @param block_name [String, nil] the name of the block.
|
|
16
16
|
# @param document_filename [String, nil] the filename of the document.
|
|
17
|
-
# @param inherited_block_names [Array<String>, nil] the names of
|
|
17
|
+
# @param inherited_block_names [Array<String>, nil] the names of
|
|
18
|
+
# the inherited blocks.
|
|
18
19
|
# @param inherited_dependencies [?, nil] the dependecy hierarcy.
|
|
19
20
|
# @param inherited_lines [Array<String>, nil] the inherited lines of code.
|
|
20
|
-
def initialize(
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
def initialize(
|
|
22
|
+
block_name: nil, display_menu: nil, document_filename: nil,
|
|
23
|
+
inherited_block_names: [], inherited_dependencies: nil,
|
|
24
|
+
inherited_lines: nil, keep_code: false, prior_block_was_link: nil
|
|
25
|
+
)
|
|
23
26
|
@block_name = block_name
|
|
24
27
|
@display_menu = display_menu
|
|
25
28
|
@document_filename = document_filename
|
|
@@ -28,10 +31,12 @@ module MarkdownExec
|
|
|
28
31
|
@inherited_lines = inherited_lines
|
|
29
32
|
@keep_code = keep_code
|
|
30
33
|
@prior_block_was_link = prior_block_was_link
|
|
34
|
+
wwt :link_state, self, caller.deref
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
# Creates an empty LinkState instance.
|
|
34
|
-
# @return [LinkState] an instance with all attributes
|
|
38
|
+
# @return [LinkState] an instance with all attributes
|
|
39
|
+
# set to their default values.
|
|
35
40
|
def self.empty
|
|
36
41
|
new
|
|
37
42
|
end
|
|
@@ -99,12 +104,14 @@ module MarkdownExec
|
|
|
99
104
|
@history = []
|
|
100
105
|
end
|
|
101
106
|
|
|
102
|
-
# Peeks at the most recent LinkState, returns an empty LinkState
|
|
107
|
+
# Peeks at the most recent LinkState, returns an empty LinkState
|
|
108
|
+
# if stack is empty.
|
|
103
109
|
def peek
|
|
104
110
|
@history.last || LinkState.empty
|
|
105
111
|
end
|
|
106
112
|
|
|
107
|
-
# Pops the most recent LinkState off the stack, returns an empty LinkState
|
|
113
|
+
# Pops the most recent LinkState off the stack, returns an empty LinkState
|
|
114
|
+
# if stack is empty.
|
|
108
115
|
def pop
|
|
109
116
|
@history.pop || LinkState.empty
|
|
110
117
|
end
|
|
@@ -131,10 +138,14 @@ if $PROGRAM_NAME == __FILE__
|
|
|
131
138
|
class TestLinkHistory < Minitest::Test
|
|
132
139
|
def setup
|
|
133
140
|
@link_history = LinkHistory.new
|
|
134
|
-
@link_state1 = LinkState.new(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
@link_state1 = LinkState.new(
|
|
142
|
+
block_name: 'block1', document_filename: 'document1.txt',
|
|
143
|
+
inherited_lines: ['code1.rb']
|
|
144
|
+
)
|
|
145
|
+
@link_state2 = LinkState.new(
|
|
146
|
+
block_name: 'block2', document_filename: 'document2.txt',
|
|
147
|
+
inherited_lines: ['code2.rb']
|
|
148
|
+
)
|
|
138
149
|
end
|
|
139
150
|
|
|
140
151
|
def test_push
|
data/lib/mdoc.rb
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
require_relative 'block_types'
|
|
7
7
|
require_relative 'collapser'
|
|
8
|
+
require_relative 'command_result'
|
|
8
9
|
require_relative 'filter'
|
|
9
10
|
|
|
10
11
|
$pd = false unless defined?($pd)
|
|
@@ -12,7 +13,7 @@ $pd = false unless defined?($pd)
|
|
|
12
13
|
module MarkdownExec
|
|
13
14
|
class MenuFilter
|
|
14
15
|
def initialize(opts)
|
|
15
|
-
@opts = opts.merge(
|
|
16
|
+
@opts = opts.merge(block_name_hide_custom_match: nil)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def fcb_in_menu?(fcb)
|
|
@@ -38,10 +39,10 @@ module MarkdownExec
|
|
|
38
39
|
false
|
|
39
40
|
else
|
|
40
41
|
@opts[:hide_blocks_by_name] &&
|
|
41
|
-
((@opts[:
|
|
42
|
+
((@opts[:block_name_hide_custom_match]&.present? &&
|
|
43
|
+
block.s2title&.match(Regexp.new(@opts[:block_name_hide_custom_match]))) ||
|
|
44
|
+
(@opts[:block_name_hidden_match]&.present? &&
|
|
42
45
|
block.s2title&.match(Regexp.new(@opts[:block_name_hidden_match]))) ||
|
|
43
|
-
(@opts[:block_name_include_match]&.present? &&
|
|
44
|
-
block.s2title&.match(Regexp.new(@opts[:block_name_include_match]))) ||
|
|
45
46
|
(@opts[:block_name_wrapper_match]&.present? &&
|
|
46
47
|
block.s2title&.match(Regexp.new(@opts[:block_name_wrapper_match])))) &&
|
|
47
48
|
(block.s2title&.present? || block[:label]&.present?)
|
|
@@ -85,7 +86,7 @@ module MarkdownExec
|
|
|
85
86
|
end
|
|
86
87
|
end
|
|
87
88
|
|
|
88
|
-
#
|
|
89
|
+
# Generates a shell command to redirect a block's body to either a shell variable or a file.
|
|
89
90
|
#
|
|
90
91
|
# @param [Hash] fcb A hash containing information about the script block's stdout and body.
|
|
91
92
|
# @option fcb [Hash] :stdout A hash specifying the stdout details.
|
|
@@ -93,10 +94,10 @@ module MarkdownExec
|
|
|
93
94
|
# @option stdout [String] :name The name of the variable or file to which the body will be output.
|
|
94
95
|
# @option fcb [Array<String>] :body An array of strings representing the lines of the script block's body.
|
|
95
96
|
#
|
|
96
|
-
# @return [String] A string containing the
|
|
97
|
+
# @return [String] A string containing the shell command to redirect the script block's body.
|
|
97
98
|
# If stdout[:type] is true, the command will export the body to a shell variable.
|
|
98
99
|
# If stdout[:type] is false, the command will write the body to a file.
|
|
99
|
-
def
|
|
100
|
+
def code_for_fcb_body_into_var_or_file(fcb)
|
|
100
101
|
stdout = fcb[:stdout]
|
|
101
102
|
body = fcb.body.join("\n")
|
|
102
103
|
if stdout[:type]
|
|
@@ -168,44 +169,31 @@ module MarkdownExec
|
|
|
168
169
|
#
|
|
169
170
|
def collect_recursively_required_code(
|
|
170
171
|
anyname:, block_source:,
|
|
171
|
-
label_body: true, label_format_above: nil, label_format_below: nil
|
|
172
|
+
label_body: true, label_format_above: nil, label_format_below: nil,
|
|
173
|
+
inherited_lines: [] ###s
|
|
172
174
|
)
|
|
175
|
+
raise 'unexpected label_body' unless label_body
|
|
176
|
+
|
|
173
177
|
block_search = collect_block_dependencies(anyname: anyname)
|
|
174
178
|
if block_search[:blocks]
|
|
175
179
|
blocks = collect_wrapped_blocks(block_search[:blocks])
|
|
176
180
|
# !!t blocks.count
|
|
177
181
|
|
|
182
|
+
code_inherit = blocks.map do |fcb|
|
|
183
|
+
process_block_to_code(
|
|
184
|
+
fcb, block_source,
|
|
185
|
+
label_body, label_format_above, label_format_below,
|
|
186
|
+
inherited_lines: inherited_lines
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
|
|
178
190
|
block_search.merge(
|
|
179
191
|
{ block_names: blocks.map(&:pub_name),
|
|
180
|
-
code:
|
|
181
|
-
|
|
182
|
-
collect_block_code_cann(fcb)
|
|
183
|
-
elsif fcb[:stdout]
|
|
184
|
-
collect_block_code_stdout(fcb)
|
|
185
|
-
elsif [BlockType::OPTS].include? fcb.type
|
|
186
|
-
fcb.body # entire body is returned to requesing block
|
|
187
|
-
|
|
188
|
-
elsif [BlockType::LINK,
|
|
189
|
-
BlockType::LOAD,
|
|
190
|
-
BlockType::UX,
|
|
191
|
-
BlockType::VARS].include? fcb.type
|
|
192
|
-
nil # Vars for all types are collected later
|
|
193
|
-
elsif fcb[:chrome] # for Link blocks like History
|
|
194
|
-
nil
|
|
195
|
-
elsif fcb.type == BlockType::PORT
|
|
196
|
-
generate_env_variable_shell_commands(fcb)
|
|
197
|
-
elsif label_body
|
|
198
|
-
generate_label_body_code(
|
|
199
|
-
fcb, block_source,
|
|
200
|
-
label_format_above, label_format_below
|
|
201
|
-
)
|
|
202
|
-
else # raw body
|
|
203
|
-
fcb.body
|
|
204
|
-
end
|
|
205
|
-
end.compact.flatten(1).compact }
|
|
192
|
+
code: code_inherit.map(&:first).compact.flatten(1).compact,
|
|
193
|
+
inherit: code_inherit.map(&:last).compact.flatten(1).compact }
|
|
206
194
|
)
|
|
207
195
|
else
|
|
208
|
-
block_search.merge({ block_names: [], code: [] })
|
|
196
|
+
block_search.merge({ block_names: [], code: [], inherit: [] })
|
|
209
197
|
end
|
|
210
198
|
rescue StandardError
|
|
211
199
|
error_handler('collect_recursively_required_code')
|
|
@@ -237,6 +225,8 @@ module MarkdownExec
|
|
|
237
225
|
table_not_split.select { |fcb| fcb.code_name_included?(wrap_after) }
|
|
238
226
|
end.flatten(1)
|
|
239
227
|
end.flatten(1).compact
|
|
228
|
+
rescue StandardError
|
|
229
|
+
wwe $!
|
|
240
230
|
end
|
|
241
231
|
|
|
242
232
|
def error_handler(name = '', opts = {})
|
|
@@ -252,7 +242,7 @@ module MarkdownExec
|
|
|
252
242
|
# @return [Array<Hash>] An array of code blocks that match the options.
|
|
253
243
|
#
|
|
254
244
|
def fcbs_per_options(opts = {})
|
|
255
|
-
options = opts.merge(
|
|
245
|
+
options = opts.merge(block_name_hide_custom_match: nil)
|
|
256
246
|
selrows = @table.select do |fcb_title_groups|
|
|
257
247
|
Filter.fcb_select? options, fcb_title_groups
|
|
258
248
|
end
|
|
@@ -305,6 +295,8 @@ module MarkdownExec
|
|
|
305
295
|
!next_element.nil? &&
|
|
306
296
|
next_element.shell.present?)
|
|
307
297
|
end
|
|
298
|
+
rescue StandardError
|
|
299
|
+
wwe $!
|
|
308
300
|
end
|
|
309
301
|
|
|
310
302
|
# Generates shell code lines to set environment variables named in the body of the given object.
|
|
@@ -328,7 +320,7 @@ module MarkdownExec
|
|
|
328
320
|
end
|
|
329
321
|
end
|
|
330
322
|
|
|
331
|
-
#
|
|
323
|
+
# Wraps a code block body with formatted labels above and below the main content.
|
|
332
324
|
# The labels and content are based on the provided format strings and the body of the given object.
|
|
333
325
|
#
|
|
334
326
|
# @param fcb [Object] An object with a `pub_name` method that returns a string, and a `body` method that returns an array of strings.
|
|
@@ -343,8 +335,8 @@ module MarkdownExec
|
|
|
343
335
|
# and `label_format_below` is "End of %{block_name}", the method will return:
|
|
344
336
|
# ["Start of Example_Block", "line1", "line2", "End of Example_Block"]
|
|
345
337
|
#
|
|
346
|
-
def
|
|
347
|
-
|
|
338
|
+
def wrap_block_body_with_labels(fcb, block_source, label_format_above,
|
|
339
|
+
label_format_below)
|
|
348
340
|
block_name_for_bash_comment = fcb.pub_name.gsub(/\s+/, '_')
|
|
349
341
|
|
|
350
342
|
label_above = if label_format_above.present?
|
|
@@ -403,16 +395,83 @@ module MarkdownExec
|
|
|
403
395
|
false
|
|
404
396
|
else
|
|
405
397
|
opts[:hide_blocks_by_name] &&
|
|
406
|
-
((opts[:
|
|
398
|
+
((opts[:block_name_hide_custom_match]&.present? &&
|
|
399
|
+
block.s2title&.match(Regexp.new(opts[:block_name_hide_custom_match]))) ||
|
|
400
|
+
(opts[:block_name_hidden_match]&.present? &&
|
|
407
401
|
block.s2title&.match(Regexp.new(opts[:block_name_hidden_match]))) ||
|
|
408
|
-
(opts[:block_name_include_match]&.present? &&
|
|
409
|
-
block.s2title&.match(Regexp.new(opts[:block_name_include_match]))) ||
|
|
410
402
|
(opts[:block_name_wrapper_match]&.present? &&
|
|
411
403
|
block.s2title&.match(Regexp.new(opts[:block_name_wrapper_match])))) &&
|
|
412
404
|
(block.s2title&.present? || block[:label]&.present?)
|
|
413
405
|
end
|
|
414
406
|
end
|
|
415
407
|
|
|
408
|
+
# Processes a single code block and returns its code representation.
|
|
409
|
+
#
|
|
410
|
+
# @param fcb [Hash] The code block to process.
|
|
411
|
+
# @param block_source [Hash] Additional information for label generation.
|
|
412
|
+
# @param label_body [Boolean] Whether to generate labels around the body.
|
|
413
|
+
# @param label_format_above [String, nil] Format string for label above content.
|
|
414
|
+
# @param label_format_below [String, nil] Format string for label below content.
|
|
415
|
+
# @return [String, Array, nil] The code representation of the block, or nil if the block should be skipped.
|
|
416
|
+
#
|
|
417
|
+
def process_block_to_code(fcb, block_source, label_body, label_format_above,
|
|
418
|
+
label_format_below, inherited_lines: [])
|
|
419
|
+
raise 'unexpected label_body' unless label_body
|
|
420
|
+
|
|
421
|
+
new_inherited_lines = []
|
|
422
|
+
|
|
423
|
+
new_code_lines = if fcb[:cann]
|
|
424
|
+
collect_block_code_cann(fcb)
|
|
425
|
+
elsif fcb[:stdout]
|
|
426
|
+
code_for_fcb_body_into_var_or_file(fcb)
|
|
427
|
+
elsif [BlockType::OPTS].include? fcb.type
|
|
428
|
+
fcb.body # entire body is returned to requesing block
|
|
429
|
+
|
|
430
|
+
elsif [BlockType::LINK,
|
|
431
|
+
BlockType::LOAD,
|
|
432
|
+
BlockType::UX,
|
|
433
|
+
BlockType::VARS].include? fcb.type
|
|
434
|
+
nil # Vars for all types are collected later
|
|
435
|
+
elsif fcb[:chrome] # for Link blocks like History
|
|
436
|
+
nil
|
|
437
|
+
elsif fcb.type == BlockType::PORT
|
|
438
|
+
generate_env_variable_shell_commands(fcb)
|
|
439
|
+
elsif label_body
|
|
440
|
+
raise 'unexpected type' if fcb.type != BlockType::SHELL
|
|
441
|
+
|
|
442
|
+
# BlockType:: SHELL block
|
|
443
|
+
if fcb.start_line =~ /@eval/ ###s
|
|
444
|
+
command_result = HashDelegator.execute_bash_script_lines(
|
|
445
|
+
code_lines: inherited_lines + fcb.body,
|
|
446
|
+
export: OpenStruct.new(exportable: true, name: ''),
|
|
447
|
+
force: true,
|
|
448
|
+
shell: fcb.shell || 'bash'
|
|
449
|
+
)
|
|
450
|
+
command_result.new_lines.map { _1[:text] }.tap do
|
|
451
|
+
if fcb.start_line =~ /@inherit/ ###s
|
|
452
|
+
new_inherited_lines = _1
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
elsif fcb.start_line =~ /@inherit/ ###s
|
|
457
|
+
# raw body
|
|
458
|
+
### expansions?
|
|
459
|
+
new_inherited_lines = fcb.body
|
|
460
|
+
nil # collect later or return as code to inherit
|
|
461
|
+
|
|
462
|
+
else
|
|
463
|
+
wrap_block_body_with_labels(
|
|
464
|
+
fcb, block_source,
|
|
465
|
+
label_format_above, label_format_below
|
|
466
|
+
)
|
|
467
|
+
end
|
|
468
|
+
else # raw body
|
|
469
|
+
fcb.body
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
[new_code_lines, new_inherited_lines].tap { wwr _1 }
|
|
473
|
+
end
|
|
474
|
+
|
|
416
475
|
# Recursively fetches required code blocks for a given list of requirements.
|
|
417
476
|
#
|
|
418
477
|
# @param reqs [Array<String>] An array of requirements to start the recursion from.
|
|
@@ -620,7 +679,7 @@ if $PROGRAM_NAME == __FILE__
|
|
|
620
679
|
|
|
621
680
|
def test_hide_menu_block_on_name
|
|
622
681
|
opts = { hide_blocks_by_name: true,
|
|
623
|
-
|
|
682
|
+
block_name_hide_custom_match: 'block1' }
|
|
624
683
|
block = FCB.new(s2title: 'block1')
|
|
625
684
|
result = @doc.hide_menu_block_on_name(opts, block)
|
|
626
685
|
assert result # this should be true based on the given logic
|
|
@@ -628,7 +687,7 @@ if $PROGRAM_NAME == __FILE__
|
|
|
628
687
|
|
|
629
688
|
def test_fcbs_per_options
|
|
630
689
|
opts = { hide_blocks_by_name: true,
|
|
631
|
-
|
|
690
|
+
block_name_hide_custom_match: 'block1' }
|
|
632
691
|
result = @doc.fcbs_per_options(opts)
|
|
633
692
|
assert_equal [@table[1], @table[2]], result
|
|
634
693
|
end if false ### broken test
|