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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +1 -1
- data/bats/block-type-ux-required-variables.bats +0 -12
- data/bats/block-type-ux-sources.bats +0 -8
- data/bats/cli.bats +12 -0
- data/bats/import-with-text-substitution.bats +9 -0
- data/bats/load-vars-state-demo.bats +26 -0
- data/bats/options.bats +1 -1
- data/docs/dev/import-substitution-basic.md +5 -0
- data/docs/dev/import-substitution-compare.md +12 -0
- data/docs/dev/import-substitution-export.md +18 -0
- data/docs/dev/import-substitution-long.md +12 -0
- data/docs/dev/import-substitution-mixed.md +9 -0
- data/docs/dev/import-substitution-plant.md +11 -0
- data/docs/dev/import-substitution-quotes.md +7 -0
- data/docs/dev/import-substitution-research.md +11 -0
- data/docs/dev/import-substitution-simple.md +5 -0
- data/docs/dev/import-substitution-special.md +10 -0
- data/docs/dev/import-substitution-taxonomy.md +14 -0
- data/docs/dev/import-with-text-substitution.md +57 -0
- data/docs/dev/linked-file.md +3 -0
- data/docs/dev/load-mode-demo.md +163 -0
- data/docs/dev/requiring-blocks.md +1 -0
- data/examples/import_with_substitution_demo.md +48 -0
- data/examples/imports/mixed_template.md +33 -0
- data/examples/imports/organism_template.md +42 -0
- data/examples/imports/template_mustache.md +22 -0
- data/examples/imports/template_vars.md +22 -0
- data/examples/raw_replacement_demo.md +42 -0
- data/examples/recent_discoveries_demo.md +43 -0
- data/examples/template_syntax_demo.md +24 -0
- data/lib/cached_nested_file_reader.rb +174 -28
- data/lib/command_result.rb +3 -2
- data/lib/constants.rb +5 -0
- data/lib/evaluate_shell_expressions.rb +0 -1
- data/lib/exceptions.rb +10 -2
- data/lib/fcb.rb +20 -14
- data/lib/hash_delegator.rb +192 -125
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/mdoc.rb +2 -1
- data/lib/menu.src.yml +2 -1
- data/lib/menu.yml +2 -1
- data/lib/ww.rb +24 -0
- 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
|
15
|
-
# to process '#import filename' directives. When
|
16
|
-
#
|
17
|
-
#
|
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(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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[
|
82
|
-
rescue Errno::ENOENT =>
|
83
|
-
warn_format('readlines', "#{
|
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
|
205
|
+
@file1.write("Line1\nLine2\n @import #{@file2.path}\nLine3")
|
101
206
|
@file1.rewind
|
102
|
-
@reader = CachedNestedFileReader.new(
|
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
|
|
data/lib/command_result.rb
CHANGED
@@ -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
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(
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
|