markdown_exec 2.0.3.2 → 2.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|