markdown_exec 3.2.0 → 3.4.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +5 -4
  5. data/bats/block-type-ux-echo-hash-transform.bats +1 -1
  6. data/bats/block-type-ux-exec-hash-transform.bats +8 -0
  7. data/bats/block-type-ux-exec-hash.bats +15 -0
  8. data/bats/block-type-ux-force.bats +9 -0
  9. data/bats/block-type-ux-formats.bats +8 -0
  10. data/bats/block-type-ux-no-name.bats +8 -0
  11. data/bats/block-type-ux-readonly.bats +1 -1
  12. data/bats/block-type-ux-row-format.bats +1 -1
  13. data/bats/command-substitution-options.bats +2 -2
  14. data/bats/import-directive-line-continuation.bats +9 -0
  15. data/bats/import-directive-parameter-symbols.bats +9 -0
  16. data/bats/import-duplicates.bats +4 -2
  17. data/bats/import-parameter-symbols.bats +8 -0
  18. data/bats/markup.bats +1 -1
  19. data/bats/option-expansion.bats +1 -1
  20. data/bats/options.bats +1 -1
  21. data/bats/table-column-truncate.bats +1 -1
  22. data/bats/table.bats +1 -1
  23. data/bats/test_helper.bash +4 -3
  24. data/bin/tab_completion.sh +5 -1
  25. data/docs/dev/block-type-ux-echo-hash-transform.md +14 -12
  26. data/docs/dev/block-type-ux-exec-hash-transform.md +37 -0
  27. data/docs/dev/block-type-ux-exec-hash.md +93 -0
  28. data/docs/dev/block-type-ux-force.md +20 -0
  29. data/docs/dev/block-type-ux-formats.md +58 -0
  30. data/docs/dev/block-type-ux-no-name.md +17 -0
  31. data/docs/dev/block-type-ux-row-format.md +1 -1
  32. data/docs/dev/hexdump_format.md +267 -0
  33. data/docs/dev/import/parameter-symbols.md +6 -0
  34. data/docs/dev/import-directive-line-continuation.md +6 -0
  35. data/docs/dev/import-directive-parameter-symbols.md +9 -0
  36. data/docs/dev/import-parameter-symbols-template.md +24 -0
  37. data/docs/dev/import-parameter-symbols.md +6 -0
  38. data/docs/dev/load-vars-state-demo.md +35 -0
  39. data/docs/dev/table-column-truncate.md +1 -1
  40. data/docs/ux-blocks-examples.md +2 -3
  41. data/examples/import_with_substitution_demo.md +130 -26
  42. data/examples/imports/organism_template.md +86 -29
  43. data/lib/cached_nested_file_reader.rb +279 -29
  44. data/lib/constants.rb +8 -1
  45. data/lib/env_interface.rb +13 -7
  46. data/lib/evaluate_shell_expressions.rb +1 -0
  47. data/lib/fcb.rb +133 -33
  48. data/lib/format_table.rb +77 -29
  49. data/lib/fout.rb +5 -0
  50. data/lib/hash_delegator.rb +1159 -348
  51. data/lib/markdown_exec/version.rb +1 -1
  52. data/lib/markdown_exec.rb +2 -0
  53. data/lib/mdoc.rb +13 -11
  54. data/lib/menu.src.yml +166 -62
  55. data/lib/menu.yml +143 -59
  56. data/lib/string_util.rb +80 -0
  57. data/lib/table_extractor.rb +170 -64
  58. data/lib/ww.rb +810 -36
  59. metadata +22 -2
data/lib/fcb.rb CHANGED
@@ -7,15 +7,23 @@ require_relative 'namer'
7
7
  BT_UX_FLD_REQUIRED = 'required'
8
8
  def parse_yaml_of_ux_block(
9
9
  data,
10
- menu_format: nil,
11
10
  prompt: nil,
12
11
  validate: nil
13
12
  )
14
- export = data['export']
15
- export = data if export.nil?
16
- name = export['name']
13
+ export = data if (export = data['export']).nil?
17
14
 
