markdown_exec 3.1.1 → 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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +3 -3
  5. data/bats/block-type-ux-auto.bats +1 -1
  6. data/bats/block-type-ux-default.bats +1 -1
  7. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  8. data/bats/block-type-ux-echo-hash.bats +2 -2
  9. data/bats/block-type-ux-exec-hash-transform.bats +8 -0
  10. data/bats/block-type-ux-exec-hash.bats +15 -0
  11. data/bats/block-type-ux-exec.bats +1 -1
  12. data/bats/block-type-ux-force.bats +9 -0
  13. data/bats/block-type-ux-formats.bats +8 -0
  14. data/bats/block-type-ux-readonly.bats +1 -1
  15. data/bats/block-type-ux-row-format.bats +1 -1
  16. data/bats/block-type-ux-transform.bats +1 -1
  17. data/bats/import-directive-parameter-symbols.bats +9 -0
  18. data/bats/import-duplicates.bats +4 -2
  19. data/bats/import-parameter-symbols.bats +8 -0
  20. data/bats/markup.bats +1 -1
  21. data/bats/options.bats +1 -1
  22. data/bin/tab_completion.sh +5 -1
  23. data/docs/dev/block-type-ux-echo-hash-transform.md +14 -12
  24. data/docs/dev/block-type-ux-exec-hash-transform.md +37 -0
  25. data/docs/dev/block-type-ux-exec-hash.md +93 -0
  26. data/docs/dev/block-type-ux-force.md +20 -0
  27. data/docs/dev/block-type-ux-formats.md +58 -0
  28. data/docs/dev/hexdump_format.md +267 -0
  29. data/docs/dev/import/parameter-symbols.md +6 -0
  30. data/docs/dev/import-directive-parameter-symbols.md +9 -0
  31. data/docs/dev/import-parameter-symbols-template.md +24 -0
  32. data/docs/dev/import-parameter-symbols.md +6 -0
  33. data/docs/dev/load-vars-state-demo.md +35 -0
  34. data/docs/ux-blocks-examples.md +3 -3
  35. data/examples/import_with_substitution_demo.md +130 -26
  36. data/examples/imports/organism_template.md +86 -29
  37. data/lib/cached_nested_file_reader.rb +265 -27
  38. data/lib/constants.rb +8 -1
  39. data/lib/env_interface.rb +13 -7
  40. data/lib/evaluate_shell_expressions.rb +1 -0
  41. data/lib/fcb.rb +123 -19
  42. data/lib/format_table.rb +56 -23
  43. data/lib/fout.rb +5 -0
  44. data/lib/hash_delegator.rb +1196 -337
  45. data/lib/markdown_exec/version.rb +2 -1
  46. data/lib/markdown_exec.rb +2 -0
  47. data/lib/mdoc.rb +12 -6
  48. data/lib/menu.src.yml +150 -33
  49. data/lib/menu.yml +126 -31
  50. data/lib/string_util.rb +80 -0
  51. data/lib/table_extractor.rb +170 -64
  52. data/lib/ww.rb +328 -30
  53. metadata +18 -2
