markdown_exec 2.7.4 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b2dff995a0986c3e248185703f187d49b9cabaabdfae999af261b524ebac1d9
4
- data.tar.gz: 4303e59dc01d2c600872b506cb365d1de0452a93bacb13c47358525e80d39ad8
3
+ metadata.gz: c36bb5c2d9e91eeeae59067f91fc88a3ad8e6b1a27ebfb474ff900903d030042
4
+ data.tar.gz: b002fc24e78006e830fdc66bb0be4df05fe9df62e6c036965eefbb7047f37ec7
5
5
  SHA512:
6
- metadata.gz: ab35fe5e0b66c683133f65e9937cdf3336231e3522aed575bedceae8a7044fcf155b11c7e28b324720858dfa53f1443e35b8c5b188796300aa499bb6071930f1
7
- data.tar.gz: e8146b88c20bf43c4f43c6d19dd016a249f3227f42450196bf1a40b3356a1f37ae53c645122d25b285ae58c806df3724dd5e32d2e1ac120b45e8f4f86fe7ebbf
6
+ metadata.gz: 8eedac9012c2c0103afe99a8ea0cd235b26b3ab62618b35d79af6f685a56195818d28ba70588f93d754c76e5c4b3bb524de0ed09fd97864bdc79a424015911a9
7
+ data.tar.gz: d24b8c56e50416032de644f00d11f6bb81689617e47da57ec1e4b39a693c867dedff268ddd00e023bc134fc40ee9ae9b452a808131d129b378508464ac0793f5
data/CHANGELOG.md CHANGED
@@ -1,6 +1,34 @@
1
1
  # Changelog
2
2
 
3
- ## [2.7.4] - 2025-02-02
3
+ ## [2.8.0] - 2025-02-10
4
+
5
+ ### Added
6
+
7
+ - Block type `ux` to facilitate the evaluation, display, and editing of shell variables.
8
+ The body of the `ux` block is in YAML.
9
+
10
+ A valid shell variable name is required as block key `name`. The remaining block keys are optional.
11
+
12
+ When the block is executed, its value is computed from the `default`, `exec`, `prompt`, `transform`, and `validate` keys and its output is assigned to the shell variable.
13
+
14
+ When a document is loaded, if one or more block names match `document_load_ux_block_name`, they are executed.
15
+ `ux_auto_load_force_default` limits the setting of the shell variable resulting from the execution of blocks executed at this time.
16
+
17
+ When an `ux` block is executed (after initial document load):
18
+ If the `default` value is the `exec` symbol, the command in the `exec` key is executed and its output is processed.
19
+ Else if the `allowed` value has one or more items, the user must pick from one of the items.
20
+ Else if the `prompt` value exists, the user must enter a value or nothing for the `default` value. The user is prompted with `prompt_ux_enter_a_value`.
21
+ Else the `default` value, as a string, is processed.
22
+
23
+ The output is validated/parsed by the regular expression in `validate`.
24
+ If the string matches, named groups are formatted with `menu_ux_row_format` and assigned to the shell variable.
25
+
26
+ The default `menu_ux_row_format` looks like a shell variable assignment.
27
+ If the output of `menu_ux_row_format` matches an immediately preceding table, the row is merged into that table.
28
+ Else the output is decorated with `menu_ux_color`.
29
+ - Improve final output of `test` Rake task so failures are visible.
30
+
31
+ ## [2.7.5] - 2025-02-02
4
32
 
5
33
  ### Added
6
34
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- markdown_exec (2.7.4)
4
+ markdown_exec (2.8.0)
5
5
  clipboard (~> 1.3.6)
6
6
  open3 (~> 0.1.1)
7
7
  optparse (~> 0.1.1)
data/Rakefile CHANGED
@@ -155,9 +155,30 @@ end
155
155
 
156
156
  desc 'test'
157
157
  task :test do
158
- system 'bundle exec rspec'
158
+ success = true
159
+
160
+ # Run all tests and track failures
161
+ rspec_success = system('bundle exec rspec')
162
+ success = false unless rspec_success
163
+
159
164
  Rake::Task['minitest'].invoke
165
+ minitest_success = $?.success?
166
+ success = false unless minitest_success
167
+
160
168
  Rake::Task['bats'].invoke
169
+ bats_success = $?.success?
170
+ success = false unless bats_success
171
+
172
+ # Report failures and exit with non-zero status if any test failed
173
+ unless success
174
+ failed_tests = []
175
+ failed_tests << 'RSpec' unless rspec_success
176
+ failed_tests << 'Minitest' unless minitest_success
177
+ failed_tests << 'Bats' unless bats_success
178
+
179
+ puts "\nThe following test suites failed: #{failed_tests.join(', ')}"
180
+ exit 1
181
+ end
161
182
  end
162
183
 
163
184
  private
@@ -4,5 +4,5 @@ load 'test_helper'
4
4
 
5
5
  @test 'Command substitution' {
6
6
  spec_mde_xansi_dname_doc_blocks_expect docs/dev/command-substitution.md \
7
- 'CURRENT BASE NAME IS: MARKDOWN_EXEC_current base name is: markdown_exec_current base name is: markdown_exec_| current base name |_| ----------------- |_| markdown_exec |_: notice the string is not expanded in Shell block types (names or body)._ echo "current base name is now $(basename `pwd`)"__load: file_markdown_exec.sh'
7
+ 'CURRENT BASE NAME IS: MARKDOWN_EXEC_current base name is: markdown_exec_current base name is: markdown_exec_| current base name |_| ----------------- |_| markdown_exec |_: notice the string is not expanded in Shell block types (names or body)._ echo "current base name is now $(basename `pwd`)"__load: file_markdown_exec.sh_Status not zero: $(err)'
8
8
  }
@@ -4,5 +4,5 @@ load 'test_helper'
4
4
 