18
- raise "Name is missing in UX block: #{data.inspect}" unless name.present?
15
+ # a single variable name is required to display a single value
16
+ menu_format = export['format'] || export['menu_format']
17
+ name = export['name']
18
+ # if name is missing, use the last key in the echo or exec hashes
19
+ if !name&.present?
20
+ name = if export['echo'].is_a? Hash
21
+ export['echo'].keys.last
22
+ elsif export['exec'].is_a? Hash
23
+ export['exec'].keys.last
24
+ end
25
+ end
26
+ raise "Name is missing in UX block: #{data.inspect}" unless name.present? || menu_format.present?
19
27
 
20
28
  OpenStruct.new(
21
29
  act: export['act'],
@@ -25,7 +33,7 @@ def parse_yaml_of_ux_block(
25
33
  exec: export['exec'],
26
34
  force: export['force'],
27
35
  init: export['init'],
28
- menu_format: export['format'] || export['menu_format'] || menu_format,
36
+ menu_format: menu_format,
29
37
  name: name,
30
38
  prompt: export['prompt'] || prompt,
31
39
  readonly: export['readonly'].nil? ? false : export['readonly'],
@@ -75,14 +83,6 @@ module MarkdownExec
75
83
  full&.to_s&.pub_name(**kwargs)
76
84
  end
77
85
 
78
- def body
79
- @attrs[:body]
80
- end
81
-
82
- def body=(value)
83
- @attrs[:body] = value
84
- end
85
-
86
86
  def code_name_included?(*names)
87
87
  names.include?(@attrs[:oname])
88
88
  end
@@ -101,7 +101,6 @@ module MarkdownExec
101
101
  # 2024-08-04 match nickname
102
102
  # may not exist if block name is duplicated
103
103
  def delete_matching_name!(dependencies)
104
- ####
105
104
  dependencies.delete(@attrs[:id]) ||
106
105
  dependencies.delete(@attrs[:dname]) ||
107
106
  dependencies.delete(@attrs[:nickname]) ||
@@ -137,6 +136,7 @@ module MarkdownExec
137
136
  # @return [Object] The modified functional code block with updated
138
137
  # summary attributes.
139
138
  def for_menu!(
139
+ appopts:,
140
140
  block_calls_scan:,
141
141
  block_name_match:,
142
142
  block_name_nick_match:,
@@ -172,12 +172,19 @@ module MarkdownExec
172
172
  when Hash
173
173
  export = parse_yaml_of_ux_block(
174
174
  data,
175
- menu_format: menu_format,
176
175
  prompt: prompt
177
176
  )
178
177
 
178
+ if !export.menu_format || export.menu_format.empty?
179
+ format_symbol = option_to_format_ux_block(export)
180
+ export.menu_format = appopts[format_symbol]
181
+ if !export.menu_format || export.menu_format.empty?
182
+ export.menu_format = appopts[:menu_ux_row_format]
183
+ end
184
+ end
185
+ @attrs[:oname] = oname = format(export.menu_format, export.to_h)
186
+
179
187
  @attrs[:center] = table_center
180
- oname = @attrs[:oname] = format(export.menu_format, export.to_h)
181
188
  @attrs[:readonly] = export.readonly
182
189
  else
183
190
  # triggered by an empty or non-YAML block
@@ -190,7 +197,8 @@ module MarkdownExec
190
197
  end
191
198
 
192
199
  @attrs[:dname] = HashDelegator.indent_all_lines(
193
- (yield oname, BLOCK_TYPE_COLOR_OPTIONS[@attrs[:type]]),
200
+ # yield the text and option name for the color
201
+ (yield oname, option_to_decorate_ux_block),
194
202
  @attrs[:indent]
195
203
  )
196
204
 
@@ -208,6 +216,38 @@ module MarkdownExec
208
216
  end.join("\n")
209
217
  end
210
218
 
219
+ def self.is_allow?(export)
220
+ export&.allow&.present?
221
+ end
222
+
223
+ def is_allow?
224
+ FCB.is_allow?(export)
225
+ end
226
+
227
+ def self.is_echo?(export)
228
+ export&.echo&.present?
229
+ end
230
+
231
+ def is_echo?
232
+ FCB.is_echo?(export)
233
+ end
234
+
235
+ def self.is_edit?(export)
236
+ export&.edit&.present?
237
+ end
238
+
239
+ def is_edit?
240
+ FCB.is_edit?(export)
241
+ end
242
+
243
+ def self.is_exec?(export)
244
+ export&.exec&.present?
245
+ end
246
+
247
+ def is_exec?
248
+ FCB.is_exec?(export)
249
+ end
250
+
211
251
  def self.act_source(export)
212
252
  # If `false`, the UX block is not activated.
213
253
  # If one of `:allow`, `:echo`, `:edit`, or `:exec` is specified,
@@ -215,18 +255,22 @@ module MarkdownExec
215
255
  # If not present, the default value is `:edit`.
216
256
  if export.act.nil?
217
257
  export.act = if export.init.to_s == 'false'
218
- if export.allow.present?
258
+ # if export.allow.present?
259
+ if FCB.is_allow?(export)
219
260
  UxActSource::ALLOW
220
- elsif export.echo.present?
261
+ # elsif export.echo.present?
262
+ elsif FCB.is_echo?(export)
221
263
  UxActSource::ECHO
222
- elsif export.edit.present?
264
+ # elsif export.edit.present?
265
+ elsif FCB.is_edit?(export)
223
266
  UxActSource::EDIT
224
- elsif export.exec.present?
267
+ # elsif export.exec.present?
268
+ elsif FCB.is_exec?(export)
225
269
  UxActSource::EXEC
226
270
  else
227
271
  UxActSource::EDIT
228
272
  end
229
- elsif export.allow.present?
273
+ elsif FCB.is_allow?(export)
230
274
  UxActSource::ALLOW
231
275
  else
232
276
  UxActSource::EDIT
@@ -246,13 +290,15 @@ module MarkdownExec
246
290
  # `:allow`, `:default`, `:echo`, or `:exec` is present.
247
291
  if export.init.nil?
248
292
  export.init = case
249
- when export.allow.present?
293
+ when FCB.is_allow?(export)
250
294
  UxActSource::ALLOW
251
295
  when export.default.present?
252
296
  UxActSource::DEFAULT
253
- when export.echo.present?
297
+ # when export.echo.present?
298
+ when FCB.is_echo?(export)
254
299
  UxActSource::ECHO
255
- when export.exec.present?
300
+ # when export.exec.present?
301
+ when FCB.is_exec?(export)
256
302
  UxActSource::EXEC
257
303
  else
258
304
  UxActSource::FALSE
@@ -265,7 +311,8 @@ module MarkdownExec
265
311
  # :reek:ManualDispatch
266
312
  # 2024-08-04 match nickname
267
313
  def is_dependency_of?(dependency_names)
268
- dependency_names.include?(@attrs[:dname]) ||
314
+ dependency_names.include?(@attrs[:id]) ||
315
+ dependency_names.include?(@attrs[:dname]) ||
269
316
  dependency_names.include?(@attrs[:nickname]) ||
270
317
  dependency_names.include?(@attrs[:oname]) ||
271
318
  dependency_names.include?(@attrs.pub_name) ||
@@ -281,12 +328,16 @@ module MarkdownExec
281
328
  end
282
329
 
283
330
  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
331
+ if /^ItrBlk/.match(name)
332
+ @attrs[:id] == name
333
+ else
334
+ @attrs[:id] == name ||
335
+ @attrs[:dname] == name ||
336
+ @attrs[:nickname] == name ||
337
+ @attrs[:oname] == name ||
338
+ @attrs.pub_name == name ||
339
+ @attrs[:s2title] == name
340
+ end
290
341
  end
291
342
 
292
343
  # true if this is a line split block
@@ -343,6 +394,54 @@ module MarkdownExec
343
394
  end
344
395
  end
345
396
 
397
+ # calc the decoration sybol for the current block
398
+ def option_to_decorate_ux_block
399
+ symbol_or_hash = BLOCK_TYPE_COLOR_OPTIONS[@attrs[:type]]
400
+ if @attrs[:type] == BlockType::UX
401
+ # only UX blocks accept a symbol or a hash
402
+ if symbol_or_hash.is_a? Hash
403
+ # default to the first symbol
404
+ symbol = symbol_or_hash.first.last
405
+ symbol_or_hash.each_key do |key|
406
+ if key == true
407
+ symbol = symbol_or_hash[key]
408
+ break
409
+ elsif symbol_or_hash[key].present? && send(key)
410
+ symbol = symbol_or_hash[key]
411
+ break
412
+ end
413
+ end
414
+ symbol
415
+ else
416
+ # only symbol
417
+ symbol_or_hash
418
+ end
419
+ else
420
+ # only symbol
421
+ symbol_or_hash
422
+ end
423
+ end
424
+
425
+ def option_to_format_ux_block(export)
426
+ if export.readonly
427
+ :menu_ux_row_format_readonly
428
+ else
429
+ case FCB.act_source(export)
430
+ when UxActSource::ALLOW
431
+ :menu_ux_row_format_allow
432
+ when UxActSource::ECHO
433
+ :menu_ux_row_format_echo
434
+ when UxActSource::EDIT
435
+ :menu_ux_row_format_edit
436
+ when UxActSource::EXEC
437
+ :menu_ux_row_format_exec
438
+ else
439
+ # this UX block does not have a format, treat as editable
440
+ :menu_ux_row_format_edit
441
+ end
442
+ end
443
+ end
444
+
346
445
  def respond_to_missing?(method_name, include_private = false)
347
446
  @attrs.key?(method_name.to_sym) || super
348
447
  end
@@ -392,6 +491,7 @@ module MarkdownExec
392
491
  # Replace variables in each line of `body` if `body` is present
393
492
  return unless @attrs[:body]
394
493
 
494
+ # save body for YAML and re-interpretation
395
495
  @attrs[:raw_body] ||= @attrs[:body]
396
496
  @attrs[:body] = @attrs[:body]&.map do |line|
397
497
  if line.empty?
data/lib/format_table.rb CHANGED
@@ -141,8 +141,11 @@ module MarkdownTableFormatter
141
141
  end
142
142
 
143
143
  def format_table__hs(
144
- lines:, column_count:, decorate: nil,
145
- table_width: nil,
144
+ column_count:,
145
+ decorate: nil,
146
+ lines:,
147
+ max_table_width: nil,
148
+ table:,
146
149
  truncate: true
147
150
  )
148
151
  unless column_count.positive?
@@ -152,18 +155,33 @@ 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)
159
164
 
160
- unless table_width.nil?
161
- sum_column_widths = column_widths.sum + ((column_count * 3) + 5)
162
- if sum_column_widths > table_width
163
- ratio = table_width.to_f / sum_column_widths
165
+ unless max_table_width.nil?
166
+ # each column has a frame width of 3 characters
167
+ # border and space before and after each and 1 final border
168
+ borders_width = (column_count * 3) + 1
169
+
170
+ full_column_table_width = column_widths.sum + borders_width
171
+
172
+ if full_column_table_width > max_table_width
173
+ text_width_sum = full_column_table_width - borders_width
174
+ available_text_width = max_table_width - borders_width
175
+ ratio = available_text_width.to_f / text_width_sum
176
+
177
+ # distribute the width across the columns
164
178
  column_widths.each_with_index do |width, i|
165
179
  column_widths[i] = (width * ratio).to_i
166
180
  end
181
+
182
+ # the last column fills the remaining width
183
+ column_widths[column_widths.count - 1] =
184
+ available_text_width - column_widths.sum + column_widths.last
167
185
  end
168
186
  end
169
187
 
@@ -182,18 +200,22 @@ module MarkdownTableFormatter
182
200
  result
183
201
  end
184
202
 
185
- def raw_lines_into_row_role_cells(lines, column_count)
203
+ def raw_lines_into_row_role_cells(
204
+ lines, column_count, delimiter:
205
+ )
186
206
  role = :header_row
187
207
  counter = -1
188
208
 
189
209
  ret = []
190
210
  lines.each do |line|
191
- line += '|' unless line.end_with?('|')
211
+ line += delimiter unless line.end_with?(delimiter)
212
+
192
213
  counter += 1
193
214
 
194
215
  role = role_for_raw_row(role, line)
195
216
  counter = reset_counter_if_needed(role, counter)
196
- cells = split_decorated_row_into_cells(line, column_count)
217
+ cells = split_decorated_row_into_cells(line, column_count,
218
+ delimiter: delimiter)
197
219
  ret << OpenStruct.new(cells: cells, role: role, counter: counter)
198
220
  end
199
221
  ret
@@ -220,8 +242,8 @@ module MarkdownTableFormatter
220
242
  end
221
243
  end
222
244
 
223
- def split_decorated_row_into_cells(line, column_count)
224
- cells = line.split('|').map(&:strip)[1..-1]
245
+ def split_decorated_row_into_cells(line, column_count, delimiter: '|')
246
+ cells = line.split(delimiter).map(&:strip)[1..-1]
225
247
  cells&.slice(0, column_count)&.fill('', cells.length...column_count)
226
248
  end
227
249
  end
@@ -229,6 +251,7 @@ end
229
251
  return if $PROGRAM_NAME != __FILE__
230
252
 
231
253
  require 'minitest/autorun'
254
+ require_relative 'ww'
232
255
 
233
256
  class TestMarkdownTableFormatter < Minitest::Test
234
257
  def setup
@@ -239,11 +262,12 @@ class TestMarkdownTableFormatter < Minitest::Test
239
262
  '| Row 2 Col 1 | Row 2 Col 2 | Row 2 Col 3 |'
240
263
  ]
241
264
  @column_count = 3
265
+ @table = { delimiter: '|' }
242
266
  end
243
267
 
244
268
  def test_format_table
245
269
  result = MarkdownTableFormatter.format_table(
246
- column_count: @column_count, lines: @lines
270
+ column_count: @column_count, lines: @lines, table: @table
247
271
  )
248
272
  expected = [
249
273
  '| Header 1 | Header 2 | Header 3 |',
@@ -259,7 +283,8 @@ class TestMarkdownTableFormatter < Minitest::Test
259
283
  result = MarkdownTableFormatter.format_table(
260
284
  column_count: @column_count,
261
285
  decorate: decorate,
262
- lines: @lines
286
+ lines: @lines,
287
+ table: @table
263
288
  )
264
289
  expected = [
265
290
  '| HEADER 1 | HEADER 2 | HEADER 3 |',
@@ -280,7 +305,8 @@ class TestMarkdownTableFormatter < Minitest::Test
280
305
  ]
281
306
  result = MarkdownTableFormatter.format_table(
282
307
  lines: lines_with_empty,
283
- column_count: @column_count
308
+ column_count: @column_count,
309
+ table: @table
284
310
  )
285
311
  expected = [
286
312
  '| Header 1 | Header 2 | Header 3 |',
@@ -299,7 +325,8 @@ class TestMarkdownTableFormatter < Minitest::Test
299
325
  ]
300
326
  result = MarkdownTableFormatter.format_table(
301
327
  lines: lines_with_alignment,
302
- column_count: @column_count
328
+ column_count: @column_count,
329
+ table: @table
303
330
  )
304
331
  expected = [
305
332
  '| Header 1 | Header 2 | Header 3 |',
@@ -310,6 +337,10 @@ class TestMarkdownTableFormatter < Minitest::Test
310
337
  end
311
338
 
312
339
  class TestFormatTable < Minitest::Test
340
+ def setup
341
+ @table = { delimiter: '|' }
342
+ end
343
+
313
344
  def test_basic_formatting
314
345
  lines = [
315
346
  '| Species| Genus| Family',
@@ -326,7 +357,8 @@ class TestFormatTable < Minitest::Test
326
357
  ]
327
358
  assert_equal expected, MarkdownTableFormatter.format_table(
328
359
  lines: lines,
329
- column_count: column_count
360
+ column_count: column_count,
361
+ table: @table
330
362
  )
331
363
  end
332
364
 
@@ -344,7 +376,8 @@ class TestFormatTable < Minitest::Test
344
376
  ]
345
377
  assert_equal expected, MarkdownTableFormatter.format_table(
346
378
  lines: lines,
347
- column_count: column_count
379
+ column_count: column_count,
380
+ table: @table
348
381
  )
349
382
  end
350
383
 
@@ -358,13 +391,15 @@ class TestFormatTable < Minitest::Test
358
391
  # "| A | B | C ",
359
392
  # "| 1 | 2 | 3 "
360
393
  # ]
361
- # assert_equal expected, MarkdownTableFormatter.format_table(lines, column_count)
394
+ # assert_equal expected,
395
+ # MarkdownTableFormatter.format_table(lines, column_count)
362
396
  # end
363
397
 
364
398
  def test_empty_input
365
399
  assert_equal [], MarkdownTableFormatter.format_table(
366
400
  lines: [],
367
- column_count: 3
401
+ column_count: 3,
402
+ table: @table
368
403
  )
369
404
  end
370
405
 
@@ -382,7 +417,8 @@ class TestFormatTable < Minitest::Test
382
417
  ]
383
418
  assert_equal expected, MarkdownTableFormatter.format_table(
384
419
  lines: lines,
385
- column_count: column_count
420
+ column_count: column_count,
421
+ table: @table
386
422
  )
387
423
  end
388
424
 
@@ -398,12 +434,17 @@ class TestFormatTable < Minitest::Test
398
434
  ]