data/lib/fcb.rb CHANGED
@@ -25,7 +25,7 @@ def parse_yaml_of_ux_block(
25
25
  exec: export['exec'],
26
26
  force: export['force'],
27
27
  init: export['init'],
28
- menu_format: export['format'] || export['menu_format'] || menu_format,
28
+ menu_format: export['format'] || export['menu_format'], # || menu_format,####
29
29
  name: name,
30
30
  prompt: export['prompt'] || prompt,
31
31
  readonly: export['readonly'].nil? ? false : export['readonly'],
@@ -93,7 +93,8 @@ module MarkdownExec
93
93
  # 2024-08-04 match nickname
94
94
  # may not exist if block name is duplicated
95
95
  def delete_matching_name!(dependencies)
96
- dependencies.delete(@attrs[:dname]) ||
96
+ dependencies.delete(@attrs[:id]) ||
97
+ dependencies.delete(@attrs[:dname]) ||
97
98
  dependencies.delete(@attrs[:nickname]) ||
98
99
  dependencies.delete(@attrs[:oname]) ||
99
100
  dependencies.delete(@attrs.pub_name) ||
@@ -127,6 +128,7 @@ module MarkdownExec
127
128
  # @return [Object] The modified functional code block with updated
128
129
  # summary attributes.
129
130
  def for_menu!(
131
+ appopts:,
130
132
  block_calls_scan:,
131
133
  block_name_match:,
132
134
  block_name_nick_match:,
@@ -162,12 +164,19 @@ module MarkdownExec
162
164
  when Hash
163
165
  export = parse_yaml_of_ux_block(
164
166
  data,
165
- menu_format: menu_format,
166
167
  prompt: prompt
167
168
  )
168
169
 
170
+ if !export.menu_format || export.menu_format.empty?
171
+ format_symbol = option_to_format_ux_block(export)
172
+ export.menu_format = appopts[format_symbol]
173
+ if !export.menu_format || export.menu_format.empty?
174
+ export.menu_format = appopts[:menu_ux_row_format]
175
+ end
176
+ end
177
+ @attrs[:oname] = oname = format(export.menu_format, export.to_h)
178
+
169
179
  @attrs[:center] = table_center
170
- oname = @attrs[:oname] = format(export.menu_format, export.to_h)
171
180
  @attrs[:readonly] = export.readonly
172
181
  else
173
182
  # triggered by an empty or non-YAML block
@@ -180,7 +189,8 @@ module MarkdownExec
180
189
  end
181
190
 
182
191
  @attrs[:dname] = HashDelegator.indent_all_lines(
183
- (yield oname, BLOCK_TYPE_COLOR_OPTIONS[@attrs[:type]]),
192
+ # yield the text and option name for the color
193
+ (yield oname, option_to_decorate_ux_block),
184
194
  @attrs[:indent]
185
195
  )
186
196
 
@@ -198,6 +208,38 @@ module MarkdownExec
198
208
  end.join("\n")
199
209
  end
200
210
 
211
+ def self.is_allow?(export)
212
+ export&.allow&.present?
213
+ end
214
+
215
+ def is_allow?
216
+ FCB.is_allow?(export)
217
+ end
218
+
219
+ def self.is_echo?(export)
220
+ export&.echo&.present?
221
+ end
222
+
223
+ def is_echo?
224
+ FCB.is_echo?(export)
225
+ end
226
+
227
+ def self.is_edit?(export)
228
+ export&.edit&.present?
229
+ end
230
+
231
+ def is_edit?
232
+ FCB.is_edit?(export)
233
+ end
234
+
235
+ def self.is_exec?(export)
236
+ export&.exec&.present?
237
+ end
238
+
239
+ def is_exec?
240
+ FCB.is_exec?(export)
241
+ end
242
+
201
243
  def self.act_source(export)
202
244
  # If `false`, the UX block is not activated.
203
245
  # If one of `:allow`, `:echo`, `:edit`, or `:exec` is specified,
@@ -205,18 +247,22 @@ module MarkdownExec
205
247
  # If not present, the default value is `:edit`.
206
248
  if export.act.nil?
207
249
  export.act = if export.init.to_s == 'false'
208
- if export.allow.present?
250
+ # if export.allow.present?
251
+ if FCB.is_allow?(export)
209
252
  UxActSource::ALLOW
210
- elsif export.echo.present?
253
+ # elsif export.echo.present?
254
+ elsif FCB.is_echo?(export)
211
255
  UxActSource::ECHO
212
- elsif export.edit.present?
256
+ # elsif export.edit.present?
257
+ elsif FCB.is_edit?(export)
213
258
  UxActSource::EDIT
214
- elsif export.exec.present?
259
+ # elsif export.exec.present?
260
+ elsif FCB.is_exec?(export)
215
261
  UxActSource::EXEC
216
262
  else
217
263
  UxActSource::EDIT
218
264
  end
219
- elsif export.allow.present?
265
+ elsif FCB.is_allow?(export)
220
266
  UxActSource::ALLOW
221
267
  else
222
268
  UxActSource::EDIT
@@ -236,13 +282,15 @@ module MarkdownExec
236
282
  # `:allow`, `:default`, `:echo`, or `:exec` is present.
237
283
  if export.init.nil?
238
284
  export.init = case
239
- when export.allow.present?
285
+ when FCB.is_allow?(export)
240
286
  UxActSource::ALLOW
241
287
  when export.default.present?
242
288
  UxActSource::DEFAULT
243
- when export.echo.present?
289
+ # when export.echo.present?
290
+ when FCB.is_echo?(export)
244
291
  UxActSource::ECHO
245
- when export.exec.present?
292
+ # when export.exec.present?
293
+ when FCB.is_exec?(export)
246
294
  UxActSource::EXEC
247
295
  else
248
296
  UxActSource::FALSE
@@ -255,7 +303,8 @@ module MarkdownExec
255
303
  # :reek:ManualDispatch
256
304
  # 2024-08-04 match nickname
257
305
  def is_dependency_of?(dependency_names)
258
- dependency_names.include?(@attrs[:dname]) ||
306
+ dependency_names.include?(@attrs[:id]) ||
307
+ dependency_names.include?(@attrs[:dname]) ||
259
308
  dependency_names.include?(@attrs[:nickname]) ||
260
309
  dependency_names.include?(@attrs[:oname]) ||
261
310
  dependency_names.include?(@attrs.pub_name) ||
@@ -271,11 +320,16 @@ module MarkdownExec
271
320
  end
272
321
 
273
322
  def is_named?(name)
274
- @attrs[:dname] == name ||
275
- @attrs[:nickname] == name ||
276
- @attrs[:oname] == name ||
277
- @attrs.pub_name == name ||
278
- @attrs[:s2title] == name
323
+ if /^ItrBlk/.match(name)
324
+ @attrs[:id] == name
325
+ else
326
+ @attrs[:id] == name ||
327
+ @attrs[:dname] == name ||
328
+ @attrs[:nickname] == name ||
329
+ @attrs[:oname] == name ||
330
+ @attrs.pub_name == name ||
331
+ @attrs[:s2title] == name
332
+ end
279
333
  end
280
334
 
281
335
  # true if this is a line split block
@@ -332,6 +386,54 @@ module MarkdownExec
332
386
  end
333
387
  end
334
388
 
389
+ # calc the decoration sybol for the current block
390
+ def option_to_decorate_ux_block
391
+ symbol_or_hash = BLOCK_TYPE_COLOR_OPTIONS[@attrs[:type]]
392
+ if @attrs[:type] == BlockType::UX
393
+ # only UX blocks accept a symbol or a hash
394
+ if symbol_or_hash.is_a? Hash
395
+ # default to the first symbol
396
+ symbol = symbol_or_hash.first.last
397
+ symbol_or_hash.keys.each do |key|
398
+ if key == true
399
+ symbol = symbol_or_hash[key]
400
+ break
401
+ elsif symbol_or_hash[key].present? && send(key)
402
+ symbol = symbol_or_hash[key]
403
+ break
404
+ end
405
+ end
406
+ symbol
407
+ else
408
+ # only symbol
409
+ symbol_or_hash
410
+ end
411
+ else
412
+ # only symbol
413
+ symbol_or_hash
414
+ end
415
+ end
416
+
417
+ def option_to_format_ux_block(export)
418
+ if export.readonly
419
+ :menu_ux_row_format_readonly
420
+ else
421
+ case FCB.act_source(export)
422
+ when UxActSource::ALLOW
423
+ :menu_ux_row_format_allow
424
+ when UxActSource::ECHO
425
+ :menu_ux_row_format_echo
426
+ when UxActSource::EDIT
427
+ :menu_ux_row_format_edit
428
+ when UxActSource::EXEC
429
+ :menu_ux_row_format_exec
430
+ else
431
+ # this UX block does not have a format, treat as editable
432
+ :menu_ux_row_format_edit
433
+ end
434
+ end
435
+ end
436
+
335
437
  def respond_to_missing?(method_name, include_private = false)
336
438
  @attrs.key?(method_name.to_sym) || super
337
439
  end
@@ -361,6 +463,7 @@ module MarkdownExec
361
463
  end
362
464
 
363
465
  # Expand variables in attributes
466
+ # @return [void]
364
467
  def expand_variables_in_attributes!(pattern, replacements)
365
468
  @attrs[:raw_dname] ||= @attrs[:dname]
366
469
  @attrs[:dname] = @attrs[:dname]&.gsub(pattern) do |match|
@@ -380,6 +483,7 @@ module MarkdownExec
380
483
  # Replace variables in each line of `body` if `body` is present
381
484
  return unless @attrs[:body]
382
485
 
486
+ # save body for YAML and re-interpretation
383
487
  @attrs[:raw_body] ||= @attrs[:body]
384
488
  @attrs[:body] = @attrs[:body]&.map do |line|
385
489
  if line.empty?
data/lib/format_table.rb CHANGED
@@ -141,7 +141,10 @@ module MarkdownTableFormatter
141
141
  end
142
142
 
143
143
  def format_table__hs(
144
- lines:, column_count:, decorate: nil,
144
+ column_count:,
145
+ lines:,
146
+ table:,
147
+ decorate: nil,
145
148
  table_width: nil,
146
149
  truncate: true
147
150
  )
@@ -152,7 +155,9 @@ module MarkdownTableFormatter
152
155
  end
153
156
  end
154
157
 
155
- rows = raw_lines_into_row_role_cells(lines, column_count)
158
+ rows = raw_lines_into_row_role_cells(
159
+ lines, column_count, delimiter: table[:delimiter]
160
+ )
156
161
 
157
162
  alignment_indicators, column_widths =
158
163
  calculate_column_alignment_and_widths(rows, column_count)
@@ -182,18 +187,22 @@ module MarkdownTableFormatter
182
187
  result
183
188
  end
184
189
 
185
- def raw_lines_into_row_role_cells(lines, column_count)
190
+ def raw_lines_into_row_role_cells(
191
+ lines, column_count, delimiter:
192
+ )
186
193
  role = :header_row
187
194
  counter = -1
188
195
 
189
196
  ret = []
190
197
  lines.each do |line|
191
- line += '|' unless line.end_with?('|')
198
+ line += delimiter unless line.end_with?(delimiter)
199
+
192
200
  counter += 1
193
201
 
194
202
  role = role_for_raw_row(role, line)
195
203
  counter = reset_counter_if_needed(role, counter)
196
- cells = split_decorated_row_into_cells(line, column_count)
204
+ cells = split_decorated_row_into_cells(line, column_count,
205
+ delimiter: delimiter)
197
206
  ret << OpenStruct.new(cells: cells, role: role, counter: counter)
198
207
  end
199
208
  ret
@@ -220,8 +229,8 @@ module MarkdownTableFormatter
220
229
  end
221
230
  end
222
231
 
223
- def split_decorated_row_into_cells(line, column_count)
224
- cells = line.split('|').map(&:strip)[1..-1]
232
+ def split_decorated_row_into_cells(line, column_count, delimiter: '|')
233
+ cells = line.split(delimiter).map(&:strip)[1..-1]
225
234
  cells&.slice(0, column_count)&.fill('', cells.length...column_count)
226
235
  end
227
236
  end
@@ -239,11 +248,12 @@ class TestMarkdownTableFormatter < Minitest::Test
239
248
  '| Row 2 Col 1 | Row 2 Col 2 | Row 2 Col 3 |'
240
249
  ]
241
250
  @column_count = 3
251
+ @table = { delimiter: '|' }
242
252
  end
243
253
 
244
254
  def test_format_table
245
255
  result = MarkdownTableFormatter.format_table(
246
- column_count: @column_count, lines: @lines
256
+ column_count: @column_count, lines: @lines, table: @table
247
257
  )
248
258
  expected = [
249
259
  '| Header 1 | Header 2 | Header 3 |',
@@ -259,7 +269,8 @@ class TestMarkdownTableFormatter < Minitest::Test
259
269
  result = MarkdownTableFormatter.format_table(
260
270
  column_count: @column_count,
261
271
  decorate: decorate,
262
- lines: @lines
272
+ lines: @lines,
273
+ table: @table
263
274
  )
264
275
  expected = [
265
276
  '| HEADER 1 | HEADER 2 | HEADER 3 |',
@@ -280,7 +291,8 @@ class TestMarkdownTableFormatter < Minitest::Test
280
291
  ]
281
292
  result = MarkdownTableFormatter.format_table(
282
293
  lines: lines_with_empty,
283
- column_count: @column_count
294
+ column_count: @column_count,
295
+ table: @table
284
296
  )
285
297
  expected = [
286
298
  '| Header 1 | Header 2 | Header 3 |',
@@ -299,7 +311,8 @@ class TestMarkdownTableFormatter < Minitest::Test
299
311
  ]
300
312
  result = MarkdownTableFormatter.format_table(
301
313
  lines: lines_with_alignment,
302
- column_count: @column_count
314
+ column_count: @column_count,
315
+ table: @table
303
316
  )
304
317
  expected = [
305
318
  '| Header 1 | Header 2 | Header 3 |',
@@ -310,6 +323,10 @@ class TestMarkdownTableFormatter < Minitest::Test
310
323
  end
311
324
 
312
325
  class TestFormatTable < Minitest::Test
326
+ def setup
327
+ @table = { delimiter: '|' }
328
+ end
329
+
313
330
  def test_basic_formatting
314
331
  lines = [
315
332
  '| Species| Genus| Family',
@@ -326,7 +343,8 @@ class TestFormatTable < Minitest::Test
326
343
  ]
327
344
  assert_equal expected, MarkdownTableFormatter.format_table(
328
345
  lines: lines,
329
- column_count: column_count
346
+ column_count: column_count,
347
+ table: @table
330
348
  )
331
349
  end
332
350
 
@@ -344,7 +362,8 @@ class TestFormatTable < Minitest::Test
344
362
  ]
345
363
  assert_equal expected, MarkdownTableFormatter.format_table(
346
364
  lines: lines,
347
- column_count: column_count
365
+ column_count: column_count,
366
+ table: @table
348
367
  )
349
368
  end
350
369
 
@@ -364,7 +383,8 @@ class TestFormatTable < Minitest::Test
364
383
  def test_empty_input
365
384
  assert_equal [], MarkdownTableFormatter.format_table(
366
385
  lines: [],
367
- column_count: 3
386
+ column_count: 3,
387
+ table: @table
368
388
  )
369
389
  end
370
390
 
@@ -382,7 +402,8 @@ class TestFormatTable < Minitest::Test
382
402
  ]
383
403
  assert_equal expected, MarkdownTableFormatter.format_table(
384
404
  lines: lines,
385
- column_count: column_count
405
+ column_count: column_count,
406
+ table: @table
386
407
  )
387
408
  end
388
409
 
@@ -398,12 +419,17 @@ class TestFormatTable < Minitest::Test
398
419
  ]