5
5
  @test 'Text and Headings' {
6
6
  spec_mde_xansi_dname_doc_blocks_expect docs/dev/line-wrapping.md \
7
- " DEMO WRAPPING LONG LINES__MDE detects the screen's dimensions:_height (lines) and width (characters)__Normal document text is displayed as_disabled menu lines. The width of these_lines is limited according to the screen's_width.__Test Indented Lines__ Indented with two spaces, this line_ should wrap in an aesthetically pleasing_ way.__ Indented with a tab, this line should_ wrap in an aesthetically pleasing way.__ SPECIES GENUS FAMILY ORDER CLASS PHYLUM_ KINGDOM DOMAIN_species genus family order class phylum_kingdom domain"
7
+ " DEMO WRAPPING LONG LINES__MDE detects the screen's dimensions:_height (lines) and width (characters)__Normal document text is displayed as_disabled menu lines. The width of these_lines is limited according to the_screen's width.__Test Indented Lines__ Indented with two spaces, this line_ should wrap in an aesthetically_ pleasing way.__ Indented with a tab, this line should_ wrap in an aesthetically pleasing_ way.__ SPECIES GENUS FAMILY ORDER CLASS PHYLUM_ KINGDOM DOMAIN_species genus family order class phylum_kingdom domain"
8
8
  }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bats
2
+
3
+ load 'test_helper'
4
+
5
+ @test 'Tables - truncate columns' {
6
+ spec_mde_xansi_dname_doc_blocks_expect docs/dev/table-column-truncate.md \
7
+ 'DEMONSTRATE TRUNCATION OF TEXT IN TABLE CELLS__| Common Name | Species | Genus | Family | Year Di |_| ------------ | ------------ | ----- | ------- | ------- |_| Tapanuli Ora | Pongo tapanu | Pongo | Hominid | 2017 |_| Psychedelic | Histiophryne | Histi | Antenna | 2009 |_| Ruby Seadrag | Phyllopteryx | Phyll | Syngnat | 2015 |_| Illacme tobi | Illacme tobi | Illac | Siphono | 2016 |_| Spiny Dandel | Taraxacum ja | Tarax | Asterac | 2022 |__'
8
+ }
data/bats/table.bats CHANGED
@@ -4,7 +4,7 @@ load 'test_helper'
4
4
 
5
5
  @test 'Tables - indented' {
6
6
  spec_mde_xansi_dname_doc_blocks_expect docs/dev/table-indent.md \
7
- 'DEMONSTRATE TABLE INDENTATION__Table flush at left._Centered columns._| Common Name | Species | Genus | Family | Year Discovered |_| -------------------- | ------------------------- | ------------ | ------------- | --------------- |_| Tapanuli Orangutan | Pongo tapanuliensis | Pongo | Hominidae | 2017 |_| Psychedelic Frogfish | Histiophryne psychedelica | Histiophryne | Antennariidae | 2009 |_| Ruby Seadragon | Phyllopteryx dewysea | Phyllopteryx | Syngnathidae | 2015 |__ Table indented with two spaces._ Left-justified columns._ | Common Name | Species | Genus | Family | Year Discovered |_ | -------------------------- | -------------- | ------- | --------------- | --------------- |_ | Illacme tobini (Millipede) | Illacme tobini | Illacme | Siphonorhinidae | 2016 |__ Table indented with one tab._ Right-justified columns._ | Common Name | Species | Genus | Family | Year Discovered |_ | --------------- | ------------------- | --------- | ---------- | --------------- |_ | Spiny Dandelion | Taraxacum japonicum | Taraxacum | Asteraceae | 2022 |'
7
+ 'DEMONSTRATE TABLE INDENTATION__Table flush at left._Centered columns._| Common Name | Species | Genus | Family | Year Discover |_| ------------------ | ---------------------- | ---------- | ----------- | ------------- |_| Tapanuli Orangutan | Pongo tapanuliensis | Pongo | Hominidae | 2017 |_| Psychedelic Frogfi | Histiophryne psychedel | Histiophry | Antennariid | 2009 |_| Ruby Seadragon | Phyllopteryx dewysea | Phyllopter | Syngnathida | 2015 |__ Table indented with two spaces._ Left-justified columns._ | Common Name | Species | Genus | Family | Year Discovere |_ | ------------------------- | ------------- | ------ | -------------- | -------------- |_ | Illacme tobini (Millipede | Illacme tobin | Illacm | Siphonorhinida | 2016 |__ Table indented with one tab._ Right-justified columns._ | Common Name | Species | Genus | Family | Year Discovered |_ | --------------- | ------------------- | --------- | ---------- | --------------- |_ | Spiny Dandelion | Taraxacum japonicum | Taraxacum | Asteraceae | 2022 |'
8
8
  }
9
9
 
10
10
  @test 'Tables - invalid' {
@@ -13,7 +13,7 @@ __filedirs_all()
13
13
  }
14
14
 
15
15
  _mde_echo_version() {
16
- echo "2.7.4"
16
+ echo "2.8.0"
17
17
  }
18
18
 
