markdown_exec 2.0.3.2 → 2.0.5
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 +4 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/bin/tab_completion.sh +11 -3
- data/examples/linked.md +23 -3
- data/examples/search.md +21 -0
- data/lib/directory_searcher.rb +26 -24
- data/lib/find_files.rb +48 -7
- data/lib/hash_delegator.rb +175 -55
- data/lib/input_sequencer.rb +6 -6
- data/lib/markdown_exec/version.rb +1 -1
- data/lib/markdown_exec.rb +195 -33
- data/lib/menu.src.yml +15 -1
- data/lib/menu.yml +14 -2
- data/lib/std_out_err_logger.rb +12 -14
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb96e0d1b92398066dedcaf7017ee6d7deea37ef9ba19fa089de6c4e5aebf27d
|
|
4
|
+
data.tar.gz: f8b8ffcb0f30fbec9321a4a9f14ae7568b16b31e40833046fb1ab16ddcf28c1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96a95683c52b4a7546d2cb1ce399dd6c278a7783305d70e12a0a8ac5c70df739705a0ccb06a14f8861ca5794193f201ab185cd7e994e6fcc4eef56a0f038504e
|
|
7
|
+
data.tar.gz: 51991405ec7dae5c31dcc1ff6294172279d046975647ca8676b4e28918d2ff954a19e671bae4cc7381963816f8b0826b4723c4bf61fb6a35fd8fce25e808ed63
|
data/.rubocop.yml
CHANGED
|
@@ -16,6 +16,7 @@ Layout/LineContinuationLeadingSpace:
|
|
|
16
16
|
Layout/LineLength: # 2024-01-21 temp disable
|
|
17
17
|
Enabled: false
|
|
18
18
|
Max: 96
|
|
19
|
+
Max: 120
|
|
19
20
|
|
|
20
21
|
Lint/Debugger:
|
|
21
22
|
Enabled: false
|
|
@@ -83,6 +84,9 @@ Style/MixinUsage:
|
|
|
83
84
|
Style/MultilineBlockChain:
|
|
84
85
|
Enabled: false
|
|
85
86
|
|
|
87
|
+
Style/OpenStructUse:
|
|
88
|
+
Enabled: false
|
|
89
|
+
|
|
86
90
|
Style/PerlBackrefs: # Prefer ::Regexp.last_match.post_match over $'.
|
|
87
91
|
Enabled: false
|
|
88
92
|
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.5] - 2024-04-24
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Open option: Search for keyword in block names instead of entire document.
|
|
8
|
+
|
|
9
|
+
## [2.0.4] - 2024-04-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Option to match and open directories and files.
|
|
14
|
+
|
|
3
15
|
## [2.0.3] - 2024-04-15
|
|
4
16
|
|
|
5
17
|
### Added
|
data/Gemfile.lock
CHANGED
data/bin/tab_completion.sh
CHANGED
|
@@ -13,7 +13,7 @@ __filedirs_all()
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
_mde_echo_version() {
|
|
16
|
-
echo "2.0.
|
|
16
|
+
echo "2.0.5"
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
_mde() {
|
|
@@ -66,6 +66,10 @@ _mde() {
|
|
|
66
66
|
|
|
67
67
|
--list-count) COMPREPLY="32"; return 0 ;;
|
|
68
68
|
|
|
69
|
+
--open) COMPREPLY="''"; return 0 ;;
|
|
70
|
+
|
|
71
|
+
-o) COMPREPLY="''"; return 0 ;;
|
|
72
|
+
|
|
69
73
|
--output-execution-summary) COMPREPLY="0"; return 0 ;;
|
|
70
74
|
|
|
71
75
|
--output-script) COMPREPLY="0"; return 0 ;;
|
|
@@ -98,7 +102,7 @@ _mde() {
|
|
|
98
102
|
# present matching option names
|
|
99
103
|
#
|
|
100
104
|
if [[ ${cur} == -* ]] ; then
|
|
101
|
-
opts=("--block-name" "--config" "--debug" "--dump-dump-delegate-object" "--dump-blocks-in-file" "--dump-dump-inherited-block_names" "--dump-dump-inherited-dependencies" "--dump-dump-inherited-lines" "--dump-menu-blocks" "--dump-selected-block" "--exit" "--filename" "--find" "--find-path" "--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")
|
|
105
|
+
opts=("--block-name" "--config" "--debug" "--dump-dump-delegate-object" "--dump-blocks-in-file" "--dump-dump-inherited-block_names" "--dump-dump-inherited-dependencies" "--dump-dump-inherited-lines" "--dump-menu-blocks" "--dump-selected-block" "--exit" "--filename" "--find" "--find-path" "--help" "--how" "--list-blocks" "--list-count" "--list-default-env" "--list-default-yaml" "--list-docs" "--list-recent-output" "--list-recent-scripts" "--open" "--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")
|
|
102
106
|
COMPREPLY=( $(compgen -W "$(printf "'%s' " "${opts[@]}")" -- "${cur}") )
|
|
103
107
|
|
|
104
108
|
return 0
|
|
@@ -151,6 +155,10 @@ _mde() {
|
|
|
151
155
|
|
|
152
156
|
--list-count) COMPREPLY=".INT.1-."; return 0 ;;
|
|
153
157
|
|
|
158
|
+
--open) COMPREPLY=".OPEN."; return 0 ;;
|
|
159
|
+
|
|
160
|
+
-o) COMPREPLY=".OPEN."; return 0 ;;
|
|
161
|
+
|
|
154
162
|
--output-execution-summary) COMPREPLY=".BOOL."; return 0 ;;
|
|
155
163
|
|
|
156
164
|
--output-script) COMPREPLY=".BOOL."; return 0 ;;
|
|
@@ -186,4 +194,4 @@ _mde() {
|
|
|
186
194
|
|
|
187
195
|
complete -o filenames -o nospace -F _mde mde
|
|
188
196
|
# _mde_echo_version
|
|
189
|
-
# echo "Updated: 2024-04-
|
|
197
|
+
# echo "Updated: 2024-04-26 15:37:23 UTC"
|
data/examples/linked.md
CHANGED
|
@@ -9,7 +9,29 @@ user_must_approve: false
|
|
|
9
9
|
block: (display_variable)
|
|
10
10
|
eval: true
|
|
11
11
|
```
|
|
12
|
-
|
|
12
|
+
Block name with all chars.
|
|
13
|
+
/ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
|
|
14
|
+
/ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
|
|
15
|
+
```link :!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
|
|
16
|
+
file: examples/linked2.md
|
|
17
|
+
block: show_vars
|
|
18
|
+
vars:
|
|
19
|
+
page2_var_via_environment: for_page2_from_page1_via_current_environment
|
|
20
|
+
```
|
|
21
|
+
```link :¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
|
|
22
|
+
file: examples/linked2.md
|
|
23
|
+
block: show_vars
|
|
24
|
+
vars:
|
|
25
|
+
page2_var_via_environment: for_page2_from_page1_via_current_environment
|
|
26
|
+
```
|
|
27
|
+
Block with no title is displayed correctly in a single line (for a Bash comment).
|
|
28
|
+
```link
|
|
29
|
+
file: examples/linked2.md
|
|
30
|
+
block: show_vars
|
|
31
|
+
vars:
|
|
32
|
+
page2_var_via_environment: for_page2_from_page1_via_current_environment
|
|
33
|
+
```
|
|
34
|
+
Spaces in variable value are unchanged.
|
|
13
35
|
```link :link_with_vars_with_spaces
|
|
14
36
|
vars:
|
|
15
37
|
test: "1 2 3"
|
|
@@ -124,11 +146,9 @@ load: examples/fail*
|
|
|
124
146
|
```link :load_glob_with_format
|
|
125
147
|
load: "%{home}/examples/load*.sh"
|
|
126
148
|
```
|
|
127
|
-
|
|
128
149
|
```link :save_glob_load*
|
|
129
150
|
save: examples/*.sh
|
|
130
151
|
```
|
|
131
|
-
|
|
132
152
|
```link :save_glob_*
|
|
133
153
|
save: examples/*.sh
|
|
134
154
|
```
|
data/examples/search.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Demonstrate keyword search in documents.
|
|
2
|
+
Keywords in body: monkey secret
|
|
3
|
+
|
|
4
|
+
Keyword in untyped block name.
|
|
5
|
+
``` :monkey1
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
Keyword in Bash block name.
|
|
9
|
+
```bash :monkey2
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Keyword in block body.
|
|
13
|
+
```
|
|
14
|
+
monkey3
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Keyword in Link block body.
|
|
18
|
+
```link
|
|
19
|
+
vars:
|
|
20
|
+
monkey4: 4
|
|
21
|
+
```
|
data/lib/directory_searcher.rb
CHANGED
|
@@ -110,7 +110,7 @@ class DirectorySearcher
|
|
|
110
110
|
|
|
111
111
|
# Searches for the pattern in directory names.
|
|
112
112
|
# @return [Array<String>] List of matching directory names.
|
|
113
|
-
def
|
|
113
|
+
def find_directory_names
|
|
114
114
|
match_dirs = []
|
|
115
115
|
@paths.each do |path|
|
|
116
116
|
Find.find(path) do |p|
|
|
@@ -123,7 +123,7 @@ class DirectorySearcher
|
|
|
123
123
|
|
|
124
124
|
# Searches for the pattern in file names.
|
|
125
125
|
# @return [Array<String>] List of matching file names.
|
|
126
|
-
def
|
|
126
|
+
def find_file_names
|
|
127
127
|
match_files = []
|
|
128
128
|
@paths.each do |path|
|
|
129
129
|
Find.find(path) do |p|
|
|
@@ -147,7 +147,7 @@ class DirectorySearcher
|
|
|
147
147
|
|
|
148
148
|
# Searches for the pattern in the contents of the files and returns matches along with their file paths and line numbers.
|
|
149
149
|
# @return [Hash] A hash where each key is a file path and each value is an array of hashes with :line_number and :line keys.
|
|
150
|
-
def
|
|
150
|
+
def find_file_contents
|
|
151
151
|
match_details = {}
|
|
152
152
|
|
|
153
153
|
@paths.each do |path|
|
|
@@ -160,9 +160,11 @@ class DirectorySearcher
|
|
|
160
160
|
begin
|
|
161
161
|
File.foreach(p).with_index(1) do |line, line_num| # Index starts from 1 for line numbers
|
|
162
162
|
line_utf8 = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
|
|
163
|
-
|
|
163
|
+
|
|
164
|
+
line_utf8 = yield(line_utf8) if block_given?
|
|
165
|
+
|
|
166
|
+
if line_utf8&.match?(@pattern)
|
|
164
167
|
match_details[p] ||= []
|
|
165
|
-
# match_details[p] << { number: line_num, line: line_utf8.chomp }
|
|
166
168
|
match_details[p] << IndexedLine.new(line_num, line_utf8.chomp)
|
|
167
169
|
end
|
|
168
170
|
end
|
|
@@ -178,7 +180,7 @@ class DirectorySearcher
|
|
|
178
180
|
|
|
179
181
|
# # Searches for the pattern in the contents of the files.
|
|
180
182
|
# # @return [Array<String>] List of matching lines from files.
|
|
181
|
-
# def
|
|
183
|
+
# def find_file_contents
|
|
182
184
|
# match_lines = []
|
|
183
185
|
# @paths.each do |path|
|
|
184
186
|
# Find.find(path) do |p|
|
|
@@ -219,22 +221,22 @@ if $PROGRAM_NAME == __FILE__
|
|
|
219
221
|
@searcher = DirectorySearcher.new(@pattern, @paths)
|
|
220
222
|
end
|
|
221
223
|
|
|
222
|
-
# Test
|
|
223
|
-
def
|
|
224
|
+
# Test find_directory_names method
|
|
225
|
+
def test_find_directory_names
|
|
224
226
|
# Add assertions based on your test directory structure and expected results
|
|
225
|
-
assert_equal [], @searcher.
|
|
227
|
+
assert_equal [], @searcher.find_directory_names
|
|
226
228
|
end
|
|
227
229
|
|
|
228
|
-
# Test
|
|
229
|
-
def
|
|
230
|
+
# Test find_file_names method
|
|
231
|
+
def test_find_file_names
|
|
230
232
|
# Add assertions based on your test directory structure and expected results
|
|
231
|
-
assert_equal [], @searcher.
|
|
233
|
+
assert_equal [], @searcher.find_file_names
|
|
232
234
|
end
|
|
233
235
|
|
|
234
|
-
# Test
|
|
235
|
-
def
|
|
236
|
+
# Test find_file_contents method
|
|
237
|
+
def test_find_file_contents
|
|
236
238
|
# Add assertions based on your test directory structure and expected results
|
|
237
|
-
assert_equal ({}), @searcher.
|
|
239
|
+
assert_equal ({}), @searcher.find_file_contents
|
|
238
240
|
end
|
|
239
241
|
end
|
|
240
242
|
|
|
@@ -249,26 +251,26 @@ if $PROGRAM_NAME == __FILE__
|
|
|
249
251
|
filename_glob: @filename_glob)
|
|
250
252
|
end
|
|
251
253
|
|
|
252
|
-
# Test
|
|
253
|
-
def
|
|
254
|
+
# Test find_directory_names method for 'spec'
|
|
255
|
+
def test_find_directory_names_for_spec
|
|
254
256
|
# Replace with actual expected directory names containing 'spec'
|
|
255
257
|
expected_dirs = ['./spec']
|
|
256
|
-
assert_equal expected_dirs, @searcher_spec.
|
|
258
|
+
assert_equal expected_dirs, @searcher_spec.find_directory_names
|
|
257
259
|
end
|
|
258
260
|
|
|
259
|
-
# Test
|
|
260
|
-
def
|
|
261
|
+
# Test find_file_names method for 'spec'
|
|
262
|
+
def test_find_file_names_for_spec
|
|
261
263
|
# Replace with actual expected file names containing 'spec'
|
|
262
264
|
expected_files = ['./spec/cli_spec.rb', './spec/env_spec.rb',
|
|
263
265
|
'./spec/markdown_exec_spec.rb', './spec/tap_spec.rb']
|
|
264
|
-
assert_equal expected_files, @searcher_spec.
|
|
266
|
+
assert_equal expected_files, @searcher_spec.find_file_names
|
|
265
267
|
end
|
|
266
268
|
|
|
267
|
-
# # Test
|
|
268
|
-
# def
|
|
269
|
+
# # Test find_file_contents method for 'spec'
|
|
270
|
+
# def test_find_file_contents_for_spec
|
|
269
271
|
# # Replace with actual expected lines containing 'spec'
|
|
270
272
|
# expected_lines = {['Line with spec 1', 'Line with spec 2']}
|
|
271
|
-
# assert_equal expected_lines, @searcher_spec.
|
|
273
|
+
# assert_equal expected_lines, @searcher_spec.find_file_contents
|
|
272
274
|
# end
|
|
273
275
|
end
|
|
274
276
|
|
data/lib/find_files.rb
CHANGED
|
@@ -4,25 +4,29 @@
|
|
|
4
4
|
# encoding=utf-8
|
|
5
5
|
# version 2024-01-15
|
|
6
6
|
|
|
7
|
-
# Finds files matching a given pattern within specified directory paths
|
|
7
|
+
# Finds files matching a given pattern within specified directory paths while optionally excluding
|
|
8
|
+
# "." and ".." entries and directory names from the results.
|
|
8
9
|
#
|
|
9
|
-
# The function takes a pattern (filename or pattern with wildcards)
|
|
10
|
+
# The function takes a pattern (filename or pattern with wildcards), an array of paths, and an
|
|
11
|
+
# option to exclude directory entries and special entries "." and "..".
|
|
10
12
|
# It searches for files matching the pattern within each of the specified paths. Hidden files
|
|
11
|
-
# are
|
|
13
|
+
# are included in the search. The search can include subdirectories depending on the
|
|
12
14
|
# path specification (e.g., 'dir/**' for recursive search).
|
|
13
15
|
#
|
|
14
16
|
# Args:
|
|
15
17
|
# pattern (String): A filename or a pattern string with wildcards.
|
|
16
18
|
# paths (Array<String>): An array of directory paths where the search will be performed.
|
|
17
19
|
# Paths can include wildcards for recursive search.
|
|
20
|
+
# exclude_dirs (Boolean): If true, excludes "." and ".." and directory names from the results.
|
|
18
21
|
#
|
|
19
22
|
# Returns:
|
|
20
|
-
# Array<String>: A unique list of file paths that match the given pattern in the specified paths
|
|
23
|
+
# Array<String>: A unique list of file paths that match the given pattern in the specified paths,
|
|
24
|
+
# excluding directories if exclude_dirs is true.
|
|
21
25
|
#
|
|
22
26
|
# Example:
|
|
23
|
-
# find_files('version.rb', ['lib/**', 'spec'])
|
|
27
|
+
# find_files('version.rb', ['lib/**', 'spec'], true)
|
|
24
28
|
# # This might return file paths like ['lib/markdown_exec/version.rb', 'spec/version_spec.rb'].
|
|
25
|
-
def find_files(pattern, paths = ['', Dir.pwd])
|
|
29
|
+
def find_files(pattern, paths = ['', Dir.pwd], exclude_dirs: false)
|
|
26
30
|
matched_files = []
|
|
27
31
|
|
|
28
32
|
paths.each do |path_with_wildcard|
|
|
@@ -30,12 +34,49 @@ def find_files(pattern, paths = ['', Dir.pwd])
|
|
|
30
34
|
search_pattern = File.join(path_with_wildcard, pattern)
|
|
31
35
|
|
|
32
36
|
# Use Dir.glob with the File::FNM_DOTMATCH flag to include hidden files
|
|
33
|
-
|
|
37
|
+
files = Dir.glob(search_pattern, File::FNM_DOTMATCH)
|
|
38
|
+
|
|
39
|
+
# Optionally exclude "." and ".." and directory names
|
|
40
|
+
files.reject! { |file| file.end_with?('/.', '/..') || File.directory?(file) } if exclude_dirs
|
|
41
|
+
|
|
42
|
+
matched_files += files
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
matched_files.uniq
|
|
37
46
|
end
|
|
38
47
|
|
|
48
|
+
# # Finds files matching a given pattern within specified directory paths.
|
|
49
|
+
# #
|
|
50
|
+
# # The function takes a pattern (filename or pattern with wildcards) and an array of paths.
|
|
51
|
+
# # It searches for files matching the pattern within each of the specified paths. Hidden files
|
|
52
|
+
# # are also included in the search. The search can include subdirectories depending on the
|
|
53
|
+
# # path specification (e.g., 'dir/**' for recursive search).
|
|
54
|
+
# #
|
|
55
|
+
# # Args:
|
|
56
|
+
# # pattern (String): A filename or a pattern string with wildcards.
|
|
57
|
+
# # paths (Array<String>): An array of directory paths where the search will be performed.
|
|
58
|
+
# # Paths can include wildcards for recursive search.
|
|
59
|
+
# #
|
|
60
|
+
# # Returns:
|
|
61
|
+
# # Array<String>: A unique list of file paths that match the given pattern in the specified paths.
|
|
62
|
+
# #
|
|
63
|
+
# # Example:
|
|
64
|
+
# # find_files('version.rb', ['lib/**', 'spec'])
|
|
65
|
+
# # # This might return file paths like ['lib/markdown_exec/version.rb', 'spec/version_spec.rb'].
|
|
66
|
+
# def find_files(pattern, paths = ['', Dir.pwd])
|
|
67
|
+
# matched_files = []
|
|
68
|
+
|
|
69
|
+
# paths.each do |path_with_wildcard|
|
|
70
|
+
# # Combine the path with the wildcard and the pattern
|
|
71
|
+
# search_pattern = File.join(path_with_wildcard, pattern)
|
|
72
|
+
|
|
73
|
+
# # Use Dir.glob with the File::FNM_DOTMATCH flag to include hidden files
|
|
74
|
+
# matched_files += Dir.glob(search_pattern, File::FNM_DOTMATCH)
|
|
75
|
+
# end
|
|
76
|
+
|
|
77
|
+
# matched_files.uniq
|
|
78
|
+
# end
|
|
79
|
+
|
|
39
80
|
return if $PROGRAM_NAME != __FILE__
|
|
40
81
|
|
|
41
82
|
# example CLI
|
data/lib/hash_delegator.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
# encoding=utf-8
|
|
5
5
|
|
|
6
|
-
require 'English'
|
|
7
6
|
require 'clipboard'
|
|
7
|
+
require 'English'
|
|
8
8
|
require 'fileutils'
|
|
9
9
|
require 'open3'
|
|
10
10
|
require 'optparse'
|
|
@@ -22,7 +22,6 @@ require_relative 'block_label'
|
|
|
22
22
|
require_relative 'block_types'
|
|
23
23
|
require_relative 'cached_nested_file_reader'
|
|
24
24
|
require_relative 'constants'
|
|
25
|
-
require_relative 'std_out_err_logger'
|
|
26
25
|
require_relative 'directory_searcher'
|
|
27
26
|
require_relative 'exceptions'
|
|
28
27
|
require_relative 'fcb'
|
|
@@ -32,6 +31,7 @@ require_relative 'hash'
|
|
|
32
31
|
require_relative 'link_history'
|
|
33
32
|
require_relative 'mdoc'
|
|
34
33
|
require_relative 'regexp'
|
|
34
|
+
require_relative 'std_out_err_logger'
|
|
35
35
|
require_relative 'string_util'
|
|
36
36
|
|
|
37
37
|
class String
|
|
@@ -232,16 +232,46 @@ module HashDelegatorSelf
|
|
|
232
232
|
FileUtils.rm_f(path)
|
|
233
233
|
end
|
|
234
234
|
|
|
235
|
-
# Evaluates the given string as Ruby code
|
|
235
|
+
# Evaluates the given string as Ruby code within a safe context.
|
|
236
236
|
# If an error occurs, it calls the error_handler method with 'safeval'.
|
|
237
237
|
# @param str [String] The string to be evaluated.
|
|
238
238
|
# @return [Object] The result of evaluating the string.
|
|
239
239
|
def safeval(str)
|
|
240
|
-
|
|
240
|
+
# # Restricting to evaluate only expressions
|
|
241
|
+
# unless str.match?(/\A\s*\w+\s*[\+\-\*\/\=\%\&\|\<\>\!]+\s*\w+\s*\z/)
|
|
242
|
+
# error_handler('safeval') # 'Invalid expression'
|
|
243
|
+
# return
|
|
244
|
+
# end
|
|
245
|
+
|
|
246
|
+
# # Whitelisting allowed operations
|
|
247
|
+
# allowed_methods = %w[+ - * / == != < > <= >= && || % & |]
|
|
248
|
+
# unless allowed_methods.any? { |op| str.include?(op) }
|
|
249
|
+
# error_handler('safeval', 'Operation not allowed')
|
|
250
|
+
# return
|
|
251
|
+
# end
|
|
252
|
+
|
|
253
|
+
# # Sanitize input (example: removing potentially harmful characters)
|
|
254
|
+
# str = str.gsub(/[^0-9\+\-\*\/\(\)\<\>\!\=\%\&\|]/, '')
|
|
255
|
+
|
|
256
|
+
# Evaluate the sanitized string
|
|
257
|
+
result = nil
|
|
258
|
+
binding.eval("result = #{str}")
|
|
259
|
+
|
|
260
|
+
result
|
|
241
261
|
rescue StandardError # catches NameError, StandardError
|
|
242
262
|
error_handler('safeval')
|
|
243
263
|
end
|
|
244
264
|
|
|
265
|
+
# # Evaluates the given string as Ruby code and rescues any StandardErrors.
|
|
266
|
+
# # If an error occurs, it calls the error_handler method with 'safeval'.
|
|
267
|
+
# # @param str [String] The string to be evaluated.
|
|
268
|
+
# # @return [Object] The result of evaluating the string.
|
|
269
|
+
# def safeval(str)
|
|
270
|
+
# eval(str)
|
|
271
|
+
# rescue StandardError # catches NameError, StandardError
|
|
272
|
+
# error_handler('safeval')
|
|
273
|
+
# end
|
|
274
|
+
|
|
245
275
|
def set_file_permissions(file_path, chmod_value)
|
|
246
276
|
File.chmod(chmod_value, file_path)
|
|
247
277
|
end
|
|
@@ -365,6 +395,23 @@ module PathUtils
|
|
|
365
395
|
end
|
|
366
396
|
end
|
|
367
397
|
|
|
398
|
+
class BashCommentFormatter
|
|
399
|
+
# Formats a multi-line string into a format safe for use in Bash comments.
|
|
400
|
+
def self.format_comment(input_string)
|
|
401
|
+
return '# ' if input_string.nil?
|
|
402
|
+
return '# ' if input_string.empty?
|
|
403
|
+
|
|
404
|
+
formatted = input_string.split("\n").map do |line|
|
|
405
|
+
"# #{line.gsub('#', '\#')}"
|
|
406
|
+
end
|
|
407
|
+
formatted.join("\n")
|
|
408
|
+
end
|
|
409
|
+
# # fit oname in single bash comment
|
|
410
|
+
# def oname_for_bash_comment(oname)
|
|
411
|
+
# oname.gsub("\n", ' ~ ').gsub(/ +/, ' ')
|
|
412
|
+
# end
|
|
413
|
+
end
|
|
414
|
+
|
|
368
415
|
module MarkdownExec
|
|
369
416
|
class DebugHelper
|
|
370
417
|
# Class-level variable to store history of printed messages
|
|
@@ -1304,6 +1351,18 @@ module MarkdownExec
|
|
|
1304
1351
|
PathUtils.resolve_path_or_substitute(gets.chomp, filespec)
|
|
1305
1352
|
end
|
|
1306
1353
|
|
|
1354
|
+
# def read_block_name(line)
|
|
1355
|
+
# bm = extract_named_captures_from_option(line, @delegate_object[:block_name_match])
|
|
1356
|
+
# name = bm[:title]
|
|
1357
|
+
|
|
1358
|
+
# if @delegate_object[:block_name_nick_match].present? && line =~ Regexp.new(@delegate_object[:block_name_nick_match])
|
|
1359
|
+
# name = $~[0]
|
|
1360
|
+
# else
|
|
1361
|
+
# name = bm && bm[1] ? bm[:title] : name
|
|
1362
|
+
# end
|
|
1363
|
+
# name
|
|
1364
|
+
# end
|
|
1365
|
+
|
|
1307
1366
|
# Handle expression with wildcard characters
|
|
1308
1367
|
# allow user to select or enter
|
|
1309
1368
|
def save_filespec_wildcard_expansion(filespec)
|
|
@@ -1336,28 +1395,28 @@ module MarkdownExec
|
|
|
1336
1395
|
}
|
|
1337
1396
|
end
|
|
1338
1397
|
|
|
1339
|
-
# # Loads auto link block.
|
|
1340
|
-
# def load_auto_link_block(all_blocks, link_state, mdoc, block_source:)
|
|
1341
|
-
# block_name = @delegate_object[:document_load_link_block_name]
|
|
1342
|
-
# return unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
|
|
1398
|
+
# # Loads auto link block.
|
|
1399
|
+
# def load_auto_link_block(all_blocks, link_state, mdoc, block_source:)
|
|
1400
|
+
# block_name = @delegate_object[:document_load_link_block_name]
|
|
1401
|
+
# return unless block_name.present? && @most_recent_loaded_filename != @delegate_object[:filename]
|
|
1343
1402
|
|
|
1344
|
-
# block = HashDelegator.block_find(all_blocks, :oname, block_name)
|
|
1345
|
-
# return unless block
|
|
1403
|
+
# block = HashDelegator.block_find(all_blocks, :oname, block_name)
|
|
1404
|
+
# return unless block
|
|
1346
1405
|
|
|
1347
|
-
# if block.fetch(:shell, '') != BlockType::LINK
|
|
1348
|
-
# HashDelegator.error_handler('must be Link block type', { abort: true })
|
|
1406
|
+
# if block.fetch(:shell, '') != BlockType::LINK
|
|
1407
|
+
# HashDelegator.error_handler('must be Link block type', { abort: true })
|
|
1349
1408
|
|
|
1350
|
-
# else
|
|
1351
|
-
# # debounce_reset
|
|
1352
|
-
# push_link_history_and_trigger_load(
|
|
1353
|
-
# link_block_body: block.fetch(:body, ''),
|
|
1354
|
-
# mdoc: mdoc,
|
|
1355
|
-
# selected: block,
|
|
1356
|
-
# link_state: link_state,
|
|
1357
|
-
# block_source: block_source
|
|
1358
|
-
# )
|
|
1359
|
-
# end
|
|
1360
|
-
# end
|
|
1409
|
+
# else
|
|
1410
|
+
# # debounce_reset
|
|
1411
|
+
# push_link_history_and_trigger_load(
|
|
1412
|
+
# link_block_body: block.fetch(:body, ''),
|
|
1413
|
+
# mdoc: mdoc,
|
|
1414
|
+
# selected: block,
|
|
1415
|
+
# link_state: link_state,
|
|
1416
|
+
# block_source: block_source
|
|
1417
|
+
# )
|
|
1418
|
+
# end
|
|
1419
|
+
# end
|
|
1361
1420
|
|
|
1362
1421
|
# Loads auto blocks based on delegate object settings and updates if new filename is detected.
|
|
1363
1422
|
# Executes a specified block once per filename.
|
|
@@ -1754,7 +1813,7 @@ module MarkdownExec
|
|
|
1754
1813
|
# load key and values from link block into current environment
|
|
1755
1814
|
#
|
|
1756
1815
|
if link_block_data[LinkKeys::Vars]
|
|
1757
|
-
code_lines.push
|
|
1816
|
+
code_lines.push BashCommentFormatter.format_comment(selected[:oname])
|
|
1758
1817
|
(link_block_data[LinkKeys::Vars] || []).each do |(key, value)|
|
|
1759
1818
|
ENV[key] = value.to_s
|
|
1760
1819
|
code_lines.push(assign_key_value_in_bash(key, value))
|
|
@@ -1796,6 +1855,22 @@ module MarkdownExec
|
|
|
1796
1855
|
end
|
|
1797
1856
|
end
|
|
1798
1857
|
|
|
1858
|
+
# Check if the delegate object responds to a given method.
|
|
1859
|
+
# @param method_name [Symbol] The name of the method to check.
|
|
1860
|
+
# @param include_private [Boolean] Whether to include private methods in the check.
|
|
1861
|
+
# @return [Boolean] true if the delegate object responds to the method, false otherwise.
|
|
1862
|
+
def respond_to?(method_name, include_private = false)
|
|
1863
|
+
if super
|
|
1864
|
+
true
|
|
1865
|
+
elsif @delegate_object.respond_to?(method_name, include_private)
|
|
1866
|
+
true
|
|
1867
|
+
elsif method_name.to_s.end_with?('=') && @delegate_object.respond_to?(:[]=, include_private)
|
|
1868
|
+
true
|
|
1869
|
+
else
|
|
1870
|
+
@delegate_object.respond_to?(method_name, include_private)
|
|
1871
|
+
end
|
|
1872
|
+
end
|
|
1873
|
+
|
|
1799
1874
|
def runtime_exception(exception_sym, name, items)
|
|
1800
1875
|
if @delegate_object[exception_sym] != 0
|
|
1801
1876
|
data = { name: name, detail: items.join(', ') }
|
|
@@ -1907,9 +1982,9 @@ module MarkdownExec
|
|
|
1907
1982
|
debounce_reset
|
|
1908
1983
|
link_state = LinkState.new
|
|
1909
1984
|
options_state = read_show_options_and_trigger_reuse(
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1985
|
+
selected: @dml_block_state.block,
|
|
1986
|
+
link_state: link_state
|
|
1987
|
+
)
|
|
1913
1988
|
|
|
1914
1989
|
@menu_base_options.merge!(options_state.options)
|
|
1915
1990
|
@delegate_object.merge!(options_state.options)
|
|
@@ -2103,15 +2178,25 @@ module MarkdownExec
|
|
|
2103
2178
|
|
|
2104
2179
|
# Presents a TTY prompt to select an option or exit, returns metadata including option and selected
|
|
2105
2180
|
def select_option_with_metadata(prompt_text, names, opts = {})
|
|
2181
|
+
## configure to environment
|
|
2182
|
+
#
|
|
2183
|
+
unless opts[:select_page_height].positive?
|
|
2184
|
+
require 'io/console'
|
|
2185
|
+
opts[:per_page] = opts[:select_page_height] = [IO.console.winsize[0] - 3, 4].max
|
|
2186
|
+
end
|
|
2187
|
+
|
|
2188
|
+
# crashes if all menu options are disabled
|
|
2106
2189
|
selection = @prompt.select(prompt_text,
|
|
2107
2190
|
names,
|
|
2108
2191
|
opts.merge(filter: true))
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2192
|
+
item = names.find do |item|
|
|
2193
|
+
if item.instance_of?(Hash)
|
|
2194
|
+
item[:dname] == selection
|
|
2195
|
+
else
|
|
2196
|
+
item == selection
|
|
2197
|
+
end
|
|
2198
|
+
end
|
|
2199
|
+
item = { dname: item } if item.instance_of?(String)
|
|
2115
2200
|
unless item
|
|
2116
2201
|
HashDelegator.error_handler('select_option_with_metadata', error: 'menu item not found')
|
|
2117
2202
|
exit 1
|
|
@@ -2318,10 +2403,6 @@ module MarkdownExec
|
|
|
2318
2403
|
end
|
|
2319
2404
|
|
|
2320
2405
|
sph = @delegate_object[:select_page_height]
|
|
2321
|
-
unless sph.positive?
|
|
2322
|
-
require 'io/console'
|
|
2323
|
-
sph = [IO.console.winsize[0] - 3, 4].max
|
|
2324
|
-
end
|
|
2325
2406
|
selection_opts.merge!(per_page: sph)
|
|
2326
2407
|
|
|
2327
2408
|
selected_option = select_option_with_metadata(prompt_title, block_menu,
|
|
@@ -2387,9 +2468,9 @@ module MarkdownExec
|
|
|
2387
2468
|
clean_hash_recursively(value)
|
|
2388
2469
|
when Struct
|
|
2389
2470
|
struct_hash = value.to_h # Convert the Struct to a hash
|
|
2390
|
-
|
|
2471
|
+
clean_hash_recursively(struct_hash) # Clean the hash
|
|
2391
2472
|
# Return the cleaned hash instead of updating the Struct
|
|
2392
|
-
|
|
2473
|
+
|
|
2393
2474
|
else
|
|
2394
2475
|
value
|
|
2395
2476
|
end
|
|
@@ -2402,9 +2483,7 @@ module MarkdownExec
|
|
|
2402
2483
|
obj[key] = cleaned_value if value.is_a?(Hash) || value.is_a?(Struct)
|
|
2403
2484
|
end
|
|
2404
2485
|
|
|
2405
|
-
if obj.is_a?(Hash)
|
|
2406
|
-
obj.select! { |key, value| ![nil, '', [], {}, nil].include?(value) }
|
|
2407
|
-
end
|
|
2486
|
+
obj.reject! { |_key, value| [nil, '', [], {}, nil].include?(value) } if obj.is_a?(Hash)
|
|
2408
2487
|
|
|
2409
2488
|
obj
|
|
2410
2489
|
end
|
|
@@ -2438,7 +2517,42 @@ Bundler.require(:default)
|
|
|
2438
2517
|
require 'minitest/autorun'
|
|
2439
2518
|
require 'mocha/minitest'
|
|
2440
2519
|
|
|
2441
|
-
|
|
2520
|
+
class BashCommentFormatterTest < Minitest::Test
|
|
2521
|
+
# Test formatting a normal string without special characters
|
|
2522
|
+
def test_format_simple_string
|
|
2523
|
+
input = 'This is a simple comment.'
|
|
2524
|
+
expected = '# This is a simple comment.'
|
|
2525
|
+
assert_equal expected, BashCommentFormatter.format_comment(input)
|
|
2526
|
+
end
|
|
2527
|
+
|
|
2528
|
+
# Test formatting a string containing hash characters
|
|
2529
|
+
def test_format_string_with_hash
|
|
2530
|
+
input = 'This is a #comment with hash.'
|
|
2531
|
+
expected = '# This is a \\#comment with hash.'
|
|
2532
|
+
assert_equal expected, BashCommentFormatter.format_comment(input)
|
|
2533
|
+
end
|
|
2534
|
+
|
|
2535
|
+
# Test formatting an empty string
|
|
2536
|
+
def test_format_empty_string
|
|
2537
|
+
input = ''
|
|
2538
|
+
expected = '# '
|
|
2539
|
+
assert_equal expected, BashCommentFormatter.format_comment(input)
|
|
2540
|
+
end
|
|
2541
|
+
|
|
2542
|
+
# Test formatting a multi-line string
|
|
2543
|
+
def test_format_multi_line_string
|
|
2544
|
+
input = "This is the first line.\nThis is the second line."
|
|
2545
|
+
expected = "# This is the first line.\n# This is the second line."
|
|
2546
|
+
assert_equal expected, BashCommentFormatter.format_comment(input)
|
|
2547
|
+
end
|
|
2548
|
+
|
|
2549
|
+
# Test formatting strings with leading and trailing whitespace
|
|
2550
|
+
def test_format_whitespace
|
|
2551
|
+
input = ' This has leading and trailing spaces '
|
|
2552
|
+
expected = '# This has leading and trailing spaces '
|
|
2553
|
+
assert_equal expected, BashCommentFormatter.format_comment(input)
|
|
2554
|
+
end
|
|
2555
|
+
end
|
|
2442
2556
|
|
|
2443
2557
|
module MarkdownExec
|
|
2444
2558
|
class TestHashDelegator0 < Minitest::Test
|
|
@@ -3294,31 +3408,37 @@ module MarkdownExec
|
|
|
3294
3408
|
|
|
3295
3409
|
class PathUtilsTest < Minitest::Test
|
|
3296
3410
|
def test_absolute_path_returns_unchanged
|
|
3297
|
-
absolute_path =
|
|
3298
|
-
expression =
|
|
3411
|
+
absolute_path = '/usr/local/bin'
|
|
3412
|
+
expression = 'path/to/*/directory'
|
|
3299
3413
|
assert_equal absolute_path, PathUtils.resolve_path_or_substitute(absolute_path, expression)
|
|
3300
3414
|
end
|
|
3301
3415
|
|
|
3302
3416
|
def test_relative_path_gets_substituted
|
|
3303
|
-
relative_path =
|
|
3304
|
-
expression =
|
|
3305
|
-
expected_output =
|
|
3417
|
+
relative_path = 'my_folder'
|
|
3418
|
+
expression = 'path/to/*/directory'
|
|
3419
|
+
expected_output = 'path/to/my_folder/directory'
|
|
3306
3420
|
assert_equal expected_output, PathUtils.resolve_path_or_substitute(relative_path, expression)
|
|
3307
3421
|
end
|
|
3308
3422
|
|
|
3309
3423
|
def test_path_with_no_slash_substitutes_correctly
|
|
3310
|
-
relative_path =
|
|
3311
|
-
expression =
|
|
3312
|
-
expected_output =
|
|
3424
|
+
relative_path = 'data'
|
|
3425
|
+
expression = 'path/to/*/directory'
|
|
3426
|
+
expected_output = 'path/to/data/directory'
|
|
3313
3427
|
assert_equal expected_output, PathUtils.resolve_path_or_substitute(relative_path, expression)
|
|
3314
3428
|
end
|
|
3315
3429
|
|
|
3316
3430
|
def test_empty_path_substitution
|
|
3317
|
-
empty_path =
|
|
3318
|
-
expression =
|
|
3319
|
-
expected_output =
|
|
3431
|
+
empty_path = ''
|
|
3432
|
+
expression = 'path/to/*/directory'
|
|
3433
|
+
expected_output = 'path/to//directory'
|
|
3320
3434
|
assert_equal expected_output, PathUtils.resolve_path_or_substitute(empty_path, expression)
|
|
3321
3435
|
end
|
|
3322
|
-
end
|
|
3323
3436
|
|
|
3437
|
+
# Test formatting a string containing UTF-8 characters
|
|
3438
|
+
def test_format_utf8_characters
|
|
3439
|
+
input = 'Unicode test: ā, ö, 💻, and 🚀 are fun!'
|
|
3440
|
+
expected = '# Unicode test: ā, ö, 💻, and 🚀 are fun!'
|
|
3441
|
+
assert_equal expected, BashCommentFormatter.format_comment(input)
|
|
3442
|
+
end
|
|
3443
|
+
end
|
|
3324
3444
|
end # module MarkdownExec
|
data/lib/input_sequencer.rb
CHANGED
|
@@ -19,7 +19,7 @@ class InputSequencer
|
|
|
19
19
|
@document_filename = document_filename
|
|
20
20
|
@current_block = nil
|
|
21
21
|
@block_queue = initial_blocks
|
|
22
|
-
@debug = Env
|
|
22
|
+
@debug = Env.env_bool('INPUT_SEQUENCER_DEBUG', default: false)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# Merges the current menu state with the next, prioritizing the next state's values.
|
|
@@ -33,9 +33,11 @@ class InputSequencer
|
|
|
33
33
|
inherited_lines: next_state.inherited_lines,
|
|
34
34
|
prior_block_was_link: next_state.prior_block_was_link.nil? ? current.prior_block_was_link : next_state.prior_block_was_link
|
|
35
35
|
)
|
|
36
|
+
# rubocop:disable Style/RescueStandardError
|
|
36
37
|
rescue
|
|
37
38
|
pp $!, $@
|
|
38
39
|
exit 1
|
|
40
|
+
# rubocop:enable Style/RescueStandardError
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
# Generates the next menu state based on provided attributes.
|
|
@@ -69,7 +71,6 @@ class InputSequencer
|
|
|
69
71
|
loop do
|
|
70
72
|
break if run_yield(:parse_document, now_menu.document_filename, &block) == :break
|
|
71
73
|
|
|
72
|
-
pp [__LINE__, 'exit_when_bq_empty', exit_when_bq_empty, '@block_queue', @block_queue, 'now_menu', now_menu] if @debug
|
|
73
74
|
# self.imw_ins now_menu, 'now_menu'
|
|
74
75
|
|
|
75
76
|
break if exit_when_bq_empty && bq_is_empty? && !now_menu.prior_block_was_link
|
|
@@ -81,14 +82,13 @@ class InputSequencer
|
|
|
81
82
|
choice = run_yield :user_choice, &block
|
|
82
83
|
|
|
83
84
|
if choice.nil?
|
|
84
|
-
raise
|
|
85
|
+
raise 'Block not recognized.'
|
|
85
86
|
break
|
|
86
87
|
end
|
|
87
88
|
break if run_yield(:exit?, choice&.downcase, &block) # Exit loop and method to terminate the app
|
|
88
89
|
|
|
89
90
|
next_state = run_yield :execute_block, choice, &block
|
|
90
91
|
# imw_ins next_state, 'next_state'
|
|
91
|
-
pp [__LINE__, 'next_state', next_state] if @debug
|
|
92
92
|
return :break if next_state == :break
|
|
93
93
|
|
|
94
94
|
next_menu = next_state
|
|
@@ -102,7 +102,6 @@ class InputSequencer
|
|
|
102
102
|
block_name = @block_queue.shift
|
|
103
103
|
end
|
|
104
104
|
# self.imw_ins block_name, 'block_name'
|
|
105
|
-
pp [__LINE__, 'block_name', block_name] if @debug
|
|
106
105
|
|
|
107
106
|
next_menu = if block_name == '.'
|
|
108
107
|
exit_when_bq_empty = false
|
|
@@ -112,15 +111,16 @@ class InputSequencer
|
|
|
112
111
|
state.display_menu = bq_is_empty?
|
|
113
112
|
state
|
|
114
113
|
end
|
|
115
|
-
pp [__LINE__, 'next_menu', next_menu] if @debug
|
|
116
114
|
next_menu
|
|
117
115
|
# imw_ins next_menu, 'next_menu'
|
|
118
116
|
end
|
|
119
117
|
now_menu = InputSequencer.merge_link_state(now_menu, next_menu)
|
|
120
118
|
end
|
|
119
|
+
# rubocop:disable Style/RescueStandardError
|
|
121
120
|
rescue
|
|
122
121
|
pp $!, $@
|
|
123
122
|
exit 1
|
|
123
|
+
# rubocop:enable Style/RescueStandardError
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
|
data/lib/markdown_exec.rb
CHANGED
|
@@ -9,6 +9,7 @@ require 'fileutils'
|
|
|
9
9
|
require 'open3'
|
|
10
10
|
require 'optparse'
|
|
11
11
|
require 'shellwords'
|
|
12
|
+
require 'time'
|
|
12
13
|
require 'tmpdir'
|
|
13
14
|
require 'tty-prompt'
|
|
14
15
|
require 'yaml'
|
|
@@ -23,6 +24,7 @@ require_relative 'env'
|
|
|
23
24
|
require_relative 'exceptions'
|
|
24
25
|
require_relative 'fcb'
|
|
25
26
|
require_relative 'filter'
|
|
27
|
+
require_relative 'find_files'
|
|
26
28
|
require_relative 'fout'
|
|
27
29
|
require_relative 'hash_delegator'
|
|
28
30
|
require_relative 'input_sequencer'
|
|
@@ -102,6 +104,137 @@ end
|
|
|
102
104
|
module MarkdownExec
|
|
103
105
|
include Exceptions
|
|
104
106
|
|
|
107
|
+
class FileInMenu
|
|
108
|
+
# Prepends the age of the file in days to the file name for display in a menu.
|
|
109
|
+
# @param filename [String] the name of the file
|
|
110
|
+
# @return [String] modified file name with age prepended
|
|
111
|
+
def self.for_menu(filename)
|
|
112
|
+
file_age = (Time.now - File.mtime(filename)) / (60 * 60 * 24 * 30)
|
|
113
|
+
|
|
114
|
+
" #{Histogram.display(file_age, 0, 11, 12, inverse: false)}: #{filename}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Removes the age from the string to retrieve the original file name.
|
|
118
|
+
# @param filename_with_age [String] the modified file name with age
|
|
119
|
+
# @return [String] the original file name
|
|
120
|
+
def self.from_menu(filename_with_age)
|
|
121
|
+
filename_with_age.split(': ', 2).last
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# A class that generates a histogram bar in terminal using xterm-256 color codes.
|
|
126
|
+
class Histogram
|
|
127
|
+
# Generates and prints a histogram bar for a given value within a specified range and width, with an option for inverse display.
|
|
128
|
+
# @param integer_value [Integer] the value to represent in the histogram
|
|
129
|
+
# @param min [Integer] the minimum value of the range
|
|
130
|
+
# @param max [Integer] the maximum value of the range
|
|
131
|
+
# @param width [Integer] the total width of the histogram in characters
|
|
132
|
+
# @param inverse [Boolean] whether the histogram is displayed in inverse order (right to left)
|
|
133
|
+
def self.display(integer_value, min, max, width, inverse: false)
|
|
134
|
+
return if max <= min # Ensure the range is valid
|
|
135
|
+
|
|
136
|
+
# Normalize the value within the range 0 to 1
|
|
137
|
+
normalized_value = [0, [(integer_value - min).to_f / (max - min), 1].min].max
|
|
138
|
+
|
|
139
|
+
# Calculate how many characters should be filled
|
|
140
|
+
filled_length = (normalized_value * width).round
|
|
141
|
+
|
|
142
|
+
# # Generate the histogram bar using xterm-256 colors (color code 42 is green)
|
|
143
|
+
# filled_bar = "\e[48;5;42m" + ' ' * filled_length + "\e[0m"
|
|
144
|
+
filled_bar = ('¤' * filled_length).fg_rgbh_AF_AF_00
|
|
145
|
+
empty_bar = ' ' * (width - filled_length)
|
|
146
|
+
|
|
147
|
+
# Determine the order of filled and empty parts based on the inverse flag
|
|
148
|
+
inverse ? (empty_bar + filled_bar) : (filled_bar + empty_bar)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class MenuBuilder
|
|
153
|
+
def initialize
|
|
154
|
+
@chrome_color = :cyan
|
|
155
|
+
@o_color = :red
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def build_menu(file_names, directory_names, found_in_block_names, files_in_directories, vbn)
|
|
159
|
+
choices = []
|
|
160
|
+
|
|
161
|
+
# Adding section title and data for file names
|
|
162
|
+
choices << { disabled: '', name: "in #{file_names[:section_title]}".send(@chrome_color) }
|
|
163
|
+
choices += file_names[:data].map { |str| FileInMenu.for_menu(str) }
|
|
164
|
+
|
|
165
|
+
# Conditionally add directory names if data is present
|
|
166
|
+
unless directory_names[:data].count.zero?
|
|
167
|
+
choices << { disabled: '', name: "in #{directory_names[:section_title]}".send(@chrome_color) }
|
|
168
|
+
choices += files_in_directories
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Adding found in block names
|
|
172
|
+
choices << { disabled: '', name: "in #{found_in_block_names[:section_title]}".send(@chrome_color) }
|
|
173
|
+
|
|
174
|
+
choices += vbn
|
|
175
|
+
|
|
176
|
+
choices
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
class SearchResultsReport < DirectorySearcher
|
|
181
|
+
def directory_names(search_options, highlight_value)
|
|
182
|
+
matched_directories = find_directory_names
|
|
183
|
+
{
|
|
184
|
+
section_title: 'directory names',
|
|
185
|
+
data: matched_directories,
|
|
186
|
+
formatted_text: [{ content: AnsiFormatter.new(search_options).format_and_highlight_array(matched_directories, highlight: [highlight_value]) }]
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def found_in_block_names(search_options, highlight_value, formspec: '=%<index>4.d: %<line>s')
|
|
191
|
+
matched_contents = (find_file_contents do |line|
|
|
192
|
+
read_block_name(line, search_options[:fenced_start_and_end_regex], search_options[:block_name_match], search_options[:block_name_nick_match])
|
|
193
|
+
end).map.with_index do |(file, contents), index|
|
|
194
|
+
# [file, contents.map { |detail| format(formspec, detail.index, detail.line) }, index]
|
|
195
|
+
[file, contents.map { |detail| format(formspec, { index: detail.index, line: detail.line }) }, index]
|
|
196
|
+
end
|
|
197
|
+
{
|
|
198
|
+
section_title: 'block names',
|
|
199
|
+
data: matched_contents.map(&:first),
|
|
200
|
+
formatted_text: matched_contents.map do |(file, details, index)|
|
|
201
|
+
{ header: format('- %3.d: %s', index + 1, file),
|
|
202
|
+
content: AnsiFormatter.new(search_options).format_and_highlight_array(
|
|
203
|
+
details,
|
|
204
|
+
highlight: [highlight_value]
|
|
205
|
+
) }
|
|
206
|
+
end,
|
|
207
|
+
matched_contents: matched_contents
|
|
208
|
+
}
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def file_names(search_options, highlight_value)
|
|
212
|
+
matched_files = find_file_names
|
|
213
|
+
{
|
|
214
|
+
section_title: 'file names',
|
|
215
|
+
data: matched_files,
|
|
216
|
+
formatted_text: [{ content: AnsiFormatter.new(search_options).format_and_highlight_array(
|
|
217
|
+
matched_files, highlight: [highlight_value]
|
|
218
|
+
).join("\n") }]
|
|
219
|
+
}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def read_block_name(line, fenced_start_and_end_regex, block_name_match, block_name_nick_match)
|
|
223
|
+
return unless line.match(fenced_start_and_end_regex)
|
|
224
|
+
|
|
225
|
+
bm = extract_named_captures_from_option(line, block_name_match)
|
|
226
|
+
return if bm.nil?
|
|
227
|
+
|
|
228
|
+
name = bm[:title]
|
|
229
|
+
|
|
230
|
+
if block_name_nick_match.present? && line =~ Regexp.new(block_name_nick_match)
|
|
231
|
+
$~[0]
|
|
232
|
+
else
|
|
233
|
+
bm && bm[1] ? bm[:title] : name
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
105
238
|
##
|
|
106
239
|
#
|
|
107
240
|
# :reek:DuplicateMethodCall { allow_calls: ['block', 'item', 'lm', 'opts', 'option', '@options', 'required_blocks'] }
|
|
@@ -286,6 +419,8 @@ module MarkdownExec
|
|
|
286
419
|
@options[:path] = pos
|
|
287
420
|
elsif File.exist?(pos)
|
|
288
421
|
@options[:filename] = pos
|
|
422
|
+
elsif @options[:default_find_select_open]
|
|
423
|
+
find_value(pos, execute_chosen_found: true)
|
|
289
424
|
else
|
|
290
425
|
raise FileMissingError, pos, caller
|
|
291
426
|
end
|
|
@@ -303,6 +438,60 @@ module MarkdownExec
|
|
|
303
438
|
error_handler('finalize_cli_argument_processing')
|
|
304
439
|
end
|
|
305
440
|
|
|
441
|
+
# return { exit: true } to cause app to exit
|
|
442
|
+
def find_value(value, execute_chosen_found: false)
|
|
443
|
+
find_path = @options[:find_path].present? ? @options[:find_path] : @options[:path]
|
|
444
|
+
@fout.fout 'Searching in: ' \
|
|
445
|
+
"#{HashDelegator.new(@options).string_send_color(find_path,
|
|
446
|
+
:menu_chrome_color)}"
|
|
447
|
+
searcher = SearchResultsReport.new(value, [find_path])
|
|
448
|
+
file_names = searcher.file_names(options, value)
|
|
449
|
+
found_in_block_names = searcher.found_in_block_names(options, value, formspec: '%<line>s')
|
|
450
|
+
directory_names = searcher.directory_names(options, value)
|
|
451
|
+
|
|
452
|
+
### search in file contents (block names, chrome, or text)
|
|
453
|
+
[found_in_block_names,
|
|
454
|
+
directory_names,
|
|
455
|
+
file_names].each do |data|
|
|
456
|
+
next if data[:data].count.zero?
|
|
457
|
+
next unless data[:formatted_text]
|
|
458
|
+
|
|
459
|
+
@fout.fout "In #{data[:section_title]}" if data[:section_title]
|
|
460
|
+
data[:formatted_text].each do |fi|
|
|
461
|
+
@fout.fout fi[:header] if fi[:header]
|
|
462
|
+
@fout.fout fi[:content] if fi[:content]
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
return { exit: true } unless execute_chosen_found
|
|
466
|
+
|
|
467
|
+
## pick a document to open
|
|
468
|
+
#
|
|
469
|
+
files_in_directories = directory_names[:data].map do |dn|
|
|
470
|
+
find_files('*', [dn], exclude_dirs: true)
|
|
471
|
+
end.flatten(1).map { |str| FileInMenu.for_menu(str) }
|
|
472
|
+
|
|
473
|
+
return { exit: true } unless file_names[:data]&.count.positive? || files_in_directories&.count.positive? || found_in_block_names[:data]&.count.positive?
|
|
474
|
+
|
|
475
|
+
vbn = found_in_block_names[:matched_contents].map do |matched_contents|
|
|
476
|
+
filename, details, = matched_contents
|
|
477
|
+
nexo = AnsiFormatter.new(@options).format_and_highlight_array(
|
|
478
|
+
details,
|
|
479
|
+
highlight: [value]
|
|
480
|
+
)
|
|
481
|
+
[FileInMenu.for_menu(filename)] + nexo.map { |str| { disabled: '', name: (' ' * 20) + str } }
|
|
482
|
+
end.flatten
|
|
483
|
+
|
|
484
|
+
choices = MenuBuilder.new.build_menu(file_names, directory_names, found_in_block_names, files_in_directories, vbn)
|
|
485
|
+
|
|
486
|
+
@options[:filename] = FileInMenu.from_menu(
|
|
487
|
+
select_document_if_multiple(
|
|
488
|
+
choices,
|
|
489
|
+
prompt: options[:prompt_select_md].to_s + ' ¤ Age in months'.fg_rgbh_AF_AF_00
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
{ exit: false }
|
|
493
|
+
end
|
|
494
|
+
|
|
306
495
|
## Sets up the options and returns the parsed arguments
|
|
307
496
|
#
|
|
308
497
|
def initialize_and_parse_cli_options
|
|
@@ -344,35 +533,9 @@ module MarkdownExec
|
|
|
344
533
|
->(value) { tap_config value: value }
|
|
345
534
|
when 'exit'
|
|
346
535
|
->(_) { exit }
|
|
347
|
-
when 'find'
|
|
536
|
+
when 'find', 'open'
|
|
348
537
|
->(value) {
|
|
349
|
-
|
|
350
|
-
@fout.fout 'Searching in: ' \
|
|
351
|
-
"#{HashDelegator.new(@options).string_send_color(find_path,
|
|
352
|
-
:menu_chrome_color)}"
|
|
353
|
-
searcher = DirectorySearcher.new(value, [find_path])
|
|
354
|
-
|
|
355
|
-
@fout.fout 'In file contents'
|
|
356
|
-
hash = searcher.search_in_file_contents
|
|
357
|
-
hash.each.with_index do |(key, v2), i1|
|
|
358
|
-
@fout.fout format('- %3.d: %s', i1 + 1, key)
|
|
359
|
-
@fout.fout AnsiFormatter.new(options).format_and_highlight_array(
|
|
360
|
-
v2.map { |nl| format('=%4.d: %s', nl.index, nl.line) },
|
|
361
|
-
highlight: [value]
|
|
362
|
-
)
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
@fout.fout 'In directory names'
|
|
366
|
-
@fout.fout AnsiFormatter.new(options).format_and_highlight_array(
|
|
367
|
-
searcher.search_in_directory_names, highlight: [value]
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
@fout.fout 'In file names'
|
|
371
|
-
@fout.fout AnsiFormatter.new(options).format_and_highlight_array(
|
|
372
|
-
searcher.search_in_file_names, highlight: [value]
|
|
373
|
-
).join("\n")
|
|
374
|
-
|
|
375
|
-
exit
|
|
538
|
+
exit if find_value(value, execute_chosen_found: procname == 'open').fetch(:exit, false)
|
|
376
539
|
}
|
|
377
540
|
when 'help'
|
|
378
541
|
->(_) {
|
|
@@ -553,9 +716,7 @@ module MarkdownExec
|
|
|
553
716
|
end
|
|
554
717
|
|
|
555
718
|
def saved_name_split(name)
|
|
556
|
-
# rubocop:disable Layout/LineLength
|
|
557
719
|
mf = /#{@options[:saved_script_filename_prefix]}_(?<time>[0-9\-]+)_(?<file>.+)_,_(?<block>.+)\.sh/.match(name)
|
|
558
|
-
# rubocop:enable Layout/LineLength
|
|
559
720
|
return unless mf
|
|
560
721
|
|
|
561
722
|
@options[:block_name] = mf[:block]
|
|
@@ -563,13 +724,13 @@ module MarkdownExec
|
|
|
563
724
|
@options[:saved_filename_replacement])
|
|
564
725
|
end
|
|
565
726
|
|
|
566
|
-
def select_document_if_multiple(files = list_markdown_files_in_path)
|
|
727
|
+
def select_document_if_multiple(files = list_markdown_files_in_path, prompt: options[:prompt_select_md].to_s)
|
|
567
728
|
return files[0] if (count = files.count) == 1
|
|
568
729
|
|
|
569
730
|
return unless count >= 2
|
|
570
731
|
|
|
571
732
|
opts = options.dup
|
|
572
|
-
select_option_or_exit(HashDelegator.new(@options).string_send_color(
|
|
733
|
+
select_option_or_exit(HashDelegator.new(@options).string_send_color(prompt, :prompt_color_after_script_execution),
|
|
573
734
|
files,
|
|
574
735
|
opts.merge(per_page: opts[:select_page_height]))
|
|
575
736
|
end
|
|
@@ -578,7 +739,8 @@ module MarkdownExec
|
|
|
578
739
|
def select_option_or_exit(prompt_text, strings, opts = {})
|
|
579
740
|
result = @options.select_option_with_metadata(prompt_text, strings,
|
|
580
741
|
opts)
|
|
581
|
-
|
|
742
|
+
### 2024-04-20 what for?
|
|
743
|
+
# return unless result.fetch(:option, nil)
|
|
582
744
|
|
|
583
745
|
result[:selected]
|
|
584
746
|
end
|
data/lib/menu.src.yml
CHANGED
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
- :arg_name: BOOL
|
|
83
83
|
:default: true
|
|
84
84
|
:description: debounce_execution
|
|
85
|
-
:env_var:
|
|
85
|
+
:env_var: MDE_DEBOUNCE_EXECUTION
|
|
86
86
|
:opt_name: debounce_execution
|
|
87
87
|
:procname: val_as_bool
|
|
88
88
|
|
|
@@ -94,6 +94,13 @@
|
|
|
94
94
|
:procname: debug
|
|
95
95
|
:short_name: d
|
|
96
96
|
|
|
97
|
+
- :arg_name: BOOL
|
|
98
|
+
:default: true
|
|
99
|
+
:description: default_find_select_open
|
|
100
|
+
:env_var: MDE_DEFAULT_FIND_SELECT_OPEN
|
|
101
|
+
:opt_name: default_find_select_open
|
|
102
|
+
:procname: val_as_bool
|
|
103
|
+
|
|
97
104
|
- :default: "> "
|
|
98
105
|
:env_var: MDE_DISPLAY_LEVEL_XBASE_PREFIX
|
|
99
106
|
:opt_name: display_level_xbase_prefix
|
|
@@ -716,6 +723,13 @@
|
|
|
716
723
|
:opt_name: no_chrome
|
|
717
724
|
:procname: val_as_bool
|
|
718
725
|
|
|
726
|
+
- :arg_name: OPEN
|
|
727
|
+
:default: ''
|
|
728
|
+
:description: Find argument in documents, present list, and open user selection
|
|
729
|
+
:long_name: open
|
|
730
|
+
:procname: open
|
|
731
|
+
:short_name: o
|
|
732
|
+
|
|
719
733
|
- :default:
|
|
720
734
|
:description: Expression to match to start collecting lines
|
|
721
735
|
:env_var: MDE_OUTPUT_ASSIGNMENT_BEGIN
|
data/lib/menu.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MDE - Markdown Executor (2.0.
|
|
1
|
+
# MDE - Markdown Executor (2.0.5)
|
|
2
2
|
---
|
|
3
3
|
- :description: Show current configuration values
|
|
4
4
|
:procname: show_config
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
- :arg_name: BOOL
|
|
70
70
|
:default: true
|
|
71
71
|
:description: debounce_execution
|
|
72
|
-
:env_var:
|
|
72
|
+
:env_var: MDE_DEBOUNCE_EXECUTION
|
|
73
73
|
:opt_name: debounce_execution
|
|
74
74
|
:procname: val_as_bool
|
|
75
75
|
- :arg_name: BOOL
|
|
@@ -79,6 +79,12 @@
|
|
|
79
79
|
:long_name: debug
|
|
80
80
|
:procname: debug
|
|
81
81
|
:short_name: d
|
|
82
|
+
- :arg_name: BOOL
|
|
83
|
+
:default: true
|
|
84
|
+
:description: default_find_select_open
|
|
85
|
+
:env_var: MDE_DEFAULT_FIND_SELECT_OPEN
|
|
86
|
+
:opt_name: default_find_select_open
|
|
87
|
+
:procname: val_as_bool
|
|
82
88
|
- :default: "> "
|
|
83
89
|
:env_var: MDE_DISPLAY_LEVEL_XBASE_PREFIX
|
|
84
90
|
:opt_name: display_level_xbase_prefix
|
|
@@ -600,6 +606,12 @@
|
|
|
600
606
|
:env_var: MDE_NO_CHROME
|
|
601
607
|
:opt_name: no_chrome
|
|
602
608
|
:procname: val_as_bool
|
|
609
|
+
- :arg_name: OPEN
|
|
610
|
+
:default: ''
|
|
611
|
+
:description: Find argument in documents, present list, and open user selection
|
|
612
|
+
:long_name: open
|
|
613
|
+
:procname: open
|
|
614
|
+
:short_name: o
|
|
603
615
|
- :default:
|
|
604
616
|
:description: Expression to match to start collecting lines
|
|
605
617
|
:env_var: MDE_OUTPUT_ASSIGNMENT_BEGIN
|
data/lib/std_out_err_logger.rb
CHANGED
|
@@ -18,8 +18,8 @@ class StdOutErrLogger < Logger
|
|
|
18
18
|
# def initialize(file = nil)
|
|
19
19
|
def initialize(file = "#{__dir__}/../tmp/hash_delegator_next_link_state.yaml")
|
|
20
20
|
@file = file
|
|
21
|
-
super(file ||
|
|
22
|
-
self.formatter = proc do |
|
|
21
|
+
super(file || $stdout)
|
|
22
|
+
self.formatter = proc do |_severity, _datetime, _progname, msg|
|
|
23
23
|
"#{msg}\n"
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -36,14 +36,12 @@ class StdOutErrLogger < Logger
|
|
|
36
36
|
if @file
|
|
37
37
|
super
|
|
38
38
|
else
|
|
39
|
-
|
|
39
|
+
warn(out)
|
|
40
40
|
end
|
|
41
|
+
elsif @file
|
|
42
|
+
super
|
|
41
43
|
else
|
|
42
|
-
|
|
43
|
-
super
|
|
44
|
-
else
|
|
45
|
-
$stdout.puts(out)
|
|
46
|
-
end
|
|
44
|
+
$stdout.puts(out)
|
|
47
45
|
end
|
|
48
46
|
end
|
|
49
47
|
end
|
|
@@ -81,40 +79,40 @@ class StdOutErrLoggerTest < Minitest::Test
|
|
|
81
79
|
|
|
82
80
|
def test_logging_info
|
|
83
81
|
logger = StdOutErrLogger.new
|
|
84
|
-
logger.info(
|
|
82
|
+
logger.info('Info message')
|
|
85
83
|
assert_equal "Info message\n", $stdout.string
|
|
86
84
|
assert_empty $stderr.string
|
|
87
85
|
end
|
|
88
86
|
|
|
89
87
|
def test_logging_warning
|
|
90
88
|
logger = StdOutErrLogger.new
|
|
91
|
-
logger.warn(
|
|
89
|
+
logger.warn('Warning message')
|
|
92
90
|
assert_empty $stdout.string
|
|
93
91
|
assert_equal "Warning message\n", $stderr.string
|
|
94
92
|
end
|
|
95
93
|
|
|
96
94
|
def test_logging_error
|
|
97
95
|
logger = StdOutErrLogger.new
|
|
98
|
-
logger.error(
|
|
96
|
+
logger.error('Error message')
|
|
99
97
|
assert_empty $stdout.string
|
|
100
98
|
assert_equal "Error message\n", $stderr.string
|
|
101
99
|
end
|
|
102
100
|
|
|
103
101
|
def test_logging_with_array
|
|
104
102
|
logger = StdOutErrLogger.new
|
|
105
|
-
logger.info([
|
|
103
|
+
logger.info(['Message line 1', 'Message line 2'])
|
|
106
104
|
assert_equal "Message line 1\nMessage line 2\n", $stdout.string
|
|
107
105
|
end
|
|
108
106
|
|
|
109
107
|
def test_logging_with_block
|
|
110
108
|
logger = StdOutErrLogger.new
|
|
111
|
-
logger.info {
|
|
109
|
+
logger.info { 'Block message' }
|
|
112
110
|
assert_equal "Block message\n", $stdout.string
|
|
113
111
|
end
|
|
114
112
|
|
|
115
113
|
def test_logging_unknown_severity
|
|
116
114
|
logger = StdOutErrLogger.new
|
|
117
|
-
logger.add(Logger::UNKNOWN,
|
|
115
|
+
logger.add(Logger::UNKNOWN, 'Unknown severity message')
|
|
118
116
|
assert_empty $stdout.string
|
|
119
117
|
assert_equal "Unknown severity message\n", $stderr.string
|
|
120
118
|
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.0.
|
|
4
|
+
version: 2.0.5
|
|
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-04-
|
|
11
|
+
date: 2024-04-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: clipboard
|
|
@@ -133,6 +133,7 @@ files:
|
|
|
133
133
|
- examples/pass-through.md
|
|
134
134
|
- examples/plant.md
|
|
135
135
|
- examples/port.md
|
|
136
|
+
- examples/search.md
|
|
136
137
|
- examples/vars.md
|
|
137
138
|
- examples/wrap.md
|
|
138
139
|
- lib/ansi_formatter.rb
|