399
420
  assert_equal expected, MarkdownTableFormatter.format_table(
400
421
  lines: lines,
401
- column_count: column_count
422
+ column_count: column_count,
423
+ table: @table
402
424
  )
403
425
  end
404
426
  end
405
427
 
406
428
  class TestFormatTable2 < Minitest::Test
429
+ def setup
430
+ @table = { delimiter: '|' }
431
+ end
432
+
407
433
  def test_basic_formatting
408
434
  lines = [
409
435
  '| Name | Age | City |',
@@ -417,7 +443,8 @@ class TestFormatTable2 < Minitest::Test
417
443
  ]
418
444
  assert_equal expected_output, MarkdownTableFormatter.format_table(
419
445
  lines: lines,
420
- column_count: 3
446
+ column_count: 3,
447
+ table: @table
421
448
  )
422
449
  end
423
450
 
@@ -434,7 +461,8 @@ class TestFormatTable2 < Minitest::Test
434
461
  ]
435
462
  assert_equal expected_output, MarkdownTableFormatter.format_table(
436
463
  lines: lines,
437
- column_count: 3
464
+ column_count: 3,
465
+ table: @table
438
466
  )
439
467
  end
440
468
 
@@ -451,7 +479,8 @@ class TestFormatTable2 < Minitest::Test
451
479
  ]
