markdown_exec 3.0.8 → 3.1.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/Gemfile.lock +1 -1
  4. data/bats/block-type-ux-required-variables.bats +0 -12
  5. data/bats/block-type-ux-sources.bats +0 -8
  6. data/bats/cli.bats +12 -0
  7. data/bats/import-with-text-substitution.bats +9 -0
  8. data/bats/load-vars-state-demo.bats +26 -0
  9. data/bats/options.bats +1 -1
  10. data/docs/dev/import-substitution-basic.md +5 -0
  11. data/docs/dev/import-substitution-compare.md +12 -0
  12. data/docs/dev/import-substitution-export.md +18 -0
  13. data/docs/dev/import-substitution-long.md +12 -0
  14. data/docs/dev/import-substitution-mixed.md +9 -0
  15. data/docs/dev/import-substitution-plant.md +11 -0
  16. data/docs/dev/import-substitution-quotes.md +7 -0
  17. data/docs/dev/import-substitution-research.md +11 -0
  18. data/docs/dev/import-substitution-simple.md +5 -0
  19. data/docs/dev/import-substitution-special.md +10 -0
  20. data/docs/dev/import-substitution-taxonomy.md +14 -0
  21. data/docs/dev/import-with-text-substitution.md +57 -0
  22. data/docs/dev/linked-file.md +3 -0
  23. data/docs/dev/load-mode-demo.md +163 -0
  24. data/docs/dev/requiring-blocks.md +1 -0
  25. data/examples/import_with_substitution_demo.md +48 -0
  26. data/examples/imports/mixed_template.md +33 -0
  27. data/examples/imports/organism_template.md +42 -0
  28. data/examples/imports/template_mustache.md +22 -0
  29. data/examples/imports/template_vars.md +22 -0
  30. data/examples/raw_replacement_demo.md +42 -0
  31. data/examples/recent_discoveries_demo.md +43 -0
  32. data/examples/template_syntax_demo.md +24 -0
  33. data/lib/cached_nested_file_reader.rb +174 -28
  34. data/lib/command_result.rb +3 -2
  35. data/lib/constants.rb +5 -0
  36. data/lib/evaluate_shell_expressions.rb +0 -1
  37. data/lib/exceptions.rb +10 -2
  38. data/lib/fcb.rb +20 -14
  39. data/lib/hash_delegator.rb +192 -125
  40. data/lib/markdown_exec/version.rb +1 -1
  41. data/lib/mdoc.rb +2 -1
  42. data/lib/menu.src.yml +2 -1
  43. data/lib/menu.yml +2 -1
  44. data/lib/ww.rb +24 -0
  45. metadata +25 -2
