markdown_exec 3.5.1 → 3.6.0

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 (127) 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 +19 -1
  7. data/Gemfile.lock +1 -1
  8. data/README.md +1 -1
  9. data/Rakefile +2 -0
  10. data/ai-principles.md +516 -0
  11. data/architecture-decisions.md +190 -0
  12. data/bats/block-hide.bats +1 -1
  13. data/bats/block-type-bash.bats +5 -5
  14. data/bats/block-type-link.bats +1 -1
  15. data/bats/block-type-opts.bats +3 -3
  16. data/bats/block-type-port.bats +2 -2
  17. data/bats/block-type-shell-context-eval.bats +48 -0
  18. data/bats/block-type-shell-require-ux.bats +2 -2
  19. data/bats/block-type-ux-allowed.bats +4 -4
  20. data/bats/block-type-ux-auto.bats +1 -1
  21. data/bats/block-type-ux-chained.bats +1 -1
  22. data/bats/block-type-ux-default.bats +1 -1
  23. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  24. data/bats/block-type-ux-echo-hash.bats +2 -2
  25. data/bats/block-type-ux-echo.bats +4 -4
  26. data/bats/block-type-ux-exec-hash-transform.bats +1 -1
  27. data/bats/block-type-ux-exec-hash.bats +2 -2
  28. data/bats/block-type-ux-exec.bats +1 -1
  29. data/bats/block-type-ux-force.bats +2 -2
  30. data/bats/block-type-ux-formats.bats +1 -1
  31. data/bats/block-type-ux-hidden.bats +1 -1
  32. data/bats/block-type-ux-invalid.bats +2 -2
  33. data/bats/block-type-ux-readonly.bats +1 -1
  34. data/bats/block-type-ux-require-chained.bats +2 -2
  35. data/bats/block-type-ux-require-context.bats +2 -2
  36. data/bats/block-type-ux-require.bats +3 -3
  37. data/bats/block-type-ux-required-variables.bats +1 -1
  38. data/bats/block-type-ux-row-format.bats +1 -1
  39. data/bats/block-type-ux-sources.bats +4 -4
  40. data/bats/block-type-ux-transform.bats +1 -1
  41. data/bats/block-type-vars.bats +3 -3
  42. data/bats/border.bats +1 -1
  43. data/bats/cli.bats +11 -11
  44. data/bats/command-substitution-options.bats +2 -2
  45. data/bats/command-substitution.bats +1 -1
  46. data/bats/document-shell.bats +3 -3
  47. data/bats/history.bats +5 -5
  48. data/bats/import-conflict.bats +1 -1
  49. data/bats/import-directive-line-continuation.bats +1 -1
  50. data/bats/import-directive-parameter-symbols.bats +1 -1
  51. data/bats/import-duplicates.bats +6 -6
  52. data/bats/import-parameter-symbols.bats +1 -1
  53. data/bats/import-with-text-substitution.bats +1 -1
  54. data/bats/import.bats +4 -4
  55. data/bats/indented-block-type-vars.bats +1 -1
  56. data/bats/indented-multi-line-output.bats +1 -1
  57. data/bats/line-decor-dynamic.bats +1 -1
  58. data/bats/line-wrapping.bats +1 -1
  59. data/bats/load-vars-state-demo.bats +8 -8
  60. data/bats/markup.bats +4 -4
  61. data/bats/mde.bats +4 -4
  62. data/bats/option-expansion.bats +1 -1
  63. data/bats/options-collapse.bats +4 -4
  64. data/bats/options.bats +47 -17
  65. data/bats/plain.bats +1 -1
  66. data/bats/publish.bats +2 -2
  67. data/bats/table-column-truncate.bats +1 -1
  68. data/bats/table.bats +2 -2
  69. data/bats/variable-expansion-multiline.bats +1 -1
  70. data/bats/variable-expansion.bats +6 -6
  71. data/bin/tab_completion.sh +3 -3
  72. data/conversation-template.md +611 -0
  73. data/docs/block-execution-modes.md +177 -0
  74. data/docs/block-filtering.md +252 -0
  75. data/docs/block-naming-patterns.md +210 -0
  76. data/docs/block-scanning-patterns.md +248 -0
  77. data/docs/cli-reference.md +370 -0
  78. data/docs/dev/bats-document-configuration.md +1 -1
  79. data/docs/dev/block-hide.md +1 -1
  80. data/docs/dev/block-type-shell-context-eval.md +52 -0
  81. data/docs/dev/block-type-shell-require-ux.md +6 -2
  82. data/docs/dev/block-type-ux-echo.md +3 -3
  83. data/docs/dev/block-type-ux-force.md +1 -1
  84. data/docs/dev/block-type-ux-require-chained.md +1 -1
  85. data/docs/dev/block-type-ux-require.md +2 -2
  86. data/docs/dev/block-type-ux-transform.md +5 -4
  87. data/docs/dev/import-parameter-symbols.md +1 -1
  88. data/docs/dev/linked-file.md +1 -1
  89. data/docs/dev/load-vars-state-demo.md +1 -1
  90. data/docs/dev/print_bytes.md +3 -0
  91. data/docs/dev/requiring-blocks.md +1 -1
  92. data/docs/dev/shebang.md +6 -0
  93. data/docs/dev/specs.md +2 -2
  94. data/docs/docker-testing.md +5 -0
  95. data/docs/execution-control.md +384 -0
  96. data/docs/getting-started.md +209 -0
  97. data/docs/import-options.md +391 -0
  98. data/docs/shell-script-evaluation.md +78 -0
  99. data/docs/tab-completion.md +7 -0
  100. data/docs/ux-blocks.md +376 -0
  101. data/examples/link-blocks-vars.md +2 -2
  102. data/examples/linked.md +1 -1
  103. data/examples/linked1.md +1 -1
  104. data/examples/opts-blocks.md +2 -2
  105. data/examples/port-blocks.md +1 -1
  106. data/examples/variable-expansion.md +1 -1
  107. data/implementation-decisions.md +212 -0
  108. data/lib/cached_nested_file_reader.rb +145 -5
  109. data/lib/command_result.rb +27 -6
  110. data/lib/executed_shell_command.rb +512 -0
  111. data/lib/filter.rb +7 -7
  112. data/lib/hash_delegator.rb +699 -605
  113. data/lib/input_sequencer.rb +4 -3
  114. data/lib/link_history.rb +95 -36
  115. data/lib/markdown_exec/version.rb +1 -1
  116. data/lib/mdoc.rb +138 -51
  117. data/lib/menu.src.yml +115 -88
  118. data/lib/menu.yml +154 -88
  119. data/lib/transformed_shell_command.rb +449 -0
  120. data/lib/wl.rb +15 -0
  121. data/lib/ww.rb +17 -6
  122. data/requirements.md +111 -0
  123. data/semantic-tokens.md +132 -0
  124. data/tasks.md +69 -0
  125. metadata +29 -4
  126. data/docs/ux-blocks-examples.md +0 -120
  127. data/docs/ux-blocks-init-act.md +0 -100