19
19
  _mde() {
@@ -10,6 +10,9 @@ dump_selected_block: false # Dump selected block
10
10
  execute_in_own_window: false
11
11
 
12
12
  menu_divider_format:
13
+
14
+ menu_for_saved_lines: false
15
+
13
16
  menu_with_back: false
14
17
  menu_with_exit: false
15
18
  menu_with_shell: false
@@ -11,6 +11,7 @@ echo "current base name is now $(basename `pwd`)"
11
11
  ```link
12
12
  load: file_$(basename `pwd`).sh
13
13
  ```
14
+ Status not zero: $(err)
14
15
  / This should not be evaluated $(err). It errs with "Error: HashDelegator.blocks_from_nested_files -- Shell script execution failed: /bin/bash: line 2: err: command not found"
15
16
  @import bats-document-configuration.md
16
17
  ```opts :(document_opts)
@@ -0,0 +1,17 @@
1
+ # Demonstrate Truncation of Text in Table Cells
2
+
3
+ | Common Name| Species| Genus| Family| Year Discovered
4
+ | -| -|:-|-:|:-:
5
+ | Tapanuli Orangutan| Pongo tapanuliensis| Pongo| Hominidae| 2017
6
+ | Psychedelic Frogfish| Histiophryne psychedelica| Histiophryne| Antennariidae| 2009
7
+ | Ruby Seadragon| Phyllopteryx dewysea| Phyllopteryx| Syngnathidae| 2015
8
+ | Illacme tobini (Millipede)| Illacme tobini| Illacme| Siphonorhinidae| 2016
9
+ | Spiny Dandelion| Taraxacum japonicum| Taraxacum| Asteraceae| 2022
10
+ ```
11
+ ```
12
+ @import bats-document-configuration.md
13
+ ```opts :(document_opts)
14
+ heading1_center: false
15
+ screen_width: 60
16
+ table_center: false
17
+ ```
@@ -22,5 +22,6 @@ Centered columns.
22
22
  @import bats-document-configuration.md
23
23
  ```opts :(document_opts)
24
24
  heading1_center: false
25
+ screen_width: 100
25
26
  table_center: false
26
27
  ```
data/examples/colors.md CHANGED
@@ -33,6 +33,7 @@ menu_opts_color: fg_rgbh_1f_00_1f
33
33
  menu_opts_set_color: fg_rgbh_7f_00_1f
34
34
  menu_save_color: fg_rgbh_e0_e0_20
35
35
  menu_task_color: fg_rgbh_1f_1f_1f
36
+ menu_ux_color: fg_rgbh_2f_c0_2f
36
37
  menu_vars_color: fg_rgbh_1f_a0_1f
37
38
  menu_vars_set_color: fg_rgbh_00_1f_1f
38
39
  output_execution_label_name_color: fg_rgbh_00_1f_00
@@ -60,6 +61,7 @@ menu_opts_set_color: fg_rgbh_7f_00_ff
60
61
  # menu_save_color: fg_rgbh_ea_ea_20
61
62
  menu_save_color: fg_rgbh_ff_ff_20
62
63
  menu_task_color: fg_rgbh_ff_ff_ff
64
+ menu_ux_color: fg_rgbh_df_c0_df
63
65
  menu_vars_color: fg_rgbh_ff_a0_ff
64
66
  menu_vars_set_color: fg_rgbh_00_ff_ff
65
67
  output_execution_label_name_color: fg_rgbh_00_ff_00
data/lib/block_types.rb CHANGED
@@ -14,6 +14,7 @@ class BlockType
14
14
  SAVE = 'save',
15
15
  SHELL = 'shell',
16
16
  TEXT = 'text',
17
+ UX = 'ux',
17
18
  VARS = 'vars',
18
19
  VIEW = 'view',
19
20
  YAML = 'yaml'
data/lib/constants.rb CHANGED
@@ -27,12 +27,12 @@ BLOCK_TYPE_COLOR_OPTIONS = {
27
27
  BlockType::OPTS => :menu_opts_color,
28
28
  BlockType::SAVE => :menu_save_color,
29
29
  BlockType::SHELL => :menu_bash_color,
30
+ BlockType::UX => :menu_ux_color,
30
31
  BlockType::VARS => :menu_vars_color
31
32
  }.freeze
32
33
 
33
-
34
34
  COLLAPSIBLE_SYMBOL_COLLAPSED = '⬢' # '<+>' # '∆'
35
- COLLAPSIBLE_SYMBOL_EXPANDED = '⬡' # '< >' # '…'
35
+ COLLAPSIBLE_SYMBOL_EXPANDED = '⬡' # '< >' # '…'
36
36
 
37
37
  # in regexp (?<collapse>[+-~]?)
38
38
  COLLAPSIBLE_TOKEN_COLLAPSE = '+'
@@ -5,8 +5,12 @@
5
5
 
6
6
  require 'open3'
7
7
 
8
+ class EvaluateShellExpression
9
+ StatusFail = :script_execution_failed unless const_defined?(:StatusFail)
10
+ end
11
+
8
12
  def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
9
- key_format: "%%<%s>",
13
+ key_format: '%%<%s>',
10
14
  initial_code_required: false)
11
15
  # !!p initial_code expressions key_format shell
12
16
  return if (initial_code_required && (initial_code.nil? || initial_code.empty?)) ||
@@ -18,23 +22,26 @@ def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
18
22
 
19
23
  # Construct a single shell script
20
24
  script = initial_code.dup
21
- expressions.each_with_index do |(key, expression), index|
25
+ expressions.each_with_index do |(_key, expression), index|
22
26
  script << "\necho #{token}#{index}\n"
23
27
  script << expression << "\n"
24
28
  end
25
29
 
26
30
  # Execute
27
- stdout_str, stderr_str, status = Open3.capture3(shell, "-c", script)
31
+ stdout_str, stderr_str, status = Open3.capture3(shell, '-c', script)
28
32
 
29
33
  unless status.success?
30
- raise "Shell script execution failed: #{stderr_str}"
34
+ return EvaluateShellExpression::StatusFail
31
35
  end
32
36
 
33
37
  # Extract output for expressions
34
38
  result_hash = {}
35
- stdout_str.split(/\n?#{token}\d+\n/)[1..-1].tap do |output_parts|
36
- expressions.each_with_index do |(key, _expression), index|
37
- result_hash[sprintf(key_format, key)] = output_parts[index].chomp
39
+ part = stdout_str.split(/\n?#{token}\d+\n/)
40
+ unless part.empty?
41
+ part[1..-1].tap do |output_parts|
42
+ expressions.each_with_index do |(key, _expression), index|
43
+ result_hash[format(key_format, key)] = output_parts[index].chomp
44
+ end
38
45
  end
39
46
  end
40
47
 
@@ -58,23 +65,23 @@ class TestShellExpressionEvaluator < Minitest::Test
58
65
  end
59
66
 
60
67
  def test_single_expression
61
- expressions = { "greeting" => "echo 'Hello, World!'" }
68
+ expressions = { 'greeting' => "echo 'Hello, World!'" }
62
69
  result = evaluate_shell_expressions(@initial_code, expressions)
63
70
 
64
- assert_equal "Hello, World!", result["%<greeting>"]
71
+ assert_equal 'Hello, World!', result['%<greeting>']
65
72
  end
66
73
 
67
74
  def test_multiple_expressions
68
75
  expressions = {
69
- "greeting" => "echo 'Hello, World!'",
70
- "date" => "date +%Y-%m-%d",
71
- "kernel" => "uname -r"
76
+ 'greeting' => "echo 'Hello, World!'",
77
+ 'date' => 'date +%Y-%m-%d',
78
+ 'kernel' => 'uname -r'
72
79
  }
73
80
  result = evaluate_shell_expressions(@initial_code, expressions)
74
81
 
75
- assert_equal "Hello, World!", result["%<greeting>"]
76
- assert_match /\d{4}-\d{2}-\d{2}/, result["%<date>"]
77
- assert_match /\d+\.\d+\.\d+/, result["%<kernel>"]
82
+ assert_equal 'Hello, World!', result['%<greeting>']
83
+ assert_match(/\d{4}-\d{2}-\d{2}/, result['%<date>'])
84
+ assert_match(/\d+\.\d+\.\d+/, result['%<kernel>'])
78
85
  end
79
86
 
80
87
  def test_empty_expressions_list
@@ -85,13 +92,11 @@ class TestShellExpressionEvaluator < Minitest::Test
85
92
  end
86
93
 
87
94
  def test_invalid_expression
88
- expressions = { "invalid" => "invalid_command" }
95
+ expressions = { 'invalid' => 'invalid_command' }
89
96
 
90
- error = assert_raises(RuntimeError) do
91
- evaluate_shell_expressions(@initial_code, expressions)
92
- end
97
+ result = evaluate_shell_expressions(@initial_code, expressions)
93
98
 
94
- assert_match /Shell script execution failed/, error.message
99
+ assert_equal EvaluateShellExpression::StatusFail, result
95
100
  end
96
101
 
97
102
  def test_initial_code_execution
@@ -99,17 +104,17 @@ class TestShellExpressionEvaluator < Minitest::Test
99
104
  #!/bin/sh
100
105
  echo "Custom setup message"
101
106
  BASH
102
- expressions = { "test" => "echo Test after initial setup" }
107
+ expressions = { 'test' => 'echo Test after initial setup' }
103
108
 
104
109
  result = evaluate_shell_expressions(initial_code, expressions)
105
110
 
106
- assert_equal "Test after initial setup", result["%<test>"]
111
+ assert_equal 'Test after initial setup', result['%<test>']
107
112
  end
108
113
 
109
114
  def test_large_number_of_expressions
110
- expressions = (1..100).map { |i|
115
+ expressions = (1..100).map do |i|
111
116
  ["expr_#{i}", "echo Expression #{i}"]
112
- }.to_h
117
+ end.to_h
113
118
 
114
119
  result = evaluate_shell_expressions(@initial_code, expressions)
115
120
 
data/lib/fcb.rb CHANGED
@@ -5,6 +5,29 @@
5
5
  require 'digest'
6
6
  require_relative 'namer'
7
7
 
8
+ def parse_yaml_of_ux_block(
9
+ data,
10
+ menu_format: nil,
11
+ prompt: nil,
12
+ validate: nil
13
+ )
14
+ export = data['export']
15
+ export = data if export.nil?
16
+ name = export['name']
17
+ raise "Invalid data: #{data.inspect}" unless name.present?
18
+
19
+ OpenStruct.new(
20
+ allowed: export['allowed'],
21
+ default: export['default'],
22
+ exec: export['exec'],
23
+ menu_format: export['menu_format'] || menu_format,
24
+ name: name,
25
+ prompt: export['prompt'] || prompt,
26
+ transform: export['transform'],
27
+ validate: export['validate'] || validate
28
+ )
29
+ end
30
+
8
31
  module MarkdownExec
9
32
  class Error < StandardError; end
10
33
 
@@ -106,12 +129,14 @@ module MarkdownExec
106
129
  # @return [Object] The modified functional code block with updated
107
130
  # summary attributes.
108
131
  def for_menu!(
109
- block_calls_scan: @delegate_object[:block_calls_scan],
110
- block_name_match: @delegate_object[:block_name_match],
111
- block_name_nick_match: @delegate_object[:block_name_nick_match],
112
- id: ''
132
+ block_calls_scan:,
133
+ block_name_match:,
134
+ block_name_nick_match:,
135
+ id: '',
136
+ menu_format:,
137
+ prompt:,
138
+ table_center:
113
139
  )
114
- # binding.irb
115
140
  call = @attrs[:call] = @attrs[:start_line]&.match(
116
141
  Regexp.new(block_calls_scan)
117
142
  )&.fetch(1, nil)
@@ -130,6 +155,23 @@ module MarkdownExec
130
155
  end
131
156
  @attrs[:title] = @attrs[:oname] = oname
132
157
  @attrs[:id] = id
158
+
159
+ if @attrs[:type] == BlockType::UX
160
+ case data = YAML.load(@attrs[:body].join("\n"))
161
+ when Hash
162
+ export = parse_yaml_of_ux_block(
163
+ data,
164
+ menu_format: menu_format,
165
+ prompt: prompt
166
+ )
167
+
168
+ @attrs[:center] = table_center
169
+ oname = @attrs[:oname] = format(export.menu_format, export.to_h)
170
+ else
171
+ raise "Invalid data type: #{data.inspect}"
172
+ end
173
+ end
174
+
133
175
  @attrs[:dname] = HashDelegator.indent_all_lines(
134
176
  (yield oname, BLOCK_TYPE_COLOR_OPTIONS[@attrs[:type]]),
135
177
  @attrs[:indent]
data/lib/format_table.rb CHANGED
@@ -65,12 +65,15 @@ module MarkdownTableFormatter
65
65
 
66
66
  def format_cell(cell, align, width, truncate: true)
67
67
  plain_string = cell.gsub(/\033\[[\d;]+m|\033\[0m/, '')
68
+ exceeded = plain_string.length > width
68
69
  truncated = false
69
70
  ret = TrackedString.new(
70
71
  case
71
- when truncate && plain_string.length > width
72
+ when truncate && exceeded
72
73
  truncated = true
73
- plain_string[0, width]
74
+ plain_string[0, width] ### s/trim decorated text
75
+ when exceeded
76
+ cell
74
77
  when align == :center
75
78
  cell.center(width)
76
79
  when align == :right
@@ -79,6 +82,7 @@ module MarkdownTableFormatter
79
82
  cell.ljust(width)
80
83
  end
81
84
  )
85
+ ret.exceeded = exceeded
82
86
  ret.truncated = truncated
83
87
  ret
84
88
  end
@@ -152,11 +156,8 @@ module MarkdownTableFormatter
152
156
  alignment_indicators, column_widths =
153
157
  calculate_column_alignment_and_widths(rows, column_count)
154
158
 
155
- # ww0 'table_width', table_width
156
- # ww0 'truncate', truncate
157
-
158
159
  unless table_width.nil?
159
- sum_column_widths = column_widths.sum
160
+ sum_column_widths = column_widths.sum + ((column_count * 3) + 5)
160
161
  if sum_column_widths > table_width
161
162
  ratio = table_width.to_f / sum_column_widths
162
163
  column_widths.each_with_index do |width, i|
@@ -168,7 +169,7 @@ module MarkdownTableFormatter
168
169
  format_rows__hs(
169
170
  rows, alignment_indicators, column_widths, decorate,
170
171
  truncate: truncate
171
- ).tap { |ret| binding.irb if ret.class == TrackedString }
172
+ )
172
173
  end
173
174
 
174
175
  def insert_every_other(array, obj)
@@ -92,6 +92,10 @@ module HashDelegatorSelf
92
92
  blocks.find { |item| item.send(msg) == value } || default
93
93
  end
94
94
 
95
+ def block_match(blocks, msg, value, default = nil)
96
+ blocks.select { |item| value =~ item.send(msg) }
97
+ end
98
+
95
99
  def block_select(blocks, msg, value, default = nil)
96
100
  blocks.select { |item| item.send(msg) == value }
97
101
  end
@@ -297,14 +301,16 @@ module HashDelegatorSelf
297
301
  truncate: $table_cell_truncate
298
302
  )
299
303
 
300
- truncated_table_cell = false
304
+ exceeded_table_cell = false # any cell in table is exceeded
305
+ truncated_table_cell = false # any cell in table is truncated
301
306
  table__hs.each do |table_hs|
302
307
  table_hs.substrings.each do |substrings|
303
308
  substrings.each do |node|
304
- if node[:text].class == TrackedString
305
- truncated_table_cell = node[:text].truncated
306
- break if truncated_table_cell
307
- end
309
+ next unless node[:text].class == TrackedString
310
+
311
+ exceeded_table_cell ||= node[:text].exceeded
312
+ truncated_table_cell = node[:text].truncated
313
+ break if truncated_table_cell
308
314
  end
309
315
  break if truncated_table_cell
310
316
  end
@@ -337,9 +343,9 @@ module HashDelegatorSelf
337
343
 
338
344
  if ind.zero?
339
345
  fcb.truncated_table_cell = truncated_table_cell
340
- # if truncated_table_cell
341
- fcb.delete_key(:disabled)
342
- # end
346
+ if exceeded_table_cell
347
+ fcb.delete_key(:disabled)
348
+ end
343
349
  end
344
350
  end
345
351
  end
@@ -588,6 +594,7 @@ module MarkdownExec
588
594
  @prompt = HashDelegator.tty_prompt_without_disabled_symbol
589
595
 
590
596
  @opts_most_recent_filename = nil
597
+ @ux_most_recent_filename = nil
591
598
  @vars_most_recent_filename = nil
592
599
  @pass_args = []
593
600
  @run_state = OpenStruct.new(
@@ -865,7 +872,6 @@ module MarkdownExec
865
872
  blocks = []
866
873
  iter_blocks_from_nested_files do |btype, fcb|
867
874
  count += 1
868
-
869
875
  case btype
870
876
  when :blocks
871
877
  if @delegate_object[:bash]
@@ -873,7 +879,10 @@ module MarkdownExec
873
879
  block_calls_scan: @delegate_object[:block_calls_scan],
874
880
  block_name_match: @delegate_object[:block_name_match],
875
881
  block_name_nick_match: @delegate_object[:block_name_nick_match],
876
- id: "#{source_id}_bfnf_b_#{count}"
882
+ id: "#{source_id}_bfnf_b_#{count}",
883
+ menu_format: @delegate_object[:menu_ux_row_format],
884
+ prompt: @delegate_object[:prompt_ux_enter_a_value],
885
+ table_center: @delegate_object[:table_center]
877
886
  ) do |oname, color|
878
887
  apply_block_type_color_option(oname, color)
879
888
  end
@@ -935,7 +944,7 @@ module MarkdownExec
935
944
  (link_state&.inherited_lines_block || ''), commands,
936
945
  initial_code_required: initial_code_required,
937
946
  key_format: key_format
938
- ) # !!t
947
+ )
939
948
  end
940
949
 
941
950
  def calc_logged_stdout_filename(block_name:)
@@ -1008,6 +1017,135 @@ module MarkdownExec
1008
1017
  ]
1009
1018
  end
1010
1019
 
1020
+ def neval(export_exec, inherited_code)
1021
+ # ww0 export_exec
1022
+ # ww0 inherited_code
1023
+ code = (inherited_code || []) + [export_exec]
1024
+ filespec = generate_temp_filename
1025
+ File.write filespec, HashDelegator.join_code_lines(code)
1026
+ File.chmod 0o755, filespec
1027
+ # ww0 File.read(filespec)
1028
+ ret = `#{filespec}`
1029
+ File.delete filespec
1030
+ ret
1031
+ end
1032
+
1033
+ # sets ENV
1034
+ def code_from_ux_block_to_set_environment_variables(
1035
+ selected, mdoc, inherited_code: nil, force: true, only_default: false
1036
+ )
1037
+ # ww0 inherited_code
1038
+ # ww0 mdoc
1039
+ exit_prompt = @delegate_object[:prompt_filespec_back]
1040
+
1041
+ required = mdoc.collect_recursively_required_code(
1042
+ anyname: selected.pub_name,
1043
+ label_format_above: @delegate_object[:shell_code_label_format_above],
1044
+ label_format_below: @delegate_object[:shell_code_label_format_below],
1045
+ block_source: block_source
1046
+ )
1047
+
1048
+ code_lines = []
1049
+ case data = YAML.load(selected.body.join("\n"))
1050
+ when Hash
1051
+ export = parse_yaml_of_ux_block(
1052
+ data,
1053
+ prompt: @delegate_object[:prompt_ux_enter_a_value],
1054
+ validate: '^(?<name>[^ ].*)$'
1055
+ )
1056
+
1057
+ exportable = true
1058
+ if only_default
1059
+ value = case export.default
1060
+ # exec > default
1061
+ when :exec
1062
+ raise unless export.exec.present?
1063
+
1064
+ n1 = neval(export.exec, (inherited_code || []) + required[:code])
1065
+
1066
+ if export.transform.present?
1067
+ if export.transform.is_a? Symbol
1068
+ n1.send(export.transform)
1069
+ else
1070
+ format(
1071
+ export.transform,
1072
+ NamedCaptureExtractor.extract_named_groups(
1073
+ n1, export.validate
1074
+ )
1075
+ )
1076
+ end
1077
+ else
1078
+ n1
1079
+ end
1080
+
1081
+ # default
1082
+ else
1083
+ export.default.to_s
1084
+ end
1085
+ else
1086
+ caps = nil
1087
+ value = nil
1088
+
1089
+ # exec > allowed
1090
+ if export.exec
1091
+ value = neval(export.exec, (inherited_code || []) + required[:code])
1092
+ caps = NamedCaptureExtractor.extract_named_groups(value,
1093
+ export.validate)
1094
+
1095
+ # allowed > prompt
1096
+ elsif export.allowed && export.allowed.count.positive?
1097
+ case (choice = prompt_select_code_filename(
1098
+ [exit_prompt] + export.allowed,
1099
+ string: export.prompt,
1100
+ color_sym: :prompt_color_after_script_execution
1101
+ ))
1102
+ when exit_prompt
1103
+ exportable = false
1104
+ else
1105
+ value = choice
1106
+ caps = NamedCaptureExtractor.extract_named_groups(value,
1107
+ export.validate)
1108
+ end
1109
+
1110
+ # prompt > default
1111
+ elsif export.prompt.present?
1112
+ begin
1113
+ while true
1114
+ print "#{export.prompt} [#{export.default}]: "
1115
+ value = gets.chomp
1116
+ value = export.default.to_s if value.empty?
1117
+ caps = NamedCaptureExtractor.extract_named_groups(value,
1118
+ export.validate)
1119
+ break if caps
1120
+
1121
+ # invalid input, retry
1122
+
1123
+ end
1124
+ rescue Interrupt
1125
+ exportable = false
1126
+ end
1127
+
1128
+ # default
1129
+ else
1130
+ value = export.default
1131
+ end
1132
+
1133
+ if exportable && export.transform.present?
1134
+ value = format(export.transform, caps)
1135
+ end
1136
+ end
1137
+
1138
+ if exportable
1139
+ ENV[export.name] = value.to_s
1140
+ code_lines.push code_line_safe_assign(export.name, value,
1141
+ force: force)
1142
+ end
1143
+ else
1144
+ raise "Invalid data type: #{data.inspect}"
1145
+ end
1146
+ code_lines
1147
+ end
1148
+
1011
1149
  # sets ENV
1012
1150
  def code_from_vars_block_to_set_environment_variables(selected)
1013
1151
  code_lines = []
@@ -1027,6 +1165,15 @@ module MarkdownExec
1027
1165
  code_lines
1028
1166
  end
1029
1167
 
1168
+ def code_line_safe_assign(name, value, force:)
1169
+ if force
1170
+ # ww0 value
1171
+ "#{name}=#{Shellwords.escape(value)}"
1172
+ else
1173
+ "[[ -z $#{name} ]] && #{name}=#{Shellwords.escape(value)}"
1174
+ end
1175
+ end
1176
+
1030
1177
  def collect_line_decor_patterns(delegate_object)
1031
1178
  extract_patterns = lambda do |key|
1032
1179
  return [] unless delegate_object[key].present?
@@ -1412,7 +1559,7 @@ module MarkdownExec
1412
1559
  @delegate_object[criteria[:color]].to_sym,
1413
1560
  decor_patterns:
1414
1561
  @decor_patterns_from_delegate_object_for_block_create,
1415
- disabled: fcb.truncated_table_cell.nil? && !(criteria[:collapsible] && @delegate_object[criteria[:collapsible]]),
1562
+ disabled: !(criteria[:collapsible] && @delegate_object[criteria[:collapsible]]),
1416
1563
  fcb: fcb,
1417
1564
  id: "#{id}.#{index}",
1418
1565
  format_option: criteria[:format] &&
@@ -1790,6 +1937,18 @@ module MarkdownExec
1790
1937
  )