@@ -0,0 +1,22 @@
1
+ # Organism Profile (Template Delimiter Mode)
2
+
3
+ **Common Name:** {{COMMON_NAME}}
4
+ **Species:** {{SPECIES}}
5
+ **Genus:** {{GENUS}}
6
+ **Family:** {{FAMILY}}
7
+ **Order:** {{ORDER}}
8
+ **Class:** {{CLASS}}
9
+ **Year Discovered:** {{YEAR_DISCOVERED}}
10
+
11
+ ```bash
12
+ echo "Organism {{COMMON_NAME}} belongs to {{SPECIES}} and was discovered in {{YEAR_DISCOVERED}}"
13
+ export ORGANISM_NAME="{{COMMON_NAME}}"
14
+ export SCIENTIFIC_NAME="{{SPECIES}}"
15
+ export TAXONOMIC_GENUS="{{GENUS}}"
16
+ export TAXONOMIC_FAMILY="{{FAMILY}}"
17
+ export TAXONOMIC_ORDER="{{ORDER}}"
18
+ export TAXONOMIC_CLASS="{{CLASS}}"
19
+ export DISCOVERY_YEAR="{{YEAR_DISCOVERED}}"
20
+ ```
21
+
22
+ Biological organism template using **{{}} delimiters** - requires `use_template_delimiters: true` configuration.
@@ -0,0 +1,22 @@
1
+ # Organism Profile (Raw Replacement)
2
+
3
+ **Common Name:** COMMON_NAME
4
+ **Species:** SPECIES
5
+ **Genus:** GENUS
6
+ **Family:** FAMILY
7
+ **Order:** ORDER
8
+ **Class:** CLASS
9
+ **Year Discovered:** YEAR_DISCOVERED
10
+
11
+ ```bash
12
+ echo "Organism COMMON_NAME belongs to SPECIES and was discovered in YEAR_DISCOVERED"
13
+ export ORGANISM_NAME="COMMON_NAME"
14
+ export SCIENTIFIC_NAME="SPECIES"
15
+ export TAXONOMIC_GENUS="GENUS"
16
+ export TAXONOMIC_FAMILY="FAMILY"
17
+ export TAXONOMIC_ORDER="ORDER"
18
+ export TAXONOMIC_CLASS="CLASS"
19
+ export DISCOVERY_YEAR="YEAR_DISCOVERED"
20
+ ```
21
+
22
+ Biological organism template using **raw replacement** - placeholders are just the taxonomic key names.
@@ -0,0 +1,42 @@
1
+ # Raw Replacement Demo
2
+
3
+ This demonstrates the primary **raw replacement** mode of the enhanced `@import` functionality using biological entity data.
4
+
5
+ ## Tapanuli Orangutan Classification
6
+
7
+ @import imports/organism_template.md COMMON_NAME="Tapanuli Orangutan" SPECIES="Pongo tapanuliensis" GENUS=Pongo FAMILY=Hominidae ORDER=Primates CLASS=Mammalia YEAR_DISCOVERED=2017
8
+
9
+ ## Psychedelic Frogfish Classification
10
+
11
+ @import imports/organism_template.md COMMON_NAME="Psychedelic Frogfish" SPECIES="Histiophryne psychedelica" GENUS=Histiophryne FAMILY=Antennariidae ORDER=Lophiiformes CLASS=Actinopterygii YEAR_DISCOVERED=2009
12
+
13
+ ## Ruby Seadragon Classification
14
+
15
+ @import imports/organism_template.md COMMON_NAME="Ruby Seadragon" SPECIES="Phyllopteryx dewysea" GENUS=Phyllopteryx FAMILY=Syngnathidae ORDER=Syngnathiformes CLASS=Actinopterygii YEAR_DISCOVERED=2015
16
+
17
+ ## How It Works
18
+
19
+ In raw replacement mode (the default), placeholders in template files are just the key names:
20
+
21
+ - `COMMON_NAME` gets replaced with the actual common name
22
+ - `SPECIES` gets replaced with the scientific species name
23
+ - `GENUS` gets replaced with the taxonomic genus
24
+ - `FAMILY` gets replaced with the taxonomic family
25
+ - etc.
26
+
27
+ This is different from template delimiter modes that use `${SPECIES}` or `{{GENUS}}`.
28
+
29
+ ## Benefits of Raw Replacement
30
+
31
+ 1. **Simple syntax** - No need for special delimiters
32
+ 2. **Clean templates** - Templates are more readable
33
+ 3. **Direct replacement** - What you see is what gets replaced
34
+ 4. **Scientific data friendly** - Works well with taxonomic classifications
35
+
36
+ ## Word Boundary Protection
37
+
38
+ Raw replacement uses word boundaries, so:
39
+
40
+ - `SPECIES` in "Species: SPECIES" gets replaced ✓
41
+ - `SPECIES` in "SUBSPECIES=unknown" does NOT get replaced ✓
42
+ - Partial matches are avoided automatically
@@ -0,0 +1,43 @@
1
+ # Recent Biological Discoveries Demo
2
+
3
+ This demonstrates the `@import` functionality with **real biological discoveries** from the last 30 years.
4
+
5
+ ## Millipede Discovery - California
6
+
7
+ @import imports/organism_template.md COMMON_NAME="Illacme tobini Millipede" SPECIES="Illacme tobini" GENUS=Illacme FAMILY=Siphonorhinidae ORDER=Siphonophorida CLASS=Diplopoda YEAR_DISCOVERED=2016
8
+
9
+ ## Fascinating Frogfish - Indonesia
10
+
11
+ @import imports/organism_template.md COMMON_NAME="Psychedelic Frogfish" SPECIES="Histiophryne psychedelica" GENUS=Histiophryne FAMILY=Antennariidae ORDER=Lophiiformes CLASS=Actinopterygii YEAR_DISCOVERED=2009
12
+
13
+ ## Endangered Primate - Sumatra
14
+
15
+ @import imports/organism_template.md COMMON_NAME="Tapanuli Orangutan" SPECIES="Pongo tapanuliensis" GENUS=Pongo FAMILY=Hominidae ORDER=Primates CLASS=Mammalia YEAR_DISCOVERED=2017
16
+
17
+ ## Unique Snake Species
18
+
19
+ @import imports/organism_template.md COMMON_NAME="Cappuccino Snake" SPECIES="Hydrodynastes bicinctus" GENUS=Hydrodynastes FAMILY=Colubridae ORDER=Squamata CLASS=Reptilia YEAR_DISCOVERED=2021
20
+
21
+ ## Plant Discovery - Japan
22
+
23
+ @import imports/organism_template.md COMMON_NAME="Spiny Dandelion" SPECIES="Taraxacum japonicum" GENUS=Taraxacum FAMILY=Asteraceae ORDER=Asterales CLASS=Magnoliopsida YEAR_DISCOVERED=2022
24
+
25
+ ## Deep Sea Discovery
26
+
27
+ @import imports/organism_template.md COMMON_NAME="Yeti Crab" SPECIES="Kiwa hirsuta" GENUS=Kiwa FAMILY=Kiwaidae ORDER=Decapoda CLASS=Malacostraca YEAR_DISCOVERED=2005
28
+
29
+ ## About These Discoveries
30
+
31
+ These organisms represent the incredible biodiversity still being discovered:
32
+
33
+ - **Tapanuli Orangutan** (2017) - The 8th known species of great ape
34
+ - **Psychedelic Frogfish** (2009) - Named for its vibrant, psychedelic coloration
35
+ - **Illacme tobini** (2016) - A millipede species with an extraordinary number of legs
36
+ - **Yeti Crab** (2005) - Found in deep-sea hydrothermal vents
37
+ - **Spiny Dandelion** (2022) - One of the most recent plant discoveries
38
+
39
+ Each entry demonstrates how the template substitution system can handle:
40
+ - Complex scientific names with multiple words
41
+ - Taxonomic hierarchy data
42
+ - Mixed data types (names, years, classifications)
43
+ - Quoted values for multi-word common names
@@ -0,0 +1,24 @@
1
+ # Template Syntax Demo
2
+
3
+ This file shows the primary **raw replacement** mode and optional template delimiter modes using biological entity data.
4
+
5
+ ## Primary Mode: Raw Replacement (Default)
6
+
7
+ @import imports/template_vars.md COMMON_NAME="Mythical Monkey" SPECIES="Cercopithecus lomamiensis" GENUS=Cercopithecus FAMILY=Cercopithecidae ORDER=Primates CLASS=Mammalia YEAR_DISCOVERED=2012
8
+
9
+ ## Template Delimiter Mode (Optional)
10
+
11
+ @import imports/template_mustache.md COMMON_NAME="Ecuadorian Glassfrog" SPECIES="Hyalinobatrachium yaku" GENUS=Hyalinobatrachium FAMILY=Centrolenidae ORDER=Anura CLASS=Amphibia YEAR_DISCOVERED=2017
12
+
13
+ *Note: Template delimiter mode requires `use_template_delimiters: true` configuration*
14
+
15
+ ## Complex Example - Plant Species
16
+
17
+ @import imports/mixed_template.md COMMON_NAME="Spiny Dandelion" SPECIES="Taraxacum japonicum" GENUS=Taraxacum FAMILY=Asteraceae ORDER=Asterales CLASS=Magnoliopsida YEAR_DISCOVERED=2022
18
+
19
+ ## How It Works
20
+
21
+ - **Raw replacement** (default): Placeholders are just key names like `COMMON_NAME`, `SPECIES`, `GENUS`
22
+ - **Template delimiters** (optional): Placeholders use `${SPECIES}` or `{{GENUS}}` syntax
23
+ - Raw replacement is simpler and more direct
24
+ - Template delimiters provide explicit boundaries when needed for taxonomic data
@@ -11,10 +11,12 @@ require_relative 'exceptions'
11
11
  require_relative 'find_files'