@@ -35,7 +35,7 @@ class InputSequencer
35
35
  document_filename: next_state.document_filename || current.document_filename,
36
36
  inherited_block_names: next_state.inherited_block_names,
37
37
  inherited_dependencies: next_state.inherited_dependencies,
38
- inherited_lines: next_state.inherited_lines,
38
+ context_code: next_state.context_code,
39
39
  prior_block_was_link: next_state.prior_block_was_link.nil? ? current.prior_block_was_link : next_state.prior_block_was_link
40
40
  )
41
41
  # rubocop:disable Style/RescueStandardError
@@ -49,13 +49,13 @@ class InputSequencer
49
49
 
50
50
  def self.next_link_state(
51
51
  block_name: nil, display_menu: nil, document_filename: nil,
52
- inherited_lines: nil, keep_code: false, prior_block_was_link: false
52
+ context_code: nil, keep_code: false, prior_block_was_link: false
53
53
  )
54
54
  MarkdownExec::LinkState.new(
55
55
  block_name: block_name,
56
56
  display_menu: display_menu,
57
57
  document_filename: document_filename,
58
- inherited_lines: inherited_lines,
58
+ context_code: context_code,
59
59
  keep_code: keep_code,
60
60
  prior_block_was_link: prior_block_was_link
61
61
  )
@@ -96,6 +96,7 @@ class InputSequencer
96
96
  if now_menu.display_menu
97
97
  # !!b
98
98
  break if run_yield(:end_of_cli, &block) == :exit
