markdown_exec 1.8.5 → 1.8.7
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/.pryrc +0 -1
- data/.rubocop.yml +0 -2
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/bin/tab_completion.sh +7 -3
- data/examples/linked.md +19 -0
- data/examples/linked1.md +10 -0
- data/examples/linked2.md +4 -0
- data/examples/opts.md +24 -2
- data/lib/ansi_formatter.rb +1 -5
- data/lib/cached_nested_file_reader.rb +1 -7
- data/lib/constants.rb +17 -0
- data/lib/directory_searcher.rb +1 -6
- data/lib/filter.rb +15 -3
- data/lib/hash_delegator.rb +557 -504
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +12 -8
- data/lib/mdoc.rb +33 -12
- data/lib/menu.src.yml +95 -16
- data/lib/menu.yml +84 -17
- data/lib/regexp_replacer.rb +58 -0
- data/lib/saved_assets.rb +10 -8
- metadata +5 -4
data/lib/hash_delegator.rb
CHANGED
@@ -37,6 +37,273 @@ class String
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
module HashDelegatorSelf
|
41
|
+
# def add_back_option(menu_blocks)
|
42
|
+
# append_chrome_block(menu_blocks, MenuState::BACK)
|
43
|
+
# end
|
44
|
+
|
45
|
+
# Applies an ANSI color method to a string using a specified color key.
|
46
|
+
# The method retrieves the color method from the provided hash. If the color key
|
47
|
+
# is not present in the hash, it uses a default color method.
|
48
|
+
# @param string [String] The string to be colored.
|
49
|
+
# @param color_methods [Hash] A hash where keys are color names (String/Symbol) and values are color methods.
|
50
|
+
# @param color_key [String, Symbol] The key representing the desired color method in the color_methods hash.
|
51
|
+
# @param default_method [String] (optional) Default color method to use if color_key is not found in color_methods. Defaults to 'plain'.
|
52
|
+
# @return [String] The colored string.
|
53
|
+
def apply_color_from_hash(string, color_methods, color_key, default_method: 'plain')
|
54
|
+
color_method = color_methods.fetch(color_key, default_method).to_sym
|
55
|
+
string.to_s.send(color_method)
|
56
|
+
end
|
57
|
+
|
58
|
+
# # Enhanced `apply_color_from_hash` method to support dynamic color transformations
|
59
|
+
# # @param string [String] The string to be colored.
|
60
|
+
# # @param color_transformations [Hash] A hash mapping color names to lambdas that apply color transformations.
|
61
|
+
# # @param color_key [String, Symbol] The key representing the desired color transformation in the color_transformations hash.
|
62
|
+
# # @param default_transformation [Proc] Default color transformation to use if color_key is not found in color_transformations.
|
63
|
+
# # @return [String] The colored string.
|
64
|
+
# def apply_color_from_hash(string, color_transformations, color_key, default_transformation: ->(str) { str })
|
65
|
+
# transformation = color_transformations.fetch(color_key.to_sym, default_transformation)
|
66
|
+
# transformation.call(string)
|
67
|
+
# end
|
68
|
+
# color_transformations = {
|
69
|
+
# red: ->(str) { "\e[31m#{str}\e[0m" }, # ANSI color code for red
|
70
|
+
# green: ->(str) { "\e[32m#{str}\e[0m" }, # ANSI color code for green
|
71
|
+
# # Add more color transformations as needed
|
72
|
+
# }
|
73
|
+
# string = "Hello, World!"
|
74
|
+
# colored_string = apply_color_from_hash(string, color_transformations, :red)
|
75
|
+
# puts colored_string # This will print the string in red
|
76
|
+
|
77
|
+
# Searches for the first element in a collection where the specified key matches a given value.
|
78
|
+
# This method is particularly useful for finding a specific hash-like object within an enumerable collection.
|
79
|
+
# If no match is found, it returns a specified default value.
|
80
|
+
#
|
81
|
+
# @param blocks [Enumerable] The collection of hash-like objects to search.
|
82
|
+
# @param key [Object] The key to search for in each element of the collection.
|
83
|
+
# @param value [Object] The value to match against each element's corresponding key value.
|
84
|
+
# @param default [Object, nil] The default value to return if no match is found (optional).
|
85
|
+
# @return [Object, nil] The first matching element or the default value if no match is found.
|
86
|
+
def block_find(blocks, key, value, default = nil)
|
87
|
+
blocks.find { |item| item[key] == value } || default
|
88
|
+
end
|
89
|
+
|
90
|
+
def code_merge(*bodies)
|
91
|
+
merge_lists(*bodies)
|
92
|
+
end
|
93
|
+
|
94
|
+
def count_matches_in_lines(lines, regex)
|
95
|
+
lines.count { |line| line.to_s.match(regex) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_directory_for_file(file_path)
|
99
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
100
|
+
end
|
101
|
+
|
102
|
+
# Creates a file at the specified path, writes the given content to it,
|
103
|
+
# and sets file permissions if required. Handles any errors encountered during the process.
|
104
|
+
#
|
105
|
+
# @param file_path [String] The path where the file will be created.
|
106
|
+
# @param content [String] The content to write into the file.
|
107
|
+
# @param chmod_value [Integer] The file permission value to set; skips if zero.
|
108
|
+
def create_file_and_write_string_with_permissions(file_path, content,
|
109
|
+
chmod_value)
|
110
|
+
create_directory_for_file(file_path)
|
111
|
+
File.write(file_path, content)
|
112
|
+
set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
|
113
|
+
rescue StandardError
|
114
|
+
error_handler('create_file_and_write_string_with_permissions')
|
115
|
+
end
|
116
|
+
|
117
|
+
# def create_temp_file
|
118
|
+
# Dir::Tmpname.create(self.class.to_s) { |path| path }
|
119
|
+
# end
|
120
|
+
|
121
|
+
# Updates the title of an FCB object from its body content if the title is nil or empty.
|
122
|
+
def default_block_title_from_body(fcb)
|
123
|
+
return unless fcb.title.nil? || fcb.title.empty?
|
124
|
+
|
125
|
+
fcb.derive_title_from_body
|
126
|
+
end
|
127
|
+
|
128
|
+
# delete the current line if it is empty and the previous is also empty
|
129
|
+
def delete_consecutive_blank_lines!(blocks_menu)
|
130
|
+
blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
|
131
|
+
prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
|
132
|
+
current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# # Deletes a temporary file specified by an environment variable.
|
137
|
+
# # Checks if the file exists before attempting to delete it and clears the environment variable afterward.
|
138
|
+
# # Any errors encountered during deletion are handled gracefully.
|
139
|
+
# def delete_required_temp_file(temp_blocks_file_path)
|
140
|
+
# return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
|
141
|
+
|
142
|
+
# HashDelegator.remove_file_without_standard_errors(temp_blocks_file_path)
|
143
|
+
# end
|
144
|
+
|
145
|
+
def error_handler(name = '', opts = {})
|
146
|
+
Exceptions.error_handler(
|
147
|
+
"HashDelegator.#{name} -- #{$!}",
|
148
|
+
opts
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
# # DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
|
153
|
+
# def first_n_caller_items(n)
|
154
|
+
# call_stack = caller
|
155
|
+
# base_path = File.realpath('.')
|
156
|
+
|
157
|
+
# # Modify the call stack to remove the base path and keep only the first n items
|
158
|
+
# call_stack.take(n + 1)[1..].map do |line|
|
159
|
+
# " . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
|
160
|
+
# end.join("\n")
|
161
|
+
# end
|
162
|
+
|
163
|
+
# Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
|
164
|
+
# It concatenates the array of strings found under the specified key in the run_state's files.
|
165
|
+
#
|
166
|
+
# @param key [Symbol] The key corresponding to the desired execution stream.
|
167
|
+
# @return [String] A concatenated string of the execution stream's contents.
|
168
|
+
def format_execution_streams(key, files = {})
|
169
|
+
(files || {}).fetch(key, []).join
|
170
|
+
end
|
171
|
+
|
172
|
+
# Indents all lines in a given string with a specified indentation string.
|
173
|
+
# @param body [String] A multi-line string to be indented.
|
174
|
+
# @param indent [String] The string used for indentation (default is an empty string).
|
175
|
+
# @return [String] A single string with each line indented as specified.
|
176
|
+
def indent_all_lines(body, indent = nil)
|
177
|
+
return body unless indent&.non_empty?
|
178
|
+
|
179
|
+
body.lines.map { |line| indent + line.chomp }.join("\n")
|
180
|
+
end
|
181
|
+
|
182
|
+
def initialize_fcb_names(fcb)
|
183
|
+
fcb.oname = fcb.dname = fcb.title || ''
|
184
|
+
end
|
185
|
+
|
186
|
+
def merge_lists(*args)
|
187
|
+
# Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
|
188
|
+
merged = args.compact.flatten
|
189
|
+
merged.empty? ? [] : merged
|
190
|
+
end
|
191
|
+
|
192
|
+
def next_link_state(block_name_from_cli, was_using_cli, block_state)
|
193
|
+
# &bsp 'next_link_state', block_name_from_cli, was_using_cli, block_state
|
194
|
+
# Set block_name based on block_name_from_cli
|
195
|
+
block_name = block_name_from_cli ? @cli_block_name : nil
|
196
|
+
# &bsp 'block_name:', block_name
|
197
|
+
|
198
|
+
# Determine the state of breaker based on was_using_cli and the block type
|
199
|
+
breaker = !block_name && !block_name_from_cli && was_using_cli && block_state.block[:shell] == BlockType::BASH
|
200
|
+
# &bsp 'breaker:', breaker
|
201
|
+
|
202
|
+
# Reset block_name_from_cli if the conditions are not met
|
203
|
+
block_name_from_cli ||= false
|
204
|
+
# &bsp 'block_name_from_cli:', block_name_from_cli
|
205
|
+
|
206
|
+
[block_name, block_name_from_cli, breaker]
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_yaml_data_from_body(body)
|
210
|
+
body.any? ? YAML.load(body.join("\n")) : {}
|
211
|
+
end
|
212
|
+
|
213
|
+
# Reads required code blocks from a temporary file specified by an environment variable.
|
214
|
+
# @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
|
215
|
+
def read_required_blocks_from_temp_file(temp_blocks_file_path)
|
216
|
+
return [] if temp_blocks_file_path.to_s.empty?
|
217
|
+
|
218
|
+
if File.exist?(temp_blocks_file_path)
|
219
|
+
File.readlines(
|
220
|
+
temp_blocks_file_path, chomp: true
|
221
|
+
)
|
222
|
+
else
|
223
|
+
[]
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def remove_file_without_standard_errors(path)
|
228
|
+
FileUtils.rm_f(path)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Evaluates the given string as Ruby code and rescues any StandardErrors.
|
232
|
+
# If an error occurs, it calls the error_handler method with 'safeval'.
|
233
|
+
# @param str [String] The string to be evaluated.
|
234
|
+
# @return [Object] The result of evaluating the string.
|
235
|
+
def safeval(str)
|
236
|
+
eval(str)
|
237
|
+
rescue StandardError # catches NameError, StandardError
|
238
|
+
error_handler('safeval')
|
239
|
+
end
|
240
|
+
|
241
|
+
def set_file_permissions(file_path, chmod_value)
|
242
|
+
File.chmod(chmod_value, file_path)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
|
246
|
+
# defines a lambda function to handle interrupts.
|
247
|
+
# @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
|
248
|
+
def tty_prompt_without_disabled_symbol
|
249
|
+
TTY::Prompt.new(
|
250
|
+
interrupt: lambda {
|
251
|
+
puts
|
252
|
+
raise TTY::Reader::InputInterrupt
|
253
|
+
},
|
254
|
+
symbols: { cross: ' ' }
|
255
|
+
)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Updates the attributes of the given fcb object and conditionally yields to a block.
|
259
|
+
# It initializes fcb names and sets the default block title from fcb's body.
|
260
|
+
# If the fcb has a body and meets certain conditions, it yields to the given block.
|
261
|
+
#
|
262
|
+
# @param fcb [Object] The fcb object whose attributes are to be updated.
|
263
|
+
# @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
|
264
|
+
# @param block [Block] An optional block to yield to if conditions are met.
|
265
|
+
def update_menu_attrib_yield_selected(fcb, selected_messages, configuration = {}, &block)
|
266
|
+
initialize_fcb_names(fcb)
|
267
|
+
return unless fcb.body
|
268
|
+
|
269
|
+
default_block_title_from_body(fcb)
|
270
|
+
MarkdownExec::Filter.yield_to_block_if_applicable(fcb, selected_messages, configuration,
|
271
|
+
&block)
|
272
|
+
end
|
273
|
+
|
274
|
+
# Writes the provided code blocks to a file.
|
275
|
+
# @param code_blocks [String] Code blocks to write into the file.
|
276
|
+
def write_code_to_file(content, path)
|
277
|
+
File.write(path, content)
|
278
|
+
end
|
279
|
+
|
280
|
+
def write_execution_output_to_file(files, filespec)
|
281
|
+
FileUtils.mkdir_p File.dirname(filespec)
|
282
|
+
|
283
|
+
File.write(
|
284
|
+
filespec,
|
285
|
+
["-STDOUT-\n",
|
286
|
+
format_execution_streams(ExecutionStreams::StdOut, files),
|
287
|
+
"-STDERR-\n",
|
288
|
+
format_execution_streams(ExecutionStreams::StdErr, files),
|
289
|
+
"-STDIN-\n",
|
290
|
+
format_execution_streams(ExecutionStreams::StdIn, files),
|
291
|
+
"\n"].join
|
292
|
+
)
|
293
|
+
end
|
294
|
+
|
295
|
+
# Yields a line as a new block if the selected message type includes :line.
|
296
|
+
# @param [String] line The line to be processed.
|
297
|
+
# @param [Array<Symbol>] selected_messages A list of message types to check.
|
298
|
+
# @param [Proc] block The block to be called with the line data.
|
299
|
+
def yield_line_if_selected(line, selected_messages, &block)
|
300
|
+
return unless block && selected_messages.include?(:line)
|
301
|
+
|
302
|
+
block.call(:line, MarkdownExec::FCB.new(body: [line]))
|
303
|
+
end
|
304
|
+
end
|
305
|
+
### require_relative 'hash_delegator_self'
|
306
|
+
|
40
307
|
# This module provides methods for compacting and converting data structures.
|
41
308
|
module CompactionHelpers
|
42
309
|
# Converts an array of key-value pairs into a hash, applying compaction to the values.
|
@@ -106,11 +373,12 @@ module MarkdownExec
|
|
106
373
|
class HashDelegator
|
107
374
|
attr_accessor :most_recent_loaded_filename, :pass_args, :run_state
|
108
375
|
|
376
|
+
extend HashDelegatorSelf
|
109
377
|
include CompactionHelpers
|
110
378
|
|
111
379
|
def initialize(delegate_object = {})
|
112
380
|
@delegate_object = delegate_object
|
113
|
-
@prompt = tty_prompt_without_disabled_symbol
|
381
|
+
@prompt = HashDelegator.tty_prompt_without_disabled_symbol
|
114
382
|
|
115
383
|
@most_recent_loaded_filename = nil
|
116
384
|
@pass_args = []
|
@@ -138,11 +406,20 @@ module MarkdownExec
|
|
138
406
|
# along with initial and final dividers, based on the delegate object's configuration.
|
139
407
|
#
|
140
408
|
# @param menu_blocks [Array] The array of menu block elements to be modified.
|
141
|
-
def add_menu_chrome_blocks!(menu_blocks)
|
409
|
+
def add_menu_chrome_blocks!(menu_blocks, link_state)
|
142
410
|
return unless @delegate_object[:menu_link_format].present?
|
143
411
|
|
412
|
+
if @delegate_object[:menu_with_inherited_lines]
|
413
|
+
add_inherited_lines(menu_blocks,
|
414
|
+
link_state)
|
415
|
+
end
|
416
|
+
|
417
|
+
# back before exit
|
144
418
|
add_back_option(menu_blocks) if should_add_back_option?
|
419
|
+
|
420
|
+
# exit after other options
|
145
421
|
add_exit_option(menu_blocks) if @delegate_object[:menu_with_exit]
|
422
|
+
|
146
423
|
add_dividers(menu_blocks)
|
147
424
|
end
|
148
425
|
|
@@ -152,13 +429,17 @@ module MarkdownExec
|
|
152
429
|
append_chrome_block(menu_blocks, MenuState::BACK)
|
153
430
|
end
|
154
431
|
|
432
|
+
def add_dividers(menu_blocks)
|
433
|
+
append_divider(menu_blocks, :initial)
|
434
|
+
append_divider(menu_blocks, :final)
|
435
|
+
end
|
436
|
+
|
155
437
|
def add_exit_option(menu_blocks)
|
156
438
|
append_chrome_block(menu_blocks, MenuState::EXIT)
|
157
439
|
end
|
158
440
|
|
159
|
-
def
|
160
|
-
|
161
|
-
append_divider(menu_blocks, :final)
|
441
|
+
def add_inherited_lines(menu_blocks, link_state)
|
442
|
+
append_inherited_lines(menu_blocks, link_state)
|
162
443
|
end
|
163
444
|
|
164
445
|
public
|
@@ -179,7 +460,7 @@ module MarkdownExec
|
|
179
460
|
end
|
180
461
|
|
181
462
|
formatted_name = format(@delegate_object[:menu_link_format],
|
182
|
-
safeval(option_name))
|
463
|
+
HashDelegator.safeval(option_name))
|
183
464
|
chrome_block = FCB.new(
|
184
465
|
chrome: true,
|
185
466
|
dname: HashDelegator.new(@delegate_object).string_send_color(
|
@@ -195,6 +476,39 @@ module MarkdownExec
|
|
195
476
|
end
|
196
477
|
end
|
197
478
|
|
479
|
+
# Appends a formatted divider to the specified position in a menu block array.
|
480
|
+
# The method checks for the presence of formatting options before appending.
|
481
|
+
#
|
482
|
+
# @param menu_blocks [Array] The array of menu block elements.
|
483
|
+
# @param position [Symbol] The position to insert the divider (:initial or :final).
|
484
|
+
def append_inherited_lines(menu_blocks, link_state, position: top)
|
485
|
+
return unless link_state.inherited_lines.present?
|
486
|
+
|
487
|
+
insert_at_top = @delegate_object[:menu_inherited_lines_at_top]
|
488
|
+
chrome_blocks = link_state.inherited_lines.map do |line|
|
489
|
+
formatted = format(@delegate_object[:menu_inherited_lines_format],
|
490
|
+
{ line: line })
|
491
|
+
FCB.new(
|
492
|
+
chrome: true,
|
493
|
+
disabled: '',
|
494
|
+
dname: HashDelegator.new(@delegate_object).string_send_color(
|
495
|
+
formatted, :menu_inherited_lines_color
|
496
|
+
),
|
497
|
+
oname: formatted
|
498
|
+
)
|
499
|
+
end
|
500
|
+
|
501
|
+
if insert_at_top
|
502
|
+
# Prepend an array of elements to the beginning
|
503
|
+
menu_blocks.unshift(*chrome_blocks)
|
504
|
+
else
|
505
|
+
# Append an array of elements to the end
|
506
|
+
menu_blocks.concat(chrome_blocks)
|
507
|
+
end
|
508
|
+
rescue StandardError
|
509
|
+
HashDelegator.error_handler('append_inherited_lines')
|
510
|
+
end
|
511
|
+
|
198
512
|
# Appends a formatted divider to the specified position in a menu block array.
|
199
513
|
# The method checks for the presence of formatting options before appending.
|
200
514
|
#
|
@@ -224,19 +538,6 @@ module MarkdownExec
|
|
224
538
|
|
225
539
|
# private
|
226
540
|
|
227
|
-
# Searches for the first element in a collection where the specified key matches a given value.
|
228
|
-
# This method is particularly useful for finding a specific hash-like object within an enumerable collection.
|
229
|
-
# If no match is found, it returns a specified default value.
|
230
|
-
#
|
231
|
-
# @param blocks [Enumerable] The collection of hash-like objects to search.
|
232
|
-
# @param key [Object] The key to search for in each element of the collection.
|
233
|
-
# @param value [Object] The value to match against each element's corresponding key value.
|
234
|
-
# @param default [Object, nil] The default value to return if no match is found (optional).
|
235
|
-
# @return [Object, nil] The first matching element or the default value if no match is found.
|
236
|
-
def block_find(blocks, key, value, default = nil)
|
237
|
-
blocks.find { |item| item[key] == value } || default
|
238
|
-
end
|
239
|
-
|
240
541
|
# Iterates through nested files to collect various types of blocks, including dividers, tasks, and others.
|
241
542
|
# The method categorizes blocks based on their type and processes them accordingly.
|
242
543
|
#
|
@@ -246,9 +547,10 @@ module MarkdownExec
|
|
246
547
|
iter_blocks_from_nested_files do |btype, fcb|
|
247
548
|
process_block_based_on_type(blocks, btype, fcb)
|
248
549
|
end
|
550
|
+
# &bc 'blocks.count:', blocks.count
|
249
551
|
blocks
|
250
552
|
rescue StandardError
|
251
|
-
error_handler('blocks_from_nested_files')
|
553
|
+
HashDelegator.error_handler('blocks_from_nested_files')
|
252
554
|
end
|
253
555
|
|
254
556
|
# private
|
@@ -273,15 +575,6 @@ module MarkdownExec
|
|
273
575
|
true
|
274
576
|
end
|
275
577
|
|
276
|
-
def code_join(*bodies)
|
277
|
-
bc = bodies&.compact
|
278
|
-
bc.count.positive? ? bc.join("\n") : nil
|
279
|
-
end
|
280
|
-
|
281
|
-
def code_merge(*bodies)
|
282
|
-
merge_lists(*bodies)
|
283
|
-
end
|
284
|
-
|
285
578
|
# Collects required code lines based on the selected block and the delegate object's configuration.
|
286
579
|
# If the block type is VARS, it also sets environment variables based on the block's content.
|
287
580
|
#
|
@@ -312,7 +605,7 @@ module MarkdownExec
|
|
312
605
|
highlight: [@delegate_object[:block_name]])
|
313
606
|
end
|
314
607
|
|
315
|
-
code_merge
|
608
|
+
HashDelegator.code_merge(link_state&.inherited_lines, required[:code])
|
316
609
|
end
|
317
610
|
|
318
611
|
def command_execute(command, args: [])
|
@@ -359,21 +652,21 @@ module MarkdownExec
|
|
359
652
|
@fout.fout "Error ENOENT: #{err.inspect}"
|
360
653
|
end
|
361
654
|
|
362
|
-
def
|
655
|
+
def load_cli_or_user_selected_block(all_blocks, menu_blocks, default)
|
363
656
|
if @delegate_object[:block_name].present?
|
364
657
|
block = all_blocks.find do |item|
|
365
658
|
item[:oname] == @delegate_object[:block_name]
|
366
|
-
end
|
659
|
+
end&.merge(block_name_from_ui: false)
|
367
660
|
else
|
368
661
|
block_state = wait_for_user_selected_block(all_blocks, menu_blocks,
|
369
662
|
default)
|
370
|
-
block = block_state.block
|
663
|
+
block = block_state.block&.merge(block_name_from_ui: true)
|
371
664
|
state = block_state.state
|
372
665
|
end
|
373
666
|
|
374
667
|
SelectedBlockMenuState.new(block, state)
|
375
668
|
rescue StandardError
|
376
|
-
error_handler('
|
669
|
+
HashDelegator.error_handler('load_cli_or_user_selected_block')
|
377
670
|
end
|
378
671
|
|
379
672
|
# This method is responsible for handling the execution of generic blocks in a markdown document.
|
@@ -383,8 +676,7 @@ module MarkdownExec
|
|
383
676
|
# @param mdoc [Object] The markdown document object containing code blocks.
|
384
677
|
# @param selected [Hash] The selected item from the menu to be executed.
|
385
678
|
# @return [LoadFileLinkState] An object indicating whether to load the next block or reuse the current one.
|
386
|
-
def
|
387
|
-
link_state = nil, block_source:)
|
679
|
+
def compile_execute_and_trigger_reuse(mdoc, selected, link_state = nil, block_source:)
|
388
680
|
required_lines = collect_required_code_lines(mdoc, selected, link_state,
|
389
681
|
block_source: block_source)
|
390
682
|
output_or_approval = @delegate_object[:output_script] || @delegate_object[:user_must_approve]
|
@@ -413,17 +705,9 @@ module MarkdownExec
|
|
413
705
|
def count_blocks_in_filename
|
414
706
|
regex = Regexp.new(@delegate_object[:fenced_start_and_end_regex])
|
415
707
|
lines = cfile.readlines(@delegate_object[:filename])
|
416
|
-
count_matches_in_lines(lines, regex) / 2
|
417
|
-
end
|
418
|
-
|
419
|
-
# private
|
420
|
-
|
421
|
-
def count_matches_in_lines(lines, regex)
|
422
|
-
lines.count { |line| line.to_s.match(regex) }
|
708
|
+
HashDelegator.count_matches_in_lines(lines, regex) / 2
|
423
709
|
end
|
424
710
|
|
425
|
-
# private
|
426
|
-
|
427
711
|
##
|
428
712
|
# Creates and adds a formatted block to the blocks array based on the provided match and format options.
|
429
713
|
# @param blocks [Array] The array of blocks to add the new block to.
|
@@ -450,12 +734,13 @@ module MarkdownExec
|
|
450
734
|
# @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
|
451
735
|
def create_and_add_chrome_blocks(blocks, fcb)
|
452
736
|
match_criteria = [
|
453
|
-
{ match: :
|
454
|
-
|
737
|
+
{ match: :heading1_match, format: :menu_heading1_format, color: :menu_heading1_color },
|
738
|
+
{ match: :heading2_match, format: :menu_heading2_format, color: :menu_heading2_color },
|
739
|
+
{ match: :heading3_match, format: :menu_heading3_format, color: :menu_heading3_color },
|
455
740
|
{ match: :menu_divider_match, format: :menu_divider_format,
|
456
741
|
color: :menu_divider_color },
|
457
|
-
{ match: :menu_note_match, format: :menu_note_format,
|
458
|
-
|
742
|
+
{ match: :menu_note_match, format: :menu_note_format, color: :menu_note_color },
|
743
|
+
{ match: :menu_task_match, format: :menu_task_format, color: :menu_task_color }
|
459
744
|
]
|
460
745
|
match_criteria.each do |criteria|
|
461
746
|
unless @delegate_object[criteria[:match]].present? &&
|
@@ -469,31 +754,10 @@ module MarkdownExec
|
|
469
754
|
end
|
470
755
|
end
|
471
756
|
|
472
|
-
# Creates a file at the specified path, writes the given content to it,
|
473
|
-
# and sets file permissions if required. Handles any errors encountered during the process.
|
474
|
-
#
|
475
|
-
# @param file_path [String] The path where the file will be created.
|
476
|
-
# @param content [String] The content to write into the file.
|
477
|
-
# @param chmod_value [Integer] The file permission value to set; skips if zero.
|
478
|
-
def create_file_and_write_string_with_permissions(file_path, content,
|
479
|
-
chmod_value)
|
480
|
-
create_directory_for_file(file_path)
|
481
|
-
File.write(file_path, content)
|
482
|
-
set_file_permissions(file_path, chmod_value) unless chmod_value.zero?
|
483
|
-
rescue StandardError
|
484
|
-
error_handler('create_file_and_write_string_with_permissions')
|
485
|
-
end
|
486
|
-
|
487
|
-
# private
|
488
|
-
|
489
|
-
def create_directory_for_file(file_path)
|
490
|
-
FileUtils.mkdir_p(File.dirname(file_path))
|
491
|
-
end
|
492
|
-
|
493
757
|
def create_divider(position)
|
494
758
|
divider_key = position == :initial ? :menu_initial_divider : :menu_final_divider
|
495
759
|
oname = format(@delegate_object[:menu_divider_format],
|
496
|
-
safeval(@delegate_object[divider_key]))
|
760
|
+
HashDelegator.safeval(@delegate_object[divider_key]))
|
497
761
|
|
498
762
|
FCB.new(
|
499
763
|
chrome: true,
|
@@ -503,38 +767,6 @@ module MarkdownExec
|
|
503
767
|
)
|
504
768
|
end
|
505
769
|
|
506
|
-
# private
|
507
|
-
|
508
|
-
def create_temp_file
|
509
|
-
Dir::Tmpname.create(self.class.to_s) { |path| path }
|
510
|
-
end
|
511
|
-
|
512
|
-
# Updates the title of an FCB object from its body content if the title is nil or empty.
|
513
|
-
def default_block_title_from_body(fcb)
|
514
|
-
return unless fcb.title.nil? || fcb.title.empty?
|
515
|
-
|
516
|
-
fcb.derive_title_from_body
|
517
|
-
end
|
518
|
-
|
519
|
-
# delete the current line if it is empty and the previous is also empty
|
520
|
-
def delete_consecutive_blank_lines!(blocks_menu)
|
521
|
-
blocks_menu.process_and_conditionally_delete! do |prev_item, current_item, _next_item|
|
522
|
-
prev_item&.fetch(:chrome, nil) && !prev_item&.fetch(:oname).present? &&
|
523
|
-
current_item&.fetch(:chrome, nil) && !current_item&.fetch(:oname).present?
|
524
|
-
end
|
525
|
-
end
|
526
|
-
|
527
|
-
# Deletes a temporary file specified by an environment variable.
|
528
|
-
# Checks if the file exists before attempting to delete it and clears the environment variable afterward.
|
529
|
-
# Any errors encountered during deletion are handled gracefully.
|
530
|
-
def delete_required_temp_file(temp_blocks_file_path)
|
531
|
-
return if temp_blocks_file_path.nil? || temp_blocks_file_path.empty?
|
532
|
-
|
533
|
-
safely_remove_file(temp_blocks_file_path)
|
534
|
-
rescue StandardError
|
535
|
-
error_handler('delete_required_temp_file')
|
536
|
-
end
|
537
|
-
|
538
770
|
# Determines the state of a selected block in the menu based on the selected option.
|
539
771
|
# It categorizes the selected option into either EXIT, BACK, or CONTINUE state.
|
540
772
|
#
|
@@ -571,15 +803,6 @@ module MarkdownExec
|
|
571
803
|
@delegate_object[:menu_divider_format].present? && @delegate_object[divider_key].present?
|
572
804
|
end
|
573
805
|
|
574
|
-
def error_handler(name = '', opts = {})
|
575
|
-
Exceptions.error_handler(
|
576
|
-
"HashDelegator.#{name} -- #{$!}",
|
577
|
-
opts
|
578
|
-
)
|
579
|
-
end
|
580
|
-
|
581
|
-
# public
|
582
|
-
|
583
806
|
# Executes a block of code that has been approved for execution.
|
584
807
|
# It sets the script block name, writes command files if required, and handles the execution
|
585
808
|
# including output formatting and summarization.
|
@@ -587,8 +810,8 @@ module MarkdownExec
|
|
587
810
|
# @param required_lines [Array<String>] The lines of code to be executed.
|
588
811
|
# @param selected [FCB] The selected functional code block object.
|
589
812
|
def execute_required_lines(required_lines = [])
|
590
|
-
#
|
591
|
-
|
813
|
+
# @run_state.script_block_name = selected[:oname]
|
814
|
+
write_command_file(required_lines) if @delegate_object[:save_executed_script]
|
592
815
|
format_and_execute_command(required_lines)
|
593
816
|
post_execution_process
|
594
817
|
end
|
@@ -602,8 +825,8 @@ module MarkdownExec
|
|
602
825
|
# @param opts [Hash] Options hash containing configuration settings.
|
603
826
|
# @param mdoc [YourMDocClass] An instance of the MDoc class.
|
604
827
|
#
|
605
|
-
def
|
606
|
-
|
828
|
+
def execute_shell_type(selected, mdoc, link_state = LinkState.new,
|
829
|
+
block_source:)
|
607
830
|
if selected.fetch(:shell, '') == BlockType::LINK
|
608
831
|
push_link_history_and_trigger_load(selected.fetch(:body, ''), mdoc, selected,
|
609
832
|
link_state)
|
@@ -618,8 +841,8 @@ module MarkdownExec
|
|
618
841
|
options_state.load_file_link_state
|
619
842
|
|
620
843
|
else
|
621
|
-
|
622
|
-
|
844
|
+
compile_execute_and_trigger_reuse(mdoc, selected, link_state,
|
845
|
+
block_source: block_source)
|
623
846
|
end
|
624
847
|
end
|
625
848
|
|
@@ -637,17 +860,6 @@ module MarkdownExec
|
|
637
860
|
string_send_color(data_string, color_sym)
|
638
861
|
end
|
639
862
|
|
640
|
-
# DebugHelper.d ["HDmm method_name: #{method_name}", "#{first_n_caller_items 1}"]
|
641
|
-
def first_n_caller_items(n)
|
642
|
-
call_stack = caller
|
643
|
-
base_path = File.realpath('.')
|
644
|
-
|
645
|
-
# Modify the call stack to remove the base path and keep only the first n items
|
646
|
-
call_stack.take(n + 1)[1..].map do |line|
|
647
|
-
" . #{line.sub(/^#{Regexp.escape(base_path)}\//, '')}"
|
648
|
-
end.join("\n")
|
649
|
-
end
|
650
|
-
|
651
863
|
def format_and_execute_command(lines)
|
652
864
|
formatted_command = lines.flatten.join("\n")
|
653
865
|
@fout.fout fetch_color(data_sym: :script_execution_head,
|
@@ -673,16 +885,6 @@ module MarkdownExec
|
|
673
885
|
string_send_color(formatted_string, color_sym)
|
674
886
|
end
|
675
887
|
|
676
|
-
# Formats and returns the execution streams (like stdin, stdout, stderr) for a given key.
|
677
|
-
# It concatenates the array of strings found under the specified key in the run_state's files.
|
678
|
-
#
|
679
|
-
# @param key [Symbol] The key corresponding to the desired execution stream.
|
680
|
-
# @return [String] A concatenated string of the execution stream's contents.
|
681
|
-
def format_execution_streams(key)
|
682
|
-
files = @run_state.files || {}
|
683
|
-
files.fetch(key, []).join
|
684
|
-
end
|
685
|
-
|
686
888
|
# Processes a block to generate its summary, modifying its attributes based on various matching criteria.
|
687
889
|
# It handles special formatting for bash blocks, extracting and setting properties like call, stdin, stdout, and dname.
|
688
890
|
#
|
@@ -712,7 +914,7 @@ module MarkdownExec
|
|
712
914
|
# It sets the block name and determines if the user clicked the back link in the menu.
|
713
915
|
#
|
714
916
|
# @param block_state [Object] An object representing the state of a block in the menu.
|
715
|
-
def
|
917
|
+
def handle_back_or_continue(block_state)
|
716
918
|
return if block_state.nil?
|
717
919
|
unless [MenuState::BACK,
|
718
920
|
MenuState::CONTINUE].include?(block_state.state)
|
@@ -745,16 +947,6 @@ module MarkdownExec
|
|
745
947
|
end
|
746
948
|
end
|
747
949
|
|
748
|
-
# Indents all lines in a given string with a specified indentation string.
|
749
|
-
# @param body [String] A multi-line string to be indented.
|
750
|
-
# @param indent [String] The string used for indentation (default is an empty string).
|
751
|
-
# @return [String] A single string with each line indented as specified.
|
752
|
-
def indent_all_lines(body, indent = nil)
|
753
|
-
return body unless indent&.non_empty?
|
754
|
-
|
755
|
-
body.lines.map { |line| indent + line.chomp }.join("\n")
|
756
|
-
end
|
757
|
-
|
758
950
|
# Initializes variables for regex and other states
|
759
951
|
def initial_state
|
760
952
|
{
|
@@ -785,11 +977,8 @@ module MarkdownExec
|
|
785
977
|
File.join @delegate_object[:saved_stdout_folder],
|
786
978
|
@delegate_object[:logged_stdout_filename]
|
787
979
|
@logged_stdout_filespec = @delegate_object[:logged_stdout_filespec]
|
788
|
-
write_execution_output_to_file
|
789
|
-
|
790
|
-
|
791
|
-
def initialize_fcb_names(fcb)
|
792
|
-
fcb.oname = fcb.dname = fcb.title || ''
|
980
|
+
HashDelegator.write_execution_output_to_file(@run_state.files,
|
981
|
+
@delegate_object[:logged_stdout_filespec])
|
793
982
|
end
|
794
983
|
|
795
984
|
# Iterates through blocks in a file, applying the provided block to each line.
|
@@ -846,7 +1035,7 @@ module MarkdownExec
|
|
846
1035
|
return
|
847
1036
|
end
|
848
1037
|
|
849
|
-
block = block_find(all_blocks, :oname, block_name)
|
1038
|
+
block = HashDelegator.block_find(all_blocks, :oname, block_name)
|
850
1039
|
return unless block
|
851
1040
|
|
852
1041
|
options_state = read_show_options_and_trigger_reuse(block)
|
@@ -867,7 +1056,7 @@ module MarkdownExec
|
|
867
1056
|
|
868
1057
|
## Handles the file loading and returns the blocks in the file and MDoc instance
|
869
1058
|
#
|
870
|
-
def mdoc_menu_and_blocks_from_nested_files
|
1059
|
+
def mdoc_menu_and_blocks_from_nested_files(link_state)
|
871
1060
|
all_blocks, mdoc = mdoc_and_blocks_from_nested_files
|
872
1061
|
|
873
1062
|
# recreate menu with new options
|
@@ -875,8 +1064,9 @@ module MarkdownExec
|
|
875
1064
|
all_blocks, mdoc = mdoc_and_blocks_from_nested_files if load_auto_blocks(all_blocks)
|
876
1065
|
|
877
1066
|
menu_blocks = mdoc.fcbs_per_options(@delegate_object)
|
878
|
-
add_menu_chrome_blocks!(menu_blocks)
|
879
|
-
|
1067
|
+
add_menu_chrome_blocks!(menu_blocks, link_state)
|
1068
|
+
### compress empty lines
|
1069
|
+
HashDelegator.delete_consecutive_blank_lines!(menu_blocks) if true
|
880
1070
|
[all_blocks, menu_blocks, mdoc]
|
881
1071
|
end
|
882
1072
|
|
@@ -895,7 +1085,7 @@ module MarkdownExec
|
|
895
1085
|
# @param option_symbol [Symbol] The symbol key for the menu option in the delegate object.
|
896
1086
|
# @return [String] The formatted or original value of the menu option.
|
897
1087
|
def menu_chrome_formatted_option(option_symbol = :menu_option_back_name)
|
898
|
-
option_value = safeval(@delegate_object.fetch(option_symbol, ''))
|
1088
|
+
option_value = HashDelegator.safeval(@delegate_object.fetch(option_symbol, ''))
|
899
1089
|
|
900
1090
|
if @delegate_object[:menu_chrome_format]
|
901
1091
|
format(@delegate_object[:menu_chrome_format], option_value)
|
@@ -904,12 +1094,6 @@ module MarkdownExec
|
|
904
1094
|
end
|
905
1095
|
end
|
906
1096
|
|
907
|
-
def merge_lists(*args)
|
908
|
-
# Filters out nil values, flattens the arrays, and ensures an empty list is returned if no valid lists are provided
|
909
|
-
merged = args.compact.flatten
|
910
|
-
merged.empty? ? [] : merged
|
911
|
-
end
|
912
|
-
|
913
1097
|
# If a method is missing, treat it as a key for the @delegate_object.
|
914
1098
|
def method_missing(method_name, *args, &block)
|
915
1099
|
if @delegate_object.respond_to?(method_name)
|
@@ -922,15 +1106,13 @@ module MarkdownExec
|
|
922
1106
|
end
|
923
1107
|
end
|
924
1108
|
|
925
|
-
def
|
926
|
-
return
|
1109
|
+
def shift_cli_argument
|
1110
|
+
return true unless @menu_base_options[:input_cli_rest].present?
|
927
1111
|
|
928
|
-
@cli_block_name = @
|
929
|
-
|
1112
|
+
@cli_block_name = @menu_base_options[:input_cli_rest].shift
|
1113
|
+
false
|
930
1114
|
end
|
931
1115
|
|
932
|
-
# private
|
933
|
-
|
934
1116
|
def output_color_formatted(data_sym, color_sym)
|
935
1117
|
formatted_string = string_send_color(@delegate_object[data_sym],
|
936
1118
|
color_sym)
|
@@ -983,16 +1165,10 @@ module MarkdownExec
|
|
983
1165
|
), level: level
|
984
1166
|
end
|
985
1167
|
|
986
|
-
# private
|
987
|
-
|
988
|
-
def parse_yaml_data_from_body(body)
|
989
|
-
body.any? ? YAML.load(body.join("\n")) : {}
|
990
|
-
end
|
991
|
-
|
992
1168
|
def pop_add_current_code_to_head_and_trigger_load(_link_state, block_names, code_lines,
|
993
1169
|
dependencies)
|
994
1170
|
pop = @link_history.pop # updatable
|
995
|
-
|
1171
|
+
next_state = LinkState.new(
|
996
1172
|
block_name: pop.block_name,
|
997
1173
|
document_filename: pop.document_filename,
|
998
1174
|
inherited_block_names:
|
@@ -1000,12 +1176,12 @@ module MarkdownExec
|
|
1000
1176
|
inherited_dependencies:
|
1001
1177
|
dependencies.merge(pop.inherited_dependencies || {}), ### merge, not replace, key data
|
1002
1178
|
inherited_lines:
|
1003
|
-
code_merge(pop.inherited_lines, code_lines)
|
1179
|
+
HashDelegator.code_merge(pop.inherited_lines, code_lines)
|
1004
1180
|
)
|
1005
|
-
@link_history.push(
|
1181
|
+
@link_history.push(next_state)
|
1006
1182
|
|
1007
|
-
|
1008
|
-
LoadFileLinkState.new(LoadFile::Load,
|
1183
|
+
next_state.block_name = nil
|
1184
|
+
LoadFileLinkState.new(LoadFile::Load, next_state)
|
1009
1185
|
end
|
1010
1186
|
|
1011
1187
|
# This method handles the back-link operation in the Markdown execution context.
|
@@ -1040,7 +1216,7 @@ module MarkdownExec
|
|
1040
1216
|
%i[block_name_include_match block_name_wrapper_match])
|
1041
1217
|
|
1042
1218
|
fcb.merge!(
|
1043
|
-
name: indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
|
1219
|
+
name: HashDelegator.indent_all_lines(fcb.dname, fcb.fetch(:indent, nil)),
|
1044
1220
|
label: BlockLabel.make(
|
1045
1221
|
body: fcb[:body],
|
1046
1222
|
filename: @delegate_object[:filename],
|
@@ -1070,10 +1246,7 @@ module MarkdownExec
|
|
1070
1246
|
when :filter
|
1071
1247
|
%i[blocks line]
|
1072
1248
|
when :line
|
1073
|
-
unless @delegate_object[:no_chrome]
|
1074
|
-
create_and_add_chrome_blocks(blocks,
|
1075
|
-
fcb)
|
1076
|
-
end
|
1249
|
+
create_and_add_chrome_blocks(blocks, fcb) unless @delegate_object[:no_chrome]
|
1077
1250
|
end
|
1078
1251
|
end
|
1079
1252
|
|
@@ -1147,7 +1320,7 @@ module MarkdownExec
|
|
1147
1320
|
# @return [LoadFileLinkState] Object indicating the next action for file loading.
|
1148
1321
|
def push_link_history_and_trigger_load(link_block_body, mdoc, selected,
|
1149
1322
|
link_state = LinkState.new)
|
1150
|
-
link_block_data = parse_yaml_data_from_body(link_block_body)
|
1323
|
+
link_block_data = HashDelegator.parse_yaml_data_from_body(link_block_body)
|
1151
1324
|
|
1152
1325
|
# load key and values from link block into current environment
|
1153
1326
|
#
|
@@ -1177,7 +1350,7 @@ module MarkdownExec
|
|
1177
1350
|
# if an eval link block, evaluate code_lines and return its standard output
|
1178
1351
|
#
|
1179
1352
|
if link_block_data.fetch('eval', false)
|
1180
|
-
all_code = code_merge(link_state&.inherited_lines, code_lines)
|
1353
|
+
all_code = HashDelegator.code_merge(link_state&.inherited_lines, code_lines)
|
1181
1354
|
output = `#{all_code.join("\n")}`.split("\n")
|
1182
1355
|
label_format_above = @delegate_object[:shell_code_label_format_above]
|
1183
1356
|
label_format_below = @delegate_object[:shell_code_label_format_below]
|
@@ -1206,7 +1379,7 @@ module MarkdownExec
|
|
1206
1379
|
curr_document_filename: @delegate_object[:filename],
|
1207
1380
|
inherited_block_names: ((link_state&.inherited_block_names || []) + block_names).sort.uniq,
|
1208
1381
|
inherited_dependencies: (link_state&.inherited_dependencies || {}).merge(dependencies || {}), ### merge, not replace, key data
|
1209
|
-
inherited_lines: code_merge(link_state&.inherited_lines, code_lines),
|
1382
|
+
inherited_lines: HashDelegator.code_merge(link_state&.inherited_lines, code_lines),
|
1210
1383
|
next_block_name: link_block_data['block'] || '',
|
1211
1384
|
next_document_filename: next_document_filename,
|
1212
1385
|
next_load_file: next_document_filename == @delegate_object[:filename] ? LoadFile::Reuse : LoadFile::Load
|
@@ -1214,20 +1387,6 @@ module MarkdownExec
|
|
1214
1387
|
end
|
1215
1388
|
end
|
1216
1389
|
|
1217
|
-
# Reads required code blocks from a temporary file specified by an environment variable.
|
1218
|
-
# @return [Array<String>] Lines read from the temporary file, or an empty array if file is not found or path is empty.
|
1219
|
-
def read_required_blocks_from_temp_file(temp_blocks_file_path)
|
1220
|
-
return [] if temp_blocks_file_path.to_s.empty?
|
1221
|
-
|
1222
|
-
if File.exist?(temp_blocks_file_path)
|
1223
|
-
File.readlines(
|
1224
|
-
temp_blocks_file_path, chomp: true
|
1225
|
-
)
|
1226
|
-
else
|
1227
|
-
[]
|
1228
|
-
end
|
1229
|
-
end
|
1230
|
-
|
1231
1390
|
def runtime_exception(exception_sym, name, items)
|
1232
1391
|
if @delegate_object[exception_sym] != 0
|
1233
1392
|
data = { name: name, detail: items.join(', ') }
|
@@ -1247,20 +1406,6 @@ module MarkdownExec
|
|
1247
1406
|
exit @delegate_object[exception_sym]
|
1248
1407
|
end
|
1249
1408
|
|
1250
|
-
def safely_remove_file(path)
|
1251
|
-
FileUtils.rm_f(path)
|
1252
|
-
end
|
1253
|
-
|
1254
|
-
# Evaluates the given string as Ruby code and rescues any StandardErrors.
|
1255
|
-
# If an error occurs, it calls the error_handler method with 'safeval'.
|
1256
|
-
# @param str [String] The string to be evaluated.
|
1257
|
-
# @return [Object] The result of evaluating the string.
|
1258
|
-
def safeval(str)
|
1259
|
-
eval(str)
|
1260
|
-
rescue StandardError
|
1261
|
-
error_handler('safeval')
|
1262
|
-
end
|
1263
|
-
|
1264
1409
|
def save_to_file(required_lines)
|
1265
1410
|
write_command_file(required_lines)
|
1266
1411
|
@fout.fout "File saved: #{@run_state.saved_filespec}"
|
@@ -1272,7 +1417,7 @@ module MarkdownExec
|
|
1272
1417
|
# Markdown document, obtain approval, and execute the chosen block of code.
|
1273
1418
|
#
|
1274
1419
|
# @return [Nil] Returns nil if no code block is selected or an error occurs.
|
1275
|
-
def
|
1420
|
+
def document_menu_loop
|
1276
1421
|
@menu_base_options = @delegate_object
|
1277
1422
|
link_state = LinkState.new(
|
1278
1423
|
block_name: @delegate_object[:block_name],
|
@@ -1280,94 +1425,146 @@ module MarkdownExec
|
|
1280
1425
|
)
|
1281
1426
|
block_name_from_cli = link_state.block_name.present?
|
1282
1427
|
@cli_block_name = link_state.block_name
|
1283
|
-
|
1428
|
+
now_using_cli = block_name_from_cli
|
1284
1429
|
menu_default_dname = nil
|
1285
1430
|
|
1286
1431
|
loop do
|
1287
|
-
loop
|
1288
|
-
|
1289
|
-
|
1290
|
-
@delegate_object[:filename] = link_state.document_filename
|
1291
|
-
link_state.block_name = @delegate_object[:block_name] =
|
1292
|
-
block_name_from_cli ? @cli_block_name : link_state.block_name
|
1293
|
-
|
1294
|
-
# update @delegate_object and @menu_base_options in auto_load
|
1295
|
-
#
|
1296
|
-
blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files
|
1432
|
+
# &bsp 'loop', block_name_from_cli, @cli_block_name
|
1433
|
+
block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc = \
|
1434
|
+
set_delobj_menu_loop_vars(block_name_from_cli, now_using_cli, link_state)
|
1297
1435
|
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1436
|
+
# cli or user selection
|
1437
|
+
#
|
1438
|
+
block_state = load_cli_or_user_selected_block(blocks_in_file, menu_blocks,
|
1439
|
+
menu_default_dname)
|
1440
|
+
# &bsp 'block_name_from_cli:',block_name_from_cli
|
1441
|
+
if !block_state
|
1442
|
+
HashDelegator.error_handler('block_state missing', { abort: true })
|
1443
|
+
elsif block_state.state == MenuState::EXIT
|
1444
|
+
# &bsp 'load_cli_or_user_selected_block -> break'
|
1445
|
+
break
|
1446
|
+
end
|
1304
1447
|
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
warn format_and_highlight_dependencies(
|
1313
|
-
compact_and_index_hash(menu_blocks),
|
1314
|
-
label: 'menu_blocks'
|
1315
|
-
)
|
1316
|
-
end
|
1317
|
-
if @delegate_object[:dump_inherited_lines]
|
1318
|
-
warn format_and_highlight_lines(
|
1319
|
-
link_state.inherited_lines,
|
1320
|
-
label: 'inherited_lines'
|
1321
|
-
)
|
1322
|
-
end
|
1448
|
+
dump_and_warn_block_state(block_state.block)
|
1449
|
+
link_state, menu_default_dname = exec_bash_next_state(block_state.block, mdoc,
|
1450
|
+
link_state)
|
1451
|
+
if prompt_user_exit(block_name_from_cli, block_state.block)
|
1452
|
+
# &bsp 'prompt_user_exit -> break'
|
1453
|
+
break
|
1454
|
+
end
|
1323
1455
|
|
1324
|
-
|
1325
|
-
|
1326
|
-
return if block_state.state == MenuState::EXIT
|
1456
|
+
link_state.block_name, block_name_from_cli, cli_break = \
|
1457
|
+
HashDelegator.next_link_state(!shift_cli_argument, now_using_cli, block_state)
|
1327
1458
|
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1459
|
+
if !block_state.block[:block_name_from_ui] && cli_break
|
1460
|
+
# &bsp '!block_name_from_ui + cli_break -> break'
|
1461
|
+
break
|
1462
|
+
end
|
1463
|
+
end
|
1464
|
+
rescue StandardError
|
1465
|
+
HashDelegator.error_handler('document_menu_loop',
|
1466
|
+
{ abort: true })
|
1467
|
+
end
|
1333
1468
|
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1469
|
+
def exec_bash_next_state(block_state_block, mdoc, link_state)
|
1470
|
+
lfls = execute_shell_type(
|
1471
|
+
block_state_block,
|
1472
|
+
mdoc,
|
1473
|
+
link_state,
|
1474
|
+
block_source: { document_filename: @delegate_object[:filename] }
|
1475
|
+
)
|
1337
1476
|
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
block_source: { document_filename: @delegate_object[:filename] }
|
1343
|
-
)
|
1344
|
-
load_file = load_file_link_state.load_file
|
1345
|
-
link_state = load_file_link_state.link_state
|
1477
|
+
# if the same menu is being displayed, collect the display name of the selected menu item for use as the default item
|
1478
|
+
[lfls.link_state,
|
1479
|
+
lfls.load_file == LoadFile::Load ? nil : block_state_block[:dname]]
|
1480
|
+
end
|
1346
1481
|
|
1347
|
-
|
1348
|
-
|
1482
|
+
def set_delobj_menu_loop_vars(block_name_from_cli, now_using_cli, link_state)
|
1483
|
+
block_name_from_cli, now_using_cli = \
|
1484
|
+
manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
|
1485
|
+
set_delob_filename_block_name(link_state, block_name_from_cli)
|
1349
1486
|
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
@delegate_object[:pause_after_script_execution] &&
|
1355
|
-
prompt_select_continue == MenuState::EXIT
|
1356
|
-
return
|
1357
|
-
end
|
1487
|
+
# update @delegate_object and @menu_base_options in auto_load
|
1488
|
+
#
|
1489
|
+
blocks_in_file, menu_blocks, mdoc = mdoc_menu_and_blocks_from_nested_files(link_state)
|
1490
|
+
dump_delobj(blocks_in_file, menu_blocks, link_state)
|
1358
1491
|
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1492
|
+
[block_name_from_cli, now_using_cli, blocks_in_file, menu_blocks, mdoc]
|
1493
|
+
end
|
1494
|
+
|
1495
|
+
# user prompt to exit if the menu will be displayed again
|
1496
|
+
#
|
1497
|
+
def prompt_user_exit(block_name_from_cli, block_state_block)
|
1498
|
+
!block_name_from_cli &&
|
1499
|
+
block_state_block[:shell] == BlockType::BASH &&
|
1500
|
+
@delegate_object[:pause_after_script_execution] &&
|
1501
|
+
prompt_select_continue == MenuState::EXIT
|
1502
|
+
end
|
1365
1503
|
|
1366
|
-
|
1504
|
+
def manage_cli_selection_state(block_name_from_cli, now_using_cli, link_state)
|
1505
|
+
if block_name_from_cli && @cli_block_name == @menu_base_options[:menu_persist_block_name]
|
1506
|
+
# &bsp 'pause cli control, allow user to select block'
|
1507
|
+
block_name_from_cli = false
|
1508
|
+
now_using_cli = false
|
1509
|
+
@menu_base_options[:block_name] = \
|
1510
|
+
@delegate_object[:block_name] = \
|
1511
|
+
link_state.block_name = \
|
1512
|
+
@cli_block_name = nil
|
1367
1513
|
end
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1514
|
+
|
1515
|
+
@delegate_object = @menu_base_options.dup
|
1516
|
+
@menu_user_clicked_back_link = false
|
1517
|
+
[block_name_from_cli, now_using_cli]
|
1518
|
+
end
|
1519
|
+
|
1520
|
+
# Update the block name in the link state and delegate object.
|
1521
|
+
#
|
1522
|
+
# This method updates the block name based on whether it was specified
|
1523
|
+
# through the CLI or derived from the link state.
|
1524
|
+
#
|
1525
|
+
# @param link_state [LinkState] The current link state object.
|
1526
|
+
# @param block_name_from_cli [Boolean] Indicates if the block name is from CLI.
|
1527
|
+
def set_delob_filename_block_name(link_state, block_name_from_cli)
|
1528
|
+
@delegate_object[:filename] = link_state.document_filename
|
1529
|
+
link_state.block_name = @delegate_object[:block_name] =
|
1530
|
+
block_name_from_cli ? @cli_block_name : link_state.block_name
|
1531
|
+
end
|
1532
|
+
|
1533
|
+
# Outputs warnings based on the delegate object's configuration
|
1534
|
+
#
|
1535
|
+
# @param delegate_object [Hash] The delegate object containing configuration flags.
|
1536
|
+
# @param blocks_in_file [Hash] Hash of blocks present in the file.
|
1537
|
+
# @param menu_blocks [Hash] Hash of menu blocks.
|
1538
|
+
# @param link_state [LinkState] Current state of the link.
|
1539
|
+
def dump_delobj(blocks_in_file, menu_blocks, link_state)
|
1540
|
+
if @delegate_object[:dump_delegate_object]
|
1541
|
+
warn format_and_highlight_hash(@delegate_object, label: '@delegate_object')
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
if @delegate_object[:dump_blocks_in_file]
|
1545
|
+
warn format_and_highlight_dependencies(compact_and_index_hash(blocks_in_file),
|
1546
|
+
label: 'blocks_in_file')
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
if @delegate_object[:dump_menu_blocks]
|
1550
|
+
warn format_and_highlight_dependencies(compact_and_index_hash(menu_blocks),
|
1551
|
+
label: 'menu_blocks')
|
1552
|
+
end
|
1553
|
+
|
1554
|
+
return unless @delegate_object[:dump_inherited_lines]
|
1555
|
+
|
1556
|
+
warn format_and_highlight_lines(link_state.inherited_lines, label: 'inherited_lines')
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
def dump_and_warn_block_state(block_state_block)
|
1560
|
+
if block_state_block.nil?
|
1561
|
+
Exceptions.warn_format("Block not found -- name: #{@delegate_object[:block_name]}",
|
1562
|
+
{ abort: true })
|
1563
|
+
end
|
1564
|
+
|
1565
|
+
return unless @delegate_object[:dump_selected_block]
|
1566
|
+
|
1567
|
+
warn block_state_block.to_yaml.sub(/^(?:---\n)?/, "Block:\n")
|
1371
1568
|
end
|
1372
1569
|
|
1373
1570
|
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
|
@@ -1393,7 +1590,7 @@ module MarkdownExec
|
|
1393
1590
|
rescue TTY::Reader::InputInterrupt
|
1394
1591
|
exit 1
|
1395
1592
|
rescue StandardError
|
1396
|
-
error_handler('select_option_with_metadata')
|
1593
|
+
HashDelegator.error_handler('select_option_with_metadata')
|
1397
1594
|
end
|
1398
1595
|
|
1399
1596
|
def set_environment_variables_for_block(selected)
|
@@ -1407,22 +1604,8 @@ module MarkdownExec
|
|
1407
1604
|
end
|
1408
1605
|
end
|
1409
1606
|
|
1410
|
-
def set_environment_variables_per_array(vars)
|
1411
|
-
vars ||= []
|
1412
|
-
vars.each { |key, value| ENV[key] = value.to_s }
|
1413
|
-
end
|
1414
|
-
|
1415
|
-
def set_file_permissions(file_path, chmod_value)
|
1416
|
-
File.chmod(chmod_value, file_path)
|
1417
|
-
end
|
1418
|
-
|
1419
|
-
def set_script_block_name(selected)
|
1420
|
-
@run_state.script_block_name = selected[:oname]
|
1421
|
-
end
|
1422
|
-
|
1423
1607
|
def should_add_back_option?
|
1424
1608
|
@delegate_object[:menu_with_back] && @link_history.prior_state_exist?
|
1425
|
-
# @delegate_object[:menu_with_back] && link_history_prior_state_exist?
|
1426
1609
|
end
|
1427
1610
|
|
1428
1611
|
# Initializes a new fenced code block (FCB) object based on the provided line and heading information.
|
@@ -1466,44 +1649,8 @@ module MarkdownExec
|
|
1466
1649
|
# @param color_sym [Symbol] The symbol representing the color method.
|
1467
1650
|
# @param default [String] Default color method to use if color_sym is not found in @delegate_object.
|
1468
1651
|
# @return [String] The string with the applied color method.
|
1469
|
-
def string_send_color(string, color_sym
|
1470
|
-
|
1471
|
-
string.to_s.send(color_method)
|
1472
|
-
end
|
1473
|
-
|
1474
|
-
# Creates a TTY prompt with custom settings. Specifically, it disables the default 'cross' symbol and
|
1475
|
-
# defines a lambda function to handle interrupts.
|
1476
|
-
# @return [TTY::Prompt] A new TTY::Prompt instance with specified configurations.
|
1477
|
-
def tty_prompt_without_disabled_symbol
|
1478
|
-
TTY::Prompt.new(
|
1479
|
-
interrupt: lambda {
|
1480
|
-
puts
|
1481
|
-
raise TTY::Reader::InputInterrupt
|
1482
|
-
},
|
1483
|
-
symbols: { cross: ' ' }
|
1484
|
-
)
|
1485
|
-
end
|
1486
|
-
|
1487
|
-
# Updates the hierarchy of document headings based on the given line.
|
1488
|
-
# Utilizes regular expressions to identify heading levels.
|
1489
|
-
# @param line [String] The line of text to check for headings.
|
1490
|
-
# @param headings [Array<String>] Current headings hierarchy.
|
1491
|
-
# @return [Array<String>] Updated headings hierarchy.
|
1492
|
-
def update_document_headings(line, headings)
|
1493
|
-
heading3_match = Regexp.new(@delegate_object[:heading3_match])
|
1494
|
-
heading2_match = Regexp.new(@delegate_object[:heading2_match])
|
1495
|
-
heading1_match = Regexp.new(@delegate_object[:heading1_match])
|
1496
|
-
|
1497
|
-
case line
|
1498
|
-
when heading3_match
|
1499
|
-
[headings[0], headings[1], $~[:name]]
|
1500
|
-
when heading2_match
|
1501
|
-
[headings[0], $~[:name]]
|
1502
|
-
when heading1_match
|
1503
|
-
[$~[:name]]
|
1504
|
-
else
|
1505
|
-
headings
|
1506
|
-
end
|
1652
|
+
def string_send_color(string, color_sym)
|
1653
|
+
HashDelegator.apply_color_from_hash(string, @delegate_object, color_sym)
|
1507
1654
|
end
|
1508
1655
|
|
1509
1656
|
##
|
@@ -1528,16 +1675,12 @@ module MarkdownExec
|
|
1528
1675
|
def update_line_and_block_state(nested_line, state, selected_messages,
|
1529
1676
|
&block)
|
1530
1677
|
line = nested_line.to_s
|
1531
|
-
if @delegate_object[:menu_blocks_with_headings]
|
1532
|
-
state[:headings] = update_document_headings(line, state[:headings])
|
1533
|
-
end
|
1534
|
-
|
1535
1678
|
if line.match(@delegate_object[:fenced_start_and_end_regex])
|
1536
1679
|
if state[:in_fenced_block]
|
1537
1680
|
## end of code block
|
1538
1681
|
#
|
1539
|
-
update_menu_attrib_yield_selected(state[:fcb], selected_messages,
|
1540
|
-
|
1682
|
+
HashDelegator.update_menu_attrib_yield_selected(state[:fcb], selected_messages, @delegate_object,
|
1683
|
+
&block)
|
1541
1684
|
state[:in_fenced_block] = false
|
1542
1685
|
else
|
1543
1686
|
## start of code block
|
@@ -1558,29 +1701,14 @@ module MarkdownExec
|
|
1558
1701
|
elsif nested_line[:depth].zero? || @delegate_object[:menu_include_imported_notes]
|
1559
1702
|
# add line if it is depth 0 or option allows it
|
1560
1703
|
#
|
1561
|
-
yield_line_if_selected(line, selected_messages, &block)
|
1704
|
+
HashDelegator.yield_line_if_selected(line, selected_messages, &block)
|
1562
1705
|
|
1563
1706
|
else
|
1564
|
-
# '
|
1707
|
+
# &bsp 'line is not recognized for block state'
|
1565
1708
|
|
1566
1709
|
end
|
1567
1710
|
end
|
1568
1711
|
|
1569
|
-
# Updates the attributes of the given fcb object and conditionally yields to a block.
|
1570
|
-
# It initializes fcb names and sets the default block title from fcb's body.
|
1571
|
-
# If the fcb has a body and meets certain conditions, it yields to the given block.
|
1572
|
-
#
|
1573
|
-
# @param fcb [Object] The fcb object whose attributes are to be updated.
|
1574
|
-
# @param selected_messages [Array<Symbol>] A list of message types to determine if yielding is applicable.
|
1575
|
-
# @param block [Block] An optional block to yield to if conditions are met.
|
1576
|
-
def update_menu_attrib_yield_selected(fcb, selected_messages, &block)
|
1577
|
-
initialize_fcb_names(fcb)
|
1578
|
-
return unless fcb.body
|
1579
|
-
|
1580
|
-
default_block_title_from_body(fcb)
|
1581
|
-
yield_to_block_if_applicable(fcb, selected_messages, &block)
|
1582
|
-
end
|
1583
|
-
|
1584
1712
|
# Processes YAML data from the selected menu item, updating delegate objects and optionally printing formatted output.
|
1585
1713
|
# @param selected [Hash] Selected item from the menu containing a YAML body.
|
1586
1714
|
# @param tgt2 [Hash, nil] An optional target hash to update with YAML data.
|
@@ -1610,10 +1738,10 @@ module MarkdownExec
|
|
1610
1738
|
|
1611
1739
|
def wait_for_user_selected_block(all_blocks, menu_blocks, default)
|
1612
1740
|
block_state = wait_for_user_selection(all_blocks, menu_blocks, default)
|
1613
|
-
|
1741
|
+
handle_back_or_continue(block_state)
|
1614
1742
|
block_state
|
1615
1743
|
rescue StandardError
|
1616
|
-
error_handler('wait_for_user_selected_block')
|
1744
|
+
HashDelegator.error_handler('wait_for_user_selected_block')
|
1617
1745
|
end
|
1618
1746
|
|
1619
1747
|
def wait_for_user_selection(_all_blocks, menu_blocks, default)
|
@@ -1664,32 +1792,13 @@ module MarkdownExec
|
|
1664
1792
|
"# time: #{time_now}\n" \
|
1665
1793
|
"#{required_lines.flatten.join("\n")}\n"
|
1666
1794
|
|
1667
|
-
create_file_and_write_string_with_permissions(
|
1795
|
+
HashDelegator.create_file_and_write_string_with_permissions(
|
1668
1796
|
@run_state.saved_filespec,
|
1669
1797
|
content,
|
1670
1798
|
@delegate_object[:saved_script_chmod]
|
1671
1799
|
)
|
1672
1800
|
rescue StandardError
|
1673
|
-
error_handler('write_command_file')
|
1674
|
-
end
|
1675
|
-
|
1676
|
-
def save_executed_script_if_specified(lines)
|
1677
|
-
write_command_file(lines) if @delegate_object[:save_executed_script]
|
1678
|
-
end
|
1679
|
-
|
1680
|
-
def write_execution_output_to_file
|
1681
|
-
FileUtils.mkdir_p File.dirname(@delegate_object[:logged_stdout_filespec])
|
1682
|
-
|
1683
|
-
File.write(
|
1684
|
-
@delegate_object[:logged_stdout_filespec],
|
1685
|
-
["-STDOUT-\n",
|
1686
|
-
format_execution_streams(ExecutionStreams::StdOut),
|
1687
|
-
"-STDERR-\n",
|
1688
|
-
format_execution_streams(ExecutionStreams::StdErr),
|
1689
|
-
"-STDIN-\n",
|
1690
|
-
format_execution_streams(ExecutionStreams::StdIn),
|
1691
|
-
"\n"].join
|
1692
|
-
)
|
1801
|
+
HashDelegator.error_handler('write_command_file')
|
1693
1802
|
end
|
1694
1803
|
|
1695
1804
|
# Writes required code blocks to a temporary file and sets an environment variable with its path.
|
@@ -1707,40 +1816,10 @@ module MarkdownExec
|
|
1707
1816
|
[]
|
1708
1817
|
end
|
1709
1818
|
|
1710
|
-
code_blocks = (read_required_blocks_from_temp_file(import_filename) +
|
1819
|
+
code_blocks = (HashDelegator.read_required_blocks_from_temp_file(import_filename) +
|
1711
1820
|
c1).join("\n")
|
1712
1821
|
|
1713
|
-
write_code_to_file(code_blocks, temp_file_path)
|
1714
|
-
end
|
1715
|
-
|
1716
|
-
# Writes the provided code blocks to a file.
|
1717
|
-
# @param code_blocks [String] Code blocks to write into the file.
|
1718
|
-
def write_code_to_file(content, path)
|
1719
|
-
File.write(path, content)
|
1720
|
-
end
|
1721
|
-
|
1722
|
-
# Yields a line as a new block if the selected message type includes :line.
|
1723
|
-
# @param [String] line The line to be processed.
|
1724
|
-
# @param [Array<Symbol>] selected_messages A list of message types to check.
|
1725
|
-
# @param [Proc] block The block to be called with the line data.
|
1726
|
-
def yield_line_if_selected(line, selected_messages, &block)
|
1727
|
-
return unless block && selected_messages.include?(:line)
|
1728
|
-
|
1729
|
-
block.call(:line, FCB.new(body: [line]))
|
1730
|
-
end
|
1731
|
-
|
1732
|
-
# Yields to the provided block with specified parameters if certain conditions are met.
|
1733
|
-
# The method checks if a block is given, if the selected_messages include :blocks,
|
1734
|
-
# and if the fcb_select? method from MarkdownExec::Filter returns true for the given fcb.
|
1735
|
-
#
|
1736
|
-
# @param fcb [Object] The object to be evaluated and potentially passed to the block.
|
1737
|
-
# @param selected_messages [Array<Symbol>] A collection of message types, one of which must be :blocks.
|
1738
|
-
# @param block [Block] A block to be called if conditions are met.
|
1739
|
-
def yield_to_block_if_applicable(fcb, selected_messages, &block)
|
1740
|
-
if block_given? && selected_messages.include?(:blocks) &&
|
1741
|
-
MarkdownExec::Filter.fcb_select?(@delegate_object, fcb)
|
1742
|
-
block.call :blocks, fcb
|
1743
|
-
end
|
1822
|
+
HashDelegator.write_code_to_file(code_blocks, temp_file_path)
|
1744
1823
|
end
|
1745
1824
|
end
|
1746
1825
|
end
|
@@ -1810,30 +1889,30 @@ if $PROGRAM_NAME == __FILE__
|
|
1810
1889
|
body = "Line 1\nLine 2"
|
1811
1890
|
indent = ' ' # Two spaces
|
1812
1891
|
expected_result = " Line 1\n Line 2"
|
1813
|
-
assert_equal expected_result,
|
1892
|
+
assert_equal expected_result, HashDelegator.indent_all_lines(body, indent)
|
1814
1893
|
end
|
1815
1894
|
|
1816
1895
|
def test_indent_all_lines_without_indent
|
1817
1896
|
body = "Line 1\nLine 2"
|
1818
1897
|
indent = nil
|
1819
1898
|
|
1820
|
-
assert_equal body,
|
1899
|
+
assert_equal body, HashDelegator.indent_all_lines(body, indent)
|
1821
1900
|
end
|
1822
1901
|
|
1823
1902
|
def test_indent_all_lines_with_empty_indent
|
1824
1903
|
body = "Line 1\nLine 2"
|
1825
1904
|
indent = ''
|
1826
1905
|
|
1827
|
-
assert_equal body,
|
1906
|
+
assert_equal body, HashDelegator.indent_all_lines(body, indent)
|
1828
1907
|
end
|
1829
1908
|
|
1830
1909
|
def test_safeval_successful_evaluation
|
1831
|
-
assert_equal 4,
|
1910
|
+
assert_equal 4, HashDelegator.safeval('2 + 2')
|
1832
1911
|
end
|
1833
1912
|
|
1834
1913
|
def test_safeval_rescue_from_error
|
1835
|
-
|
1836
|
-
assert_nil
|
1914
|
+
HashDelegator.stubs(:error_handler).with('safeval')
|
1915
|
+
assert_nil HashDelegator.safeval('invalid code')
|
1837
1916
|
end
|
1838
1917
|
|
1839
1918
|
def test_set_fcb_title
|
@@ -1861,7 +1940,7 @@ if $PROGRAM_NAME == __FILE__
|
|
1861
1940
|
input_output_data.each do |data|
|
1862
1941
|
input = data[:input]
|
1863
1942
|
output = data[:output]
|
1864
|
-
|
1943
|
+
HashDelegator.default_block_title_from_body(input)
|
1865
1944
|
assert_equal output, input.title
|
1866
1945
|
end
|
1867
1946
|
end
|
@@ -1876,7 +1955,7 @@ if $PROGRAM_NAME == __FILE__
|
|
1876
1955
|
menu_divider_color: :color
|
1877
1956
|
})
|
1878
1957
|
@hd.stubs(:string_send_color).returns('Formatted Divider')
|
1879
|
-
|
1958
|
+
HashDelegator.stubs(:safeval).returns('Safe Value')
|
1880
1959
|
end
|
1881
1960
|
|
1882
1961
|
def test_append_divider_initial
|
@@ -1911,19 +1990,19 @@ if $PROGRAM_NAME == __FILE__
|
|
1911
1990
|
|
1912
1991
|
def test_block_find_with_match
|
1913
1992
|
blocks = [{ key: 'value1' }, { key: 'value2' }]
|
1914
|
-
result =
|
1993
|
+
result = HashDelegator.block_find(blocks, :key, 'value1')
|
1915
1994
|
assert_equal({ key: 'value1' }, result)
|
1916
1995
|
end
|
1917
1996
|
|
1918
1997
|
def test_block_find_without_match
|
1919
1998
|
blocks = [{ key: 'value1' }, { key: 'value2' }]
|
1920
|
-
result =
|
1999
|
+
result = HashDelegator.block_find(blocks, :key, 'value3')
|
1921
2000
|
assert_nil result
|
1922
2001
|
end
|
1923
2002
|
|
1924
2003
|
def test_block_find_with_default
|
1925
2004
|
blocks = [{ key: 'value1' }, { key: 'value2' }]
|
1926
|
-
result =
|
2005
|
+
result = HashDelegator.block_find(blocks, :key, 'value3', 'default')
|
1927
2006
|
assert_equal 'default', result
|
1928
2007
|
end
|
1929
2008
|
end
|
@@ -1935,7 +2014,7 @@ if $PROGRAM_NAME == __FILE__
|
|
1935
2014
|
@hd.stubs(:get_block_summary).returns(FCB.new)
|
1936
2015
|
@hd.stubs(:create_and_add_chrome_blocks)
|
1937
2016
|
@hd.instance_variable_set(:@delegate_object, {})
|
1938
|
-
|
2017
|
+
HashDelegator.stubs(:error_handler)
|
1939
2018
|
end
|
1940
2019
|
|
1941
2020
|
def test_blocks_from_nested_files
|
@@ -1961,7 +2040,7 @@ if $PROGRAM_NAME == __FILE__
|
|
1961
2040
|
@hd.instance_variable_set(:@delegate_object, {})
|
1962
2041
|
@mdoc = mock('YourMDocClass')
|
1963
2042
|
@selected = { shell: BlockType::VARS, body: ['key: value'] }
|
1964
|
-
|
2043
|
+
HashDelegator.stubs(:read_required_blocks_from_temp_file).returns([])
|
1965
2044
|
@hd.stubs(:string_send_color)
|
1966
2045
|
@hd.stubs(:print)
|
1967
2046
|
end
|
@@ -1979,7 +2058,7 @@ if $PROGRAM_NAME == __FILE__
|
|
1979
2058
|
def setup
|
1980
2059
|
@hd = HashDelegator.new
|
1981
2060
|
@hd.instance_variable_set(:@delegate_object, {})
|
1982
|
-
|
2061
|
+
HashDelegator.stubs(:error_handler)
|
1983
2062
|
@hd.stubs(:wait_for_user_selected_block)
|
1984
2063
|
end
|
1985
2064
|
|
@@ -1988,9 +2067,9 @@ if $PROGRAM_NAME == __FILE__
|
|
1988
2067
|
@hd.instance_variable_set(:@delegate_object,
|
1989
2068
|
{ block_name: 'block1' })
|
1990
2069
|
|
1991
|
-
result = @hd.
|
2070
|
+
result = @hd.load_cli_or_user_selected_block(all_blocks, [], nil)
|
1992
2071
|
|
1993
|
-
assert_equal all_blocks.first, result.block
|
2072
|
+
assert_equal all_blocks.first.merge(block_name_from_ui: false), result.block
|
1994
2073
|
assert_nil result.state
|
1995
2074
|
end
|
1996
2075
|
|
@@ -1999,9 +2078,9 @@ if $PROGRAM_NAME == __FILE__
|
|
1999
2078
|
:some_state)
|
2000
2079
|
@hd.stubs(:wait_for_user_selected_block).returns(block_state)
|
2001
2080
|
|
2002
|
-
result = @hd.
|
2081
|
+
result = @hd.load_cli_or_user_selected_block([], [], nil)
|
2003
2082
|
|
2004
|
-
assert_equal block_state.block, result.block
|
2083
|
+
assert_equal block_state.block.merge(block_name_from_ui: true), result.block
|
2005
2084
|
assert_equal :some_state, result.state
|
2006
2085
|
end
|
2007
2086
|
end
|
@@ -2038,7 +2117,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2038
2117
|
class TestHashDelegatorCreateAndWriteFile < Minitest::Test
|
2039
2118
|
def setup
|
2040
2119
|
@hd = HashDelegator.new
|
2041
|
-
|
2120
|
+
HashDelegator.stubs(:error_handler)
|
2042
2121
|
FileUtils.stubs(:mkdir_p)
|
2043
2122
|
File.stubs(:write)
|
2044
2123
|
File.stubs(:chmod)
|
@@ -2053,8 +2132,8 @@ if $PROGRAM_NAME == __FILE__
|
|
2053
2132
|
File.expects(:write).with(file_path, content).once
|
2054
2133
|
File.expects(:chmod).with(chmod_value, file_path).once
|
2055
2134
|
|
2056
|
-
|
2057
|
-
|
2135
|
+
HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
|
2136
|
+
chmod_value)
|
2058
2137
|
|
2059
2138
|
assert true # Placeholder for actual test assertions
|
2060
2139
|
end
|
@@ -2068,8 +2147,8 @@ if $PROGRAM_NAME == __FILE__
|
|
2068
2147
|
File.expects(:write).with(file_path, content).once
|
2069
2148
|
File.expects(:chmod).never
|
2070
2149
|
|
2071
|
-
|
2072
|
-
|
2150
|
+
HashDelegator.create_file_and_write_string_with_permissions(file_path, content,
|
2151
|
+
chmod_value)
|
2073
2152
|
|
2074
2153
|
assert true # Placeholder for actual test assertions
|
2075
2154
|
end
|
@@ -2200,11 +2279,8 @@ if $PROGRAM_NAME == __FILE__
|
|
2200
2279
|
end
|
2201
2280
|
|
2202
2281
|
def test_format_execution_streams_with_valid_key
|
2203
|
-
|
2204
|
-
|
2205
|
-
] })
|
2206
|
-
|
2207
|
-
result = @hd.format_execution_streams(:stdout)
|
2282
|
+
result = HashDelegator.format_execution_streams(:stdout,
|
2283
|
+
{ stdout: %w[output1 output2] })
|
2208
2284
|
|
2209
2285
|
assert_equal 'output1output2', result
|
2210
2286
|
end
|
@@ -2212,7 +2288,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2212
2288
|
def test_format_execution_streams_with_empty_key
|
2213
2289
|
@hd.instance_variable_get(:@run_state).stubs(:files).returns({})
|
2214
2290
|
|
2215
|
-
result =
|
2291
|
+
result = HashDelegator.format_execution_streams(:stderr)
|
2216
2292
|
|
2217
2293
|
assert_equal '', result
|
2218
2294
|
end
|
@@ -2220,7 +2296,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2220
2296
|
def test_format_execution_streams_with_nil_files
|
2221
2297
|
@hd.instance_variable_get(:@run_state).stubs(:files).returns(nil)
|
2222
2298
|
|
2223
|
-
result =
|
2299
|
+
result = HashDelegator.format_execution_streams(:stdin)
|
2224
2300
|
|
2225
2301
|
assert_equal '', result
|
2226
2302
|
end
|
@@ -2251,33 +2327,33 @@ if $PROGRAM_NAME == __FILE__
|
|
2251
2327
|
@mock_block_state = mock('block_state')
|
2252
2328
|
end
|
2253
2329
|
|
2254
|
-
def
|
2330
|
+
def test_handle_back_or_continue_with_back
|
2255
2331
|
@mock_block_state.stubs(:state).returns(MenuState::BACK)
|
2256
2332
|
@mock_block_state.stubs(:block).returns({ oname: 'sample_block' })
|
2257
2333
|
|
2258
|
-
@hd.
|
2334
|
+
@hd.handle_back_or_continue(@mock_block_state)
|
2259
2335
|
|
2260
2336
|
assert_equal 'sample_block',
|
2261
2337
|
@hd.instance_variable_get(:@delegate_object)[:block_name]
|
2262
2338
|
assert @hd.instance_variable_get(:@menu_user_clicked_back_link)
|
2263
2339
|
end
|
2264
2340
|
|
2265
|
-
def
|
2341
|
+
def test_handle_back_or_continue_with_continue
|
2266
2342
|
@mock_block_state.stubs(:state).returns(MenuState::CONTINUE)
|
2267
2343
|
@mock_block_state.stubs(:block).returns({ oname: 'another_block' })
|
2268
2344
|
|
2269
|
-
@hd.
|
2345
|
+
@hd.handle_back_or_continue(@mock_block_state)
|
2270
2346
|
|
2271
2347
|
assert_equal 'another_block',
|
2272
2348
|
@hd.instance_variable_get(:@delegate_object)[:block_name]
|
2273
2349
|
refute @hd.instance_variable_get(:@menu_user_clicked_back_link)
|
2274
2350
|
end
|
2275
2351
|
|
2276
|
-
def
|
2352
|
+
def test_handle_back_or_continue_with_other
|
2277
2353
|
@mock_block_state.stubs(:state).returns(nil) # MenuState::OTHER
|
2278
2354
|
@mock_block_state.stubs(:block).returns({ oname: 'other_block' })
|
2279
2355
|
|
2280
|
-
@hd.
|
2356
|
+
@hd.handle_back_or_continue(@mock_block_state)
|
2281
2357
|
|
2282
2358
|
assert_nil @hd.instance_variable_get(:@delegate_object)[:block_name]
|
2283
2359
|
assert_nil @hd.instance_variable_get(:@menu_user_clicked_back_link)
|
@@ -2291,7 +2367,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2291
2367
|
@selected_item = mock('FCB')
|
2292
2368
|
end
|
2293
2369
|
|
2294
|
-
def
|
2370
|
+
def test_compile_execute_and_trigger_reuse_without_user_approval
|
2295
2371
|
# Mock the delegate object configuration
|
2296
2372
|
@hd.instance_variable_set(:@delegate_object,
|
2297
2373
|
{ output_script: false,
|
@@ -2301,7 +2377,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2301
2377
|
# Expectations and assertions go here
|
2302
2378
|
end
|
2303
2379
|
|
2304
|
-
def
|
2380
|
+
def test_compile_execute_and_trigger_reuse_with_user_approval
|
2305
2381
|
# Mock the delegate object configuration
|
2306
2382
|
@hd.instance_variable_set(:@delegate_object,
|
2307
2383
|
{ output_script: false,
|
@@ -2311,7 +2387,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2311
2387
|
# Expectations and assertions go here
|
2312
2388
|
end
|
2313
2389
|
|
2314
|
-
def
|
2390
|
+
def test_compile_execute_and_trigger_reuse_with_output_script
|
2315
2391
|
# Mock the delegate object configuration
|
2316
2392
|
@hd.instance_variable_set(:@delegate_object,
|
2317
2393
|
{ output_script: true,
|
@@ -2422,7 +2498,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2422
2498
|
menu_option_back_name: "'Back'",
|
2423
2499
|
menu_chrome_format: '-- %s --'
|
2424
2500
|
})
|
2425
|
-
|
2501
|
+
HashDelegator.stubs(:safeval).with("'Back'").returns('Back')
|
2426
2502
|
end
|
2427
2503
|
|
2428
2504
|
def test_menu_chrome_formatted_option_with_format
|
@@ -2477,7 +2553,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2477
2553
|
|
2478
2554
|
def test_yield_line_if_selected_with_line
|
2479
2555
|
block_called = false
|
2480
|
-
|
2556
|
+
HashDelegator.yield_line_if_selected('Test line', [:line]) do |type, content|
|
2481
2557
|
block_called = true
|
2482
2558
|
assert_equal :line, type
|
2483
2559
|
assert_equal 'Test line', content.body[0]
|
@@ -2487,70 +2563,47 @@ if $PROGRAM_NAME == __FILE__
|
|
2487
2563
|
|
2488
2564
|
def test_yield_line_if_selected_without_line
|
2489
2565
|
block_called = false
|
2490
|
-
|
2566
|
+
HashDelegator.yield_line_if_selected('Test line', [:other]) do |_|
|
2491
2567
|
block_called = true
|
2492
2568
|
end
|
2493
2569
|
refute block_called
|
2494
2570
|
end
|
2495
2571
|
|
2496
2572
|
def test_yield_line_if_selected_without_block
|
2497
|
-
result =
|
2573
|
+
result = HashDelegator.yield_line_if_selected('Test line', [:line])
|
2498
2574
|
assert_nil result
|
2499
2575
|
end
|
2500
2576
|
end
|
2501
2577
|
|
2502
|
-
class TestHashDelegator < Minitest::Test
|
2503
|
-
def setup
|
2504
|
-
@hd = HashDelegator.new
|
2505
|
-
@hd.instance_variable_set(:@delegate_object, {
|
2506
|
-
heading1_match: '^# (?<name>.+)$',
|
2507
|
-
heading2_match: '^## (?<name>.+)$',
|
2508
|
-
heading3_match: '^### (?<name>.+)$'
|
2509
|
-
})
|
2510
|
-
end
|
2511
|
-
|
2512
|
-
def test_update_document_headings
|
2513
|
-
assert_equal(['Heading 1'],
|
2514
|
-
@hd.update_document_headings('# Heading 1', []))
|
2515
|
-
assert_equal(['Heading 1', 'Heading 2'],
|
2516
|
-
@hd.update_document_headings('## Heading 2',
|
2517
|
-
['Heading 1']))
|
2518
|
-
assert_equal(['Heading 1', 'Heading 2', 'Heading 3'],
|
2519
|
-
@hd.update_document_headings('### Heading 3',
|
2520
|
-
['Heading 1', 'Heading 2']))
|
2521
|
-
assert_equal([], @hd.update_document_headings('Regular text', []))
|
2522
|
-
end
|
2523
|
-
end
|
2524
|
-
|
2525
2578
|
class TestHashDelegatorUpdateMenuAttribYieldSelectedWithBody < Minitest::Test
|
2526
2579
|
def setup
|
2527
2580
|
@hd = HashDelegator.new
|
2528
2581
|
@fcb = mock('Fcb')
|
2529
2582
|
@fcb.stubs(:body).returns(true)
|
2530
|
-
|
2531
|
-
|
2532
|
-
|
2583
|
+
HashDelegator.stubs(:initialize_fcb_names)
|
2584
|
+
HashDelegator.stubs(:default_block_title_from_body)
|
2585
|
+
Filter.stubs(:yield_to_block_if_applicable)
|
2533
2586
|
end
|
2534
2587
|
|
2535
2588
|
def test_update_menu_attrib_yield_selected_with_body
|
2536
|
-
|
2537
|
-
|
2538
|
-
|
2589
|
+
HashDelegator.expects(:initialize_fcb_names).with(@fcb)
|
2590
|
+
HashDelegator.expects(:default_block_title_from_body).with(@fcb)
|
2591
|
+
Filter.expects(:yield_to_block_if_applicable).with(@fcb, [:some_message], {})
|
2539
2592
|
|
2540
|
-
|
2593
|
+
HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
|
2541
2594
|
end
|
2542
2595
|
|
2543
2596
|
def test_update_menu_attrib_yield_selected_without_body
|
2544
2597
|
@fcb.stubs(:body).returns(nil)
|
2545
|
-
|
2546
|
-
|
2598
|
+
HashDelegator.expects(:initialize_fcb_names).with(@fcb)
|
2599
|
+
HashDelegator.update_menu_attrib_yield_selected(@fcb, [:some_message])
|
2547
2600
|
end
|
2548
2601
|
end
|
2549
2602
|
|
2550
2603
|
class TestHashDelegatorWaitForUserSelectedBlock < Minitest::Test
|
2551
2604
|
def setup
|
2552
2605
|
@hd = HashDelegator.new
|
2553
|
-
|
2606
|
+
HashDelegator.stubs(:error_handler)
|
2554
2607
|
end
|
2555
2608
|
|
2556
2609
|
def test_wait_for_user_selected_block_with_back_state
|
@@ -2592,7 +2645,7 @@ if $PROGRAM_NAME == __FILE__
|
|
2592
2645
|
|
2593
2646
|
def test_yield_to_block_if_applicable_with_correct_conditions
|
2594
2647
|
block_called = false
|
2595
|
-
|
2648
|
+
Filter.yield_to_block_if_applicable(@fcb, [:blocks]) do |type, fcb|
|
2596
2649
|
block_called = true
|
2597
2650
|
assert_equal :blocks, type
|
2598
2651
|
assert_equal @fcb, fcb
|
@@ -2601,14 +2654,14 @@ if $PROGRAM_NAME == __FILE__
|
|
2601
2654
|
end
|
2602
2655
|
|
2603
2656
|
def test_yield_to_block_if_applicable_without_block
|
2604
|
-
result =
|
2657
|
+
result = Filter.yield_to_block_if_applicable(@fcb, [:blocks])
|
2605
2658
|
assert_nil result
|
2606
2659
|
end
|
2607
2660
|
|
2608
2661
|
def test_yield_to_block_if_applicable_with_incorrect_conditions
|
2609
2662
|
block_called = false
|
2610
2663
|
MarkdownExec::Filter.stubs(:fcb_select?).returns(false)
|
2611
|
-
|
2664
|
+
Filter.yield_to_block_if_applicable(@fcb, [:non_blocks]) do |_|
|
2612
2665
|
block_called = true
|
2613
2666
|
end
|
2614
2667
|
refute block_called
|