12
12
 
13
13
  ##
14
- # The CachedNestedFileReader class provides functionality to read file lines with the ability
15
- # to process '#import filename' directives. When such a directive is encountered in a file,
16
- # the corresponding 'filename' is read and its contents are inserted at that location.
17
- # This class caches read files to avoid re-reading the same file multiple times.
14
+ # The CachedNestedFileReader class provides functionality to read file
15
+ # lines with the ability to process '#import filename' directives. When
16
+ # such a directive is encountered in a file, the corresponding 'filename'
17
+ # is read and its contents are inserted at that location.
18
+ # This class caches read files to avoid re-reading the same file multiple
19
+ # times.
18
20
  # It allows clients to read lines with or without providing a block.
19
21
  #
20
22
  class CachedNestedFileReader
@@ -41,11 +43,14 @@ class CachedNestedFileReader
41
43
 
42
44
  # yield each line to the block
43
45
  # return the processed lines
44
- def readlines(filename, depth = 0, context: '', import_paths: nil,
45
- indention: '', &block)
46
- if @file_cache.key?(filename)
47
- @file_cache[filename].each(&block) if block
48
- return @file_cache[filename]
46
+ def readlines(
47
+ filename, depth = 0, context: '', import_paths: nil,
48
+ indention: '', substitutions: {}, use_template_delimiters: false, &block
49
+ )
50
+ cache_key = build_cache_key(filename, substitutions)
51
+ if @file_cache.key?(cache_key)
52
+ @file_cache[cache_key].each(&block) if block
53
+ return @file_cache[cache_key]
49
54
  end