99
+
99
100
  # !!b
100
101
 
101
102
  exit_when_bq_empty = false
data/lib/link_history.rb CHANGED
@@ -14,24 +14,32 @@ 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
- # @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)
20
+ # @param context_code [Array<String>, nil] the context code (shell code that provides
21
+ # the necessary code and data for the evaluation of individual blocks).
22
+ # @param context_code [Array<String>, nil] Deprecated: Use context_code instead.
23
+ def initialize(
24
+ block_name: nil, display_menu: nil, document_filename: nil,
25
+ inherited_block_names: [], inherited_dependencies: nil,
26
+ context_code: nil, keep_code: false, prior_block_was_link: nil
27
+ )
23
28
  @block_name = block_name
24
29
  @display_menu = display_menu
25
30
  @document_filename = document_filename
26
31
  @inherited_block_names = inherited_block_names
27
32
  @inherited_dependencies = inherited_dependencies
28
- @inherited_lines = inherited_lines
33
+ # Support both new and deprecated parameter names
34
+ @context_code = context_code
29
35
  @keep_code = keep_code
30
36
  @prior_block_was_link = prior_block_was_link
37
+ wwt :link_state, self, caller.deref
31
38
  end
32
39
 
33
40
  # Creates an empty LinkState instance.
34
- # @return [LinkState] an instance with all attributes set to their default values.
41
+ # @return [LinkState] an instance with all attributes
42
+ # set to their default values.
35
43
  def self.empty
36
44
  new
37
45
  end
@@ -46,50 +54,50 @@ module MarkdownExec
46
54
  other.document_filename == document_filename &&
47
55
  other.inherited_block_names == inherited_block_names &&
48
56
  other.inherited_dependencies == inherited_dependencies &&
49
- other.inherited_lines == inherited_lines &&
57
+ other.context_code == context_code &&
50
58
  other.keep_code == keep_code &&
51
59
  other.prior_block_was_link == prior_block_was_link
52
60
  end
53
61
 
54
- def inherited_lines
55
- @inherited_lines.tap do |ret|
56
- pp ['LinkState.inherited_lines() ->', ret] if $pd
62
+ def context_code
63
+ @context_code.tap do |ret|
64
+ pp ['LinkState.context_code() ->', ret] if $pd
57
65
  end
58
66
  end
59
67
 
60
- def inherited_lines=(value)
61
- @inherited_lines = value.tap do |ret|
62
- pp ['LinkState.inherited_lines=() ->', ret, caller.deref(3).last] if $pd
68
+ def context_code=(value)
69
+ @context_code = value.tap do |ret|
70
+ pp ['LinkState.context_code=() ->', ret, caller.deref(3).last] if $pd
63
71
  end
64
72
  end
65
73
 
66
- def inherited_lines_append(value)
67
- @inherited_lines = ((@inherited_lines || []) + value).tap do |ret|
68
- pp ['LinkState.inherited_lines_append() ->', ret] if $pd
74
+ def context_code_append(value)
75
+ @context_code = ((@context_code || []) + value).tap do |ret|
76
+ pp ['LinkState.context_code_append() ->', ret] if $pd
69
77
  end
70
78
  end
71
79
 
72
- def inherited_lines_block
73
- (@inherited_lines || []).join("\n").tap do |ret|
74
- pp ['LinkState.inherited_lines_block() ->', ret] if $pd
80
+ def context_code_block
81
+ (@context_code || []).join("\n").tap do |ret|
82
+ pp ['LinkState.context_code_block() ->', ret] if $pd
75
83
  end
76
84
  end
77
85
 
78
- def inherited_lines_count
79
- (@inherited_lines&.count || 0).tap do |ret|
80
- pp ['LinkState.inherited_lines_count() ->', ret] if $pd
86
+ def context_code_count
87
+ (@context_code&.count || 0).tap do |ret|
88
+ pp ['LinkState.context_code_count() ->', ret] if $pd
81
89
  end
82
90
  end
83
91
 
84
- def inherited_lines_map(&block)
85
- @inherited_lines.map(&block).tap do |ret|
86
- pp ['LinkState.inherited_lines_map() ->', ret] if $pd
92
+ def context_code_map(&block)
93
+ @context_code.map(&block).tap do |ret|
94
+ pp ['LinkState.context_code_map() ->', ret] if $pd
87
95
  end
