markdown_exec 3.2.0 → 3.3.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 +27 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +3 -3
- data/bats/block-type-ux-auto.bats +1 -1
- data/bats/block-type-ux-default.bats +1 -1
- data/bats/block-type-ux-echo-hash-transform.bats +1 -1
- data/bats/block-type-ux-echo-hash.bats +2 -2
- data/bats/block-type-ux-exec-hash-transform.bats +8 -0
- data/bats/block-type-ux-exec-hash.bats +15 -0
- data/bats/block-type-ux-exec.bats +1 -1
- data/bats/block-type-ux-force.bats +9 -0
- data/bats/block-type-ux-formats.bats +8 -0
- data/bats/block-type-ux-readonly.bats +1 -1
- data/bats/block-type-ux-row-format.bats +1 -1
- data/bats/block-type-ux-transform.bats +1 -1
- data/bats/import-directive-parameter-symbols.bats +9 -0
- data/bats/import-duplicates.bats +4 -2
- data/bats/import-parameter-symbols.bats +8 -0
- data/bats/markup.bats +1 -1
- data/bats/options.bats +1 -1
- data/bin/tab_completion.sh +5 -1
- data/docs/dev/block-type-ux-echo-hash-transform.md +14 -12
- data/docs/dev/block-type-ux-exec-hash-transform.md +37 -0
- data/docs/dev/block-type-ux-exec-hash.md +93 -0
- data/docs/dev/block-type-ux-force.md +20 -0
- data/docs/dev/block-type-ux-formats.md +58 -0
- data/docs/dev/hexdump_format.md +267 -0
- data/docs/dev/import/parameter-symbols.md +6 -0
- data/docs/dev/import-directive-parameter-symbols.md +9 -0
- data/docs/dev/import-parameter-symbols-template.md +24 -0
- data/docs/dev/import-parameter-symbols.md +6 -0
- data/docs/dev/load-vars-state-demo.md +35 -0
- data/docs/ux-blocks-examples.md +2 -3
- data/examples/import_with_substitution_demo.md +130 -26
- data/examples/imports/organism_template.md +86 -29
- data/lib/cached_nested_file_reader.rb +265 -27
- data/lib/constants.rb +8 -1
- data/lib/env_interface.rb +13 -7
- data/lib/evaluate_shell_expressions.rb +1 -0
- data/lib/fcb.rb +120 -28
- data/lib/format_table.rb +56 -23
- data/lib/fout.rb +5 -0
- data/lib/hash_delegator.rb +1158 -347
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +2 -0
- data/lib/mdoc.rb +13 -11
- data/lib/menu.src.yml +139 -34
- data/lib/menu.yml +116 -32
- data/lib/string_util.rb +80 -0
- data/lib/table_extractor.rb +170 -64
- data/lib/ww.rb +325 -29
- metadata +18 -2
@@ -9,6 +9,7 @@ require 'fileutils'
|
|
9
9
|
require_relative 'constants'
|
10
10
|
require_relative 'exceptions'
|
11
11
|
require_relative 'find_files'
|
12
|
+
require_relative 'ww'
|
12
13
|
|
13
14
|
##
|
14
15
|
# The CachedNestedFileReader class provides functionality to read file
|
@@ -22,9 +23,29 @@ require_relative 'find_files'
|
|
22
23
|
class CachedNestedFileReader
|
23
24
|
include Exceptions
|
24
25
|
|
25
|
-
def initialize(
|
26
|
+
def initialize(
|
27
|
+
import_directive_line_pattern:,
|
28
|
+
import_directive_parameter_scan:,
|
29
|
+
import_parameter_variable_assignment:,
|
30
|
+
shell:,
|
31
|
+
shell_block_name:,
|
32
|
+
symbol_command_substitution:,
|
33
|
+
symbol_evaluated_expression:,
|
34
|
+
symbol_force_quoted_literal:,
|
35
|
+
symbol_raw_literal:,
|
36
|
+
symbol_variable_reference:
|
37
|
+
)
|
26
38
|
@file_cache = {}
|
27
|
-
@
|
39
|
+
@import_directive_line_pattern = import_directive_line_pattern
|
40
|
+
@import_directive_parameter_scan = import_directive_parameter_scan
|
41
|
+
@import_parameter_variable_assignment = import_parameter_variable_assignment
|
42
|
+
@shell = shell
|
43
|
+
@shell_block_name = shell_block_name
|
44
|
+
@symbol_command_substitution = symbol_command_substitution
|
45
|
+
@symbol_evaluated_expression = symbol_evaluated_expression
|
46
|
+
@symbol_force_quoted_literal = symbol_force_quoted_literal
|
47
|
+
@symbol_raw_literal = symbol_raw_literal
|
48
|
+
@symbol_variable_reference = symbol_variable_reference
|
28
49
|
end
|
29
50
|
|
30
51
|
def error_handler(name = '', opts = {})
|
@@ -45,25 +66,56 @@ class CachedNestedFileReader
|
|
45
66
|
# return the processed lines
|
46
67
|
def readlines(
|
47
68
|
filename, depth = 0, context: '', import_paths: nil,
|
48
|
-
indention: '', substitutions: {}, use_template_delimiters: false,
|
69
|
+
indention: '', substitutions: {}, use_template_delimiters: false,
|
70
|
+
clear_cache: true,
|
71
|
+
read_cache: false,
|
72
|
+
&block
|
49
73
|
)
|
74
|
+
# clear cache if requested
|
75
|
+
@file_cache.clear if clear_cache
|
76
|
+
|
50
77
|
cache_key = build_cache_key(filename, substitutions)
|
51
78
|
if @file_cache.key?(cache_key)
|
79
|
+
return ["# dup #{cache_key}"] unless read_cache
|
80
|
+
|
52
81
|
@file_cache[cache_key].each(&block) if block
|
53
82
|
return @file_cache[cache_key]
|
83
|
+
|
84
|
+
# do not return duplicates per filename and substitutions
|
85
|
+
# return an indicator that the file was already read
|
86
|
+
|
54
87
|
end
|
55
88
|
raise Errno::ENOENT, filename unless filename
|
56
89
|
|
57
90
|
directory_path = File.dirname(filename)
|
58
91
|
processed_lines = []
|
59
92
|
File.readlines(filename, chomp: true).each.with_index do |line, ind|
|
60
|
-
|
93
|
+
wwt :readline, 'depth:', depth, 'filename:', filename, 'ind:', ind,
|
94
|
+
'line:', line
|
95
|
+
if Regexp.new(@import_directive_line_pattern) =~ line
|
61
96
|
name_strip = $~[:name].strip
|
62
97
|
params_string = $~[:params] || ''
|
63
98
|
import_indention = indention + $~[:indention]
|
64
99
|
|
65
100
|
# Parse parameters for text substitution
|
66
|
-
import_substitutions = parse_import_params(params_string)
|
101
|
+
import_substitutions, add_code = parse_import_params(params_string)
|
102
|
+
if add_code
|
103
|
+
# strings as NestedLines
|
104
|
+
add_lines = add_code.map.with_index do |line, ind2|
|
105
|
+
nested_line = NestedLine.new(
|
106
|
+
line,
|
107
|
+
depth + 1,
|
108
|
+
import_indention,
|
109
|
+
filename,
|
110
|
+
ind2
|
111
|
+
)
|
112
|
+
block&.call(nested_line)
|
113
|
+
|
114
|
+
nested_line
|
115
|
+
end
|
116
|
+
ww 'add_lines:', add_lines
|
117
|
+
processed_lines += add_lines
|
118
|
+
end
|
67
119
|
merged_substitutions = substitutions.merge(import_substitutions)
|
68
120
|
|
69
121
|
included_file_path =
|
@@ -78,15 +130,28 @@ class CachedNestedFileReader
|
|
78
130
|
|
79
131
|
raise Errno::ENOENT, name_strip unless included_file_path
|
80
132
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
133
|
+
# Create a cache key for the imported file that includes both filename and parameters
|
134
|
+
imported_cache_key = build_import_cache_key(included_file_path,
|
135
|
+
name_strip, params_string, merged_substitutions)
|
136
|
+
|
137
|
+
# Check if we've already loaded this specific import
|
138
|
+
if @file_cache.key?(imported_cache_key)
|
139
|
+
imported_lines = @file_cache[imported_cache_key]
|
140
|
+
else
|
141
|
+
imported_lines = readlines(
|
142
|
+
included_file_path, depth + 1,
|
143
|
+
context: "#{filename}:#{ind + 1}",
|
144
|
+
import_paths: import_paths,
|
145
|
+
indention: import_indention,
|
146
|
+
substitutions: merged_substitutions,
|
147
|
+
use_template_delimiters: use_template_delimiters,
|
148
|
+
clear_cache: false,
|
149
|
+
&block
|
150
|
+
)
|
151
|
+
|
152
|
+
# Cache the imported lines with the specific import cache key
|
153
|
+
@file_cache[imported_cache_key] = imported_lines
|
154
|
+
end
|
90
155
|
|
91
156
|
# Apply text substitutions to imported content
|
92
157
|
processed_imported_lines = apply_substitutions(
|
@@ -105,6 +170,7 @@ class CachedNestedFileReader
|
|
105
170
|
end
|
106
171
|
end
|
107
172
|
|
173
|
+
wwt :read_document_code, 'processed_lines:', processed_lines
|
108
174
|
@file_cache[cache_key] = processed_lines
|
109
175
|
rescue Errno::ENOENT => err
|
110
176
|
warn_format('readlines', "#{err} @@ #{context}",
|
@@ -113,21 +179,91 @@ class CachedNestedFileReader
|
|
113
179
|
|
114
180
|
private
|
115
181
|
|
182
|
+
def shell_code_block_for_assignment(key, expression)
|
183
|
+
["```#{@shell} :#{@shell_block_name}",
|
184
|
+
format(@import_parameter_variable_assignment,
|
185
|
+
{ key: key, value: expression }),
|
186
|
+
'```'].tap { wwr _1 }
|
187
|
+
end
|
188
|
+
|
116
189
|
# Parse key=value parameters from the import line
|
117
190
|
def parse_import_params(params_string)
|
118
191
|
return {} if params_string.nil? || params_string.strip.empty?
|
119
192
|
|
193
|
+
add_code = []
|
120
194
|
params = {}
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
) do |key, quoted_double, quoted_single, unquoted|
|
195
|
+
|
196
|
+
# First loop: store all scanned parameters in a temporary variable
|
197
|
+
scanned_params = []
|
198
|
+
params_string.scan(@import_directive_parameter_scan) do |key, op, quoted_double, quoted_single, unquoted|
|
125
199
|
value = quoted_double || quoted_single || unquoted
|
126
|
-
|
127
|
-
# otherwise, the text is not available for other substitutions
|
128
|
-
params[key] = value if key != value
|
200
|
+
scanned_params << [key, op, value]
|
129
201
|
end
|
130
|
-
|
202
|
+
wwt 'scanned_params:', scanned_params
|
203
|
+
|
204
|
+
# Preceding loop: select items where op = '='
|
205
|
+
equal_op_params = scanned_params.select { |_key, op, _value| op == '=' }
|
206
|
+
wwt 'equal_op_params:', equal_op_params
|
207
|
+
# Process remaining parameters (non-equal operators)
|
208
|
+
scanned_params.each do |key, op, value|
|
209
|
+
wwt :import, 'key:', key, 'op:', op, 'value:', value
|
210
|
+
|
211
|
+
# adjust key for current stem value
|
212
|
+
varname = key
|
213
|
+
case op
|
214
|
+
when @symbol_command_substitution,
|
215
|
+
@symbol_evaluated_expression,
|
216
|
+
@symbol_force_quoted_literal,
|
217
|
+
@symbol_variable_reference
|
218
|
+
# perform all substitutions per equal_op_params
|
219
|
+
equal_op_params.each do |equal_key, _equal_op, equal_value|
|
220
|
+
varname = varname.gsub(equal_key, equal_value)
|
221
|
+
end
|
222
|
+
wwt :import_key, 'varname:', varname
|
223
|
+
end
|
224
|
+
|
225
|
+
case op
|
226
|
+
when @symbol_raw_literal
|
227
|
+
# skip replacement of equal values otherwise,
|
228
|
+
# the text is not available for other substitutions
|
229
|
+
next unless key != value
|
230
|
+
|
231
|
+
# replace the literal below
|
232
|
+
when @symbol_command_substitution
|
233
|
+
# add code to set the variable to the value of the parameter
|
234
|
+
add_code += shell_code_block_for_assignment(
|
235
|
+
varname, %($(#{value}))
|
236
|
+
)
|
237
|
+
# replace key with expansion of the added variable
|
238
|
+
value = "${#{varname}}"
|
239
|
+
when @symbol_evaluated_expression
|
240
|
+
# add code to set the variable to the value of the parameter
|
241
|
+
add_code += shell_code_block_for_assignment(
|
242
|
+
varname, %("#{value}")
|
243
|
+
)
|
244
|
+
# replace key with expansion of the added variable
|
245
|
+
value = "${#{varname}}"
|
246
|
+
when @symbol_force_quoted_literal
|
247
|
+
# add code to set the variable to the value of the parameter
|
248
|
+
add_code += shell_code_block_for_assignment(
|
249
|
+
varname, Shellwords.escape(value)
|
250
|
+
)
|
251
|
+
# replace key with expansion of the added variable
|
252
|
+
value = "${#{varname}}"
|
253
|
+
when @symbol_variable_reference
|
254
|
+
# variable exists
|
255
|
+
value = "${#{value}}"
|
256
|
+
else
|
257
|
+
wwe "Invalid op '#{op}'"
|
258
|
+
end
|
259
|
+
|
260
|
+
params[key] = value
|
261
|
+
end
|
262
|
+
[params, add_code].tap do
|
263
|
+
wwr 'params:', _1[0], 'add_code:', _1[1]
|
264
|
+
end
|
265
|
+
rescue StandardError
|
266
|
+
wwe $!
|
131
267
|
end
|
132
268
|
|
133
269
|
# Apply text substitutions to a collection of NestedLine objects
|
@@ -155,8 +291,10 @@ class CachedNestedFileReader
|
|
155
291
|
if use_template_delimiters
|
156
292
|
# Replace template-style placeholders: ${KEY} or {{KEY}}
|
157
293
|
substitutions.each do |key, value|
|
158
|
-
substituted_line = substituted_line.gsub(
|
159
|
-
|
294
|
+
substituted_line = substituted_line.gsub(
|
295
|
+
/\$\{#{Regexp.escape(key)}\}/,
|
296
|
+
value
|
297
|
+
)
|
160
298
|
substituted_line = substituted_line.gsub(
|
161
299
|
/\{\{#{Regexp.escape(key)}\}\}/, value
|
162
300
|
)
|
@@ -168,7 +306,6 @@ class CachedNestedFileReader
|
|
168
306
|
# Replace each key with a unique temporary placeholder
|
169
307
|
substitutions.each_with_index do |(key, value), index|
|
170
308
|
temp_placeholder = "__MDE_TEMP_#{index}__"
|
171
|
-
# pattern = /\b#{Regexp.escape(key)}\b/
|
172
309
|
pattern = Regexp.new(Regexp.escape(key))
|
173
310
|
substituted_line = substituted_line.gsub(pattern, temp_placeholder)
|
174
311
|
temp_placeholders[temp_placeholder] = value
|
@@ -190,6 +327,13 @@ class CachedNestedFileReader
|
|
190
327
|
substitution_hash = substitutions.sort.to_h.hash
|
191
328
|
"#{filename}##{substitution_hash}"
|
192
329
|
end
|
330
|
+
|
331
|
+
# Build a cache key specifically for imported files
|
332
|
+
def build_import_cache_key(filename, name_strip, params_string, substitutions)
|
333
|
+
# Sort parameters for consistent key
|
334
|
+
sorted_params = substitutions.sort.to_h.hash
|
335
|
+
"#{filename}##{name_strip}##{params_string}##{sorted_params}"
|
336
|
+
end
|
193
337
|
end
|
194
338
|
|
195
339
|
return if $PROGRAM_NAME != __FILE__
|
@@ -207,7 +351,18 @@ class CachedNestedFileReaderTest < Minitest::Test
|
|
207
351
|
@file1.write("Line1\nLine2\n @import #{@file2.path}\nLine3")
|
208
352
|
@file1.rewind
|
209
353
|
@reader = CachedNestedFileReader.new(
|
210
|
-
|
354
|
+
import_directive_line_pattern:
|
355
|
+
/^(?<indention> *)@import +(?<name>\S+)(?<params>(?: +[A-Za-z_]\w*=(?:"[^"]*"|'[^']*'|\S+))*) *$/,
|
356
|
+
import_directive_parameter_scan:
|
357
|
+
/([A-Za-z_]\w*)(:=|\?=|!=|=)(?:"([^"]*)"|'([^']*)'|(\S+))/,
|
358
|
+
import_parameter_variable_assignment: '%{key}=%{value}',
|
359
|
+
shell: 'bash',
|
360
|
+
shell_block_name: '(document_shell)',
|
361
|
+
symbol_command_substitution: ':c=',
|
362
|
+
symbol_evaluated_expression: ':e=',
|
363
|
+
symbol_raw_literal: '=',
|
364
|
+
symbol_force_quoted_literal: ':q=',
|
365
|
+
symbol_variable_reference: ':v='
|
211
366
|
)
|
212
367
|
end
|
213
368
|
|
@@ -278,9 +433,92 @@ class CachedNestedFileReaderTest < Minitest::Test
|
|
278
433
|
@file2.reopen(@file2.path, 'w') { |f| f.write('ChangedLine') }
|
279
434
|
|
280
435
|
# Second read (should read from cache, not the changed file)
|
281
|
-
result2 = @reader.readlines(@file2.path
|
436
|
+
result2 = @reader.readlines(@file2.path, clear_cache: false,
|
437
|
+
read_cache: true).map(&:to_s)
|
282
438
|
|
283
439
|
assert_equal result1, result2
|
284
440
|
assert_equal %w[ImportedLine1 ImportedLine2], result2
|
285
441
|
end
|
442
|
+
|
443
|
+
def test_import_caching_with_same_parameters
|
444
|
+
# Create a file that will be imported multiple times
|
445
|
+
shared_file = Tempfile.new('shared.txt')
|
446
|
+
shared_file.write("Shared content line 1\nShared content line 2")
|
447
|
+
shared_file.rewind
|
448
|
+
|
449
|
+
# Create a file that imports the same file multiple times with same parameters
|
450
|
+
importing_file = Tempfile.new('importing_multiple.txt')
|
451
|
+
importing_file.write("Start\n @import #{shared_file.path} PARAM=value\nMiddle\n @import #{shared_file.path} PARAM=value\nEnd")
|
452
|
+
importing_file.rewind
|
453
|
+
|
454
|
+
# Track how many times the shared file is actually read
|
455
|
+
read_count = 0
|
456
|
+
original_readlines = File.method(:readlines)
|
457
|
+
File.define_singleton_method(:readlines) do |filename, **opts|
|
458
|
+
if filename == shared_file.path
|
459
|
+
read_count += 1
|
460
|
+
end
|
461
|
+
original_readlines.call(filename, **opts)
|
462
|
+
end
|
463
|
+
|
464
|
+
result = @reader.readlines(importing_file.path).map(&:to_s)
|
465
|
+
|
466
|
+
# The shared file should only be read once, not twice
|
467
|
+
assert_equal 1, read_count,
|
468
|
+
'Shared file should only be read once when imported with same parameters'
|
469
|
+
|
470
|
+
# Verify the content is correct
|
471
|
+
expected = ['Start', ' Shared content line 1', ' Shared content line 2',
|
472
|
+
'Middle', ' Shared content line 1', ' Shared content line 2', 'End']
|
473
|
+
assert_equal expected, result
|
474
|
+
|
475
|
+
# Restore original method
|
476
|
+
File.define_singleton_method(:readlines, original_readlines)
|
477
|
+
|
478
|
+
shared_file.close
|
479
|
+
shared_file.unlink
|
480
|
+
importing_file.close
|
481
|
+
importing_file.unlink
|
482
|
+
end
|
483
|
+
|
484
|
+
def test_import_caching_with_different_parameters
|
485
|
+
# Create a file that will be imported with different parameters
|
486
|
+
template_file = Tempfile.new('template.txt')
|
487
|
+
template_file.write('Hello NAME, your ID is ID')
|
488
|
+
template_file.rewind
|
489
|
+
|
490
|
+
# Create a file that imports the same file with different parameters
|
491
|
+
importing_file = Tempfile.new('importing_different.txt')
|
492
|
+
importing_file.write("Users:\n @import #{template_file.path} NAME=Alice ID=123\n @import #{template_file.path} NAME=Bob ID=456\nEnd")
|
493
|
+
importing_file.rewind
|
494
|
+
|
495
|
+
# Track how many times the template file is actually read
|
496
|
+
read_count = 0
|
497
|
+
original_readlines = File.method(:readlines)
|
498
|
+
File.define_singleton_method(:readlines) do |filename, **opts|
|
499
|
+
if filename == template_file.path
|
500
|
+
read_count += 1
|
501
|
+
end
|
502
|
+
original_readlines.call(filename, **opts)
|
503
|
+
end
|
504
|
+
|
505
|
+
result = @reader.readlines(importing_file.path).map(&:to_s)
|
506
|
+
|
507
|
+
# The template file should be read twice since parameters are different
|
508
|
+
assert_equal 2, read_count,
|
509
|
+
'Template file should be read twice when imported with different parameters'
|
510
|
+
|
511
|
+
# Verify the content is correct
|
512
|
+
expected = ['Users:', ' Hello Alice, your 123 is 123',
|
513
|
+
' Hello Bob, your 456 is 456', 'End']
|
514
|
+
assert_equal expected, result
|
515
|
+
|
516
|
+
# Restore original method
|
517
|
+
File.define_singleton_method(:readlines, original_readlines)
|
518
|
+
|
519
|
+
template_file.close
|
520
|
+
template_file.unlink
|
521
|
+
importing_file.close
|
522
|
+
importing_file.unlink
|
523
|
+
end
|
286
524
|
end
|
data/lib/constants.rb
CHANGED
@@ -27,7 +27,14 @@ BLOCK_TYPE_COLOR_OPTIONS = {
|
|
27
27
|
BlockType::OPTS => :menu_opts_color,
|
28
28
|
BlockType::SAVE => :menu_save_color,
|
29
29
|
BlockType::SHELL => :menu_bash_color,
|
30
|
-
BlockType::UX =>
|
30
|
+
BlockType::UX => {
|
31
|
+
:is_allow? => :menu_ux_color_allow,
|
32
|
+
:is_echo? => :menu_ux_color_echo,
|
33
|
+
:is_edit? => :menu_ux_color_edit,
|
34
|
+
:is_exec? => :menu_ux_color_exec,
|
35
|
+
:readonly => :menu_ux_color_readonly,
|
36
|
+
true => :menu_ux_color
|
37
|
+
},
|
31
38
|
BlockType::VARS => :menu_vars_color
|
32
39
|
}.freeze
|
33
40
|
|
data/lib/env_interface.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative 'ww'
|
3
4
|
|
4
5
|
# A class that provides an interface to ENV variables with customizable getters and setters
|
@@ -10,11 +11,15 @@ class EnvInterface
|
|
10
11
|
# @param transform [Proc] Optional transformation to apply to the value
|
11
12
|
# @return [Object] The environment variable value
|
12
13
|
def get(key, default: nil, transform: nil)
|
13
|
-
|
14
|
-
value = ENV[key]
|
15
|
-
return default if value.nil?
|
14
|
+
value = ENV.fetch(key, nil)
|
16
15
|
|
17
|
-
|
16
|
+
if value.nil?
|
17
|
+
default
|
18
|
+
else
|
19
|
+
transform ? transform.call(value) : value
|
20
|
+
end.tap do
|
21
|
+
wwt :env, key, _1
|
22
|
+
end
|
18
23
|
end
|
19
24
|
|
20
25
|
# Set an environment variable with optional transformation
|
@@ -23,9 +28,10 @@ wwt :env, key, \
|
|
23
28
|
# @param transform [Proc] Optional transformation to apply before setting
|
24
29
|
# @return [String] The set value
|
25
30
|
def set(key, value, transform: nil)
|
26
|
-
wwt :env, key, caller.deref[0..3], value
|
27
31
|
transformed_value = transform ? transform.call(value) : value
|
28
|
-
ENV[key] = transformed_value.to_s
|
32
|
+
ENV[key] = transformed_value.to_s.tap do
|
33
|
+
wwt :env, key, _1
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
# Check if an environment variable exists
|
@@ -54,4 +60,4 @@ wwt :env, key, caller.deref[0..3], value
|
|
54
60
|
ENV.clear
|
55
61
|
end
|
56
62
|
end
|
57
|
-
end
|
63
|
+
end
|
@@ -25,6 +25,7 @@ def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
|
|
25
25
|
script << "\necho #{token}#{index}\n"
|
26
26
|
script << expression << "\n"
|
27
27
|
end
|
28
|
+
wwt :eval, 'script:', script
|
28
29
|
|
29
30
|
# Execute
|
30
31
|
stdout_str, _, status = Open3.capture3(shell, '-c', script)
|