markdown_exec 3.0.9 → 3.1.1

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 +37 -0
  3. data/Gemfile.lock +1 -1
  4. data/bats/block-type-ux-require-chained.bats +9 -0
  5. data/bats/block-type-ux-require.bats +6 -0
  6. data/bats/block-type-ux-required-variables.bats +0 -12
  7. data/bats/block-type-ux-sources.bats +0 -8
  8. data/bats/import-with-text-substitution.bats +9 -0
  9. data/bats/load-vars-state-demo.bats +26 -0
  10. data/bats/options.bats +1 -1
  11. data/docs/dev/block-type-ux-require-chained.md +33 -0
  12. data/docs/dev/block-type-ux-require.md +8 -0
  13. data/docs/dev/import-substitution-basic.md +5 -0
  14. data/docs/dev/import-substitution-compare.md +12 -0
  15. data/docs/dev/import-substitution-export.md +18 -0
  16. data/docs/dev/import-substitution-long.md +12 -0
  17. data/docs/dev/import-substitution-mixed.md +9 -0
  18. data/docs/dev/import-substitution-plant.md +11 -0
  19. data/docs/dev/import-substitution-quotes.md +7 -0
  20. data/docs/dev/import-substitution-research.md +11 -0
  21. data/docs/dev/import-substitution-simple.md +5 -0
  22. data/docs/dev/import-substitution-special.md +10 -0
  23. data/docs/dev/import-substitution-taxonomy.md +14 -0
  24. data/docs/dev/import-with-text-substitution.md +57 -0
  25. data/docs/dev/load-mode-demo.md +163 -0
  26. data/examples/import_with_substitution_demo.md +48 -0
  27. data/examples/imports/mixed_template.md +33 -0
  28. data/examples/imports/organism_template.md +42 -0
  29. data/examples/imports/template_mustache.md +22 -0
  30. data/examples/imports/template_vars.md +22 -0
  31. data/examples/raw_replacement_demo.md +42 -0
  32. data/examples/recent_discoveries_demo.md +43 -0
  33. data/examples/template_syntax_demo.md +24 -0
  34. data/lib/cached_nested_file_reader.rb +176 -28
  35. data/lib/command_result.rb +3 -2
  36. data/lib/constants.rb +5 -0
  37. data/lib/env_interface.rb +2 -2
  38. data/lib/exceptions.rb +10 -2
  39. data/lib/fcb.rb +2 -3
  40. data/lib/hash_delegator.rb +134 -78
  41. data/lib/markdown_exec/version.rb +1 -1
  42. data/lib/menu.src.yml +2 -1
  43. data/lib/menu.yml +2 -1
  44. data/lib/ww.rb +28 -1
  45. metadata +27 -2