88
96
  end
89
97
 
90
- def inherited_lines_present?
91
- @inherited_lines.present?.tap do |ret|
92
- pp ['LinkState.inherited_lines_present?() ->', ret] if $pd
98
+ def context_code_present?
99
+ @context_code.present?.tap do |ret|
100
+ pp ['LinkState.context_code_present?() ->', ret] if $pd
93
101
  end
94
102
  end
95
103
  end
@@ -99,12 +107,14 @@ module MarkdownExec
99
107
  @history = []
100
108
  end
101
109
 
102
- # Peeks at the most recent LinkState, returns an empty LinkState if stack is empty.
110
+ # Peeks at the most recent LinkState, returns an empty LinkState
111
+ # if stack is empty.
103
112
  def peek
104
113
  @history.last || LinkState.empty
105
114
  end
106
115
 
107
- # Pops the most recent LinkState off the stack, returns an empty LinkState if stack is empty.
116
+ # Pops the most recent LinkState off the stack, returns an empty LinkState
117
+ # if stack is empty.
108
118
  def pop
109
119
  @history.pop || LinkState.empty
110
120
  end
@@ -131,10 +141,14 @@ if $PROGRAM_NAME == __FILE__
131
141
  class TestLinkHistory < Minitest::Test
132
142
  def setup
133
143
  @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'])
144
+ @link_state1 = LinkState.new(
145
+ block_name: 'block1', document_filename: 'document1.txt',
146
+ context_code: ['code1.rb']
147
+ )
148
+ @link_state2 = LinkState.new(
149
+ block_name: 'block2', document_filename: 'document2.txt',
150
+ context_code: ['code2.rb']
151
+ )
138
152
  end
139
153
 
140
154
  def test_push
@@ -156,6 +170,51 @@ if $PROGRAM_NAME == __FILE__
156
170
  def test_pop_empty
157
171
  assert_equal LinkState.empty, @link_history.pop
158
172
  end
173
+
174
+ def test_context_code_accessor
175
+ state = LinkState.new(context_code: %w[line1 line2])
176
+ assert_equal %w[line1 line2], state.context_code
177
+ end
178
+
179
+ def test_context_code_block
180
+ state = LinkState.new(context_code: %w[line1 line2])
181
+ assert_equal "line1\nline2", state.context_code_block
182
+ end
183
+
184
+ def test_context_code_append
185
+ state = LinkState.new(context_code: ['line1'])
186
+ state.context_code_append(['line2'])
187
+ assert_equal %w[line1 line2], state.context_code
188
+ end
189
+
190
+ def test_context_code_count
191
+ state = LinkState.new(context_code: %w[line1 line2 line3])
192
+ assert_equal 3, state.context_code_count
193
+ end
194
+
195
+ def test_context_code_present?
196
+ state_with_code = LinkState.new(context_code: ['line1'])
197
+ state_without_code = LinkState.new(context_code: nil)
198
+ assert state_with_code.context_code_present?
199
+ refute state_without_code.context_code_present?
200
+ end
201
+
202
+ def test_context_code_equality
203
+ state1 = LinkState.new(context_code: ['line1'])
204
+ state2 = LinkState.new(context_code: ['line1'])
205
+ state3 = LinkState.new(context_code: ['line2'])
206
+ assert_equal state1, state2
207
+ refute_equal state1, state3
208
+ end
209
+
210
+ def test_deprecated_context_code_methods
211
+ state = LinkState.new(context_code: %w[line1 line2])
212
+ # Test backward compatibility
213
+ assert_equal %w[line1 line2], state.context_code
214
+ assert_equal "line1\nline2", state.context_code_block
215
+ assert_equal 2, state.context_code_count
216
+ assert state.context_code_present?
217
+ end
159
218
  end
160
219
  end
161
220
  end
@@ -169,7 +228,7 @@ To generate the Ruby classes `LinkState` and `LinkHistory` with their current fe
169
228
  Create Ruby classes `LinkState` and `LinkHistory` with the following specifications:
170
229
 
171
230
  1. **Class `LinkState`**:
172
- - Attributes: `block_name`, `document_filename`, `inherited_lines`.
231
+ - Attributes: `block_name`, `document_filename`, `context_code`.
173
232
  - Initialize with optional parameters for each attribute, defaulting to `nil`.