1791
1938
  next_state_set_code(selected, link_state, required_lines)
1792
1939
 
1940
+ elsif selected.type == BlockType::UX
1941
+ debounce_reset
1942
+ next_state_append_code(
1943
+ selected,
1944
+ link_state,
1945
+ code_from_ux_block_to_set_environment_variables(
1946
+ selected,
1947
+ @dml_mdoc,
1948
+ inherited_code: @dml_link_state.inherited_lines
1949
+ )
1950
+ )
1951
+
1793
1952
  elsif selected.type == BlockType::VARS
1794
1953
  debounce_reset
1795
1954
  next_state_append_code(selected, link_state,
@@ -2296,7 +2455,7 @@ module MarkdownExec
2296
2455
 
2297
2456
  variable_counts = count_named_group_occurrences(blocks, pattern,
2298
2457
  group_name: group_name)
2299
- return if variable_counts.nil?
2458
+ return if variable_counts.nil? || variable_counts == {}
2300
2459
 
2301
2460
  echo_commands = generate_echo_commands(variable_counts, echo_format)
2302
2461
 
@@ -2307,6 +2466,7 @@ module MarkdownExec
2307
2466
  )
2308
2467
 
2309
2468
  return if replacements.nil?
2469
+ return if replacements == EvaluateShellExpression::StatusFail
2310
2470
 