50
55
  raise Errno::ENOENT, filename unless filename
51
56
 
@@ -54,35 +59,135 @@ class CachedNestedFileReader
54
59
  File.readlines(filename, chomp: true).each.with_index do |line, ind|
55
60
  if Regexp.new(@import_pattern) =~ line
56
61
  name_strip = $~[:name].strip
62
+ params_string = $~[:params] || ''
57
63
  import_indention = indention + $~[:indention]
58
- included_file_path = if name_strip =~ %r{^/}
59
- name_strip
60
- elsif import_paths
61
- find_files(name_strip,
62
- import_paths + [directory_path])&.first
63
- else
64
- File.join(directory_path, name_strip)
65
- end
64
+
65
+ # Parse parameters for text substitution
66
+ import_substitutions = parse_import_params(params_string)
67
+ merged_substitutions = substitutions.merge(import_substitutions)
68
+
69
+ included_file_path =
70
+ if name_strip =~ %r{^/}
71
+ name_strip
72
+ elsif import_paths
73
+ find_files(name_strip,
74
+ import_paths + [directory_path])&.first
75
+ else
76
+ File.join(directory_path, name_strip)
77
+ end
66
78
 
67
79
  raise Errno::ENOENT, name_strip unless included_file_path
68
80
 
69
- processed_lines += readlines(included_file_path, depth + 1,
70
- context: "#{filename}:#{ind + 1}",
71
- import_paths: import_paths,
72
- indention: import_indention,
73
- &block)
81
+ imported_lines = readlines(
82
+ included_file_path, depth + 1,
83
+ context: "#{filename}:#{ind + 1}",
84
+ import_paths: import_paths,
85
+ indention: import_indention,
86
+ substitutions: merged_substitutions,
87
+ use_template_delimiters: use_template_delimiters,
88
+ &block
89
+ )
90
+
91
+ # Apply text substitutions to imported content
92
+ processed_imported_lines = apply_substitutions(
93
+ imported_lines,
94
+ import_substitutions, use_template_delimiters
95
+ )
96
+ processed_lines += processed_imported_lines
74
97
  else
