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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5a409e4d991db71ebae3bf9cfa6e7fcad695b6f5e1ffc017cffddc0ce168497
4
- data.tar.gz: 11e5a9c57f38a06a328310a0d368d9c136a959f1c75260e916cb9bd35706cda6
3
+ metadata.gz: eb96e0d1b92398066dedcaf7017ee6d7deea37ef9ba19fa089de6c4e5aebf27d
4
+ data.tar.gz: f8b8ffcb0f30fbec9321a4a9f14ae7568b16b31e40833046fb1ab16ddcf28c1b
5
5
  SHA512:
6
- metadata.gz: ac29e181ea9a2b6fbc081d7dc13288fed6990c4a215d3d9b0a25e499cfefe63346c1f648988495537c0147f954c9ddf4c76ee110a288d9a3dc1e821b47525282
7
- data.tar.gz: 992e6fa7a4438c5b580ad6b026ed351109b9b818106f647fc9c5234165abdd07bc729ff34a4bec6d0a678200a5de7c6bbbbceee2b395964a599a95622a8de06e
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- markdown_exec (2.0.3.2)
4
+ markdown_exec (2.0.5)
5
5
  clipboard (~> 1.3.6)
6
6
  open3 (~> 0.1.1)
7
7
  optparse (~> 0.1.1)
@@ -13,7 +13,7 @@ __filedirs_all()
13
13
  }
14
14
 
15
15
  _mde_echo_version() {
16
- echo "2.0.3.2"
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-18 00:24:36 UTC"
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
  ```
@@ -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
+ ```
@@ -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 search_in_directory_names
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 search_in_file_names
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 search_in_file_contents
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
- if line_utf8.match?(@pattern)
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 search_in_file_contents
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 search_in_directory_names method
223
- def test_search_in_directory_names
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.search_in_directory_names
227
+ assert_equal [], @searcher.find_directory_names
226
228
  end
227
229
 
228
- # Test search_in_file_names method
229
- def test_search_in_file_names
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.search_in_file_names
233
+ assert_equal [], @searcher.find_file_names
232
234
  end
233
235
 
234
- # Test search_in_file_contents method
235
- def test_search_in_file_contents
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.search_in_file_contents
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 search_in_directory_names method for 'spec'
253
- def test_search_in_directory_names_for_spec
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.search_in_directory_names
258
+ assert_equal expected_dirs, @searcher_spec.find_directory_names
257
259
  end
258
260
 
259
- # Test search_in_file_names method for 'spec'
260
- def test_search_in_file_names_for_spec
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.search_in_file_names
266
+ assert_equal expected_files, @searcher_spec.find_file_names
265
267
  end
266
268
 
267
- # # Test search_in_file_contents method for 'spec'
268
- # def test_search_in_file_contents_for_spec
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.search_in_file_contents
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) and an array of paths.
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 also included in the search. The search can include subdirectories depending on the
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
- matched_files += Dir.glob(search_pattern, File::FNM_DOTMATCH)
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
@@ -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 and rescues any StandardErrors.
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
- eval(str)
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 "# #{selected[:oname]}"
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
- selected: @dml_block_state.block,
1911
- link_state: link_state
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
- item = if names.first.instance_of?(String)
2111
- { dname: selection }
2112
- else
2113
- names.find { |item| item[:dname] == selection }
2114
- end
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
- cleaned_hash = clean_hash_recursively(struct_hash) # Clean the hash
2471
+ clean_hash_recursively(struct_hash) # Clean the hash
2391
2472
  # Return the cleaned hash instead of updating the Struct
2392
- return cleaned_hash
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
- require_relative 'std_out_err_logger'
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 = "/usr/local/bin"
3298
- expression = "path/to/*/directory"
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 = "my_folder"
3304
- expression = "path/to/*/directory"
3305
- expected_output = "path/to/my_folder/directory"
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 = "data"
3311
- expression = "path/to/*/directory"
3312
- expected_output = "path/to/data/directory"
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 = "path/to/*/directory"
3319
- expected_output = "path/to//directory"
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
@@ -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::env_bool('INPUT_SEQUENCER_DEBUG', default: false) ### false e
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 "Block not recognized."
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
 
@@ -7,5 +7,5 @@ module MarkdownExec
7
7
  BIN_NAME = 'mde'
8
8
  GEM_NAME = 'markdown_exec'
9
9
  TAP_DEBUG = 'MDE_DEBUG'
10
- VERSION = '2.0.3.2'
10
+ VERSION = '2.0.5'
11
11
  end
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
- find_path = @options[:find_path].present? ? @options[:find_path] : @options[:path]
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(opts[:prompt_select_md].to_s, :prompt_color_after_script_execution),
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
- return unless result.fetch(:option, nil)
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: MDE_debounce_execution
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.3.2)
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: MDE_debounce_execution
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
@@ -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 || STDOUT)
22
- self.formatter = proc do |severity, datetime, progname, msg|
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
- $stderr.puts(out)
39
+ warn(out)
40
40
  end
41
+ elsif @file
42
+ super
41
43
  else
42
- if @file
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("Info message")
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("Warning message")
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("Error message")
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(["Message line 1", "Message line 2"])
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 { "Block message" }
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, "Unknown severity message")
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.3.2
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-18 00:00:00.000000000 Z
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