399
435
  assert_equal expected, MarkdownTableFormatter.format_table(
400
436
  lines: lines,
401
- column_count: column_count
437
+ column_count: column_count,
438
+ table: @table
402
439
  )
403
440
  end
404
441
  end
405
442
 
406
443
  class TestFormatTable2 < Minitest::Test
444
+ def setup
445
+ @table = { delimiter: '|' }
446
+ end
447
+
407
448
  def test_basic_formatting
408
449
  lines = [
409
450
  '| Name | Age | City |',
@@ -417,7 +458,8 @@ class TestFormatTable2 < Minitest::Test
417
458
  ]
418
459
  assert_equal expected_output, MarkdownTableFormatter.format_table(
419
460
  lines: lines,
420
- column_count: 3
461
+ column_count: 3,
462
+ table: @table
421
463
  )
422
464
  end
423
465
 
@@ -434,7 +476,8 @@ class TestFormatTable2 < Minitest::Test
434
476
  ]
435
477
  assert_equal expected_output, MarkdownTableFormatter.format_table(
436
478
  lines: lines,
437
- column_count: 3
479
+ column_count: 3,
480
+ table: @table
438
481
  )
439
482
  end
440
483
 
@@ -451,7 +494,8 @@ class TestFormatTable2 < Minitest::Test
451
494
  ]