174
233
  - Include a class method `empty` that creates an instance with all attributes set to `nil`.
175
234
  - Implement a custom `==` method for comparing instances based on attribute values.
@@ -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.6.0'
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?)
@@ -67,25 +68,43 @@ module MarkdownExec
67
68
  @table = table
68
69
  end
69
70
 
70
- def collect_block_code_cann(fcb)
71
+ # Generates a yq (YAML query) shell command from a call annotation.
72
+ # The call annotation specifies I/O redirection patterns:
73
+ # - <$VAR_NAME: read YAML from environment variable, apply expression from block body
74
+ # - <FILE_NAME: read YAML from file, apply expression from block body
75
+ # - >$VAR_NAME: write result to environment variable
76
+ # - >FILE_NAME: write result to file
77
+ #
78
+ # @param fcb [FCB] The fenced code block containing the call annotation and body
79
+ # @return [String] A shell command string that executes the yq query
80
+ def generate_yq_command_from_call_annotation(fcb)
71
81
  body = fcb.body.join("\n")
72
82
  xcall = fcb[:cann][1..-2]
83
+ # match <$VAR_NAME, <FILE_NAME, >$VAR_NAME, >FILE_NAME
73
84
  mstdin = xcall.match(/<(?<type>\$)?(?<name>[\-.\w]+)/)
74
85
  mstdout = xcall.match(/>(?<type>\$)?(?<name>[\-.\w]+)/)
75
86
 
76
87
  yqcmd = if mstdin[:type]
88
+ # when <$VAR_NAME
89
+ # the value of the variable is the expression to apply to the YAML from the block body.
77
90
  "echo \"$#{mstdin[:name]}\" | yq '#{body}'"
78
91
  else
92
+ # when <FILE_NAME
93
+ # the block body is the expression to apply to the YAML file.
79
94
  "yq e '#{body}' '#{mstdin[:name]}'"
80
95
  end
81
96
  if mstdout[:type]
97
+ # when >$VAR_NAME
98
+ # set the value of the variable to the result of the expression
82
99
  "export #{mstdout[:name]}=$(#{yqcmd})"
83
100
  else
101
+ # when >FILE_NAME
102
+ # write the result of the expression to the file
84
103
  "#{yqcmd} > '#{mstdout[:name]}'"
85
104
  end
86
105
  end
87
106
 
88
- # Collects and formats the shell command output to redirect script block code to a file or a variable.
107
+ # Generates a shell command to copy the block's body to either a shell variable or a file.
89
108
  #
90
109
  # @param [Hash] fcb A hash containing information about the script block's stdout and body.
91
110
  # @option fcb [Hash] :stdout A hash specifying the stdout details.
@@ -93,10 +112,10 @@ module MarkdownExec
93
112
  # @option stdout [String] :name The name of the variable or file to which the body will be output.
94
113
  # @option fcb [Array<String>] :body An array of strings representing the lines of the script block's body.
95
114
  #
96
- # @return [String] A string containing the formatted shell command to output the script block's body.
115
+ # @return [String] A string containing the shell command to redirect the script block's body.
97
116
  # If stdout[:type] is true, the command will export the body to a shell variable.
98
117
  # If stdout[:type] is false, the command will write the body to a file.
99
- def collect_block_code_stdout(fcb)
118
+ def code_for_fcb_body_into_var_or_file(fcb)
100
119
  stdout = fcb[:stdout]
101
120
  body = fcb.body.join("\n")
102
121
  if stdout[:type]
@@ -164,49 +183,42 @@ module MarkdownExec
164
183
  # Collects recursively required code blocks and returns them as an array of strings.
165
184
  #
166
185
  # @param name [String] The name of the code block to start the collection from.
167
- # @return [Array<String>] An array of strings containing the collected code blocks.
186
+ # @return [OpenStruct]
168
187
  #
169
188
  def collect_recursively_required_code(
170
189
  anyname:, block_source:,
171
- label_body: true, label_format_above: nil, label_format_below: nil
190
+ label_body: true, label_format_above: nil, label_format_below: nil,
191
+ context_code: []
172
192
  )
193
+ raise 'unexpected label_body' unless label_body
194
+
173
195
  block_search = collect_block_dependencies(anyname: anyname)