2311
2471
  expand_blocks_with_replacements(blocks, replacements)
2312
2472
  end
@@ -2723,6 +2883,37 @@ module MarkdownExec
2723
2883
  true
2724
2884
  end
2725
2885
 
2886
+ def load_auto_ux_block(
2887
+ all_blocks,
2888
+ mdoc,
2889
+ block_name: @delegate_object[:document_load_ux_block_name]
2890
+ )
2891
+ unless block_name.present? &&
2892
+ @ux_most_recent_filename != @delegate_object[:filename]
2893
+ return
2894
+ end
2895
+
2896
+ blocks = HashDelegator.block_select(all_blocks, :oname, block_name)
2897
+ if blocks.empty?
2898
+ blocks = HashDelegator.block_match(all_blocks, :nickname,
2899
+ Regexp.new(block_name))
2900
+ end
2901
+ return if blocks.empty?
2902
+
2903
+ @ux_most_recent_filename = @delegate_object[:filename]
2904
+
2905
+ (blocks.each.with_object([]) do |block, merged_options|
2906
+ merged_options.push(
2907
+ code_from_ux_block_to_set_environment_variables(
2908
+ block,
2909
+ mdoc,
2910
+ force: @delegate_object[:ux_auto_load_force_default],
2911
+ only_default: true
2912
+ )
2913
+ )
2914
+ end).to_a
2915
+ end
2916
+
2726
2917
  def load_auto_vars_block(all_blocks,
2727
2918
  block_name: @delegate_object[:document_load_vars_block_name])
