markdown_exec 2.8.4 → 3.0.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 +4 -4
- data/CHANGELOG.md +33 -1
- data/Gemfile.lock +1 -1
- data/Rakefile +0 -33
- data/bats/bats.bats +2 -0
- data/bats/block-type-link.bats +1 -1
- data/bats/block-type-ux-allowed.bats +2 -2
- data/bats/block-type-ux-default.bats +8 -0
- data/bats/block-type-ux-invalid.bats +1 -1
- data/bats/{block-type-ux-preconditions.bats → block-type-ux-required-variables.bats} +1 -1
- data/bats/block-type-ux-row-format.bats +1 -1
- data/bats/block-type-ux-sources.bats +36 -0
- data/bats/border.bats +1 -1
- data/bats/cli.bats +2 -2
- data/bats/command-substitution-options.bats +14 -0
- data/bats/command-substitution.bats +1 -1
- data/bats/fail.bats +5 -2
- data/bats/indented-block-type-vars.bats +1 -1
- data/bats/markup.bats +1 -1
- data/bats/option-expansion.bats +8 -0
- data/bats/table-column-truncate.bats +1 -1
- data/bats/test_helper.bash +50 -5
- data/docs/dev/bats-document-configuration.md +1 -1
- data/docs/dev/block-type-ux-allowed.md +5 -5
- data/docs/dev/block-type-ux-auto.md +9 -5
- data/docs/dev/block-type-ux-chained.md +4 -2
- data/docs/dev/block-type-ux-default.md +42 -0
- data/docs/dev/block-type-ux-echo-hash.md +6 -1
- data/docs/dev/block-type-ux-echo.md +3 -1
- data/docs/dev/block-type-ux-exec.md +3 -4
- data/docs/dev/block-type-ux-hidden.md +3 -0
- data/docs/dev/block-type-ux-require.md +9 -18
- data/docs/dev/{block-type-ux-preconditions.md → block-type-ux-required-variables.md} +1 -2
- data/docs/dev/block-type-ux-row-format.md +3 -4
- data/docs/dev/block-type-ux-sources.md +57 -0
- data/docs/dev/block-type-ux-transform.md +0 -4
- data/docs/dev/command-substitution-options.md +61 -0
- data/docs/dev/indented-block-type-vars.md +1 -0
- data/docs/dev/menu-pagination-indent.md +123 -0
- data/docs/dev/menu-pagination.md +111 -0
- data/docs/dev/option-expansion.md +10 -0
- data/lib/ansi_formatter.rb +2 -0
- data/lib/block_cache.rb +197 -0
- data/lib/command_result.rb +57 -0
- data/lib/constants.rb +18 -0
- data/lib/error_reporting.rb +38 -0
- data/lib/evaluate_shell_expressions.rb +43 -18
- data/lib/fcb.rb +114 -11
- data/lib/hash_delegator.rb +595 -359
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +136 -45
- data/lib/mdoc.rb +74 -23
- data/lib/menu.src.yml +27 -9
- data/lib/menu.yml +23 -8
- data/lib/namer.rb +1 -3
- data/lib/value_or_exception.rb +76 -0
- metadata +18 -4
data/lib/block_cache.rb
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env -S bundle exec ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
# a ruby class contains a list of objects
|
7
|
+
class BlockCache
|
8
|
+
attr_accessor :cache
|
9
|
+
|
10
|
+
def initialize(cache = [])
|
11
|
+
@cache = cache
|
12
|
+
end
|
13
|
+
|
14
|
+
# Find an object by id
|
15
|
+
def find_by_id(id)
|
16
|
+
@cache.find { |obj| obj.id == id }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get an existing object by id or create a new one
|
20
|
+
def get_or_create(id, new_obj_proc = nil)
|
21
|
+
obj = find_by_id(id)
|
22
|
+
return obj if obj
|
23
|
+
|
24
|
+
# Create a new object if not found
|
25
|
+
if block_given?
|
26
|
+
obj = yield(id)
|
27
|
+
elsif new_obj_proc
|
28
|
+
obj = new_obj_proc.call(id)
|
29
|
+
else
|
30
|
+
raise ArgumentError, 'Block or proc required to create new object'
|
31
|
+
end
|
32
|
+
|
33
|
+
@cache << obj
|
34
|
+
obj
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add a new object to the cache
|
38
|
+
def add(obj)
|
39
|
+
raise ArgumentError,
|
40
|
+
'Object must respond to id method' unless obj.respond_to?(:id)
|
41
|
+
|
42
|
+
# Replace existing object with same id
|
43
|
+
if (existing = find_by_id(obj.id))
|
44
|
+
@cache[@cache.index(existing)] = obj
|
45
|
+
else
|
46
|
+
@cache << obj
|
47
|
+
end
|
48
|
+
obj
|
49
|
+
end
|
50
|
+
|
51
|
+
# Remove an object from the cache by id
|
52
|
+
def remove(id)
|
53
|
+
@cache.delete_if { |obj| obj.id == id }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clear the entire cache
|
57
|
+
def clear
|
58
|
+
@cache = []
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get the size of the cache
|
62
|
+
def size
|
63
|
+
@cache.size
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return unless $PROGRAM_NAME == __FILE__
|
68
|
+
|
69
|
+
require 'bundler/setup'
|
70
|
+
Bundler.require(:default)
|
71
|
+
|
72
|
+
require 'minitest/autorun'
|
73
|
+
require 'mocha/minitest'
|
74
|
+
|
75
|
+
# require_relative 'ww'
|
76
|
+
|
77
|
+
# A simple dummy object that responds to #id
|
78
|
+
class DummyObject
|
79
|
+
attr_reader :id
|
80
|
+
|
81
|
+
def initialize(id)
|
82
|
+
@id = id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Unit tests for the BlockCache class
|
87
|
+
#
|
88
|
+
# This test suite verifies correct behavior of:
|
89
|
+
# - initialization (with and without initial cache)
|
90
|
+
# - finding objects by id
|
91
|
+
# - getting or creating objects via block or proc
|
92
|
+
# - adding objects (including replacing existing ones)
|
93
|
+
# - removing objects by id
|
94
|
+
# - clearing the cache
|
95
|
+
# - reporting cache size
|
96
|
+
class TestBlockCache < Minitest::Test
|
97
|
+
def setup
|
98
|
+
@obj1 = DummyObject.new(1)
|
99
|
+
@obj2 = DummyObject.new(2)
|
100
|
+
@initial_cache = [@obj1]
|
101
|
+
@cache = BlockCache.new(@initial_cache.dup)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_initialize_defaults_to_empty_array
|
105
|
+
empty_cache = BlockCache.new
|
106
|
+
assert_equal [], empty_cache.cache
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_initialize_with_provided_array
|
110
|
+
assert_equal @initial_cache, @cache.cache
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_find_by_id_returns_matching_object
|
114
|
+
assert_equal @obj1, @cache.find_by_id(1)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_find_by_id_returns_nil_when_not_found
|
118
|
+
assert_nil @cache.find_by_id(999)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_get_or_create_returns_existing_object
|
122
|
+
found = @cache.get_or_create(1) { |id| DummyObject.new(id) }
|
123
|
+
assert_equal @obj1, found
|
124
|
+
assert_equal 1, @cache.size
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_get_or_create_creates_with_block
|
128
|
+
new_obj = @cache.get_or_create(3) { |id| DummyObject.new(id) }
|
129
|
+
assert_instance_of DummyObject, new_obj
|
130
|
+
assert_equal 3, new_obj.id
|
131
|
+
assert_includes @cache.cache, new_obj
|
132
|
+
assert_equal 2, @cache.size
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_get_or_create_creates_with_proc
|
136
|
+
factory = proc { |id| DummyObject.new(id * 10) }
|
137
|
+
new_obj = @cache.get_or_create(5, factory)
|
138
|
+
assert_instance_of DummyObject, new_obj
|
139
|
+
assert_equal 50, new_obj.id
|
140
|
+
assert_equal new_obj, @cache.find_by_id(50)
|
141
|
+
assert_equal 2, @cache.size
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_get_or_create_raises_without_block_or_proc
|
145
|
+
assert_raises(ArgumentError) do
|
146
|
+
@cache.get_or_create(7)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_add_appends_new_object
|
151
|
+
result = @cache.add(@obj2)
|
152
|
+
assert_equal @obj2, result
|
153
|
+
assert_equal 2, @cache.size
|
154
|
+
assert_equal [@obj1, @obj2], @cache.cache
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_add_replaces_existing_object_with_same_id
|
158
|
+
replacement = DummyObject.new(1)
|
159
|
+
result = @cache.add(replacement)
|
160
|
+
assert_equal replacement, result
|
161
|
+
assert_equal 1, @cache.size
|
162
|
+
assert_equal replacement, @cache.find_by_id(1)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_add_raises_for_object_without_id_method
|
166
|
+
invalid = Object.new
|
167
|
+
assert_raises(ArgumentError) do
|
168
|
+
@cache.add(invalid)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_remove_deletes_object_by_id
|
173
|
+
@cache.add(@obj2)
|
174
|
+
@cache.remove(1)
|
175
|
+
assert_nil @cache.find_by_id(1)
|
176
|
+
assert_equal 1, @cache.size
|
177
|
+
assert_equal [@obj2], @cache.cache
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_remove_nonexistent_id_leaves_cache_unchanged
|
181
|
+
original = @cache.cache.dup
|
182
|
+
@cache.remove(999)
|
183
|
+
assert_equal original, @cache.cache
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_clear_empties_the_cache
|
187
|
+
@cache.clear
|
188
|
+
assert_equal [], @cache.cache
|
189
|
+
assert_equal 0, @cache.size
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_size_returns_number_of_cached_objects
|
193
|
+
assert_equal 1, @cache.size
|
194
|
+
@cache.add(@obj2)
|
195
|
+
assert_equal 2, @cache.size
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env -S bundle exec ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
# Encapsulates the result of executing a system command, storing its output,
|
7
|
+
# exit status, and any number of additional, arbitrary attributes.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# result = CommandResult.new(stdout: output, exit_status: $?.exitstatus, duration: 1.23)
|
11
|
+
# result.stdout # => output
|
12
|
+
# result.exit_status # => 0
|
13
|
+
# result.duration # => 1.23
|
14
|
+
# result.new_field = 42
|
15
|
+
# result.new_field # => 42
|
16
|
+
# result.success? # => true
|
17
|
+
class CommandResult
|
18
|
+
# @param attributes [Hash{Symbol=>Object}] initial named attributes
|
19
|
+
def initialize(**attributes)
|
20
|
+
@attributes = {}
|
21
|
+
@attributes[:exit_status] = 0
|
22
|
+
@attributes[:stdout] = ''
|
23
|
+
attributes.each { |name, value| @attributes[name] = value }
|
24
|
+
end
|
25
|
+
|
26
|
+
def failure?
|
27
|
+
!success?
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean] true if the exit status is zero
|
31
|
+
def success?
|
32
|
+
exit_status.zero?
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(name, *args)
|
36
|
+
key = name.to_s.chomp('=').to_sym
|
37
|
+
|
38
|
+
if name.to_s.end_with?('=') # setter
|
39
|
+
@attributes[key] = args.first
|
40
|
+
elsif attribute?(name) # getter
|
41
|
+
@attributes[name]
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to_missing?(name, include_private = false)
|
48
|
+
key = name.to_s.chomp('=').to_sym
|
49
|
+
attribute?(key) || super
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def attribute?(name)
|
55
|
+
@attributes.key?(name)
|
56
|
+
end
|
57
|
+
end
|
data/lib/constants.rb
CHANGED
@@ -125,3 +125,21 @@ class TtyMenu
|
|
125
125
|
ENABLE = nil
|
126
126
|
DISABLE = ''
|
127
127
|
end
|
128
|
+
|
129
|
+
class ExportValueSource
|
130
|
+
ALLOW = ':allow'
|
131
|
+
DEFAULT = ':default'
|
132
|
+
ECHO = ':echo'
|
133
|
+
EDIT = ':edit'
|
134
|
+
EXEC = ':exec'
|
135
|
+
FALSE = false
|
136
|
+
end
|
137
|
+
|
138
|
+
class UxActSource
|
139
|
+
ALLOW = :allow
|
140
|
+
DEFAULT = :default
|
141
|
+
ECHO = :echo
|
142
|
+
EDIT = :edit
|
143
|
+
EXEC = :exec
|
144
|
+
FALSE = 'false'
|
145
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# encoding=utf-8
|
4
|
+
|
5
|
+
##
|
6
|
+
# Module providing standardized error‐reporting:
|
7
|
+
# logs either an Exception’s details or a simple
|
8
|
+
# string message—optionally with context—and then
|
9
|
+
# re‐raises either the original Exception or a new
|
10
|
+
# RuntimeError for string messages.
|
11
|
+
#
|
12
|
+
# Including this module gives you:
|
13
|
+
# • instance method → report_and_reraise(...)
|
14
|
+
# • class method → report_and_reraise(...)
|
15
|
+
module ErrorReporting
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def report_and_reraise(error_or_message, context: nil)
|
21
|
+
if error_or_message.is_a?(Exception)
|
22
|
+
header = +"#{error_or_message.class}: #{error_or_message.message}"
|
23
|
+
header << " (#{context})" if context
|
24
|
+
|
25
|
+
ww header
|
26
|
+
ww error_or_message.backtrace.join("\n") if error_or_message.backtrace
|
27
|
+
|
28
|
+
raise error_or_message
|
29
|
+
else
|
30
|
+
header = +error_or_message.to_s
|
31
|
+
header << " (#{context})" if context
|
32
|
+
|
33
|
+
ww header
|
34
|
+
|
35
|
+
raise error_or_message.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -10,12 +10,11 @@ class EvaluateShellExpression
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
|
13
|
-
|
14
|
-
|
13
|
+
initial_code_required: false,
|
14
|
+
occurrence_expressions: nil)
|
15
15
|
# !!p initial_code expressions key_format shell
|
16
16
|
return if (initial_code_required && (initial_code.nil? || initial_code.empty?)) ||
|
17
|
-
expressions.nil? || expressions.empty?
|
18
|
-
key_format.nil? || key_format.empty?
|
17
|
+
expressions.nil? || expressions.empty?
|
19
18
|
|
20
19
|
# token to separate output
|
21
20
|
token = "__TOKEN__#{Time.now.to_i}__"
|
@@ -28,7 +27,7 @@ def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
|
|
28
27
|
end
|
29
28
|
|
30
29
|
# Execute
|
31
|
-
stdout_str,
|
30
|
+
stdout_str, _, status = Open3.capture3(shell, '-c', script)
|
32
31
|
|
33
32
|
unless status.success?
|
34
33
|
return EvaluateShellExpression::StatusFail
|
@@ -40,7 +39,7 @@ def evaluate_shell_expressions(initial_code, expressions, shell: '/bin/bash',
|
|
40
39
|
unless part.empty?
|
41
40
|
part[1..-1].tap do |output_parts|
|
42
41
|
expressions.each_with_index do |(key, _expression), index|
|
43
|
-
result_hash[
|
42
|
+
result_hash[occurrence_expressions[key]] = output_parts[index].chomp
|
44
43
|
end
|
45
44
|
end
|
46
45
|
end
|
@@ -54,7 +53,6 @@ require 'bundler/setup'
|
|
54
53
|
Bundler.require(:default)
|
55
54
|
|
56
55
|
require 'minitest/autorun'
|
57
|
-
require 'open3'
|
58
56
|
|
59
57
|
class TestShellExpressionEvaluator < Minitest::Test
|
60
58
|
def setup
|
@@ -66,7 +64,11 @@ class TestShellExpressionEvaluator < Minitest::Test
|
|
66
64
|
|
67
65
|
def test_single_expression
|
68
66
|
expressions = { 'greeting' => "echo 'Hello, World!'" }
|
69
|
-
|
67
|
+
occurrence_expressions = { 'greeting' => '%<greeting>' }
|
68
|
+
result = evaluate_shell_expressions(
|
69
|
+
@initial_code, expressions,
|
70
|
+
occurrence_expressions: occurrence_expressions
|
71
|
+
)
|
70
72
|
|
71
73
|
assert_equal 'Hello, World!', result['%<greeting>']
|
72
74
|
end
|
@@ -77,7 +79,15 @@ class TestShellExpressionEvaluator < Minitest::Test
|
|
77
79
|
'date' => 'date +%Y-%m-%d',
|
78
80
|
'kernel' => 'uname -r'
|
79
81
|
}
|
80
|
-
|
82
|
+
occurrence_expressions = {
|
83
|
+
'date' => '%<date>',
|
84
|
+
'greeting' => '%<greeting>',
|
85
|
+
'kernel' => '%<kernel>'
|
86
|
+
}
|
87
|
+
result = evaluate_shell_expressions(
|
88
|
+
@initial_code, expressions,
|
89
|
+
occurrence_expressions: occurrence_expressions
|
90
|
+
)
|
81
91
|
|
82
92
|
assert_equal 'Hello, World!', result['%<greeting>']
|
83
93
|
assert_match(/\d{4}-\d{2}-\d{2}/, result['%<date>'])
|
@@ -86,15 +96,22 @@ class TestShellExpressionEvaluator < Minitest::Test
|
|
86
96
|
|
87
97
|
def test_empty_expressions_list
|
88
98
|
expressions = {}
|
89
|
-
|
99
|
+
occurrence_expressions = {}
|
100
|
+
result = evaluate_shell_expressions(
|
101
|
+
@initial_code, expressions,
|
102
|
+
occurrence_expressions: occurrence_expressions
|
103
|
+
)
|
90
104
|
|
91
105
|
assert_nil result
|
92
106
|
end
|
93
107
|
|
94
108
|
def test_invalid_expression
|
95
109
|
expressions = { 'invalid' => 'invalid_command' }
|
96
|
-
|
97
|
-
result = evaluate_shell_expressions(
|
110
|
+
occurrence_expressions = {}
|
111
|
+
result = evaluate_shell_expressions(
|
112
|
+
@initial_code, expressions,
|
113
|
+
occurrence_expressions: occurrence_expressions
|
114
|
+
)
|
98
115
|
|
99
116
|
assert_equal EvaluateShellExpression::StatusFail, result
|
100
117
|
end
|
@@ -105,18 +122,26 @@ class TestShellExpressionEvaluator < Minitest::Test
|
|
105
122
|
echo "Custom setup message"
|
106
123
|
BASH
|
107
124
|
expressions = { 'test' => 'echo Test after initial setup' }
|
108
|
-
|
109
|
-
result = evaluate_shell_expressions(
|
125
|
+
occurrence_expressions = { 'test' => '%<test>' }
|
126
|
+
result = evaluate_shell_expressions(
|
127
|
+
@initial_code, expressions,
|
128
|
+
occurrence_expressions: occurrence_expressions
|
129
|
+
)
|
110
130
|
|
111
131
|
assert_equal 'Test after initial setup', result['%<test>']
|
112
132
|
end
|
113
133
|
|
114
134
|
def test_large_number_of_expressions
|
115
|
-
expressions = (1..100).
|
135
|
+
expressions = (1..100).to_h do |i|
|
116
136
|
["expr_#{i}", "echo Expression #{i}"]
|
117
|
-
end
|
118
|
-
|
119
|
-
|
137
|
+
end
|
138
|
+
occurrence_expressions = (1..100).to_h do |i|
|
139
|
+
["expr_#{i}", "%<expr_#{i}>"]
|
140
|
+
end
|
141
|
+
result = evaluate_shell_expressions(
|
142
|
+
@initial_code, expressions,
|
143
|
+
occurrence_expressions: occurrence_expressions
|
144
|
+
)
|
120
145
|
|
121
146
|
expressions.each_with_index do |(key, _expression), index|
|
122
147
|
assert_equal "Expression #{index + 1}", result["%<#{key}>"]
|
data/lib/fcb.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
# encoding=utf-8
|
5
5
|
require 'digest'
|
6
6
|
require_relative 'namer'
|
7
|
-
|
7
|
+
BT_UX_FLD_REQUIRED = 'required'
|
8
8
|
def parse_yaml_of_ux_block(
|
9
9
|
data,
|
10
10
|
menu_format: nil,
|
@@ -18,15 +18,17 @@ def parse_yaml_of_ux_block(
|
|
18
18
|
raise "Name is missing in UX block: #{data.inspect}" unless name.present?
|
19
19
|
|
20
20
|
OpenStruct.new(
|
21
|
-
|
21
|
+
act: export['act'],
|
22
|
+
allow: export['allow'] || export['allowed'],
|
22
23
|
default: export['default'],
|
23
24
|
echo: export['echo'],
|
24
25
|
exec: export['exec'],
|
25
|
-
|
26
|
+
init: export['init'],
|
27
|
+
menu_format: export['format'] || export['menu_format'] || menu_format,
|
26
28
|
name: name,
|
27
|
-
preconditions: export['preconditions'],
|
28
29
|
prompt: export['prompt'] || prompt,
|
29
30
|
readonly: export['readonly'].nil? ? false : export['readonly'],
|
31
|
+
required: export['require'] || export['required'] || export[BT_UX_FLD_REQUIRED],
|
30
32
|
transform: export['transform'],
|
31
33
|
validate: export['validate'] || validate
|
32
34
|
)
|
@@ -48,6 +50,7 @@ module MarkdownExec
|
|
48
50
|
call: nil,
|
49
51
|
dname: nil,
|
50
52
|
headings: [],
|
53
|
+
id: object_id,
|
51
54
|
indent: '',
|
52
55
|
name: nil,
|
53
56
|
nickname: nil,
|
@@ -62,6 +65,15 @@ module MarkdownExec
|
|
62
65
|
}.merge(options)
|
63
66
|
end
|
64
67
|
|
68
|
+
def pub_name(**kwargs)
|
69
|
+
self.class.pub_name(@attrs, **kwargs)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.pub_name(attrs, **kwargs)
|
73
|
+
full = attrs.fetch(:nickname, nil) || attrs.fetch(:oname, nil)
|
74
|
+
full&.to_s&.pub_name(**kwargs)
|
75
|
+
end
|
76
|
+
|
65
77
|
def code_name_included?(*names)
|
66
78
|
names.include?(@attrs[:oname])
|
67
79
|
end
|
@@ -127,8 +139,10 @@ module MarkdownExec
|
|
127
139
|
)&.fetch(1, nil)
|
128
140
|
titlexcall = call ? @attrs[:title].sub("%#{call}", '') : @attrs[:title]
|
129
141
|
|
130
|
-
oname = if
|
131
|
-
|
142
|
+
oname = if is_split?
|
143
|
+
@attrs[:text]
|
144
|
+
elsif block_name_nick_match.present? &&
|
145
|
+
@attrs[:oname] =~ Regexp.new(block_name_nick_match)
|
132
146
|
@attrs[:nickname] = $~[0]
|
133
147
|
derive_title_from_body
|
134
148
|
else
|
@@ -175,7 +189,61 @@ module MarkdownExec
|
|
175
189
|
def self.format_multiline_body_as_title(body_lines)
|
176
190
|
body_lines.map.with_index do |line, index|
|
177
191
|
index.zero? ? line : " #{line}"
|
178
|
-
end.join("\n")
|
192
|
+
end.join("\n")
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.act_source(export)
|
196
|
+
# If `false`, the UX block is not activated.
|
197
|
+
# If one of `:allow`, `:echo`, `:edit`, or `:exec` is specified,
|
198
|
+
# the value is calculated or the user is prompted.
|
199
|
+
# If not present, the default value is `:edit`.
|
200
|
+
if export.act.nil?
|
201
|
+
export.act = if export.init.to_s == 'false'
|
202
|
+
if export.allow.present?
|
203
|
+
UxActSource::ALLOW
|
204
|
+
elsif export.echo.present?
|
205
|
+
UxActSource::ECHO
|
206
|
+
elsif export.edit.present?
|
207
|
+
UxActSource::EDIT
|
208
|
+
elsif export.exec.present?
|
209
|
+
UxActSource::EXEC
|
210
|
+
else
|
211
|
+
UxActSource::EDIT
|
212
|
+
end
|
213
|
+
elsif export.allow.present?
|
214
|
+
UxActSource::ALLOW
|
215
|
+
else
|
216
|
+
UxActSource::EDIT
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
export.act
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.init_source(export)
|
224
|
+
# If `false`, there is no initial value set.
|
225
|
+
# If a string, it is the initial value of the object variable.
|
226
|
+
# Otherwise, if one of `:allow`, `:echo`, or `:exec` is specified,
|
227
|
+
# the value is the output of the `echo` or `exec` evaluation
|
228
|
+
# or the first allowed value.
|
229
|
+
# If not present, the default value is whichever of
|
230
|
+
# `:allow`, `:default`, `:echo`, or `:exec` is present.
|
231
|
+
if export.init.nil?
|
232
|
+
export.init = case
|
233
|
+
when export.allow.present?
|
234
|
+
UxActSource::ALLOW
|
235
|
+
when export.default.present?
|
236
|
+
UxActSource::DEFAULT
|
237
|
+
when export.echo.present?
|
238
|
+
UxActSource::ECHO
|
239
|
+
when export.exec.present?
|
240
|
+
UxActSource::EXEC
|
241
|
+
else
|
242
|
+
UxActSource::FALSE
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
export.init
|
179
247
|
end
|
180
248
|
|
181
249
|
# :reek:ManualDispatch
|
@@ -188,6 +256,14 @@ module MarkdownExec
|
|
188
256
|
dependency_names.include?(@attrs[:s2title])
|
189
257
|
end
|
190
258
|
|
259
|
+
def is_disabled?
|
260
|
+
@attrs[:disabled] == TtyMenu::DISABLE
|
261
|
+
end
|
262
|
+
|
263
|
+
def is_enabled?
|
264
|
+
!is_disabled?
|
265
|
+
end
|
266
|
+
|
191
267
|
def is_named?(name)
|
192
268
|
@attrs[:dname] == name ||
|
193
269
|
@attrs[:nickname] == name ||
|
@@ -196,6 +272,31 @@ module MarkdownExec
|
|
196
272
|
@attrs[:s2title] == name
|
197
273
|
end
|
198
274
|
|
275
|
+
# true if this is a line split block
|
276
|
+
def is_split?
|
277
|
+
is_split_first? || is_split_rest?
|
278
|
+
end
|
279
|
+
|
280
|
+
# true if this block displays its split body
|
281
|
+
# names and nicknames are displayed instead of the body
|
282
|
+
# ux blocks display a single line for the named variable
|
283
|
+
# split blocks are: opts, shell, vars
|
284
|
+
def is_split_displayed?(opts)
|
285
|
+
@attrs[:type] != BlockType::UX &&
|
286
|
+
!(@attrs[:start_line] =~ Regexp.new(opts[:block_name_nick_match]) ||
|
287
|
+
@attrs[:start_line] =~ Regexp.new(opts[:block_name_match]))
|
288
|
+
end
|
289
|
+
|
290
|
+
# true if this is the first line in a split block
|
291
|
+
def is_split_first?
|
292
|
+
@attrs.fetch(:is_split_first, false)
|
293
|
+
end
|
294
|
+
|
295
|
+
# true if this is the second or later line in a split block
|
296
|
+
def is_split_rest?
|
297
|
+
@attrs.fetch(:is_split_rest, false)
|
298
|
+
end
|
299
|
+
|
199
300
|
# :reek:ManualDispatch
|
200
301
|
def method_missing(method, *args, &block)
|
201
302
|
method_name = method.to_s
|
@@ -331,13 +432,15 @@ if $PROGRAM_NAME == __FILE__
|
|
331
432
|
end
|
332
433
|
|
333
434
|
def test_to_h_method
|
334
|
-
assert_equal_hash @fcb_data.merge(
|
435
|
+
assert_equal_hash @fcb_data.merge(
|
436
|
+
{ id: @fcb.id, random: @fcb.random }
|
437
|
+
), @fcb.to_h
|
335
438
|
end
|
336
439
|
|
337
440
|
def test_to_yaml_method
|
338
|
-
assert_equal_hash YAML.load(@fcb_data.merge(
|
339
|
-
|
340
|
-
|
441
|
+
assert_equal_hash YAML.load(@fcb_data.merge(
|
442
|
+
{ id: @fcb.id, random: @fcb.random }
|
443
|
+
).to_yaml), YAML.load(@fcb.to_yaml)
|
341
444
|
end
|
342
445
|
|
343
446
|
def test_method_missing_getter
|