452
495
  assert_equal expected_output, MarkdownTableFormatter.format_table(
453
496
  lines: lines,
454
- column_count: 4
497
+ column_count: 4,
498
+ table: @table
455
499
  )
456
500
  end
457
501
 
@@ -468,7 +512,8 @@ class TestFormatTable2 < Minitest::Test
468
512
  ]
469
513
  assert_equal expected_output, MarkdownTableFormatter.format_table(
470
514
  lines: lines,
471
- column_count: 3
515
+ column_count: 3,
516
+ table: @table
472
517
  )
473
518
  end
474
519
 
@@ -477,7 +522,8 @@ class TestFormatTable2 < Minitest::Test
477
522
  expected_output = ['| Name | Age | City |']
478
523
  assert_equal expected_output, MarkdownTableFormatter.format_table(
479
524
  lines: lines,
480
- column_count: 3
525
+ column_count: 3,
526
+ table: @table
481
527
  )
482
528
  end
483
529
 
@@ -486,7 +532,8 @@ class TestFormatTable2 < Minitest::Test
486
532
  expected_output = []
487
533
  assert_equal expected_output, MarkdownTableFormatter.format_table(
488
534
  lines: lines,
489
- column_count: 3
535
+ column_count: 3,
536
+ table: @table
490
537
  )
491
538
  end
492
539
 
@@ -501,7 +548,8 @@ class TestFormatTable2 < Minitest::Test
501
548
  ]
502
549
  assert_equal expected_output, MarkdownTableFormatter.format_table(
503
550
  lines: lines,
504
- column_count: 3
551
+ column_count: 3,
552
+ table: @table
505
553
  )
506
554
  end
507
555
  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