2728
2919
  unless block_name.present? &&
@@ -2871,6 +3062,16 @@ module MarkdownExec
2871
3062
  reload_blocks = true
2872
3063
  end
2873
3064
 
3065
+ # load document ux block
3066
+ #
3067
+ if code_lines = load_auto_ux_block(all_blocks, mdoc)
3068
+ new_code = HashDelegator.code_merge(link_state.inherited_lines,
3069
+ code_lines)
3070
+ next_state_set_code(nil, link_state, new_code)
3071
+ link_state.inherited_lines = new_code
3072
+ reload_blocks = true
3073
+ end
3074
+
2874
3075
  # load document vars block
2875
3076
  #
2876
3077
  if code_lines = load_auto_vars_block(all_blocks)
@@ -2924,7 +3125,6 @@ module MarkdownExec
2924
3125
  HashDelegator.tables_into_columns!(menu_blocks, @delegate_object,
2925
3126
  screen_width_for_table)
2926
3127
 
2927
-
2928
3128
  [all_blocks, menu_blocks, mdoc]
2929
3129
  end
2930
3130
 
@@ -2988,7 +3188,6 @@ module MarkdownExec
2988
3188
  def menu_toggle_collapsible_block(selected)
2989
3189
  # return true if @compress_ids.key?(fcb.id) && !!@compress_ids[fcb.id]