75
- nested_line = NestedLine.new(line, depth, indention, filename, ind)
98
+ # Apply substitutions to the current line
99
+ substituted_line = apply_line_substitutions(line, substitutions,
100
+ use_template_delimiters)
101
+ nested_line = NestedLine.new(substituted_line, depth, indention,
102
+ filename, ind)
76
103
  processed_lines.push(nested_line)
77
104
  block&.call(nested_line)
78
105
  end
79
106
  end
80
107
 
81
- @file_cache[filename] = processed_lines
82
- rescue Errno::ENOENT => err_filename
83
- warn_format('readlines', "#{err_filename} @@ #{context}",
108
+ @file_cache[cache_key] = processed_lines
109
+ rescue Errno::ENOENT => err
110
+ warn_format('readlines', "#{err} @@ #{context}",
84
111
  { abort: true })
85
112
  end
113
+
114
+ private
115
+
116
+ # Parse key=value parameters from the import line
117
+ def parse_import_params(params_string)
118
+ return {} if params_string.nil? || params_string.strip.empty?
119
+
120
+ params = {}
121
+ # Match key=value pairs, handling quoted values
122
+ params_string.scan(
123
+ /([A-Za-z_]\w*)=(?:"([^"]*)"|'([^']*)'|(\S+))/
124
+ ) do |key, quoted_double, quoted_single, unquoted|
125
+ value = quoted_double || quoted_single || unquoted
126
+ params[key] = value
127
+ end
128
+ params
129
+ end
130
+
131
+ # Apply text substitutions to a collection of NestedLine objects
132
+ def apply_substitutions(
133
+ lines, substitutions, use_template_delimiters = false
134
+ )
135
+ return lines if substitutions.empty?
136
+
137
+ lines.map do |nested_line|
138
+ substituted_text = apply_line_substitutions(
139
+ nested_line.text,
140
+ substitutions, use_template_delimiters
141
+ )
142
+ NestedLine.new(substituted_text, nested_line.depth, nested_line.indention,
143
+ nested_line.filename, nested_line.index)
144
+ end
145
+ end
146
+
147
+ # Apply text substitutions to a single line
148
+ def apply_line_substitutions(line, substitutions,
149
+ use_template_delimiters = false)
150
+ return line if substitutions.empty?
151
+
152
+ substituted_line = line.dup
153
+ if use_template_delimiters
154
+ # Replace template-style placeholders: ${KEY} or {{KEY}}
155
+ substitutions.each do |key, value|
156
+ substituted_line = substituted_line.gsub(/\$\{#{Regexp.escape(key)}\}/,
157
+ value)
158
+ substituted_line = substituted_line.gsub(
159
+ /\{\{#{Regexp.escape(key)}\}\}/, value
160
+ )
161
+ end
162
+ else
163
+ # use temporary placeholders to avoid double replacement
164
+ temp_placeholders = {}
165
+
166
+ # Replace each key with a unique temporary placeholder
167
+ substitutions.each_with_index do |(key, value), index|
168
+ temp_placeholder = "__MDE_TEMP_#{index}__"
169
+ # pattern = /\b#{Regexp.escape(key)}\b/
170
+ pattern = Regexp.new(Regexp.escape(key))
171
+ substituted_line = substituted_line.gsub(pattern, temp_placeholder)
172
+ temp_placeholders[temp_placeholder] = value
173
+ end
174
+
175
+ # Replace temporary placeholders with actual values
176
+ temp_placeholders.each do |placeholder, value|
177
+ substituted_line = substituted_line.gsub(placeholder, value)
178
+ end
179
+
180
+ end
181
+ substituted_line
182
+ end
183
+
184
+ # Build cache key that includes substitutions to avoid conflicts
185
+ def build_cache_key(filename, substitutions)
186
+ return filename if substitutions.empty?
187
+
188
+ substitution_hash = substitutions.sort.to_h.hash
189
+ "#{filename}##{substitution_hash}"
190
+ end
86
191
  end
87
192
 
88
193
  return if $PROGRAM_NAME != __FILE__
@@ -97,9 +202,11 @@ class CachedNestedFileReaderTest < Minitest::Test
97
202
  @file2.rewind
98
203
 
99
204
  @file1 = Tempfile.new('test1.txt')
100
- @file1.write("Line1\nLine2\n #insert #{@file2.path}\nLine3")
205
+ @file1.write("Line1\nLine2\n @import #{@file2.path}\nLine3")
101
206
  @file1.rewind
102
- @reader = CachedNestedFileReader.new(import_pattern: /^(?<indention> *)#insert (?'name'.+)$/)
207
+ @reader = CachedNestedFileReader.new(
208
+ import_pattern: /^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/
209
+ )
103
210
  end
104
211
 
105
212
  def teardown
@@ -121,6 +228,45 @@ class CachedNestedFileReaderTest < Minitest::Test
121
228
  result
122
229
  end
123
230
 
231
+ def test_readlines_with_imports_and_substitutions
232
+ file_with_substitution = Tempfile.new('test_substitution.txt')
233
+ file_with_substitution.write("Server: HOST\nPort: PORT")
234
+ file_with_substitution.rewind
235
+
236
+ file_importing = Tempfile.new('test_importing.txt')
237
+ file_importing.write("Config:\n @import #{file_with_substitution.path} HOST=localhost PORT=8080\nEnd")
238
+ file_importing.rewind
239
+
240
+ result = @reader.readlines(file_importing.path).map(&:to_s)
241
+ assert_equal ['Config:', ' Server: localhost', ' Port: 8080', 'End'],
242
+ result
243
+
244
+ file_with_substitution.close
245
+ file_with_substitution.unlink
246
+ file_importing.close
247
+ file_importing.unlink
248
+ end
249
+
250
+ def test_readlines_with_template_delimiters
251
+ file_with_template = Tempfile.new('test_template.txt')
252
+ file_with_template.write("API_URL=${API_URL}\nVERSION={{VERSION}}")
253
+ file_with_template.rewind
254
+
255
+ file_importing = Tempfile.new('test_importing.txt')
256
+ file_importing.write("Config:\n @import #{file_with_template.path} API_URL=https://api.example.com VERSION=1.2.3\nEnd")
257
+ file_importing.rewind
258
+
259
+ result = @reader.readlines(file_importing.path,
260
+ use_template_delimiters: true).map(&:to_s)
261
+ assert_equal ['Config:', ' API_URL=https://api.example.com', ' VERSION=1.2.3', 'End'],
262
+ result
263
+
264
+ file_with_template.close
265
+ file_with_template.unlink
266
+ file_importing.close
267
+ file_importing.unlink
268
+ end
269
+
124
270
  def test_caching_functionality
125
271
  # First read
126
272
 
@@ -20,6 +20,7 @@ class CommandResult
20
20
  @attributes = {}
21
21
  @attributes[:exit_status] = 0
22
22
  @attributes[:stdout] = ''
23
+ @attributes[:warning] = ''
23
24
  attributes.each { |name, value| @attributes[name] = value }
24
25
  end
25
26
 
@@ -34,13 +35,13 @@ class CommandResult
34
35
 
35
36
  # def new_lines
36
37
  # value = @attributes[:new_lines]
37
- # ww caller.deref[0..4], value
38
+ # ww caller.deref[0..4], value
38
39
  # value
39
40
  # end
40
41
 
41
42
  # # trap assignment to new_lines
42
43
  # def new_lines=(value)
43
- # ww caller.deref[0..4], value
44
+ # ww caller.deref[0..4], value
44
45
  # @attributes[:new_lines] = value
45
46
  # end
46
47
 
data/lib/constants.rb CHANGED
@@ -72,6 +72,11 @@ end
72
72
 
73
73
  LoadFileLinkState = Struct.new(:load_file, :link_state)
74
74
 
75
+ class LoadMode
76
+ APPEND = :append
77
+ REPLACE = :replace
78
+ end
79
+
75
80
  class MenuOptions
76
81
  YES = 1
77
82
  NO = 2
@@ -45,7 +45,6 @@ def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
45
45
  end
46
46
 
47
47
  result_hash
48
-
49
48
  rescue StandardError
50
49
  ww $@, $!, caller.deref
51
50
  ww initial_code, expressions
data/lib/exceptions.rb CHANGED
@@ -5,11 +5,19 @@
5
5
  require_relative 'ansi_string'
6
6
 
7
7
  module Exceptions
8
- def self.error_handler(name = '', opts = {}, backtrace: $@, format_string: "\nError: %{name} -- %{message}", color_symbol: :red, take_count: 16)
8
+ def self.error_handler(
9
+ name = '',
10
+ opts = {},
11
+ backtrace: $@,
12
+ color_symbol: :red,
13
+ format_string: "\nError: %{name} -- %{message}",
14
+ show_backtrace: false,
15
+ take_count: 16
16
+ )
9
17
  warn(error = AnsiString.new(format(format_string,
10
18
  { name: name,
11
19
  message: $! })).send(color_symbol))
12
- if backtrace
20
+ if show_backtrace && backtrace
13
21
  warn(backtrace.select do |s|
14
22
  s.include? 'markdown_exec'
15
23
  end.reject { |s| s.include? 'vendor' }.take(take_count).map.with_index { |line, ind| " * #{ind}: #{line}" })
data/lib/fcb.rb CHANGED
@@ -23,6 +23,7 @@ def parse_yaml_of_ux_block(
23
23
  default: export['default'],
24
24
  echo: export['echo'],
25
25
  exec: export['exec'],
26
+ force: export['force'],
26
27
  init: export['init'],
27
28
  menu_format: export['format'] || export['menu_format'] || menu_format,
28
29
  name: name,
@@ -156,20 +157,25 @@ module MarkdownExec
156
157
  @attrs[:id] = id
157
158
 
158
159
  if @attrs[:type] == BlockType::UX
159
- case data = YAML.load(@attrs[:body].join("\n"))
160
- when Hash
161
- export = parse_yaml_of_ux_block(
162
- data,
163
- menu_format: menu_format,
164
- prompt: prompt
165
- )
166
-
167
- @attrs[:center] = table_center
168
- oname = @attrs[:oname] = format(export.menu_format, export.to_h)
169
- @attrs[:readonly] = export.readonly
170
- else
171
- # triggered by an empty or non-YAML block
172
- return NullResult.new(message: 'Invalid YAML', data: data)
160
+ begin
161
+ case data = YAML.load(@attrs[:body].join("\n"))
162
+ when Hash
163
+ export = parse_yaml_of_ux_block(
164
+ data,
165
+ menu_format: menu_format,
166
+ prompt: prompt
167
+ )
168
+
169
+ @attrs[:center] = table_center
170
+ oname = @attrs[:oname] = format(export.menu_format, export.to_h)
171
+ @attrs[:readonly] = export.readonly
172
+ else
173
+ # triggered by an empty or non-YAML block
174
+ return NullResult.new(message: 'Invalid YAML', data: data)
175
+ end
176
+ rescue StandardError
177
+ wwe 'Error processing block for menu', 'body:', @attrs[:body],
178
+ 'data', data, 'export', export
173
179
  end
174
180
  end
175
181