@@ -0,0 +1,33 @@
1
+ # Research Report: COMMON_NAME
2
+
3
+ **Scientific Name:** SPECIES
4
+ **Discovery Year:** YEAR_DISCOVERED
5
+
6
+ ## Summary
7
+
8
+ This research document covers COMMON_NAME (SPECIES), discovered in YEAR_DISCOVERED.
9
+
10
+ ## Taxonomic Classification
11
+
12
+ - **Genus:** GENUS
13
+ - **Family:** FAMILY
14
+ - **Order:** ORDER
15
+ - **Class:** CLASS
16
+
17
+ ```bash
18
+ # Generate biological data report
19
+ ORGANISM_NAME="COMMON_NAME"
20
+ SCIENTIFIC_NAME="SPECIES"
21
+ DISCOVERY_YEAR="YEAR_DISCOVERED"
22
+ TAXONOMIC_GENUS="GENUS"
23
+ TAXONOMIC_FAMILY="FAMILY"
24
+ TAXONOMIC_ORDER="ORDER"
25
+ TAXONOMIC_CLASS="CLASS"
26
+
27
+ echo "Generating research report for: $ORGANISM_NAME"
28
+ echo "Scientific classification: $SCIENTIFIC_NAME"
29
+ echo "Discovered in: $DISCOVERY_YEAR"
30
+ echo "Taxonomic hierarchy: $TAXONOMIC_CLASS > $TAXONOMIC_ORDER > $TAXONOMIC_FAMILY > $TAXONOMIC_GENUS"
31
+ ```
32
+
33
+ Biological research template using **raw replacement** - all taxonomic placeholders are just the key names without delimiters.
@@ -0,0 +1,42 @@
1
+ # COMMON_NAME Classification
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
+ ## Taxonomic Classification
12
+
13
+ ```bash
14
+ # Biological classification data
15
+ export COMMON_NAME="COMMON_NAME"
16
+ export SPECIES="SPECIES"
17
+ export GENUS="GENUS"
18
+ export FAMILY="FAMILY"
19
+ export ORDER="ORDER"
20
+ export CLASS="CLASS"
21
+ export YEAR_DISCOVERED="YEAR_DISCOVERED"
22
+
23
+ echo "Organism: COMMON_NAME"
24
+ echo "Scientific name: SPECIES"
25
+ echo "Discovered in: YEAR_DISCOVERED"
26
+ ```
27
+
28
+ ## Classification Hierarchy
29
+
30
+ ```yaml
31
+ organism:
32
+ common_name: COMMON_NAME
33
+ scientific_name: SPECIES
34
+ taxonomy:
35
+ genus: GENUS
36
+ family: FAMILY
37
+ order: ORDER
38
+ class: CLASS
39
+ discovery_year: YEAR_DISCOVERED
40
+ ```
41
+
42
+ Biological organism template using raw replacement for taxonomic data.
@@ -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,137 @@ 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
+ # skip replacement of equal values
127
+ # otherwise, the text is not available for other substitutions
128
+ params[key] = value if key != value
129
+ end
130
+ params
131
+ end
132
+
133
+ # Apply text substitutions to a collection of NestedLine objects
134
+ def apply_substitutions(
135
+ lines, substitutions, use_template_delimiters = false
136
+ )
137
+ return lines if substitutions.empty?
138
+
139
+ lines.map do |nested_line|
140
+ substituted_text = apply_line_substitutions(
141
+ nested_line.text,
142
+ substitutions, use_template_delimiters
143
+ )
144
+ NestedLine.new(substituted_text, nested_line.depth, nested_line.indention,
145
+ nested_line.filename, nested_line.index)
146
+ end
147
+ end
148
+
149
+ # Apply text substitutions to a single line
150
+ def apply_line_substitutions(line, substitutions,
151
+ use_template_delimiters = false)
152
+ return line if substitutions.empty?
153
+
154
+ substituted_line = line.dup
155
+ if use_template_delimiters
156
+ # Replace template-style placeholders: ${KEY} or {{KEY}}
157
+ substitutions.each do |key, value|
158
+ substituted_line = substituted_line.gsub(/\$\{#{Regexp.escape(key)}\}/,
159
+ value)
160
+ substituted_line = substituted_line.gsub(
161
+ /\{\{#{Regexp.escape(key)}\}\}/, value
162
+ )
163
+ end
164
+ else
165
+ # use temporary placeholders to avoid double replacement
166
+ temp_placeholders = {}
167
+
168
+ # Replace each key with a unique temporary placeholder
169
+ substitutions.each_with_index do |(key, value), index|
170
+ temp_placeholder = "__MDE_TEMP_#{index}__"
171
+ # pattern = /\b#{Regexp.escape(key)}\b/
172
+ pattern = Regexp.new(Regexp.escape(key))
173
+ substituted_line = substituted_line.gsub(pattern, temp_placeholder)
174
+ temp_placeholders[temp_placeholder] = value
175
+ end
176
+
177
+ # Replace temporary placeholders with actual values
178
+ temp_placeholders.each do |placeholder, value|
179
+ substituted_line = substituted_line.gsub(placeholder, value)
180
+ end
181
+
182
+ end
183
+ substituted_line
184
+ end
185
+
186
+ # Build cache key that includes substitutions to avoid conflicts
187
+ def build_cache_key(filename, substitutions)
188
+ return filename if substitutions.empty?
189
+
190
+ substitution_hash = substitutions.sort.to_h.hash
191
+ "#{filename}##{substitution_hash}"
192
+ end
86
193
  end
87
194
 
88
195
  return if $PROGRAM_NAME != __FILE__
@@ -97,9 +204,11 @@ class CachedNestedFileReaderTest < Minitest::Test
97
204
  @file2.rewind
98
205
 
99
206
  @file1 = Tempfile.new('test1.txt')
100
- @file1.write("Line1\nLine2\n #insert #{@file2.path}\nLine3")
207
+ @file1.write("Line1\nLine2\n @import #{@file2.path}\nLine3")
101
208
  @file1.rewind
102
- @reader = CachedNestedFileReader.new(import_pattern: /^(?<indention> *)#insert (?'name'.+)$/)
209
+ @reader = CachedNestedFileReader.new(
210
+ import_pattern: /^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/
211
+ )
103
212
  end
104
213
 
105
214
  def teardown
@@ -121,6 +230,45 @@ class CachedNestedFileReaderTest < Minitest::Test
121
230
  result
122
231
  end
123
232
 
233
+ def test_readlines_with_imports_and_substitutions
234
+ file_with_substitution = Tempfile.new('test_substitution.txt')
235
+ file_with_substitution.write("Server: HOST\nPort: PORT")
236
+ file_with_substitution.rewind
237
+
238
+ file_importing = Tempfile.new('test_importing.txt')
239
+ file_importing.write("Config:\n @import #{file_with_substitution.path} HOST=localhost PORT=8080\nEnd")
240
+ file_importing.rewind
241
+
242
+ result = @reader.readlines(file_importing.path).map(&:to_s)
243
+ assert_equal ['Config:', ' Server: localhost', ' Port: 8080', 'End'],
244
+ result
245
+
246
+ file_with_substitution.close
247
+ file_with_substitution.unlink
248
+ file_importing.close
249
+ file_importing.unlink
250
+ end
251
+
252
+ def test_readlines_with_template_delimiters
253
+ file_with_template = Tempfile.new('test_template.txt')
254
+ file_with_template.write("API_URL=${API_URL}\nVERSION={{VERSION}}")
255
+ file_with_template.rewind
256
+
257
+ file_importing = Tempfile.new('test_importing.txt')
258
+ file_importing.write("Config:\n @import #{file_with_template.path} API_URL=https://api.example.com VERSION=1.2.3\nEnd")
259
+ file_importing.rewind
260
+
261
+ result = @reader.readlines(file_importing.path,
262
+ use_template_delimiters: true).map(&:to_s)
263
+ assert_equal ['Config:', ' API_URL=https://api.example.com', ' VERSION=1.2.3', 'End'],
264
+ result
265
+
266
+ file_with_template.close
267
+ file_with_template.unlink
268
+ file_importing.close
269
+ file_importing.unlink
270
+ end
271
+
124
272
  def test_caching_functionality
125
273
  # First read
126
274
 
@@ -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
data/lib/env_interface.rb CHANGED
@@ -10,7 +10,7 @@ class EnvInterface
10
10
  # @param transform [Proc] Optional transformation to apply to the value
11
11
  # @return [Object] The environment variable value
12
12
  def get(key, default: nil, transform: nil)
13
- ww key, \
13
+ wwt :env, key, \
14
14
  value = ENV[key]
15
15
  return default if value.nil?
16
16
 
@@ -23,7 +23,7 @@ ww key, \
23
23
  # @param transform [Proc] Optional transformation to apply before setting
24
24
  # @return [String] The set value
25
25
  def set(key, value, transform: nil)
26
- ww key, caller.deref[0..3], value
26
+ wwt :env, key, caller.deref[0..3], value
27
27
  transformed_value = transform ? transform.call(value) : value
28
28
  ENV[key] = transformed_value.to_s
29
29
  end
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
@@ -174,9 +174,8 @@ module MarkdownExec
174
174
  return NullResult.new(message: 'Invalid YAML', data: data)
175
175
  end
176
176
  rescue StandardError
177
- ww $@, $!, caller.deref
178
- ww @attrs[:body], data, export
179
- raise StandardError, $!
177
+ wwe 'Error processing block for menu', 'body:', @attrs[:body],
178
+ 'data', data, 'export', export
180
179
  end
181
180
  end
182
181