markdown_exec 1.6 → 1.8
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/Gemfile.lock +1 -1
- data/Rakefile +2 -0
- data/bin/tab_completion.sh +19 -3
- data/examples/colors.md +48 -0
- data/examples/import0.md +41 -5
- data/examples/import1.md +9 -8
- data/examples/include.md +11 -4
- data/examples/linked1.md +8 -4
- data/examples/opts.md +7 -0
- data/lib/ansi_formatter.rb +161 -0
- data/lib/array.rb +27 -0
- data/lib/array_util.rb +21 -0
- data/lib/cached_nested_file_reader.rb +51 -31
- data/lib/constants.rb +46 -0
- data/lib/directory_searcher.rb +239 -0
- data/lib/exceptions.rb +34 -0
- data/lib/fcb.rb +41 -1
- data/lib/filter.rb +32 -17
- data/lib/fout.rb +52 -0
- data/lib/hash.rb +21 -0
- data/lib/hash_delegator.rb +2796 -0
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +190 -1474
- data/lib/mdoc.rb +148 -36
- data/lib/menu.src.yml +224 -37
- data/lib/menu.yml +209 -33
- data/lib/method_sorter.rb +19 -17
- data/lib/object_present.rb +1 -1
- data/lib/pty1.rb +16 -16
- data/lib/regexp.rb +2 -4
- data/lib/shared.rb +0 -5
- data/lib/string_util.rb +22 -0
- metadata +13 -3
- data/lib/environment_opt_parse.rb +0 -209
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4fdb41b414911d16030ffbfb8c4b07ca8c09e3684f21e123d0d99cd738dba3d
|
4
|
+
data.tar.gz: 70c1ec9fb9b795d0cbdcc87ceb03809a57af5901a70dc2b1d319c9451592d687
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abea63db83a4d785bce7c2ec1c9babebe6d78637872a068c4d6b2169832de8ea707a2c3ae426f6cd5ea548269194a797d95abdd7fceb286c4efa49da9d591bfe
|
7
|
+
data.tar.gz: 1b102cb3e898789d6ddf2a401f90cfee04403ea135a14a5067673b250855aef569ca3b39a171570930ef5946d52874d022fb19fd9cf7c1561737144aefc5ef3c
|
data/.rubocop.yml
CHANGED
@@ -14,9 +14,9 @@ Layout/LineContinuationLeadingSpace:
|
|
14
14
|
Enabled: false
|
15
15
|
|
16
16
|
Layout/LineLength:
|
17
|
-
Max:
|
17
|
+
# Max: 78
|
18
18
|
# Max: 80
|
19
|
-
|
19
|
+
Max: 96
|
20
20
|
|
21
21
|
Lint/Debugger:
|
22
22
|
Enabled: false
|
@@ -72,6 +72,9 @@ Style/FormatStringToken:
|
|
72
72
|
Style/GlobalVars:
|
73
73
|
Enabled: false
|
74
74
|
|
75
|
+
Style/Lambda:
|
76
|
+
Enabled: false
|
77
|
+
|
75
78
|
Style/MixinUsage:
|
76
79
|
Enabled: false
|
77
80
|
|
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
@@ -79,8 +79,10 @@ task :minitest do
|
|
79
79
|
commands = [
|
80
80
|
'./lib/block_label.rb',
|
81
81
|
'./lib/cached_nested_file_reader.rb',
|
82
|
+
'./lib/directory_searcher.rb',
|
82
83
|
'./lib/fcb.rb',
|
83
84
|
'./lib/filter.rb',
|
85
|
+
'./lib/hash_delegator.rb',
|
84
86
|
'./lib/markdown_exec.rb',
|
85
87
|
'./lib/mdoc.rb',
|
86
88
|
'./lib/object_present.rb',
|
data/bin/tab_completion.sh
CHANGED
@@ -13,7 +13,7 @@ __filedirs_all()
|
|
13
13
|
}
|
14
14
|
|
15
15
|
_mde_echo_version() {
|
16
|
-
echo "1.
|
16
|
+
echo "1.8"
|
17
17
|
}
|
18
18
|
|
19
19
|
_mde() {
|
@@ -40,6 +40,14 @@ _mde() {
|
|
40
40
|
|
41
41
|
-f) COMPREPLY="."; return 0 ;;
|
42
42
|
|
43
|
+
--find) COMPREPLY="''"; return 0 ;;
|
44
|
+
|
45
|
+
-?) COMPREPLY="''"; return 0 ;;
|
46
|
+
|
47
|
+
--how) COMPREPLY="''"; return 0 ;;
|
48
|
+
|
49
|
+
-?) COMPREPLY="''"; return 0 ;;
|
50
|
+
|
43
51
|
--list-count) COMPREPLY="32"; return 0 ;;
|
44
52
|
|
45
53
|
--output-execution-summary) COMPREPLY="0"; return 0 ;;
|
@@ -74,7 +82,7 @@ _mde() {
|
|
74
82
|
# present matching option names
|
75
83
|
#
|
76
84
|
if [[ ${cur} == -* ]] ; then
|
77
|
-
opts=("--block-name" "--config" "--debug" "--exit" "--filename" "--help" "--list-blocks" "--list-count" "--list-default-env" "--list-default-yaml" "--list-docs" "--list-recent-output" "--list-recent-scripts" "--output-execution-summary" "--output-script" "--output-stdout" "--path" "--pwd" "--run-last-script" "--save-executed-script" "--save-execution-output" "--saved-script-folder" "--saved-stdout-folder" "--select-recent-output" "--select-recent-script" "--tab-completions" "--user-must-approve" "--version" "--display-level")
|
85
|
+
opts=("--block-name" "--config" "--debug" "--exit" "--filename" "--find" "--help" "--how" "--list-blocks" "--list-count" "--list-default-env" "--list-default-yaml" "--list-docs" "--list-recent-output" "--list-recent-scripts" "--output-execution-summary" "--output-script" "--output-stdout" "--path" "--pwd" "--run-last-script" "--save-executed-script" "--save-execution-output" "--saved-script-folder" "--saved-stdout-folder" "--select-recent-output" "--select-recent-script" "--tab-completions" "--user-must-approve" "--version" "--display-level")
|
78
86
|
COMPREPLY=( $(compgen -W "$(printf "'%s' " "${opts[@]}")" -- "${cur}") )
|
79
87
|
|
80
88
|
return 0
|
@@ -101,6 +109,14 @@ _mde() {
|
|
101
109
|
|
102
110
|
-f) COMPREPLY=".RELATIVE_PATH."; return 0 ;;
|
103
111
|
|
112
|
+
--find) COMPREPLY=".FIND."; return 0 ;;
|
113
|
+
|
114
|
+
-?) COMPREPLY=".FIND."; return 0 ;;
|
115
|
+
|
116
|
+
--how) COMPREPLY=".HOW."; return 0 ;;
|
117
|
+
|
118
|
+
-?) COMPREPLY=".HOW."; return 0 ;;
|
119
|
+
|
104
120
|
--list-count) COMPREPLY=".INT.1-."; return 0 ;;
|
105
121
|
|
106
122
|
--output-execution-summary) COMPREPLY=".BOOL."; return 0 ;;
|
@@ -138,4 +154,4 @@ _mde() {
|
|
138
154
|
|
139
155
|
complete -o filenames -o nospace -F _mde mde
|
140
156
|
# _mde_echo_version
|
141
|
-
# echo "Updated: 2023-11
|
157
|
+
# echo "Updated: 2023-12-11 19:58:50 UTC"
|
data/examples/colors.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Demo configuring options
|
2
|
+
|
3
|
+
::: These Opts blocks set the color for all elements.
|
4
|
+
|
5
|
+
```opts :(document_options)
|
6
|
+
```
|
7
|
+
```opts :(default)
|
8
|
+
exception_color_detail: fg_rgbh_E0_E0_20 # Color of exception detail
|
9
|
+
exception_color_name: fg_rgbh_E0_20_20 # Color of exception name
|
10
|
+
execution_report_preview_frame_color: fg_rgbh_20_80_80 # execution_report_preview_frame_color
|
11
|
+
menu_bash_color: fg_rgbh_40_C0_F0 # Color of menu bash
|
12
|
+
menu_chrome_color: fg_rgbh_80_80_20 # Color of menu chrome
|
13
|
+
menu_divider_color: fg_rgbh_20_98_80 # Color of menu divider
|
14
|
+
menu_link_color: fg_rgbh_20_E0_20 # Color of menu link
|
15
|
+
menu_note_color: fg_rgbh_40_A0_A0 # Color of menu note
|
16
|
+
menu_opts_color: fg_rgbh_E0_60_E0 # Color of menu opts
|
17
|
+
menu_opts_set_color: fg_rgbh_E0_20_20 # Color of menu opts
|
18
|
+
menu_task_color: fg_rgbh_A0_20_D0 # Color of menu task
|
19
|
+
menu_vars_color: fg_rgbh_E0_80_20 # Color of menu vars
|
20
|
+
menu_vars_set_color: fg_rgbh_E0_80_20 # Color of menu vars
|
21
|
+
output_execution_label_name_color: fg_rgbh_20_D8_80 # Color of output_execution_label_name
|
22
|
+
output_execution_label_value_color: fg_rgbh_20_E0_80 # Color of output_execution_label_value
|
23
|
+
prompt_color_after_script_execution: fg_rgbh_20_E8_80 # Color of prompt after script execution
|
24
|
+
script_execution_frame_color: fg_rgbh_20_80_80 # script_execution_frame_color
|
25
|
+
script_preview_frame_color: fg_rgbh_20_80_80 # Color of output divider
|
26
|
+
warning_color: fg_rgbh_E0_E0_20 # Color of warning message
|
27
|
+
```
|
28
|
+
|
29
|
+
::: Example blocks
|
30
|
+
|
31
|
+
```bash :Bash1
|
32
|
+
```
|
33
|
+
```link :Link1
|
34
|
+
```
|
35
|
+
```opts :Opts1
|
36
|
+
```
|
37
|
+
```port :Port1
|
38
|
+
```
|
39
|
+
```vars :Vars1
|
40
|
+
```
|
41
|
+
[ ] Task1
|
42
|
+
blue; fg_rgbh_00_00_FF
|
43
|
+
green; fg_rgbh_00_FF_00
|
44
|
+
indigo; fg_rgbh_4B_00_82
|
45
|
+
orange; fg_rgbh_FF_7F_00
|
46
|
+
red; fg_rgbh_FF_00_00
|
47
|
+
violet; fg_rgbh_94_00_D3
|
48
|
+
yellow; fg_rgbh_FF_FF_00
|
data/examples/import0.md
CHANGED
@@ -1,8 +1,44 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
```
|
1
|
+
This is Page 0
|
2
|
+
|
4
3
|
@import import1.md
|
5
|
-
|
6
|
-
|
4
|
+
|
5
|
+
```opts :(document_options)
|
6
|
+
user_must_approve: true
|
7
|
+
```
|
8
|
+
|
9
|
+
::: Page 0 code blocks
|
10
|
+
|
11
|
+
```bash :page0_block1 +(page0_block2) +(page1_block2)
|
12
|
+
echo "page 0 block 1 visible"
|
13
|
+
# requires page 0 block 2
|
14
|
+
# imports page 1
|
15
|
+
# requires page 1 block 2
|
16
|
+
```
|
17
|
+
|
18
|
+
```bash :(page0_block2)
|
19
|
+
echo "page 0 block 2 hidden"
|
7
20
|
```
|
8
21
|
|
22
|
+
::: Control display of code blocks
|
23
|
+
|
24
|
+
```opts :hide_imported_blocks
|
25
|
+
menu_include_imported_blocks: false
|
26
|
+
```
|
27
|
+
|
28
|
+
```opts :show_imported_blocks
|
29
|
+
menu_include_imported_blocks: true
|
30
|
+
```
|
31
|
+
|
32
|
+
::: Control display of notes
|
33
|
+
|
34
|
+
```opts :hide_imported_notes
|
35
|
+
menu_include_imported_notes: false
|
36
|
+
```
|
37
|
+
|
38
|
+
```opts :show_imported_notes
|
39
|
+
menu_include_imported_notes: true
|
40
|
+
```
|
41
|
+
|
42
|
+
```link :page_1
|
43
|
+
file: examples/import1.md
|
44
|
+
```
|
data/examples/import1.md
CHANGED
data/examples/include.md
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
```bash :(one)
|
2
|
-
|
2
|
+
echo block "one"
|
3
3
|
```
|
4
|
+
|
4
5
|
```bash :two +(one)
|
5
|
-
|
6
|
+
echo block "two" requires one
|
6
7
|
```
|
8
|
+
|
7
9
|
```bash :(three) +two +(one)
|
8
|
-
|
10
|
+
echo block "three" requires two and one
|
9
11
|
```
|
12
|
+
|
10
13
|
```bash :four +(three)
|
11
|
-
|
14
|
+
echo block "four" requires three
|
15
|
+
```
|
16
|
+
|
17
|
+
```bash :trigger_unmet_dependency +(unmet)
|
18
|
+
echo block "five" requires an unmet dependency
|
12
19
|
```
|
data/examples/linked1.md
CHANGED
@@ -11,16 +11,20 @@ colorize_env_vars 'vars for page2' PAGE2_VAR_VIA_INHERIT page2_var_via_environme
|
|
11
11
|
colorize_env_vars 'vars for page3' PAGE3_VAR_VIA_INHERIT page3_var_via_environment
|
12
12
|
```
|
13
13
|
|
14
|
+
```bash :(vars2)
|
15
|
+
PAGE2_VAR_VIA_INHERIT=for_page2_from_page1_via_inherited_code_file
|
16
|
+
```
|
17
|
+
|
18
|
+
```link :(linked2)
|
19
|
+
file: examples/linked2.md
|
20
|
+
```
|
21
|
+
|
14
22
|
::: This Link block
|
15
23
|
::: 1. requires a block that sets environment variable PAGE2_VAR_VIA_INHERIT,
|
16
24
|
::: 2. navigates to document 2, and
|
17
25
|
::: 3. executes block "show_vars" to display the imported PAGE2_VAR_VIA_INHERIT.
|
18
26
|
::: Any script generated by page 2 will contain the inherited code.
|
19
27
|
|
20
|
-
```bash :(vars2)
|
21
|
-
PAGE2_VAR_VIA_INHERIT=for_page2_from_page1_via_inherited_code_file
|
22
|
-
```
|
23
|
-
|
24
28
|
```link :linked2_import_vars +(vars2)
|
25
29
|
file: examples/linked2.md
|
26
30
|
block: show_vars
|
data/examples/opts.md
CHANGED
@@ -19,4 +19,11 @@ menu_task_color: fg_rgb_127_127_255
|
|
19
19
|
menu_divider_color: green
|
20
20
|
menu_link_color: fg_rgbh_88_cc_66
|
21
21
|
menu_note_color: yellow
|
22
|
+
|
23
|
+
menu_note_match: "^\\s*(?<line>[^\\s/].*)\\s*$" # Pattern for notes in block selection menu; start with any char except '/'
|
22
24
|
```
|
25
|
+
|
26
|
+
note 1
|
27
|
+
note 2 ends with /
|
28
|
+
/ note 3 starts with /
|
29
|
+
note 4
|
@@ -0,0 +1,161 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
class AnsiFormatter
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def format_and_highlight_array(
|
12
|
+
data,
|
13
|
+
highlight_color_sym: :exception_color_detail,
|
14
|
+
plain_color_sym: :menu_chrome_color,
|
15
|
+
label: 'Data:',
|
16
|
+
highlight: [],
|
17
|
+
line_prefix: ' ',
|
18
|
+
line_postfix: '',
|
19
|
+
detail_sep: ''
|
20
|
+
)
|
21
|
+
(data&.map do |item|
|
22
|
+
scan_and_process_multiple_substrings(item, highlight, plain_color_sym,
|
23
|
+
highlight_color_sym).join
|
24
|
+
# color_sym = highlight.include?(item) ? highlight_color_sym : c
|
25
|
+
# string_send_color(item, color_sym)
|
26
|
+
end || []) #.join
|
27
|
+
# formatted_deps
|
28
|
+
# "#{line_prefix}#{string_send_color(label, highlight_color_sym)}#{line_postfix}\n" + formatted_deps.join("\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Formats and highlights a list of data. data are presented with indentation,
|
32
|
+
# and specific items can be highlighted in a specified color, while others are shown in a plain color.
|
33
|
+
#
|
34
|
+
# @param data [Hash] A hash of data, where each key is a dependency name,
|
35
|
+
# and its value is an array of sub-items.
|
36
|
+
# @param highlight_color_sym [Symbol] The color method to apply to highlighted items.
|
37
|
+
# Default is :exception_color_detail.
|
38
|
+
# @param plain_color_sym [Symbol] The color method for non-highlighted items.
|
39
|
+
# Default is :menu_chrome_color.
|
40
|
+
# @param label [String] The label to prefix the list of data with.
|
41
|
+
# Default is 'data:'.
|
42
|
+
# @param highlight [Array] An array of items to highlight. Each item in this array will be
|
43
|
+
# formatted with the specified highlight color.
|
44
|
+
# @param line_prefix [String] Prefix for each line. Default is ' '.
|
45
|
+
# @param line_postfix [String] Postfix for each line. Default is ''.
|
46
|
+
# @param detail_sep [String] Separator for items in the sub-list. Default is ' '.
|
47
|
+
# @return [String] A formatted string representation of the data with highlighted items.
|
48
|
+
def format_and_highlight_hash(
|
49
|
+
data,
|
50
|
+
highlight_color_sym: :exception_color_detail,
|
51
|
+
plain_color_sym: :menu_chrome_color,
|
52
|
+
label: 'Data:',
|
53
|
+
highlight: [],
|
54
|
+
line_prefix: ' ',
|
55
|
+
line_postfix: '',
|
56
|
+
detail_sep: ' '
|
57
|
+
)
|
58
|
+
formatted_deps = data&.map do |dep_name, sub_items|
|
59
|
+
formatted_sub_items = sub_items.map do |item|
|
60
|
+
color_sym = highlight.include?(item) ? highlight_color_sym : plain_color_sym
|
61
|
+
string_send_color(item, color_sym)
|
62
|
+
end.join(detail_sep)
|
63
|
+
|
64
|
+
"#{line_prefix}- #{string_send_color(dep_name,
|
65
|
+
highlight.include?(dep_name) ? highlight_color_sym : plain_color_sym)}: #{formatted_sub_items}#{line_postfix}"
|
66
|
+
end || []
|
67
|
+
|
68
|
+
"#{line_prefix}#{string_send_color(label,
|
69
|
+
highlight_color_sym)}#{line_postfix}\n" + formatted_deps.join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
# Function to scan a string and process its segments based on multiple substrings
|
73
|
+
# @param str [String] The string to scan.
|
74
|
+
# @param substrings [Array<String>] The substrings to match in the string.
|
75
|
+
# @param plain_sym [Symbol] The symbol for non-matching segments.
|
76
|
+
# @param color_sym [Symbol] The symbol for matching segments.
|
77
|
+
# @return [Array<String>] The processed segments.
|
78
|
+
def scan_and_process_multiple_substrings(str, substrings, plain_sym, color_sym)
|
79
|
+
return string_send_color(str, plain_sym) if substrings.empty? || substrings.any?(&:empty?)
|
80
|
+
|
81
|
+
results = []
|
82
|
+
remaining_str = str.dup
|
83
|
+
|
84
|
+
while remaining_str.length.positive?
|
85
|
+
match_indices = substrings.map { |substring| remaining_str.index(substring) }.compact
|
86
|
+
earliest_match = match_indices.min
|
87
|
+
|
88
|
+
if earliest_match
|
89
|
+
# Process non-matching segment before the earliest match, if any
|
90
|
+
unless earliest_match.zero?
|
91
|
+
non_matching_segment = remaining_str.slice!(0...earliest_match)
|
92
|
+
results << string_send_color(non_matching_segment, plain_sym)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find which substring has this earliest match
|
96
|
+
matching_substring = substrings.find do |substring|
|
97
|
+
remaining_str.index(substring) == earliest_match
|
98
|
+
end
|
99
|
+
|
100
|
+
if matching_substring
|
101
|
+
matching_segment = remaining_str.slice!(0...matching_substring.length)
|
102
|
+
results << string_send_color(matching_segment, color_sym)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
# Process the remaining non-matching segment
|
106
|
+
results << string_send_color(remaining_str, plain_sym)
|
107
|
+
break
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
results
|
112
|
+
end
|
113
|
+
|
114
|
+
# Function to scan a string and process its segments
|
115
|
+
# @param str [String] The string to scan.
|
116
|
+
# @param substring [String] The substring to match in the string.
|
117
|
+
# @param plain_sym [Symbol] The symbol for non-matching segments.
|
118
|
+
# @param color_sym [Symbol] The symbol for matching segments.
|
119
|
+
# @return [Array<String>] The processed segments.
|
120
|
+
def scan_and_process_string(str, substring, plain_sym, color_sym)
|
121
|
+
return string_send_color(str, plain_sym) unless substring.present?
|
122
|
+
|
123
|
+
results = []
|
124
|
+
remaining_str = str.dup
|
125
|
+
|
126
|
+
while remaining_str.length.positive?
|
127
|
+
match_index = remaining_str.index(substring)
|
128
|
+
if match_index
|
129
|
+
# Process non-matching segment before the match, if any
|
130
|
+
unless match_index.zero?
|
131
|
+
non_matching_segment = remaining_str.slice!(0...match_index)
|
132
|
+
results << string_send_color(non_matching_segment, plain_sym)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Process the matching segment
|
136
|
+
matching_segment = remaining_str.slice!(0...substring.length)
|
137
|
+
results << string_send_color(matching_segment, color_sym)
|
138
|
+
else
|
139
|
+
# Process the remaining non-matching segment
|
140
|
+
results << string_send_color(remaining_str, plain_sym)
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
results
|
146
|
+
end
|
147
|
+
|
148
|
+
# # Example usage
|
149
|
+
# scan_and_process_string("Hello world, hello universe", "hello", :plain, :color)
|
150
|
+
|
151
|
+
# Applies a color method to a string based on the provided color symbol.
|
152
|
+
# The color method is fetched from @options and applied to the string.
|
153
|
+
# @param string [String] The string to which the color will be applied.
|
154
|
+
# @param color_sym [Symbol] The symbol representing the color method.
|
155
|
+
# @param default [String] Default color method to use if color_sym is not found in @options.
|
156
|
+
# @return [String] The string with the applied color method.
|
157
|
+
def string_send_color(string, color_sym, default: 'plain')
|
158
|
+
color_method = @options.fetch(color_sym, default).to_sym
|
159
|
+
string.to_s.send(color_method)
|
160
|
+
end
|
161
|
+
end
|
data/lib/array.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
class Array
|
7
|
+
# Processes each element of the array, yielding the previous, current, and next elements to the given block.
|
8
|
+
# Deletes the current element if the block returns true.
|
9
|
+
# @return [Array] The modified array after conditional deletions.
|
10
|
+
def process_and_conditionally_delete!
|
11
|
+
i = 0
|
12
|
+
while i < length
|
13
|
+
prev_item = self[i - 1] unless i.zero?
|
14
|
+
current_item = self[i]
|
15
|
+
next_item = self[i + 1]
|
16
|
+
|
17
|
+
should_delete = yield prev_item, current_item, next_item
|
18
|
+
if should_delete
|
19
|
+
delete_at(i)
|
20
|
+
else
|
21
|
+
i += 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
data/lib/array_util.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# encoding=utf-8
|
5
|
+
|
6
|
+
module ArrayUtil
|
7
|
+
def self.partition_by_predicate(arr)
|
8
|
+
true_list = []
|
9
|
+
false_list = []
|
10
|
+
|
11
|
+
arr.each do |element|
|
12
|
+
if yield(element)
|
13
|
+
true_list << element
|
14
|
+
else
|
15
|
+
false_list << element
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
[true_list, false_list]
|
20
|
+
end
|
21
|
+
end
|
@@ -5,6 +5,16 @@
|
|
5
5
|
|
6
6
|
# version 2023-10-03
|
7
7
|
|
8
|
+
require 'fileutils'
|
9
|
+
require_relative 'exceptions'
|
10
|
+
|
11
|
+
# a struct to hold the data for a single line
|
12
|
+
NestedLine = Struct.new(:text, :depth) do
|
13
|
+
def to_s
|
14
|
+
text
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
##
|
9
19
|
# The CachedNestedFileReader class provides functionality to read file lines with the ability
|
10
20
|
# to process '#import filename' directives. When such a directive is encountered in a file,
|
@@ -13,42 +23,58 @@
|
|
13
23
|
# It allows clients to read lines with or without providing a block.
|
14
24
|
#
|
15
25
|
class CachedNestedFileReader
|
26
|
+
include Exceptions
|
27
|
+
|
16
28
|
def initialize(import_pattern: /^ *#import (.+)$/)
|
17
29
|
@file_cache = {}
|
18
30
|
@import_pattern = import_pattern
|
19
31
|
end
|
20
32
|
|
21
|
-
def
|
33
|
+
def error_handler(name = '', opts = {})
|
34
|
+
Exceptions.error_handler(
|
35
|
+
"CachedNestedFileReader.#{name} -- #{$!}",
|
36
|
+
opts
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def warn_format(name, message, opts = {})
|
41
|
+
Exceptions.warn_format(
|
42
|
+
"CachedNestedFileReader.#{name} -- #{message}",
|
43
|
+
opts
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def readlines(filename, depth = 0, &block)
|
22
48
|
if @file_cache.key?(filename)
|
23
|
-
@file_cache[filename].each(&block) if
|
49
|
+
@file_cache[filename].each(&block) if block
|
24
50
|
return @file_cache[filename]
|
25
51
|
end
|
26
52
|
|
27
53
|
directory_path = File.dirname(filename)
|
28
|
-
lines = File.readlines(filename, chomp: true)
|
54
|
+
# lines = File.readlines(filename, chomp: true)
|
29
55
|
processed_lines = []
|
30
56
|
|
31
|
-
|
32
|
-
if
|
33
|
-
|
34
|
-
|
57
|
+
File.readlines(filename, chomp: true).each do |line|
|
58
|
+
if Regexp.new(@import_pattern) =~ line
|
59
|
+
name_strip = $~[:name].strip
|
60
|
+
included_file_path = if name_strip =~ %r{^/}
|
61
|
+
name_strip
|
35
62
|
else
|
36
|
-
File.join(directory_path,
|
63
|
+
File.join(directory_path, name_strip)
|
37
64
|
end
|
38
|
-
processed_lines += readlines(included_file_path,
|
65
|
+
processed_lines += readlines(included_file_path, depth + 1,
|
66
|
+
&block)
|
39
67
|
else
|
40
|
-
|
41
|
-
|
68
|
+
nested_line = NestedLine.new(line, depth)
|
69
|
+
processed_lines.push(nested_line)
|
70
|
+
block&.call(nested_line)
|
42
71
|
end
|
43
72
|
end
|
44
73
|
|
45
74
|
@file_cache[filename] = processed_lines
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def fetch_lines(filename)
|
51
|
-
@fetch_lines_cache[filename] ||= File.readlines(filename, chomp: true)
|
75
|
+
rescue Errno::ENOENT
|
76
|
+
# Exceptions.error_handler('readlines', { abort: true })
|
77
|
+
warn_format('readlines', "No such file -- #{filename}", { abort: true })
|
52
78
|
end
|
53
79
|
end
|
54
80
|
|
@@ -56,10 +82,6 @@ if $PROGRAM_NAME == __FILE__
|
|
56
82
|
require 'minitest/autorun'
|
57
83
|
require 'tempfile'
|
58
84
|
|
59
|
-
##
|
60
|
-
# The CachedNestedFileReaderTest class provides testing for
|
61
|
-
# the CachedNestedFileReader class.
|
62
|
-
#
|
63
85
|
class CachedNestedFileReaderTest < Minitest::Test
|
64
86
|
def setup
|
65
87
|
@file2 = Tempfile.new('test2.txt')
|
@@ -69,7 +91,7 @@ if $PROGRAM_NAME == __FILE__
|
|
69
91
|
@file1 = Tempfile.new('test1.txt')
|
70
92
|
@file1.write("Line1\nLine2\n #insert #{@file2.path}\nLine3")
|
71
93
|
@file1.rewind
|
72
|
-
@reader = CachedNestedFileReader.new(import_pattern: /^ *#insert (.+)$/)
|
94
|
+
@reader = CachedNestedFileReader.new(import_pattern: /^ *#insert (?'name'.+)$/)
|
73
95
|
end
|
74
96
|
|
75
97
|
def teardown
|
@@ -81,28 +103,26 @@ if $PROGRAM_NAME == __FILE__
|
|
81
103
|
end
|
82
104
|
|
83
105
|
def test_readlines_without_imports
|
84
|
-
result =
|
85
|
-
@reader.readlines(@file2.path) { |line| result << line }
|
106
|
+
result = @reader.readlines(@file2.path).map(&:to_s)
|
86
107
|
assert_equal %w[ImportedLine1 ImportedLine2], result
|
87
108
|
end
|
88
109
|
|
89
110
|
def test_readlines_with_imports
|
90
|
-
result =
|
91
|
-
|
92
|
-
|
111
|
+
result = @reader.readlines(@file1.path).map(&:to_s)
|
112
|
+
assert_equal %w[Line1 Line2 ImportedLine1 ImportedLine2 Line3],
|
113
|
+
result
|
93
114
|
end
|
94
115
|
|
95
116
|
def test_caching_functionality
|
96
117
|
# First read
|
97
|
-
|
98
|
-
@reader.readlines(@file2.path)
|
118
|
+
|
119
|
+
result1 = @reader.readlines(@file2.path).map(&:to_s)
|
99
120
|
|
100
121
|
# Simulate file content change
|
101
122
|
@file2.reopen(@file2.path, 'w') { |f| f.write('ChangedLine') }
|
102
123
|
|
103
124
|
# Second read (should read from cache, not the changed file)
|
104
|
-
result2 =
|
105
|
-
@reader.readlines(@file2.path) { |line| result2 << line }
|
125
|
+
result2 = @reader.readlines(@file2.path).map(&:to_s)
|
106
126
|
|
107
127
|
assert_equal result1, result2
|
108
128
|
assert_equal %w[ImportedLine1 ImportedLine2], result2
|