markdown_exec 1.6 → 1.8
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 +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
|