markdown_exec 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -2
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/bin/tab_completion.sh +1 -1
- data/examples/block-names.md +39 -4
- data/lib/colorize.rb +13 -0
- data/lib/hash_delegator.rb +53 -10
- data/lib/hierarchy_string.rb +133 -0
- data/lib/input_sequencer.rb +4 -2
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +14 -7
- data/lib/mdoc.rb +14 -7
- data/lib/menu.src.yml +37 -0
- data/lib/menu.yml +29 -0
- data/lib/text_analyzer.rb +100 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40813cfbaca2f9b2e8b865e2f574ab71211c56120d5706d2f8ebc4fbb103d254
|
4
|
+
data.tar.gz: 880d121d18747d2370dff0caa90f84da9fca51633ec6e00919f690ea4821d1f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df8c99ddc62b271b1fa74036ffcb485411a1461195d68c2e1825ae3ba606e4b20de456636d0094606c4579548d4532dd46287c5c486b4ab613217ab708144709
|
7
|
+
data.tar.gz: 4b812fa4808161beaa06c3de9b0639a031446294bb8a171d599e5baa4db157660233701c1068a130c93dfbd4cd0469853867cf4c751dfda4197ffb13ee81899a
|
data/.rubocop.yml
CHANGED
@@ -15,8 +15,8 @@ Layout/LineContinuationLeadingSpace:
|
|
15
15
|
|
16
16
|
Layout/LineLength: # 2024-01-21 temp disable
|
17
17
|
# Enabled: false
|
18
|
-
|
19
|
-
Max: 96
|
18
|
+
Max: 80
|
19
|
+
# Max: 96
|
20
20
|
# Max: 120
|
21
21
|
|
22
22
|
Lint/Debugger:
|
@@ -88,6 +88,9 @@ Style/FormatStringToken:
|
|
88
88
|
Style/GlobalVars:
|
89
89
|
Enabled: false
|
90
90
|
|
91
|
+
Style/IfUnlessModifier: # 2024-08 suggests lines that are too long
|
92
|
+
Enabled: false
|
93
|
+
|
91
94
|
Style/Lambda:
|
92
95
|
Enabled: false
|
93
96
|
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [2.3.0] - 2024-08-05
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- In-line decoration of text
|
8
|
+
- ANSI graphics modes for text decoration.
|
9
|
+
- Translation from text to ANSI graphics.
|
10
|
+
- Options for decorations in three stages, with default (Main) in the middle.
|
11
|
+
|
3
12
|
## [2.2.0] - 2024-07-27
|
4
13
|
|
5
14
|
### Added
|
data/Gemfile.lock
CHANGED
data/bin/tab_completion.sh
CHANGED
data/examples/block-names.md
CHANGED
@@ -1,8 +1,32 @@
|
|
1
1
|
## Demonstrate handling of special characters in block names
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
```
|
2
|
+
|
3
|
+
::: Click below to trigger. If it prints "1","2","3","4", the Link blocks were required.
|
4
|
+
Long block names can be required by a Bash block.
|
5
|
+
```bash :calling-block +long_block_name_12345678901234567890123456789012345678901234567890 +(long_block_name_12345678901234567890123456789012345678901234567890) +[long_block_name_12345678901234567890123456789012345678901234567890]
|
6
|
+
echo '1'
|
7
|
+
```
|
8
|
+
Long block names can be used in Link blocks.
|
9
|
+
```link
|
10
|
+
block: long_block_name_12345678901234567890123456789012345678901234567890
|
11
|
+
```
|
12
|
+
```link
|
13
|
+
block: "(long_block_name_12345678901234567890123456789012345678901234567890)"
|
14
|
+
```
|
15
|
+
```link
|
16
|
+
block: "[long_block_name_12345678901234567890123456789012345678901234567890]"
|
17
|
+
```
|
18
|
+
|
19
|
+
Do not call these blocks directly.
|
20
|
+
```bash :long_block_name_12345678901234567890123456789012345678901234567890
|
21
|
+
echo '2'
|
22
|
+
```
|
23
|
+
```bash :(long_block_name_12345678901234567890123456789012345678901234567890)
|
24
|
+
echo '3'
|
25
|
+
```
|
26
|
+
```bash :[long_block_name_12345678901234567890123456789012345678901234567890]
|
27
|
+
echo '4'
|
28
|
+
```
|
29
|
+
|
6
30
|
Block names with all chars.
|
7
31
|
/ UTF-8
|
8
32
|
/ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
|
@@ -25,3 +49,14 @@ The un-named block should display correctly.
|
|
25
49
|
vars:
|
26
50
|
page2_var_via_environment: for_page2_from_page1_via_current_environment
|
27
51
|
```
|
52
|
+
The hidden block "(success)" is required above. It prints "Success".
|
53
|
+
```bash :(success)
|
54
|
+
echo "Success"
|
55
|
+
```
|
56
|
+
|
57
|
+
```opts :(document_options)
|
58
|
+
execute_in_own_window: false
|
59
|
+
output_execution_report: false
|
60
|
+
output_execution_summary: false
|
61
|
+
pause_after_script_execution: true
|
62
|
+
```
|
data/lib/colorize.rb
CHANGED
@@ -101,4 +101,17 @@ class String
|
|
101
101
|
def red; fg_rgbh_FF_00_00; end
|
102
102
|
def violet; fg_rgbh_94_00_D3; end
|
103
103
|
def yellow; fg_rgbh_FF_FF_00; end
|
104
|
+
|
105
|
+
# graphics modes
|
106
|
+
def bold; "\033[1m#{self}\033[22m"; end
|
107
|
+
def bold_italic; "\033[1m\033[3m#{self}\033[22m\033[23m"; end
|
108
|
+
def bold_underline; "\033[1m\033[4m#{self}\033[22m\033[24m"; end
|
109
|
+
def dim; "\033[2m#{self}\033[22m"; end
|
110
|
+
def italic; "\033[3m#{self}\033[23m"; end
|
111
|
+
def underline; "\033[4m#{self}\033[24m"; end
|
112
|
+
def underline_italic; "\033[4m\033[3m#{self}\033[23m\033[24m"; end
|
113
|
+
def blinking; "\033[5m#{self}\033[25m"; end
|
114
|
+
def inverse; "\033[7m#{self}\033[27m"; end
|
115
|
+
def hidden; "\033[8m#{self}\033[28m"; end
|
116
|
+
def strikethrough; "\033[9m#{self}\033[29m"; end
|
104
117
|
end
|
data/lib/hash_delegator.rb
CHANGED
@@ -29,6 +29,7 @@ require_relative 'fcb'
|
|
29
29
|
require_relative 'filter'
|
30
30
|
require_relative 'fout'
|
31
31
|
require_relative 'hash'
|
32
|
+
require_relative 'hierarchy_string'
|
32
33
|
require_relative 'link_history'
|
33
34
|
require_relative 'mdoc'
|
34
35
|
require_relative 'namer'
|
@@ -37,6 +38,7 @@ require_relative 'resize_terminal'
|
|
37
38
|
require_relative 'std_out_err_logger'
|
38
39
|
require_relative 'streams_out'
|
39
40
|
require_relative 'string_util'
|
41
|
+
require_relative 'text_analyzer'
|
40
42
|
|
41
43
|
$pd = false unless defined?($pd)
|
42
44
|
|
@@ -482,6 +484,7 @@ module MarkdownExec
|
|
482
484
|
|
483
485
|
extend HashDelegatorSelf
|
484
486
|
include CompactionHelpers
|
487
|
+
include TextAnalyzer
|
485
488
|
|
486
489
|
def initialize(delegate_object = {})
|
487
490
|
@delegate_object = delegate_object
|
@@ -668,6 +671,16 @@ module MarkdownExec
|
|
668
671
|
end
|
669
672
|
end
|
670
673
|
|
674
|
+
def apply_tree_decorations(text, color_method, decor_patterns)
|
675
|
+
tree = HierarchyString.new([{ text: text, color: color_method }])
|
676
|
+
decor_patterns.each do |pc|
|
677
|
+
analyzed_hierarchy = TextAnalyzer.analyze_hierarchy(tree.substrings, pc[:pattern],
|
678
|
+
color_method, pc[:color_method])
|
679
|
+
tree = HierarchyString.new(analyzed_hierarchy)
|
680
|
+
end
|
681
|
+
tree.decorate
|
682
|
+
end
|
683
|
+
|
671
684
|
def assign_key_value_in_bash(key, value)
|
672
685
|
if value =~ /["$\\`]/
|
673
686
|
# requiring ShellWords to write into Bash scripts
|
@@ -685,6 +698,7 @@ module MarkdownExec
|
|
685
698
|
# @return [Array<FCB>] An array of FCB objects representing the blocks.
|
686
699
|
def blocks_from_nested_files
|
687
700
|
register_console_attributes(@delegate_object)
|
701
|
+
@decor_patterns_from_delegate_object_for_block_create = collect_line_decor_patterns(@delegate_object)
|
688
702
|
|
689
703
|
blocks = []
|
690
704
|
iter_blocks_from_nested_files do |btype, fcb|
|
@@ -700,9 +714,7 @@ module MarkdownExec
|
|
700
714
|
# if matched, the block returned has properties that it is from cli and not ui
|
701
715
|
def block_state_for_name_from_cli(block_name)
|
702
716
|
SelectedBlockMenuState.new(
|
703
|
-
@dml_blocks_in_file
|
704
|
-
block_name == item.pub_name
|
705
|
-
end,
|
717
|
+
blocks_find_by_block_name(@dml_blocks_in_file, block_name),
|
706
718
|
OpenStruct.new(
|
707
719
|
block_name_from_cli: true,
|
708
720
|
block_name_from_ui: false
|
@@ -711,6 +723,14 @@ module MarkdownExec
|
|
711
723
|
)
|
712
724
|
end
|
713
725
|
|
726
|
+
def blocks_find_by_block_name(blocks, block_name)
|
727
|
+
@dml_blocks_in_file.find do |item|
|
728
|
+
# 2024-08-04 match oname for long block names
|
729
|
+
# 2024-08-04 match nickname for long block names
|
730
|
+
block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
714
734
|
# private
|
715
735
|
|
716
736
|
def calc_logged_stdout_filename(block_name:)
|
@@ -752,6 +772,23 @@ module MarkdownExec
|
|
752
772
|
true
|
753
773
|
end
|
754
774
|
|
775
|
+
def collect_line_decor_patterns(delegate_object)
|
776
|
+
extract_patterns = lambda do |key|
|
777
|
+
return [] unless delegate_object[key].present?
|
778
|
+
|
779
|
+
HashDelegator.safeval(delegate_object[key]).map do |pc|
|
780
|
+
{
|
781
|
+
color_method: pc[:color_method].to_sym,
|
782
|
+
pattern: Regexp.new(pc[:pattern])
|
783
|
+
}
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
%i[line_decor_pre line_decor_main line_decor_post].flat_map do |key|
|
788
|
+
extract_patterns.call(key)
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
755
792
|
# Collects required code lines based on the selected block and the delegate object's configuration.
|
756
793
|
# If the block type is VARS, it also sets environment variables based on the block's content.
|
757
794
|
#
|
@@ -920,6 +957,7 @@ module MarkdownExec
|
|
920
957
|
format_option:, color_method:,
|
921
958
|
case_conversion: nil,
|
922
959
|
center: nil,
|
960
|
+
decor_patterns: [],
|
923
961
|
wrap: nil)
|
924
962
|
line_cap = match_data.named_captures.transform_keys(&:to_sym)
|
925
963
|
|
@@ -970,11 +1008,14 @@ module MarkdownExec
|
|
970
1008
|
# format expects :line to be text only
|
971
1009
|
line_obj[:line] = line_obj[:text]
|
972
1010
|
oname = format(format_option, line_obj)
|
1011
|
+
|
1012
|
+
decorated = apply_tree_decorations(oname, color_method, decor_patterns)
|
1013
|
+
|
973
1014
|
line_obj[:line] = line_obj[:indent] + line_obj[:text]
|
974
1015
|
blocks.push FCB.new(
|
975
1016
|
chrome: true,
|
976
1017
|
disabled: '',
|
977
|
-
dname: line_obj[:indent] +
|
1018
|
+
dname: line_obj[:indent] + decorated,
|
978
1019
|
oname: line_obj[:text]
|
979
1020
|
)
|
980
1021
|
end
|
@@ -988,6 +1029,7 @@ module MarkdownExec
|
|
988
1029
|
# @param opts [Hash] Options containing configuration for line processing.
|
989
1030
|
# @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
|
990
1031
|
def create_and_add_chrome_blocks(blocks, fcb)
|
1032
|
+
# rubocop:disable Layout/LineLength
|
991
1033
|
match_criteria = [
|
992
1034
|
{ color: :menu_heading1_color, format: :menu_heading1_format, match: :heading1_match, center: true, case_conversion: :upcase, wrap: true },
|
993
1035
|
{ color: :menu_heading2_color, format: :menu_heading2_format, match: :heading2_match, center: true, wrap: true },
|
@@ -996,6 +1038,7 @@ module MarkdownExec
|
|
996
1038
|
{ color: :menu_note_color, format: :menu_note_format, match: :menu_note_match, wrap: true },
|
997
1039
|
{ color: :menu_task_color, format: :menu_task_format, match: :menu_task_match, wrap: true }
|
998
1040
|
]
|
1041
|
+
# rubocop:enable Layout/LineLength
|
999
1042
|
# rubocop:enable Style/UnlessElse
|
1000
1043
|
match_criteria.each do |criteria|
|
1001
1044
|
unless @delegate_object[criteria[:match]].present? &&
|
@@ -1008,6 +1051,7 @@ module MarkdownExec
|
|
1008
1051
|
case_conversion: criteria[:case_conversion],
|
1009
1052
|
center: criteria[:center],
|
1010
1053
|
color_method: @delegate_object[criteria[:color]].to_sym,
|
1054
|
+
decor_patterns: @decor_patterns_from_delegate_object_for_block_create,
|
1011
1055
|
format_option: @delegate_object[criteria[:format]],
|
1012
1056
|
match_data: mbody,
|
1013
1057
|
wrap: criteria[:wrap]
|
@@ -1224,9 +1268,8 @@ module MarkdownExec
|
|
1224
1268
|
when :user_choice
|
1225
1269
|
if @dml_link_state.block_name.present?
|
1226
1270
|
# @prior_block_was_link = true
|
1227
|
-
@dml_block_state.block = @dml_blocks_in_file
|
1228
|
-
|
1229
|
-
end
|
1271
|
+
@dml_block_state.block = blocks_find_by_block_name(@dml_blocks_in_file,
|
1272
|
+
@dml_link_state.block_name)
|
1230
1273
|
@dml_link_state.block_name = nil
|
1231
1274
|
else
|
1232
1275
|
# puts "? - Select a block to execute (or type #{$texit} to exit):"
|
@@ -2433,8 +2476,7 @@ module MarkdownExec
|
|
2433
2476
|
%i[blocks line]
|
2434
2477
|
when :line
|
2435
2478
|
unless @delegate_object[:no_chrome]
|
2436
|
-
create_and_add_chrome_blocks(blocks,
|
2437
|
-
fcb)
|
2479
|
+
create_and_add_chrome_blocks(blocks, fcb)
|
2438
2480
|
end
|
2439
2481
|
end
|
2440
2482
|
end
|
@@ -3496,7 +3538,8 @@ module MarkdownExec
|
|
3496
3538
|
|
3497
3539
|
def test_block_find_with_default
|
3498
3540
|
blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
|
3499
|
-
result = HashDelegator.block_find(blocks, :text, 'missing_value',
|
3541
|
+
result = HashDelegator.block_find(blocks, :text, 'missing_value',
|
3542
|
+
'default')
|
3500
3543
|
assert_equal 'default', result
|
3501
3544
|
end
|
3502
3545
|
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class representing a hierarchy of substrings stored as Hash nodes
|
4
|
+
class HierarchyString
|
5
|
+
attr_accessor :substrings
|
6
|
+
|
7
|
+
# Initialize with a single hash or an array of hashes
|
8
|
+
def initialize(substrings)
|
9
|
+
@substrings = parse_substrings(substrings)
|
10
|
+
end
|
11
|
+
|
12
|
+
def map_substring_text_yield(tree, &block)
|
13
|
+
case tree
|
14
|
+
when Array
|
15
|
+
tree.each.with_index do |node, ind|
|
16
|
+
case node
|
17
|
+
when String
|
18
|
+
tree[ind] = yield node
|
19
|
+
else
|
20
|
+
map_substring_text_yield(node, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
when Hash
|
24
|
+
text = yield tree[:text]
|
25
|
+
tree[:text] = text
|
26
|
+
|
27
|
+
tree
|
28
|
+
when String
|
29
|
+
yield tree
|
30
|
+
else
|
31
|
+
raise ArgumentError, 'Invalid type.'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# operate on substring
|
36
|
+
def replace_text!
|
37
|
+
map_substring_text_yield(@substrings) do |node|
|
38
|
+
case node
|
39
|
+
when Hash
|
40
|
+
text = yield node[:text]
|
41
|
+
node[:text] = text
|
42
|
+
when String
|
43
|
+
yield node
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Method to concatenate all substrings into a single string
|
49
|
+
def concatenate
|
50
|
+
concatenate_substrings(@substrings)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Method to decorate all substrings into a single string
|
54
|
+
def decorate
|
55
|
+
decorate_substrings(@substrings)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Handle string inspection methods and pass them to the concatenated string
|
59
|
+
def method_missing(method_name, *arguments, &block)
|
60
|
+
if ''.respond_to?(method_name)
|
61
|
+
concatenate.send(method_name, *arguments, &block)
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Ensure proper handling of method checks
|
68
|
+
def respond_to_missing?(method_name, include_private = false)
|
69
|
+
''.respond_to?(method_name) || super
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Parse the input substrings into a nested array of hashes structure
|
75
|
+
def parse_substrings(substrings)
|
76
|
+
case substrings
|
77
|
+
when Hash
|
78
|
+
[substrings]
|
79
|
+
when Array
|
80
|
+
substrings.map { |s| parse_substrings(s) }
|
81
|
+
else
|
82
|
+
substrings
|
83
|
+
# raise ArgumentError, 'Invalid input type. Expected Hash or Array.'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Recursively concatenate substrings
|
88
|
+
def concatenate_substrings(substrings)
|
89
|
+
substrings.map do |s|
|
90
|
+
case s
|
91
|
+
when Hash
|
92
|
+
s[:text]
|
93
|
+
when Array
|
94
|
+
concatenate_substrings(s)
|
95
|
+
end
|
96
|
+
end.join
|
97
|
+
end
|
98
|
+
|
99
|
+
# Recursively decorate substrings
|
100
|
+
def decorate_substrings(substrings, prior_color = '')
|
101
|
+
substrings.map do |s|
|
102
|
+
case s
|
103
|
+
when Hash
|
104
|
+
if s[:color]
|
105
|
+
s[:text].send(s[:color]) + prior_color
|
106
|
+
else
|
107
|
+
s[:text]
|
108
|
+
end
|
109
|
+
when Array
|
110
|
+
decorate_substrings(s, prior_color)
|
111
|
+
end
|
112
|
+
end.join
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return if $PROGRAM_NAME != __FILE__
|
117
|
+
|
118
|
+
# require 'bundler/setup'
|
119
|
+
# Bundler.require(:default)
|
120
|
+
|
121
|
+
# require 'fcb'
|
122
|
+
# require 'minitest/autorun'
|
123
|
+
|
124
|
+
# Usage
|
125
|
+
hierarchy = HierarchyString.new([{ text: 'Hello ', color: :red },
|
126
|
+
[{ text: 'World', color: :upcase },
|
127
|
+
{ text: '!' }]])
|
128
|
+
puts hierarchy.decorate
|
129
|
+
puts hierarchy.length
|
130
|
+
# puts hierarchy.concatenate # Outputs: Hello World!
|
131
|
+
# puts hierarchy.upcase # Outputs: HELLO WORLD!
|
132
|
+
# puts hierarchy.length # Outputs: 12
|
133
|
+
# puts hierarchy.gsub('World', 'Ruby') # Outputs: Hello Ruby!
|
data/lib/input_sequencer.rb
CHANGED
@@ -79,7 +79,8 @@ class InputSequencer
|
|
79
79
|
)
|
80
80
|
exit_when_bq_empty = !bq_is_empty? # true when running blocks from cli; unless "stay" is used
|
81
81
|
loop do
|
82
|
-
break if run_yield(:parse_document, now_menu.document_filename,
|
82
|
+
break if run_yield(:parse_document, now_menu.document_filename,
|
83
|
+
&block) == :break
|
83
84
|
|
84
85
|
# self.imw_ins now_menu, 'now_menu'
|
85
86
|
|
@@ -92,7 +93,8 @@ class InputSequencer
|
|
92
93
|
choice = run_yield :user_choice, &block
|
93
94
|
|
94
95
|
raise 'Block not recognized.' if choice.nil?
|
95
|
-
|
96
|
+
# Exit loop and method to terminate the app
|
97
|
+
break if run_yield(:exit?, choice&.downcase, &block)
|
96
98
|
|
97
99
|
next_state = run_yield :execute_block, choice, &block
|
98
100
|
# imw_ins next_state, 'next_state'
|
data/lib/markdown_exec.rb
CHANGED
@@ -125,7 +125,10 @@ module MarkdownExec
|
|
125
125
|
return if max <= min # Ensure the range is valid
|
126
126
|
|
127
127
|
# Normalize the value within the range 0 to 1
|
128
|
-
normalized_value = [
|
128
|
+
normalized_value = [
|
129
|
+
0,
|
130
|
+
[(integer_value - min).to_f / (max - min), 1].min
|
131
|
+
].max
|
129
132
|
|
130
133
|
# Calculate how many characters should be filled
|
131
134
|
filled_length = (normalized_value * width).round
|
@@ -146,11 +149,13 @@ module MarkdownExec
|
|
146
149
|
@o_color = :red
|
147
150
|
end
|
148
151
|
|
149
|
-
def build_menu(file_names, directory_names, found_in_block_names,
|
152
|
+
def build_menu(file_names, directory_names, found_in_block_names,
|
153
|
+
files_in_directories, vbn)
|
150
154
|
choices = []
|
151
155
|
|
152
156
|
# Adding section title and data for file names
|
153
|
-
choices << { disabled: '',
|
157
|
+
choices << { disabled: '',
|
158
|
+
name: "in #{file_names[:section_title]}".send(@chrome_color) }
|
154
159
|
choices += file_names[:data].map { |str| FileInMenu.for_menu(str) }
|
155
160
|
|
156
161
|
# Conditionally add directory names if data is present
|
@@ -464,7 +469,8 @@ module MarkdownExec
|
|
464
469
|
:menu_chrome_color)}"
|
465
470
|
searcher = SearchResultsReport.new(value, [find_path])
|
466
471
|
file_names = searcher.file_names(options, value)
|
467
|
-
found_in_block_names = searcher.found_in_block_names(options, value,
|
472
|
+
found_in_block_names = searcher.found_in_block_names(options, value,
|
473
|
+
formspec: '%<line>s')
|
468
474
|
directory_names = searcher.directory_names(options, value)
|
469
475
|
|
470
476
|
### search in file contents (block names, chrome, or text)
|
@@ -498,9 +504,10 @@ module MarkdownExec
|
|
498
504
|
details,
|
499
505
|
highlight: [value]
|
500
506
|
)
|
501
|
-
[FileInMenu.for_menu(filename)] +
|
502
|
-
|
503
|
-
|
507
|
+
[FileInMenu.for_menu(filename)] +
|
508
|
+
nexo.map do |str|
|
509
|
+
{ disabled: '', name: (' ' * 20) + str }
|
510
|
+
end
|
504
511
|
end.flatten
|
505
512
|
|
506
513
|
choices = MenuBuilder.new.build_menu(file_names, directory_names, found_in_block_names,
|
data/lib/mdoc.rb
CHANGED
@@ -87,11 +87,11 @@ module MarkdownExec
|
|
87
87
|
all_dependency_names = collect_unique_names(dependencies).push(nickname).uniq
|
88
88
|
# &bt all_dependency_names.count
|
89
89
|
|
90
|
-
# select
|
90
|
+
# select blocks in order of appearance in source documents
|
91
91
|
#
|
92
92
|
blocks = @table.select do |fcb|
|
93
|
-
#
|
94
|
-
all_dependency_names.include?(fcb.pub_name)
|
93
|
+
# 2024-08-04 match nickname
|
94
|
+
all_dependency_names.include?(fcb.pub_name) || all_dependency_names.include?(fcb.nickname) || all_dependency_names.include?(fcb.oname)
|
95
95
|
end
|
96
96
|
# &bt blocks.count
|
97
97
|
|
@@ -99,7 +99,9 @@ module MarkdownExec
|
|
99
99
|
#
|
100
100
|
unmet_dependencies = all_dependency_names.dup
|
101
101
|
blocks = blocks.map do |fcb|
|
102
|
-
|
102
|
+
# 2024-08-04 match oname for long block names
|
103
|
+
# 2024-08-04 match nickname
|
104
|
+
unmet_dependencies.delete(fcb.pub_name) || unmet_dependencies.delete(fcb.nickname) || unmet_dependencies.delete(fcb.oname) # may not exist if block name is duplicated
|
103
105
|
if (call = fcb.call)
|
104
106
|
[get_block_by_anyname("[#{call.match(/^%\((\S+) |\)/)[1]}]")
|
105
107
|
.merge({ cann: call })]
|
@@ -262,7 +264,8 @@ module MarkdownExec
|
|
262
264
|
# and `label_format_below` is "End of %{block_name}", the method will return:
|
263
265
|
# ["Start of Example_Block", "line1", "line2", "End of Example_Block"]
|
264
266
|
#
|
265
|
-
def generate_label_body_code(fcb, block_source, label_format_above,
|
267
|
+
def generate_label_body_code(fcb, block_source, label_format_above,
|
268
|
+
label_format_below)
|
266
269
|
block_name_for_bash_comment = fcb.pub_name.gsub(/\s+/, '_')
|
267
270
|
|
268
271
|
label_above = if label_format_above
|
@@ -387,7 +390,9 @@ module MarkdownExec
|
|
387
390
|
|
388
391
|
memo[source] = block.reqs
|
389
392
|
|
390
|
-
block.reqs.each
|
393
|
+
block.reqs.each do |req|
|
394
|
+
collect_dependencies(req, memo) unless memo.key?(req)
|
395
|
+
end
|
391
396
|
memo
|
392
397
|
end
|
393
398
|
|
@@ -456,7 +461,9 @@ if $PROGRAM_NAME == __FILE__
|
|
456
461
|
|
457
462
|
if false # must raise error
|
458
463
|
def test_collect_dependencies_with_nonexistent_source
|
459
|
-
assert_raises(RuntimeError)
|
464
|
+
assert_raises(RuntimeError) do
|
465
|
+
@mdoc.collect_dependencies('nonexistent')
|
466
|
+
end
|
460
467
|
end
|
461
468
|
end
|
462
469
|
|
data/lib/menu.src.yml
CHANGED
@@ -397,6 +397,43 @@
|
|
397
397
|
:opt_name: import_pattern
|
398
398
|
:procname: val_as_str
|
399
399
|
|
400
|
+
- :default:
|
401
|
+
- :color_method: :bold_underline
|
402
|
+
:pattern: '\*\*_([^_]{0,64})_\*\*'
|
403
|
+
|
404
|
+
- :color_method: :bold_italic
|
405
|
+
:pattern: '\*\*~([^~]{0,64})~\*\*'
|
406
|
+
|
407
|
+
- :color_method: :bold
|
408
|
+
:pattern: '\*\*([^*]{0,64})\*\*'
|
409
|
+
- :color_method: :bold
|
410
|
+
:pattern: '__([^_]{0,64})__'
|
411
|
+
|
412
|
+
- :color_method: :underline
|
413
|
+
:pattern: '\*([^*]{0,64})\*'
|
414
|
+
|
415
|
+
- :color_method: :underline_italic
|
416
|
+
:pattern: '_~([^_]{0,64})~_'
|
417
|
+
|
418
|
+
- :color_method: strikethrough
|
419
|
+
:pattern: '~~([^~]{0,64})~~'
|
420
|
+
:description: Line-oriented text decoration (Main)
|
421
|
+
:env_var: MDE_LINE_DECOR_MAIN
|
422
|
+
:opt_name: line_decor_main
|
423
|
+
:procname: val_as_str
|
424
|
+
|
425
|
+
- :default: []
|
426
|
+
:description: Line-oriented text decoration (Post)
|
427
|
+
:env_var: MDE_LINE_DECOR_POST
|
428
|
+
:opt_name: line_decor_post
|
429
|
+
:procname: val_as_str
|
430
|
+
|
431
|
+
- :default: []
|
432
|
+
:description: Line-oriented text decoration (Pre)
|
433
|
+
:env_var: MDE_LINE_DECOR_PRE
|
434
|
+
:opt_name: line_decor_pre
|
435
|
+
:procname: val_as_str
|
436
|
+
|
400
437
|
- :description: List blocks
|
401
438
|
:long_name: list-blocks
|
402
439
|
:opt_name: list_blocks
|
data/lib/menu.yml
CHANGED
@@ -339,6 +339,35 @@
|
|
339
339
|
:env_var: MDE_IMPORT_PATTERN
|
340
340
|
:opt_name: import_pattern
|
341
341
|
:procname: val_as_str
|
342
|
+
- :default:
|
343
|
+
- :color_method: :bold_underline
|
344
|
+
:pattern: "\\*\\*_([^_]{0,64})_\\*\\*"
|
345
|
+
- :color_method: :bold_italic
|
346
|
+
:pattern: "\\*\\*~([^~]{0,64})~\\*\\*"
|
347
|
+
- :color_method: :bold
|
348
|
+
:pattern: "\\*\\*([^*]{0,64})\\*\\*"
|
349
|
+
- :color_method: :bold
|
350
|
+
:pattern: __([^_]{0,64})__
|
351
|
+
- :color_method: :underline
|
352
|
+
:pattern: "\\*([^*]{0,64})\\*"
|
353
|
+
- :color_method: :underline_italic
|
354
|
+
:pattern: _~([^_]{0,64})~_
|
355
|
+
- :color_method: strikethrough
|
356
|
+
:pattern: "~~([^~]{0,64})~~"
|
357
|
+
:description: Line-oriented text decoration (Main)
|
358
|
+
:env_var: MDE_LINE_DECOR_MAIN
|
359
|
+
:opt_name: line_decor_main
|
360
|
+
:procname: val_as_str
|
361
|
+
- :default: []
|
362
|
+
:description: Line-oriented text decoration (Post)
|
363
|
+
:env_var: MDE_LINE_DECOR_POST
|
364
|
+
:opt_name: line_decor_post
|
365
|
+
:procname: val_as_str
|
366
|
+
- :default: []
|
367
|
+
:description: Line-oriented text decoration (Pre)
|
368
|
+
:env_var: MDE_LINE_DECOR_PRE
|
369
|
+
:opt_name: line_decor_pre
|
370
|
+
:procname: val_as_str
|
342
371
|
- :description: List blocks
|
343
372
|
:long_name: list-blocks
|
344
373
|
:opt_name: list_blocks
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TextAnalyzer
|
4
|
+
# Analyzes a hierarchical structure (String or Array) and highlights segments based on the pattern
|
5
|
+
#
|
6
|
+
# @param hierarchy [String, Array] the hierarchical structure to be analyzed
|
7
|
+
# @param pattern [Regexp] the pattern to match against the text
|
8
|
+
# @param default_color [String] the color for non-matching segments
|
9
|
+
# @param match_color [String] the color for matching segments
|
10
|
+
#
|
11
|
+
# @return [Array<Hash>, Array<Array<Hash>>] an array or nested arrays of highlighted segments
|
12
|
+
#
|
13
|
+
# @raise [ArgumentError] if the hierarchy structure is neither a String nor an Array
|
14
|
+
def self.analyze_hierarchy(hierarchy, pattern, default_color, match_color)
|
15
|
+
case hierarchy
|
16
|
+
when String
|
17
|
+
highlight_segments(hierarchy, pattern, default_color, match_color)
|
18
|
+
when Hash
|
19
|
+
decorated = highlight_segments(hierarchy[:text], pattern,
|
20
|
+
hierarchy[:color], match_color)
|
21
|
+
|
22
|
+
case decorated
|
23
|
+
when String
|
24
|
+
hierarchy
|
25
|
+
when Array
|
26
|
+
if decorated.length == 1
|
27
|
+
hierarchy
|
28
|
+
else
|
29
|
+
decorated
|
30
|
+
end
|
31
|
+
else
|
32
|
+
decorated
|
33
|
+
end
|
34
|
+
when Array
|
35
|
+
hierarchy.map do |element|
|
36
|
+
analyze_hierarchy(element, pattern, default_color, match_color)
|
37
|
+
end
|
38
|
+
when HierarchyString
|
39
|
+
|
40
|
+
hierarchy.replace_text! do |substring|
|
41
|
+
substring ### no change
|
42
|
+
end
|
43
|
+
|
44
|
+
else
|
45
|
+
binding.irb
|
46
|
+
raise ArgumentError, 'Invalid hierarchy structure'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Highlights segments of the text based on the pattern
|
51
|
+
#
|
52
|
+
# @param text [String] the text to be analyzed
|
53
|
+
# @param pattern [Regexp] the pattern to match against the text
|
54
|
+
# @param default_color [String] the color for non-matching segments
|
55
|
+
# @param match_color [String] the color for matching segments
|
56
|
+
#
|
57
|
+
# @return [Array<Hash>] an array of hashes, each containing a segment of text and its corresponding color
|
58
|
+
def self.highlight_segments(text, pattern, default_color, match_color)
|
59
|
+
segments = []
|
60
|
+
|
61
|
+
yield_matches_and_non_matches(text, pattern) do |segment, is_match|
|
62
|
+
segments << if is_match
|
63
|
+
{ text: segment, color: match_color }
|
64
|
+
else
|
65
|
+
{ text: segment, color: default_color }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
segments
|
70
|
+
end
|
71
|
+
|
72
|
+
# Yields matching and non-matching segments of the text based on the pattern
|
73
|
+
#
|
74
|
+
# @param text [String] the text to be analyzed
|
75
|
+
# @param pattern [Regexp] the pattern to match against the text
|
76
|
+
#
|
77
|
+
# @yieldparam segment [String] a segment of the text
|
78
|
+
# @yieldparam is_match [Boolean] true if the segment matches the pattern, false otherwise
|
79
|
+
def self.yield_matches_and_non_matches(text, pattern)
|
80
|
+
last_end = 0
|
81
|
+
|
82
|
+
text.scan(pattern) do |match|
|
83
|
+
match_start = Regexp.last_match.begin(0)
|
84
|
+
match_end = Regexp.last_match.end(0)
|
85
|
+
|
86
|
+
# Yield the non-matching segment before the match
|
87
|
+
yield text[last_end...match_start], false if match_start > last_end
|
88
|
+
|
89
|
+
# Yield the matching segment
|
90
|
+
yield match.first, true
|
91
|
+
|
92
|
+
last_end = match_end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Yield any remaining non-matching segment after the last match
|
96
|
+
return unless last_end < text.length
|
97
|
+
|
98
|
+
yield text[last_end..-1], false
|
99
|
+
end
|
100
|
+
end
|
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.
|
4
|
+
version: 2.3.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: 2024-08-
|
11
|
+
date: 2024-08-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clipboard
|
@@ -171,6 +171,7 @@ files:
|
|
171
171
|
- lib/fout.rb
|
172
172
|
- lib/hash.rb
|
173
173
|
- lib/hash_delegator.rb
|
174
|
+
- lib/hierarchy_string.rb
|
174
175
|
- lib/input_sequencer.rb
|
175
176
|
- lib/instance_method_wrapper.rb
|
176
177
|
- lib/link_history.rb
|
@@ -193,6 +194,7 @@ files:
|
|
193
194
|
- lib/streams_out.rb
|
194
195
|
- lib/string_util.rb
|
195
196
|
- lib/tap.rb
|
197
|
+
- lib/text_analyzer.rb
|
196
198
|
homepage: https://rubygems.org/gems/markdown_exec
|
197
199
|
licenses:
|
198
200
|
- MIT
|