2990
3190
  # return false if @expand_ids.key?(fcb.id) && !!@expand_ids[fcb.id]
2991
- # binding.irb
2992
3191
  if @compressed_ids.key?(selected.id) && !!@compressed_ids[selected.id]
2993
3192
  @compressed_ids.delete(selected.id)
2994
3193
  @expanded_ids[selected.id] = selected.level
@@ -3705,15 +3904,17 @@ module MarkdownExec
3705
3904
  end
3706
3905
 
3707
3906
  def screen_width
3708
- if @delegate_object[:screen_width] && @delegate_object[:screen_width].positive?
3709
- @delegate_object[:screen_width]
3907
+ width = @delegate_object[:screen_width]
3908
+ if width && width.positive?
3909
+ width
3710
3910
  else
3711
3911
  @delegate_object[:console_width]
3712
3912
  end
3713
3913
  end
3714
3914
 
3715
3915
  def screen_width_for_table
3716
- screen_width - prompt_margin_left_width - prompt_margin_right_width - 2 # prompt is symbol + space (width: 2)
3916
+ # menu adds newline after some lines if sized to the edge
3917
+ screen_width - prompt_margin_left_width - prompt_margin_right_width - 3 # menu prompt symbol (1) + space (1) + gap (1)
3717
3918
  end
3718
3919
 
3719
3920
  def screen_width_for_wrapping
@@ -3,10 +3,11 @@
3
3
  require_relative 'ansi_string'
4
4
 
5
5
  class TrackedString < String
6
- attr_accessor :truncated
6
+ attr_accessor :exceeded, :truncated
7
7
 
8
8
  def initialize(str)
9
- super(str)
9
+ super
10
+ @exceeded = false
10
11
  @truncated = false
11
12
  end
12
13
  end
@@ -7,5 +7,5 @@ module MarkdownExec
7
7
  BIN_NAME = 'mde'
8
8
  GEM_NAME = 'markdown_exec'
9
9
  TAP_DEBUG = 'MDE_DEBUG'
10
- VERSION = '2.7.4'
10
+ VERSION = '2.8.0'
11
11
  end
data/lib/markdown_exec.rb CHANGED
@@ -256,7 +256,7 @@ module MarkdownExec
256
256
  block_name_nick_match)
257
257
  return unless line.match(fenced_start_and_end_regex)
258
258
 
259
- bm = NamedCaptureExtractor::extract_named_groups(line, block_name_match)
259
+ bm = NamedCaptureExtractor.extract_named_groups(line, block_name_match)
260
260
  return if bm.nil?
261
261
 
262
262
  name = bm[:title]
@@ -282,7 +282,6 @@ module MarkdownExec
282
282
  include StringUtil
283
283
 
284
284
  def initialize(options = {})
285
- # ww0 'options', options, caller.deref
286
285
  @option_parser = nil
287
286
 
288
287
  @options = HashDelegator.new(options)
@@ -405,7 +404,7 @@ module MarkdownExec
405
404
  ## Executes the block specified in the options
406
405
  #
407
406
  def execute_block_with_error_handling
408
- @options.register_console_attributes(@options)
407
+ @options.register_console_attributes(@options)
409
408
  finalize_cli_argument_processing
410
409
  execute_initial_commands_and_main_loop(@options, @options.run_state)
411
410
  rescue FileMissingError
@@ -430,11 +429,9 @@ module MarkdownExec
430
429
  def execute_simple_commands(options, stage: nil)
431
430
  # !!p stage
432
431
  simple_commands(options).each do |key, (cstage, proc)|
433
- if @options[key].is_a?(TrueClass) || @options[key].present?
434
- if stage && stage == cstage
435
- proc.call
436
- return true
437
- end
432
+ if (@options[key].is_a?(TrueClass) || @options[key].present?) && (stage && stage == cstage)
433
+ proc.call
434
+ return true
438
435
  end
439
436
  end
440
437
  false
data/lib/mdoc.rb CHANGED
@@ -142,6 +142,7 @@ module MarkdownExec
142
142
  fcb.body # entire body is returned to requesing block
143
143
  elsif [BlockType::LINK,
144
144
  BlockType::LOAD,
145
+ BlockType::UX,
145
146
  BlockType::VARS].include? fcb.type
146
147
  nil
147
148
  elsif fcb[:chrome] # for Link blocks like History
data/lib/menu.src.yml CHANGED
@@ -206,6 +206,12 @@
206
206
  :default: "(document_shell)"
207
207
  :procname: val_as_str
208
208
 