174
196
  if block_search[:blocks]
175
197
  blocks = collect_wrapped_blocks(block_search[:blocks])
176
198
  # !!t blocks.count
177
199
 
200
+ context_transient_codes = blocks.map do |fcb|
201
+ process_block_to_code(
202
+ fcb, block_source,
203
+ label_body, label_format_above, label_format_below,
204
+ context_code: context_code
205
+ )
206
+ end.tap { ww anyname, _1 }
207
+
178
208
  block_search.merge(
179
- { 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 }
209
+ {
210
+ block_names: blocks.map(&:pub_name),
211
+ context_code:
212
+ context_transient_codes.map(&:context_code).compact.flatten(1).compact,
213
+ transient_code:
214
+ context_transient_codes.map(&:transient_code).compact.flatten(1).compact
215
+ }
206
216
  )
207
217
  else
208
- block_search.merge({ block_names: [], code: [] })
209
- end
218
+ block_search.merge(
219
+ { block_names: [], context_code: [], transient_code: [] }
220
+ )
221
+ end.tap { wwr _1 }
210
222
  rescue StandardError
211
223
  error_handler('collect_recursively_required_code')
212
224
  end
@@ -237,6 +249,8 @@ module MarkdownExec
237
249
  table_not_split.select { |fcb| fcb.code_name_included?(wrap_after) }
238
250
  end.flatten(1)
239
251
  end.flatten(1).compact
252
+ rescue StandardError
253
+ wwe $!
240
254
  end
241
255
 
242
256
  def error_handler(name = '', opts = {})
@@ -252,7 +266,7 @@ module MarkdownExec
252
266
  # @return [Array<Hash>] An array of code blocks that match the options.
253
267
  #
254
268
  def fcbs_per_options(opts = {})
255
- options = opts.merge(block_name_hidden_match: nil)
269
+ options = opts.merge(block_name_hide_custom_match: nil)
256
270
  selrows = @table.select do |fcb_title_groups|
257
271
  Filter.fcb_select? options, fcb_title_groups
258
272
  end
@@ -296,15 +310,15 @@ module MarkdownExec
296
310
  # remove
297
311
  # . empty chrome between code; edges are same as blanks
298
312
  #
299
- select_elements_with_neighbor_conditions(selrows) do |prev_element,
300
- current,
301
- next_element|
313
+ select_elements_with_neighbor_conditions(selrows) do |prev_element, current, next_element|
302
314
  !(current[:chrome] && !current.oname.present?) ||
303
315
  !(!prev_element.nil? &&
304
316
  prev_element.shell.present? &&
305
317
  !next_element.nil? &&
306
318
  next_element.shell.present?)
307
319
  end
320
+ rescue StandardError
321
+ wwe $!
308
322
  end
309
323
 
310
324
  # Generates shell code lines to set environment variables named in the body of the given object.
@@ -328,7 +342,7 @@ module MarkdownExec
328
342
  end
329
343
  end
330
344
 
331
- # Generates a formatted code block with labels above and below the main content.
345
+ # Wraps a code block body with formatted labels above and below the main content.
332
346
  # The labels and content are based on the provided format strings and the body of the given object.
333
347
  #
334
348
  # @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 +357,8 @@ module MarkdownExec
343
357
  # and `label_format_below` is "End of %{block_name}", the method will return:
344
358
  # ["Start of Example_Block", "line1", "line2", "End of Example_Block"]
345
359
  #
346
- def generate_label_body_code(fcb, block_source, label_format_above,
347
- label_format_below)
360
+ def wrap_block_body_with_labels(fcb, block_source, label_format_above,
361
+ label_format_below)
348
362
  block_name_for_bash_comment = fcb.pub_name.gsub(/\s+/, '_')
349
363
 
350
364
  label_above = if label_format_above.present?
@@ -403,16 +417,89 @@ module MarkdownExec
403
417
  false
404
418
  else
405
419
  opts[:hide_blocks_by_name] &&
