markdown_exec 1.3.3.5 → 1.3.7
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/.rubocop.yml +3 -0
- data/CHANGELOG.md +14 -266
- data/Gemfile.lock +1 -1
- data/Rakefile +26 -596
- data/bin/tab_completion.sh +7 -7
- data/lib/block_label.rb +82 -0
- data/lib/env_opts.rb +6 -6
- data/lib/environment_opt_parse.rb +3 -3
- data/lib/fcb.rb +133 -0
- data/lib/filter.rb +181 -0
- data/lib/markdown_block_manager.rb +195 -0
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +74 -474
- data/lib/mdoc.rb +194 -0
- data/lib/menu.src.yml +448 -0
- data/lib/menu.yml +35 -9
- data/lib/menu_options.rb +0 -0
- data/lib/menu_options.yml +0 -0
- data/lib/object_present.rb +8 -8
- data/lib/option_value.rb +88 -0
- data/lib/regexp.rb +110 -0
- data/lib/saved_assets.rb +59 -0
- data/lib/saved_files_matcher.rb +61 -0
- metadata +14 -2
data/lib/block_label.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
##
|
7
|
+
# This class is used to represent a block label which can be constructed using various components.
|
8
|
+
# It handles initialization using a hash and provides a method to create a label string.
|
9
|
+
#
|
10
|
+
# Example Usage:
|
11
|
+
# block = {
|
12
|
+
# filename: 'example.md',
|
13
|
+
# headings: ['Header1', 'Header2'],
|
14
|
+
# menu_blocks_with_docname: true,
|
15
|
+
# menu_blocks_with_headings: false,
|
16
|
+
# title: 'Sample Title',
|
17
|
+
# body: 'Sample Body',
|
18
|
+
# text: 'Sample Text'
|
19
|
+
# }
|
20
|
+
# label_obj = BlockLabel.new(block)
|
21
|
+
# label_str = label_obj.make
|
22
|
+
#
|
23
|
+
|
24
|
+
class BlockLabel
|
25
|
+
def self.make(filename:, headings:, menu_blocks_with_docname:, menu_blocks_with_headings:, title:, body:, text:)
|
26
|
+
label = title
|
27
|
+
label = body if label.nil? || label.empty?
|
28
|
+
label = text if label.nil? || label.empty?
|
29
|
+
|
30
|
+
parts = [label]
|
31
|
+
|
32
|
+
parts << headings.compact.join(' # ') if menu_blocks_with_headings
|
33
|
+
parts << filename if menu_blocks_with_docname
|
34
|
+
|
35
|
+
parts.join(' ')
|
36
|
+
rescue StandardError => err
|
37
|
+
warn(error = "ERROR ** BlockLabel.make(); #{err.inspect}")
|
38
|
+
binding.pry if $tap_enable
|
39
|
+
raise ArgumentError, error
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if $PROGRAM_NAME == __FILE__
|
44
|
+
require 'minitest/autorun'
|
45
|
+
|
46
|
+
class BlockLabelTest < Minitest::Test
|
47
|
+
def setup
|
48
|
+
@block_data = {
|
49
|
+
filename: 'example.md',
|
50
|
+
headings: %w[Header1 Header2],
|
51
|
+
menu_blocks_with_docname: true,
|
52
|
+
menu_blocks_with_headings: false,
|
53
|
+
title: 'Sample Title',
|
54
|
+
body: 'Sample Body',
|
55
|
+
text: 'Sample Text'
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_make_method
|
60
|
+
assert_equal 'Sample Title example.md', BlockLabel.make(**@block_data)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_make_method_without_title
|
64
|
+
@block_data[:title] = nil
|
65
|
+
label = BlockLabel.make(**@block_data)
|
66
|
+
assert_equal 'Sample Body example.md', label
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_make_method_without_title_and_body
|
70
|
+
@block_data[:title] = nil
|
71
|
+
@block_data[:body] = nil
|
72
|
+
label = BlockLabel.make(**@block_data)
|
73
|
+
assert_equal 'Sample Text example.md', label
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_make_method_with_headings
|
77
|
+
@block_data[:menu_blocks_with_headings] = true
|
78
|
+
label = BlockLabel.make(**@block_data)
|
79
|
+
assert_equal 'Sample Title Header1 # Header2 example.md', label
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/env_opts.rb
CHANGED
@@ -93,9 +93,9 @@ class EnvOpts
|
|
93
93
|
args_ind = 0
|
94
94
|
while args_ind < argv.count
|
95
95
|
args_consumed = 0
|
96
|
-
arg = argv.fetch(args_ind, '')
|
96
|
+
arg = argv.fetch(args_ind, '')
|
97
97
|
if arg.start_with? '--'
|
98
|
-
opt_name = arg[2..-1]
|
98
|
+
opt_name = arg[2..-1]
|
99
99
|
args_consumed = consume_arguments(opt_name,
|
100
100
|
argv.fetch(args_ind + 1, nil))
|
101
101
|
end
|
@@ -136,7 +136,7 @@ class EnvOpts
|
|
136
136
|
# option names use hyphens
|
137
137
|
#
|
138
138
|
def self.symbol_name_to_option_name(name)
|
139
|
-
name.to_s.gsub('_', '-')
|
139
|
+
name.to_s.gsub('_', '-')
|
140
140
|
end
|
141
141
|
|
142
142
|
private
|
@@ -144,7 +144,7 @@ class EnvOpts
|
|
144
144
|
# convert key name or symbol to an option name
|
145
145
|
#
|
146
146
|
def key_name_to_option_name(key)
|
147
|
-
(key.is_a?(Symbol) ? EnvOpts.symbol_name_to_option_name(key) : key)
|
147
|
+
(key.is_a?(Symbol) ? EnvOpts.symbol_name_to_option_name(key) : key)
|
148
148
|
end
|
149
149
|
|
150
150
|
# get cast of environment variable
|
@@ -175,7 +175,7 @@ class EnvOpts
|
|
175
175
|
# option names use hyphens
|
176
176
|
#
|
177
177
|
def method_name_to_option_name(name)
|
178
|
-
name.to_s.gsub('_', '-')
|
178
|
+
name.to_s.gsub('_', '-')
|
179
179
|
end
|
180
180
|
|
181
181
|
# read and write options using the option name as a method
|
@@ -187,7 +187,7 @@ class EnvOpts
|
|
187
187
|
set_key_value_as_cast(name, value)
|
188
188
|
else
|
189
189
|
@values[method_name_to_option_name(method_name)]
|
190
|
-
end
|
190
|
+
end
|
191
191
|
end
|
192
192
|
|
193
193
|
# option name to environment name
|
@@ -146,7 +146,7 @@ class EnvironmentOptParse
|
|
146
146
|
include Menu
|
147
147
|
|
148
148
|
def initialize(menu: {}, lambdas: nil, options: nil, version: nil)
|
149
|
-
@menu = if menu.
|
149
|
+
@menu = if menu.instance_of?(::String)
|
150
150
|
filetext = File.read(menu).tap_yaml 'filetext'
|
151
151
|
fileyaml = YAML.load(filetext)
|
152
152
|
fileyaml.map(&:sym_keys)
|
@@ -156,7 +156,7 @@ class EnvironmentOptParse
|
|
156
156
|
@lambdas = lambdas
|
157
157
|
@version = version || '0.1'
|
158
158
|
# @options = {}
|
159
|
-
@options = if options.
|
159
|
+
@options = if options.instance_of?(::String)
|
160
160
|
YAML.safe_load(File.read(options)).sym_keys.tap_yaml '@options'
|
161
161
|
else
|
162
162
|
{}
|
@@ -193,7 +193,7 @@ class EnvironmentOptParse
|
|
193
193
|
exit
|
194
194
|
},
|
195
195
|
val_as_bool: lambda { |value|
|
196
|
-
value.
|
196
|
+
value.instance_of?(::String) ? (value.chomp != '0') : value
|
197
197
|
},
|
198
198
|
val_as_int: ->(value) { value.to_i },
|
199
199
|
val_as_str: ->(value) { value.to_s },
|
data/lib/fcb.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
module MarkdownExec
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
# Fenced Code Block (FCB)
|
10
|
+
#
|
11
|
+
# This class represents a fenced code block in a markdown document.
|
12
|
+
# It allows for setting and getting attributes related to the code block,
|
13
|
+
# such as body, call, headings, and more.
|
14
|
+
#
|
15
|
+
class FCB
|
16
|
+
def initialize(options = {})
|
17
|
+
@attrs = {
|
18
|
+
body: nil,
|
19
|
+
call: nil,
|
20
|
+
headings: [],
|
21
|
+
name: nil,
|
22
|
+
reqs: [],
|
23
|
+
shell: '',
|
24
|
+
title: '',
|
25
|
+
random: Random.new.rand,
|
26
|
+
text: nil # displayable in menu
|
27
|
+
}.merge(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
@attrs
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_yaml
|
35
|
+
@attrs.to_yaml
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# 2023-10-07 proposed but not functional with code
|
41
|
+
#
|
42
|
+
# def method_missing(method, *args, &block)
|
43
|
+
# method_name = method.to_s
|
44
|
+
|
45
|
+
# if method_name[-1] == '='
|
46
|
+
# @attrs[method_name.chop.to_sym] = args[0]
|
47
|
+
# elsif @attrs.key?(method_name.to_sym)
|
48
|
+
# @attrs[method_name.to_sym]
|
49
|
+
# else
|
50
|
+
# super
|
51
|
+
# end
|
52
|
+
# rescue StandardError => err
|
53
|
+
# warn(error = "ERROR ** FCB.method_missing(method: #{method_name}, *args: #{args.inspect}, &block)")
|
54
|
+
# warn err.inspect
|
55
|
+
# warn(caller[0..4])
|
56
|
+
# raise err # Here, we simply propagate the original error instead of wrapping it in a StandardError.
|
57
|
+
# end
|
58
|
+
|
59
|
+
# :reek:ManualDispatch
|
60
|
+
def method_missing(method, *args, &block)
|
61
|
+
method_name = method.to_s
|
62
|
+
|
63
|
+
if @attrs.respond_to?(method_name)
|
64
|
+
@attrs.send(method_name, *args, &block)
|
65
|
+
elsif method_name[-1] == '='
|
66
|
+
@attrs[method_name.chop.to_sym] = args[0]
|
67
|
+
else
|
68
|
+
@attrs[method_name.to_sym]
|
69
|
+
end
|
70
|
+
rescue StandardError => err
|
71
|
+
warn("ERROR ** FCB.method_missing(method: #{method_name}," \
|
72
|
+
" *args: #{args.inspect}, &block)")
|
73
|
+
warn err.inspect
|
74
|
+
warn(caller[0..4])
|
75
|
+
# raise StandardError, error
|
76
|
+
raise err # Here, we simply propagate the original error instead of wrapping it in a StandardError.
|
77
|
+
end
|
78
|
+
|
79
|
+
def respond_to_missing?(method_name, _include_private = false)
|
80
|
+
@attrs.key?(method_name.to_sym) || super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if $PROGRAM_NAME == __FILE__
|
86
|
+
require 'minitest/autorun'
|
87
|
+
require 'yaml'
|
88
|
+
|
89
|
+
class FCBTest < Minitest::Test
|
90
|
+
def setup
|
91
|
+
@fcb_data = {
|
92
|
+
body: 'Sample body',
|
93
|
+
call: 'Sample call',
|
94
|
+
headings: %w[Header1 Header2],
|
95
|
+
name: 'Sample name',
|
96
|
+
reqs: %w[req1 req2],
|
97
|
+
shell: 'bash',
|
98
|
+
text: 'Sample Text',
|
99
|
+
title: 'Sample Title'
|
100
|
+
}
|
101
|
+
@fcb = MarkdownExec::FCB.new(@fcb_data)
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_initialization_with_correct_data
|
105
|
+
assert_equal 'Sample body', @fcb.body
|
106
|
+
assert_equal %w[Header1 Header2], @fcb.headings
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_to_h_method
|
110
|
+
assert_equal @fcb_data.merge({ random: @fcb.random }), @fcb.to_h
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_to_yaml_method
|
114
|
+
assert_equal YAML.load(@fcb_data.merge({ random: @fcb.random }).to_yaml),
|
115
|
+
YAML.load(@fcb.to_yaml)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_method_missing_getter
|
119
|
+
assert_equal 'Sample Title', @fcb.title
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_method_missing_setter
|
123
|
+
@fcb.title = 'New Title'
|
124
|
+
assert_equal 'New Title', @fcb.title
|
125
|
+
end
|
126
|
+
|
127
|
+
# 2023-10-09 does not trigger error; treats as option name
|
128
|
+
#
|
129
|
+
# def test_method_missing_with_unknown_method
|
130
|
+
# assert_raises(NoMethodError) { @fcb.unknown_method }
|
131
|
+
# end
|
132
|
+
end
|
133
|
+
end
|
data/lib/filter.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
module MarkdownExec
|
7
|
+
# Filter
|
8
|
+
#
|
9
|
+
# The Filter class provides utilities to determine the inclusion of fenced code blocks (FCB)
|
10
|
+
# based on a set of provided options. The primary function, `fcb_select?`, checks
|
11
|
+
# various properties of an FCB and decides whether to include or exclude it.
|
12
|
+
#
|
13
|
+
# :reek:UtilityFunction
|
14
|
+
|
15
|
+
class Filter
|
16
|
+
def self.fcb_select?(options, fcb)
|
17
|
+
filters = {
|
18
|
+
name_default: true,
|
19
|
+
name_exclude: nil,
|
20
|
+
name_select: nil,
|
21
|
+
shell_default: true,
|
22
|
+
shell_exclude: nil,
|
23
|
+
shell_select: nil,
|
24
|
+
hidden_name: nil
|
25
|
+
}
|
26
|
+
|
27
|
+
name = fcb.fetch(:name, '')
|
28
|
+
shell = fcb.fetch(:shell, '')
|
29
|
+
|
30
|
+
apply_name_filters(options, filters, name)
|
31
|
+
apply_shell_filters(options, filters, shell)
|
32
|
+
apply_other_filters(options, filters, fcb)
|
33
|
+
|
34
|
+
evaluate_filters(options, filters)
|
35
|
+
rescue StandardError => err
|
36
|
+
warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
|
37
|
+
raise err
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.apply_name_filters(options, filters, name)
|
41
|
+
if name.present? && options[:block_name]
|
42
|
+
if name =~ /#{options[:block_name]}/
|
43
|
+
filters[:name_select] = true
|
44
|
+
filters[:name_exclude] = false
|
45
|
+
else
|
46
|
+
filters[:name_exclude] = true
|
47
|
+
filters[:name_select] = false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if name.present? && filters[:name_select].nil? && options[:select_by_name_regex].present?
|
52
|
+
filters[:name_select] = !!(name =~ /#{options[:select_by_name_regex]}/)
|
53
|
+
end
|
54
|
+
|
55
|
+
unless name.present? && filters[:name_exclude].nil? && options[:exclude_by_name_regex].present?
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
filters[:name_exclude] = !!(name =~ /#{options[:exclude_by_name_regex]}/)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.apply_shell_filters(options, filters, shell)
|
63
|
+
filters[:shell_expect] = shell == 'expect'
|
64
|
+
|
65
|
+
if shell.present? && options[:select_by_shell_regex].present?
|
66
|
+
filters[:shell_select] = !!(shell =~ /#{options[:select_by_shell_regex]}/)
|
67
|
+
end
|
68
|
+
|
69
|
+
return unless shell.present? && options[:exclude_by_shell_regex].present?
|
70
|
+
|
71
|
+
filters[:shell_exclude] = !!(shell =~ /#{options[:exclude_by_shell_regex]}/)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.apply_other_filters(options, filters, fcb)
|
75
|
+
name = fcb.fetch(:name, '')
|
76
|
+
shell = fcb.fetch(:shell, '')
|
77
|
+
filters[:fcb_chrome] = fcb.fetch(:chrome, false)
|
78
|
+
|
79
|
+
if name.present? && options[:hide_blocks_by_name] &&
|
80
|
+
options[:block_name_hidden_match].present?
|
81
|
+
filters[:hidden_name] = !!(name =~ /#{options[:block_name_hidden_match]}/)
|
82
|
+
end
|
83
|
+
|
84
|
+
if shell.present? && options[:hide_blocks_by_shell] &&
|
85
|
+
options[:block_shell_hidden_match].present?
|
86
|
+
!!(shell =~ /#{options[:block_shell_hidden_match]}/)
|
87
|
+
end
|
88
|
+
|
89
|
+
return unless options[:bash_only]
|
90
|
+
|
91
|
+
filters[:shell_default] = (shell == 'bash')
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.evaluate_filters(options, filters)
|
95
|
+
if options[:no_chrome] && filters[:fcb_chrome]
|
96
|
+
false
|
97
|
+
elsif options[:exclude_expect_blocks] && filters[:shell_expect]
|
98
|
+
false
|
99
|
+
elsif filters[:hidden_name] == true
|
100
|
+
true
|
101
|
+
elsif filters[:name_exclude] == true || filters[:shell_exclude] == true || filters[:name_select] == false || filters[:shell_select] == false
|
102
|
+
false
|
103
|
+
elsif filters[:name_select] == true || filters[:shell_select] == true
|
104
|
+
true
|
105
|
+
elsif filters[:name_default] == false || filters[:shell_default] == false
|
106
|
+
false
|
107
|
+
else
|
108
|
+
true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if $PROGRAM_NAME == __FILE__
|
115
|
+
require 'minitest/autorun'
|
116
|
+
|
117
|
+
require_relative 'tap'
|
118
|
+
include Tap
|
119
|
+
|
120
|
+
class FilterTest < Minitest::Test
|
121
|
+
def test_no_chrome_condition
|
122
|
+
options = { no_chrome: true }
|
123
|
+
fcb = { chrome: true }
|
124
|
+
refute MarkdownExec::Filter.fcb_select?(options, fcb)
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_exclude_expect_blocks_condition
|
128
|
+
options = { exclude_expect_blocks: true }
|
129
|
+
fcb = { shell: 'expect' }
|
130
|
+
refute MarkdownExec::Filter.fcb_select?(options, fcb)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_hidden_name_condition
|
134
|
+
options = { hide_blocks_by_name: true, block_name_hidden_match: 'hidden' }
|
135
|
+
fcb = { name: 'hidden_block' }
|
136
|
+
assert MarkdownExec::Filter.fcb_select?(options, fcb)
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_name_exclude_condition
|
140
|
+
options = { block_name: 'test' }
|
141
|
+
fcb = { name: 'sample' }
|
142
|
+
refute MarkdownExec::Filter.fcb_select?(options, fcb)
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_shell_exclude_condition
|
146
|
+
options = { exclude_by_shell_regex: 'exclude_this' }
|
147
|
+
fcb = { shell: 'exclude_this_shell' }
|
148
|
+
refute MarkdownExec::Filter.fcb_select?(options, fcb)
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_name_select_condition
|
152
|
+
options = { select_by_name_regex: 'select' }
|
153
|
+
fcb = { name: 'select_this' }
|
154
|
+
assert MarkdownExec::Filter.fcb_select?(options, fcb)
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_shell_select_condition
|
158
|
+
options = { select_by_shell_regex: 'select_this' }
|
159
|
+
fcb = { shell: 'select_this_shell' }
|
160
|
+
assert MarkdownExec::Filter.fcb_select?(options, fcb)
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_bash_only_condition_true
|
164
|
+
options = { bash_only: true }
|
165
|
+
fcb = { shell: 'bash' }
|
166
|
+
assert MarkdownExec::Filter.fcb_select?(options, fcb)
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_bash_only_condition_false
|
170
|
+
options = { bash_only: true }
|
171
|
+
fcb = { shell: 'zsh' }
|
172
|
+
refute MarkdownExec::Filter.fcb_select?(options, fcb)
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_default_case
|
176
|
+
options = {}
|
177
|
+
fcb = {}
|
178
|
+
assert MarkdownExec::Filter.fcb_select?(options, fcb)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
require_relative 'filter'
|
7
|
+
|
8
|
+
module MarkdownExec
|
9
|
+
##
|
10
|
+
# MarkdownBlockManager represents an imported markdown document.
|
11
|
+
#
|
12
|
+
# It provides methods to extract and manipulate specific sections
|
13
|
+
# of the document, such as code blocks. It also supports recursion
|
14
|
+
# to fetch related or dependent blocks.
|
15
|
+
#
|
16
|
+
class MarkdownBlockManager
|
17
|
+
attr_reader :block_table
|
18
|
+
|
19
|
+
def initialize(block_table)
|
20
|
+
@block_table = block_table
|
21
|
+
end
|
22
|
+
|
23
|
+
def collect_required_code(name)
|
24
|
+
gather_required_blocks(name)
|
25
|
+
.map do |block|
|
26
|
+
process_block_code(block)
|
27
|
+
end.flatten(1)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_block(name, default = {})
|
31
|
+
@block_table.select { |block| block.fetch(:name, '') == name }.fetch(0, default)
|
32
|
+
end
|
33
|
+
|
34
|
+
def gather_required_blocks(name)
|
35
|
+
named_block = get_block(name)
|
36
|
+
if named_block.nil? || named_block.keys.empty?
|
37
|
+
raise "Named code block `#{name}` not found."
|
38
|
+
end
|
39
|
+
|
40
|
+
all_blocks = [named_block.fetch(:name, '')] + required_blocks(named_block[:reqs])
|
41
|
+
@block_table.select { |block| all_blocks.include? block.fetch(:name, '') }
|
42
|
+
.map do |block|
|
43
|
+
process_block_references(block)
|
44
|
+
end.flatten(1)
|
45
|
+
end
|
46
|
+
|
47
|
+
def hide_block_given_options(opts, block)
|
48
|
+
(opts[:hide_blocks_by_name] &&
|
49
|
+
block[:name]&.match(Regexp.new(opts[:block_name_hidden_match])) &&
|
50
|
+
(block[:name]&.present? || block[:label]&.present?)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def blocks_per_options(opts = {})
|
55
|
+
filtered_blocks = @block_table.select do |block_group|
|
56
|
+
Filter.block_selected? opts, block_group
|
57
|
+
end
|
58
|
+
if opts[:hide_blocks_by_name]
|
59
|
+
filtered_blocks.reject { |block| hide_block_given_options opts, block }
|
60
|
+
else
|
61
|
+
filtered_blocks
|
62
|
+
end.map do |block|
|
63
|
+
block
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def required_blocks(reqs)
|
68
|
+
return [] unless reqs
|
69
|
+
|
70
|
+
remaining = reqs
|
71
|
+
collected = []
|
72
|
+
while remaining.count.positive?
|
73
|
+
remaining = remaining.map do |req|
|
74
|
+
next if collected.include? req
|
75
|
+
|
76
|
+
collected += [req]
|
77
|
+
get_block(req).fetch(:reqs, [])
|
78
|
+
end
|
79
|
+
.compact
|
80
|
+
.flatten(1)
|
81
|
+
end
|
82
|
+
collected
|
83
|
+
end
|
84
|
+
|
85
|
+
# Helper method to process block code
|
86
|
+
def process_block_code(block)
|
87
|
+
body = block[:body].join("\n")
|
88
|
+
|
89
|
+
if block[:cann]
|
90
|
+
xcall = block[:cann][1..-2]
|
91
|
+
mstdin = xcall.match(/<(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
|
92
|
+
mstdout = xcall.match(/>(?<type>\$)?(?<name>[A-Za-z_\-.\w]+)/)
|
93
|
+
|
94
|
+
yqcmd = if mstdin[:type]
|
95
|
+
"echo \"$#{mstdin[:name]}\" | yq '#{body}'"
|
96
|
+
else
|
97
|
+
"yq e '#{body}' '#{mstdin[:name]}'"
|
98
|
+
end
|
99
|
+
if mstdout[:type]
|
100
|
+
"export #{mstdout[:name]}=$(#{yqcmd})"
|
101
|
+
else
|
102
|
+
"#{yqcmd} > '#{mstdout[:name]}'"
|
103
|
+
end
|
104
|
+
elsif block[:stdout]
|
105
|
+
stdout = block[:stdout]
|
106
|
+
body = block[:body].join("\n")
|
107
|
+
if stdout[:type]
|
108
|
+
%(export #{stdout[:name]}=$(cat <<"EOF"\n#{body}\nEOF\n))
|
109
|
+
else
|
110
|
+
"cat > '#{stdout[:name]}' <<\"EOF\"\n" \
|
111
|
+
"#{body}\n" \
|
112
|
+
"EOF\n"
|
113
|
+
end
|
114
|
+
else
|
115
|
+
block[:body]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Helper method to process block references
|
120
|
+
def process_block_references(block)
|
121
|
+
if (call = block[:call])
|
122
|
+
[get_block("[#{call.match(/^%\((\S+) |\)/)[1]}]")
|
123
|
+
.merge({ cann: call })]
|
124
|
+
else
|
125
|
+
[]
|
126
|
+
end + [block]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if $PROGRAM_NAME == __FILE__
|
132
|
+
# require 'bundler/setup'
|
133
|
+
# Bundler.require(:default)
|
134
|
+
|
135
|
+
require 'minitest/autorun'
|
136
|
+
|
137
|
+
require_relative 'tap'
|
138
|
+
include Tap
|
139
|
+
|
140
|
+
module MarkdownExec
|
141
|
+
class TestMDoc < Minitest::Test
|
142
|
+
def setup
|
143
|
+
@table = [
|
144
|
+
{ name: 'block1', body: ['code for block1'], reqs: ['block2'] },
|
145
|
+
{ name: 'block2', body: ['code for block2'], reqs: nil },
|
146
|
+
{ name: 'block3', body: ['code for block3'], reqs: ['block1'] }
|
147
|
+
]
|
148
|
+
@doc = MDoc.new(@table)
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_collect_recursively_required_code
|
152
|
+
result = @doc.collect_recursively_required_code('block1')
|
153
|
+
expected_result = @table[0][:body] + @table[1][:body]
|
154
|
+
assert_equal expected_result, result
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_get_block_by_name
|
158
|
+
result = @doc.get_block_by_name('block1')
|
159
|
+
assert_equal @table[0], result
|
160
|
+
|
161
|
+
result_missing = @doc.get_block_by_name('missing_block')
|
162
|
+
assert_equal({}, result_missing)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_get_required_blocks
|
166
|
+
result = @doc.get_required_blocks('block3')
|
167
|
+
expected_result = [@table[0], @table[1], @table[2]]
|
168
|
+
assert_equal expected_result, result
|
169
|
+
|
170
|
+
assert_raises(RuntimeError) { @doc.get_required_blocks('missing_block') }
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_hide_menu_block_per_options
|
174
|
+
opts = { hide_blocks_by_name: true, block_name_hidden_match: 'block1' }
|
175
|
+
block = { name: 'block1' }
|
176
|
+
result = @doc.hide_menu_block_per_options(opts, block)
|
177
|
+
assert result # this should be true based on the given logic
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_fcbs_per_options
|
181
|
+
opts = { hide_blocks_by_name: true, block_name_hidden_match: 'block1' }
|
182
|
+
result = @doc.fcbs_per_options(opts)
|
183
|
+
assert_equal [@table[1], @table[2]], result
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_recursively_required
|
187
|
+
result = @doc.recursively_required(['block3'])
|
188
|
+
assert_equal %w[block3 block1 block2], result
|
189
|
+
|
190
|
+
result_no_reqs = @doc.recursively_required(nil)
|
191
|
+
assert_equal [], result_no_reqs
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|