markdown_exec 2.0.3.2 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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