406
- ((opts[:block_name_hidden_match]&.present? &&
420
+ ((opts[:block_name_hide_custom_match]&.present? &&
421
+ block.s2title&.match(Regexp.new(opts[:block_name_hide_custom_match]))) ||
422
+ (opts[:block_name_hidden_match]&.present? &&
407
423
  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
424
  (opts[:block_name_wrapper_match]&.present? &&
411
425
  block.s2title&.match(Regexp.new(opts[:block_name_wrapper_match])))) &&
412
426
  (block.s2title&.present? || block[:label]&.present?)
413
427
  end
414
428
  end
415
429
 
430
+ # Processes a single code block and returns its code representation.
431
+ #
432
+ # @param fcb [Hash] The code block to process.
433
+ # @param block_source [Hash] Additional information for label generation.
434
+ # @param label_body [Boolean] Whether to generate labels around the body.
435
+ # @param label_format_above [String, nil] Format string for label above content.
436
+ # @param label_format_below [String, nil] Format string for label below content.
437
+ # @return [String, Array, nil] The code representation of the block, or nil if the block should be skipped.
438
+ #
439
+ def process_block_to_code(fcb, block_source, label_body, label_format_above,
440
+ label_format_below, context_code: [])
441
+ raise 'unexpected label_body' unless label_body
442
+
443
+ new_context_code = []
444
+
445
+ new_transient_code = if fcb[:cann]
446
+ generate_yq_command_from_call_annotation(fcb)
447
+ elsif fcb[:stdout]
448
+ # copy the block's body to either a shell variable or a file
449
+ code_for_fcb_body_into_var_or_file(fcb)
450
+ elsif [BlockType::OPTS].include? fcb.type
451
+ fcb.body # entire body is returned to requesing block
452
+
453
+ elsif [BlockType::LINK,
454
+ BlockType::LOAD,
455
+ BlockType::UX,
456
+ BlockType::VARS].include? fcb.type
457
+ nil # Vars for all types are collected later
458
+ elsif fcb[:chrome] # for Link blocks like History
459
+ nil
460
+ elsif fcb.type == BlockType::PORT
461
+ generate_env_variable_shell_commands(fcb)
462
+ elsif label_body
463
+ raise 'unexpected type' if fcb.type != BlockType::SHELL
464
+
465
+ # BlockType:: SHELL block
466
+ if fcb.start_line =~ /@eval/
467
+ command_result = HashDelegator.execute_bash_script_lines(
468
+ transient_code: context_code + fcb.body,
469
+ export: OpenStruct.new(exportable: false, name: ''),
470
+ export_name: '',
471
+ force: true,
472
+ shell: fcb.shell || 'bash'
473
+ )
474
+ command_result.stdout.split("\n").tap do
475
+ if fcb.start_line =~ /@context/
476
+ new_context_code = _1
477
+ end
478
+ end
479
+ elsif fcb.start_line =~ /@context/
480
+ # raw body
481
+ ### expansions?
482
+ new_context_code = fcb.body
483
+ [] # collect later or return as code to inherit
484
+
485
+ else
486
+ wrap_block_body_with_labels(
487
+ fcb, block_source,
488
+ label_format_above, label_format_below
489
+ )
490
+ end
491
+ else # raw body
492
+ fcb.body
493
+ end
494
+
495
+ OpenStruct.new(
496
+ context_code: new_context_code,
497
+ transient_code: new_transient_code
498
+ ).tap { wwr _1 }
499
+ rescue StandardError
500
+ wwe $!
501
+ end
502
+
416
503
  # Recursively fetches required code blocks for a given list of requirements.
417
504
  #
418
505
  # @param reqs [Array<String>] An array of requirements to start the recursion from.
@@ -620,7 +707,7 @@ if $PROGRAM_NAME == __FILE__
620
707
 
621
708
  def test_hide_menu_block_on_name
622
709
  opts = { hide_blocks_by_name: true,
623
- block_name_hidden_match: 'block1' }
710
+ block_name_hide_custom_match: 'block1' }
624
711
  block = FCB.new(s2title: 'block1')
625
712
  result = @doc.hide_menu_block_on_name(opts, block)
626
713
  assert result # this should be true based on the given logic
@@ -628,7 +715,7 @@ if $PROGRAM_NAME == __FILE__
628
715
 
629
716
  def test_fcbs_per_options
630
717
  opts = { hide_blocks_by_name: true,
631
- block_name_hidden_match: 'block1' }
718
+ block_name_hide_custom_match: 'block1' }
632
719
  result = @doc.fcbs_per_options(opts)
633
720
  assert_equal [@table[1], @table[2]], result
634
721
  end if false ### broken test