markdown_exec 1.3.3.4 → 1.3.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +16 -109
- 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 +78 -482
- 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
|