209
+ - :opt_name: document_load_ux_block_name
210
+ :env_var: MDE_DOCUMENT_LOAD_UX_BLOCK_NAME
211
+ :description: Name of UX block to load with the document
212
+ :default: "\\[.*document_ux.*\\]"
213
+ :procname: val_as_str
214
+
209
215
  - :opt_name: document_load_vars_block_name
210
216
  :env_var: MDE_DOCUMENT_LOAD_VARS_BLOCK_NAME
211
217
  :description: Name of Vars block to load with the document
@@ -1026,6 +1032,18 @@
1026
1032
  :default: ">"
1027
1033
  :procname: val_as_str
1028
1034
 
1035
+ - :opt_name: menu_ux_color
1036
+ :env_var: MDE_MENU_UX_COLOR
1037
+ :description: Color of menu ux
1038
+ :default: fg_rgbh_df_c0_df
1039
+ :procname: val_as_str
1040
+
1041
+ - :opt_name: menu_ux_row_format
1042
+ :env_var: MDE_MENU_UX_ROW_FORMAT
1043
+ :description: Format for UX row
1044
+ :default: '%{name}=${%{name}}'
1045
+ :procname: val_as_str
1046
+
1029
1047
  - :opt_name: menu_vars_color
1030
1048
  :env_var: MDE_MENU_VARS_COLOR
1031
1049
  :description: Color of menu vars
@@ -1315,6 +1333,12 @@
1315
1333
  :description: Prompt to select a saved file
1316
1334
  :procname: val_as_str
1317
1335
 
1336
+ - :opt_name: prompt_ux_enter_a_value
1337
+ :env_var: MDE_PROMPT_UX_ENTER_A_VALUE
1338
+ :description: Prompt to enter a value for UX blocks
1339
+ :default: "\nEnter a value:"
1340
+ :procname: val_as_str
1341
+
1318
1342
  - :opt_name: prompt_show_expr_format
1319
1343
  :env_var: MDE_PROMPT_SHOW_EXPR_FORMAT
1320
1344
  :description: prompt_show_expr_format
@@ -1630,6 +1654,12 @@
1630
1654
  :default: false
1631
1655
  :procname: val_as_bool
1632
1656
 
1657
+ - :opt_name: ux_auto_load_force_default
1658
+ :env_var: MDE_UX_AUTO_LOAD_FORCE_DEFAULT
1659
+ :arg_name: BOOL
1660
+ :default: true
1661
+ :procname: val_as_bool
1662
+
1633
1663
  - :opt_name: variable_expression_regexp
1634
1664
  :env_var: MDE_VARIABLE_EXPRESSION_REGEXP
1635
1665
  :description: variable_expression_regexp
data/lib/menu.yml CHANGED
@@ -171,6 +171,11 @@
171
171
  :description: Name of shell block to load with the document
172
172
  :default: "(document_shell)"
173
173
  :procname: val_as_str
174
+ - :opt_name: document_load_ux_block_name
175
+ :env_var: MDE_DOCUMENT_LOAD_UX_BLOCK_NAME
176
+ :description: Name of UX block to load with the document
177
+ :default: "\\[.*document_ux.*\\]"
178
+ :procname: val_as_str
174
179
  - :opt_name: document_load_vars_block_name
175
180
  :env_var: MDE_DOCUMENT_LOAD_VARS_BLOCK_NAME
176
181
  :description: Name of Vars block to load with the document
@@ -864,6 +869,16 @@
864
869
  :description: Symbol before each task
865
870
  :default: ">"
866
871
  :procname: val_as_str
872
+ - :opt_name: menu_ux_color
873
+ :env_var: MDE_MENU_UX_COLOR
874
+ :description: Color of menu ux
875
+ :default: fg_rgbh_df_c0_df
876
+ :procname: val_as_str
877
+ - :opt_name: menu_ux_row_format
878
+ :env_var: MDE_MENU_UX_ROW_FORMAT
879
+ :description: Format for UX row
880
+ :default: "%{name}=${%{name}}"
881
+ :procname: val_as_str
867
882
  - :opt_name: menu_vars_color
868
883
  :env_var: MDE_MENU_VARS_COLOR
869
884
  :description: Color of menu vars
@@ -1123,6 +1138,13 @@
1123
1138
  Choose a file:
1124
1139
  :description: Prompt to select a saved file
1125
1140
  :procname: val_as_str
1141
+ - :opt_name: prompt_ux_enter_a_value
1142
+ :env_var: MDE_PROMPT_UX_ENTER_A_VALUE
1143
+ :description: Prompt to enter a value for UX blocks
1144
+ :default: |2-
1145
+
1146
+ Enter a value:
1147
+ :procname: val_as_str
1126
1148
  - :opt_name: prompt_show_expr_format
1127
1149
  :env_var: MDE_PROMPT_SHOW_EXPR_FORMAT
1128
1150
  :description: prompt_show_expr_format
@@ -1391,6 +1413,11 @@
1391
1413
  :arg_name: BOOL
1392
1414
  :default: false
1393
1415
  :procname: val_as_bool
1416
+ - :opt_name: ux_auto_load_force_default
1417
+ :env_var: MDE_UX_AUTO_LOAD_FORCE_DEFAULT
1418
+ :arg_name: BOOL
1419
+ :default: true
1420
+ :procname: val_as_bool
1394
1421
  - :opt_name: variable_expression_regexp
1395
1422
  :env_var: MDE_VARIABLE_EXPRESSION_REGEXP
1396
1423
  :description: variable_expression_regexp
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdown_exec
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.4
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fareed Stevenson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-02 00:00:00.000000000 Z
11
+ date: 2025-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard
@@ -124,6 +124,7 @@ files:
124
124
  - bats/options.bats
125
125
  - bats/plain.bats
126
126
  - bats/publish.bats
127
+ - bats/table-column-truncate.bats
127
128
  - bats/table.bats
128
129
  - bats/test_helper.bash
129
130
  - bats/variable-expansion.bats
@@ -159,6 +160,7 @@ files:
159
160
  - docs/dev/screen-width.md
160
161
  - docs/dev/specs-import.md
161
162
  - docs/dev/specs.md
163
+ - docs/dev/table-column-truncate.md
162
164
  - docs/dev/table-indent.md
163
165
  - docs/dev/table-invalid.md
164
166
  - docs/dev/text-decoration.md