markdown_exec 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -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 +2 -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 +120 -28
  42. data/lib/format_table.rb +56 -23
  43. data/lib/fout.rb +5 -0
  44. data/lib/hash_delegator.rb +1158 -347
  45. data/lib/markdown_exec/version.rb +1 -1
  46. data/lib/markdown_exec.rb +2 -0
  47. data/lib/mdoc.rb +13 -11
  48. data/lib/menu.src.yml +139 -34
  49. data/lib/menu.yml +116 -32
  50. data/lib/string_util.rb +80 -0
  51. data/lib/table_extractor.rb +170 -64
  52. data/lib/ww.rb +325 -29
  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'],
@@ -75,14 +75,6 @@ module MarkdownExec
75
75
  full&.to_s&.pub_name(**kwargs)
76
76
  end
77
77
 
78
- def body
79
- @attrs[:body]
80
- end
81
-
82
- def body=(value)
83
- @attrs[:body] = value
84
- end
85
-
86
78
  def code_name_included?(*names)
87
79
  names.include?(@attrs[:oname])
88
80
  end
@@ -101,7 +93,6 @@ module MarkdownExec
101
93
  # 2024-08-04 match nickname
102
94
  # may not exist if block name is duplicated
103
95
  def delete_matching_name!(dependencies)
104
- ####
105
96
  dependencies.delete(@attrs[:id]) ||
106
97
  dependencies.delete(@attrs[:dname]) ||
107
98
  dependencies.delete(@attrs[:nickname]) ||
@@ -137,6 +128,7 @@ module MarkdownExec
137
128
  # @return [Object] The modified functional code block with updated
138
129
  # summary attributes.
139
130
  def for_menu!(
131
+ appopts:,
140
132
  block_calls_scan:,
141
133
  block_name_match:,
142
134
  block_name_nick_match:,
@@ -172,12 +164,19 @@ module MarkdownExec
172
164
  when Hash
173
165
  export = parse_yaml_of_ux_block(
174
166
  data,
175
- menu_format: menu_format,
176
167
  prompt: prompt
177
168
  )
178
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
+
179
179
  @attrs[:center] = table_center
180
- oname = @attrs[:oname] = format(export.menu_format, export.to_h)
181
180
  @attrs[:readonly] = export.readonly
182
181
  else
183
182
  # triggered by an empty or non-YAML block
@@ -190,7 +189,8 @@ module MarkdownExec
190
189
  end
191
190
 
192
191
  @attrs[:dname] = HashDelegator.indent_all_lines(
193
- (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),
194
194
  @attrs[:indent]
195
195
  )
196
196
 
@@ -208,6 +208,38 @@ module MarkdownExec
208
208
  end.join("\n")
209
209
  end
210
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
+
211
243
  def self.act_source(export)
212
244
  # If `false`, the UX block is not activated.
213
245
  # If one of `:allow`, `:echo`, `:edit`, or `:exec` is specified,
@@ -215,18 +247,22 @@ module MarkdownExec
215
247
  # If not present, the default value is `:edit`.
216
248
  if export.act.nil?
217
249
  export.act = if export.init.to_s == 'false'
218
- if export.allow.present?
250
+ # if export.allow.present?
251
+ if FCB.is_allow?(export)
219
252
  UxActSource::ALLOW
220
- elsif export.echo.present?
253
+ # elsif export.echo.present?
254
+ elsif FCB.is_echo?(export)
221
255
  UxActSource::ECHO
222
- elsif export.edit.present?
256
+ # elsif export.edit.present?
257
+ elsif FCB.is_edit?(export)
223
258
  UxActSource::EDIT
224
- elsif export.exec.present?
259
+ # elsif export.exec.present?
260
+ elsif FCB.is_exec?(export)
225
261
  UxActSource::EXEC
226
262
  else
227
263
  UxActSource::EDIT
228
264
  end
229
- elsif export.allow.present?
265
+ elsif FCB.is_allow?(export)
230
266
  UxActSource::ALLOW
231
267
  else
232
268
  UxActSource::EDIT
@@ -246,13 +282,15 @@ module MarkdownExec
246
282
  # `:allow`, `:default`, `:echo`, or `:exec` is present.
247
283
  if export.init.nil?
248
284
  export.init = case
249
- when export.allow.present?
285
+ when FCB.is_allow?(export)
250
286
  UxActSource::ALLOW
251
287
  when export.default.present?
252
288
  UxActSource::DEFAULT
253
- when export.echo.present?
289
+ # when export.echo.present?
290
+ when FCB.is_echo?(export)
254
291
  UxActSource::ECHO
255
- when export.exec.present?
292
+ # when export.exec.present?
293
+ when FCB.is_exec?(export)
256
294
  UxActSource::EXEC
257
295
  else
258
296
  UxActSource::FALSE
@@ -265,7 +303,8 @@ module MarkdownExec
265
303
  # :reek:ManualDispatch
266
304
  # 2024-08-04 match nickname
267
305
  def is_dependency_of?(dependency_names)
268
- dependency_names.include?(@attrs[:dname]) ||
306
+ dependency_names.include?(@attrs[:id]) ||
307
+ dependency_names.include?(@attrs[:dname]) ||
269
308
  dependency_names.include?(@attrs[:nickname]) ||
270
309
  dependency_names.include?(@attrs[:oname]) ||
271
310
  dependency_names.include?(@attrs.pub_name) ||
@@ -281,12 +320,16 @@ module MarkdownExec
281
320
  end
282
321
 
283
322
  def is_named?(name)
284
- @attrs[:id] == name ||
285
- @attrs[:dname] == name ||
286
- @attrs[:nickname] == name ||
287
- @attrs[:oname] == name ||
288
- @attrs.pub_name == name ||
289
- @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
290
333
  end
291
334
 
292
335
  # true if this is a line split block
@@ -343,6 +386,54 @@ module MarkdownExec
343
386
  end
344
387
  end
345
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
+
346
437
  def respond_to_missing?(method_name, include_private = false)
347
438
  @attrs.key?(method_name.to_sym) || super
348
439
  end
@@ -392,6 +483,7 @@ module MarkdownExec
392
483
  # Replace variables in each line of `body` if `body` is present
393
484
  return unless @attrs[:body]
394
485
 
486
+ # save body for YAML and re-interpretation
395
487
  @attrs[:raw_body] ||= @attrs[:body]
396
488
  @attrs[:body] = @attrs[:body]&.map do |line|
397
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