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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.ai-agent-instructions +54 -0
  3. data/.cursorrules +198 -0
  4. data/.rubocop.wide.yml +5 -0
  5. data/.rubocop.yml +7 -2
  6. data/CHANGELOG.md +12 -1
  7. data/Gemfile.lock +1 -1
  8. data/Rakefile +2 -0
  9. data/ai-principles.md +516 -0
  10. data/architecture-decisions.md +190 -0
  11. data/bats/block-hide.bats +1 -1
  12. data/bats/block-type-bash.bats +5 -5
  13. data/bats/block-type-link.bats +1 -1
  14. data/bats/block-type-opts.bats +3 -3
  15. data/bats/block-type-port.bats +2 -2
  16. data/bats/block-type-shell-require-ux.bats +2 -2
  17. data/bats/block-type-ux-allowed.bats +4 -4
  18. data/bats/block-type-ux-auto.bats +1 -1
  19. data/bats/block-type-ux-chained.bats +1 -1
  20. data/bats/block-type-ux-default.bats +1 -1
  21. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  22. data/bats/block-type-ux-echo-hash.bats +2 -2
  23. data/bats/block-type-ux-echo.bats +3 -3
  24. data/bats/block-type-ux-exec-hash-transform.bats +1 -1
  25. data/bats/block-type-ux-exec-hash.bats +2 -2
  26. data/bats/block-type-ux-exec.bats +1 -1
  27. data/bats/block-type-ux-force.bats +1 -1
  28. data/bats/block-type-ux-formats.bats +1 -1
  29. data/bats/block-type-ux-hidden.bats +1 -1
  30. data/bats/block-type-ux-invalid.bats +1 -1
  31. data/bats/block-type-ux-readonly.bats +1 -1
  32. data/bats/block-type-ux-require-chained.bats +2 -2
  33. data/bats/block-type-ux-require-context.bats +2 -2
  34. data/bats/block-type-ux-require.bats +2 -2
  35. data/bats/block-type-ux-required-variables.bats +1 -1
  36. data/bats/block-type-ux-row-format.bats +1 -1
  37. data/bats/block-type-ux-sources.bats +4 -4
  38. data/bats/block-type-ux-transform.bats +1 -1
  39. data/bats/block-type-vars.bats +3 -3
  40. data/bats/border.bats +1 -1
  41. data/bats/cli.bats +11 -11
  42. data/bats/command-substitution-options.bats +2 -2
  43. data/bats/command-substitution.bats +1 -1
  44. data/bats/document-shell.bats +1 -1
  45. data/bats/history.bats +5 -5
  46. data/bats/import-conflict.bats +1 -1
  47. data/bats/import-directive-line-continuation.bats +1 -1
  48. data/bats/import-directive-parameter-symbols.bats +1 -1
  49. data/bats/import-duplicates.bats +6 -6
  50. data/bats/import-parameter-symbols.bats +1 -1
  51. data/bats/import-with-text-substitution.bats +1 -1
  52. data/bats/import.bats +3 -3
  53. data/bats/indented-block-type-vars.bats +1 -1
  54. data/bats/indented-multi-line-output.bats +1 -1
  55. data/bats/line-decor-dynamic.bats +1 -1
  56. data/bats/line-wrapping.bats +1 -1
  57. data/bats/load-vars-state-demo.bats +4 -4
  58. data/bats/markup.bats +4 -4
  59. data/bats/mde.bats +4 -4
  60. data/bats/option-expansion.bats +1 -1
  61. data/bats/options-collapse.bats +4 -4
  62. data/bats/options.bats +47 -17
  63. data/bats/plain.bats +1 -1
  64. data/bats/publish.bats +2 -2
  65. data/bats/table-column-truncate.bats +1 -1
  66. data/bats/table.bats +2 -2
  67. data/bats/variable-expansion-multiline.bats +1 -1
  68. data/bats/variable-expansion.bats +6 -6
  69. data/conversation-template.md +611 -0
  70. data/docs/block-execution-modes.md +177 -0
  71. data/docs/block-filtering.md +252 -0
  72. data/docs/block-naming-patterns.md +210 -0
  73. data/docs/block-scanning-patterns.md +248 -0
  74. data/docs/cli-reference.md +370 -0
  75. data/docs/dev/block-hide.md +1 -1
  76. data/docs/dev/block-type-ux-transform.md +5 -4
  77. data/docs/dev/print_bytes.md +3 -0
  78. data/docs/dev/shebang.md +6 -0
  79. data/docs/docker-testing.md +5 -0
  80. data/docs/execution-control.md +384 -0
  81. data/docs/getting-started.md +209 -0
  82. data/docs/import-options.md +391 -0
  83. data/docs/tab-completion.md +7 -0
  84. data/docs/ux-blocks.md +376 -0
  85. data/examples/linked1.md +8 -1
  86. data/implementation-decisions.md +212 -0
  87. data/lib/cached_nested_file_reader.rb +138 -1
  88. data/lib/command_result.rb +27 -6
  89. data/lib/executed_shell_command.rb +512 -0
  90. data/lib/filter.rb +7 -7
  91. data/lib/hash_delegator.rb +403 -350
  92. data/lib/link_history.rb +22 -11
  93. data/lib/markdown_exec/version.rb +1 -1
  94. data/lib/mdoc.rb +103 -44
  95. data/lib/menu.src.yml +110 -83
  96. data/lib/menu.yml +149 -83
  97. data/lib/transformed_shell_command.rb +449 -0
  98. data/lib/wl.rb +15 -0
  99. data/lib/ww.rb +16 -5
  100. data/requirements.md +111 -0
  101. data/semantic-tokens.md +132 -0
  102. data/tasks.md +69 -0
  103. metadata +26 -4
  104. data/docs/ux-blocks-examples.md +0 -120
  105. 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 the inherited blocks.
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(block_name: nil, display_menu: nil, document_filename: nil,
21
- inherited_block_names: [], inherited_dependencies: nil, inherited_lines: nil,
22
- keep_code: false, prior_block_was_link: nil)
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 set to their default values.
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 if stack is empty.
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 if stack is empty.
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(block_name: 'block1', document_filename: 'document1.txt',
135
- inherited_lines: ['code1.rb'])
136
- @link_state2 = LinkState.new(block_name: 'block2', document_filename: 'document2.txt',
137
- inherited_lines: ['code2.rb'])
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
@@ -8,5 +8,5 @@ module MarkdownExec
8
8
  BIN_NAME = 'mde'
9
9
  GEM_NAME = 'markdown_exec'
10
10
  TAP_DEBUG = 'MDE_DEBUG'
11
- VERSION = '3.5.1'
11
+ VERSION = '3.5.2'
12
12
  end
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(block_name_hidden_match: nil)
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[:block_name_hidden_match]&.present? &&
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
- # Collects and formats the shell command output to redirect script block code to a file or a variable.
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 formatted shell command to output the script block's body.
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 collect_block_code_stdout(fcb)
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: blocks.map do |fcb|
181
- if fcb[:cann]
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(block_name_hidden_match: nil)
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
- # Generates a formatted code block with labels above and below the main content.
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 generate_label_body_code(fcb, block_source, label_format_above,
347
- label_format_below)
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[:block_name_hidden_match]&.present? &&
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
- block_name_hidden_match: 'block1' }
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
- block_name_hidden_match: 'block1' }
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