452
480
  assert_equal expected_output, MarkdownTableFormatter.format_table(
453
481
  lines: lines,
454
- column_count: 4
482
+ column_count: 4,
483
+ table: @table
455
484
  )
456
485
  end
457
486
 
@@ -468,7 +497,8 @@ class TestFormatTable2 < Minitest::Test
468
497
  ]
469
498
  assert_equal expected_output, MarkdownTableFormatter.format_table(
470
499
  lines: lines,
471
- column_count: 3
500
+ column_count: 3,
501
+ table: @table
472
502
  )
473
503
  end
474
504
 
@@ -477,7 +507,8 @@ class TestFormatTable2 < Minitest::Test
477
507
  expected_output = ['| Name | Age | City |']
478
508
  assert_equal expected_output, MarkdownTableFormatter.format_table(
479
509
  lines: lines,
480
- column_count: 3
510
+ column_count: 3,
511
+ table: @table
481
512
  )
482
513
  end
483
514
 
@@ -486,7 +517,8 @@ class TestFormatTable2 < Minitest::Test
486
517
  expected_output = []
487
518
  assert_equal expected_output, MarkdownTableFormatter.format_table(
488
519
  lines: lines,
489
- column_count: 3
520
+ column_count: 3,
521
+ table: @table
490
522
  )
491
523
  end
492
524
 
@@ -501,7 +533,8 @@ class TestFormatTable2 < Minitest::Test
501
533
  ]
502
534
  assert_equal expected_output, MarkdownTableFormatter.format_table(
503
535
  lines: lines,
504
- column_count: 3
536
+ column_count: 3,
537
+ table: @table
505
538
  )
506
539
  end
507
540
  end
data/lib/fout.rb CHANGED
@@ -33,6 +33,11 @@ class FOut
33
33
  puts str
34
34
  end
35
35
 
36
+ # `puts` guarantees exactly one line break per item it prints. For each
37
+ # argument (and for each element when given an Array), it appends a newline
38
+ # only if the string does not already end with "\n". If it does, `puts`
39
+ # does not add another. Net effect: trailing newlines in your data are
40
+ # consumed rather than duplicated, so "Line\n" does not create a blank line.
36
41
  def fout_list(str)
37
42
  puts str
38
43
  end