markdown_exec 1.6 → 1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/constants.rb ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ class ExecutionStreams
7
+ StdErr = :stderr
8
+ StdIn = :stdin
9
+ StdOut = :stdout
10
+ end
11
+
12
+ class LoadFile
13
+ Load = true
14
+ Reuse = false
15
+ end
16
+
17
+ LoadFileNextBlock = Struct.new(:load_file, :next_block)
18
+
19
+ class MenuControl
20
+ Fresh = false
21
+ Repeat = true
22
+ end
23
+
24
+ class MenuOptions
25
+ YES = 1
26
+ NO = 2
27
+ SCRIPT_TO_CLIPBOARD = 3
28
+ SAVE_SCRIPT = 4
29
+ end
30
+
31
+ class MenuState
32
+ BACK = :back
33
+ CONTINUE = :continue
34
+ EXIT = :exit
35
+ end
36
+
37
+ # selected block and subsequent menu state
38
+ #
39
+ SelectedBlockMenuState = Struct.new(:block, :state)
40
+
41
+ SHELL_COLOR_OPTIONS = {
42
+ BlockType::BASH => :menu_bash_color,
43
+ BlockType::LINK => :menu_link_color,
44
+ BlockType::OPTS => :menu_opts_color,
45
+ BlockType::VARS => :menu_vars_color
46
+ }.freeze
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ require 'find'
7
+
8
+ # Formats and highlights a list of dependencies. Dependencies are presented with indentation,
9
+ # and specific items can be highlighted in a specified color, while others are shown in a plain color.
10
+ #
11
+ # @param dependencies [Hash] A hash of dependencies, where each key is a dependency name,
12
+ # and its value is an array of sub-items.
13
+ # @param highlight_color_sym [Symbol] The color method to apply to highlighted items.
14
+ # Default is :exception_color_detail.
15
+ # @param plain_color_sym [Symbol] The color method for non-highlighted items.
16
+ # Default is :menu_chrome_color.
17
+ # @param label [String] The label to prefix the list of dependencies with.
18
+ # Default is 'Dependencies:'.
19
+ # @param highlight [Array] An array of items to highlight. Each item in this array will be
20
+ # formatted with the specified highlight color.
21
+ # @param line_prefix [String] Prefix for each line. Default is ' '.
22
+ # @param line_postfix [String] Postfix for each line. Default is ''.
23
+ # @param detail_sep [String] Separator for items in the sub-list. Default is ' '.
24
+ # @return [String] A formatted string representation of the dependencies with highlighted items.
25
+ def format_and_highlight_dependencies(
26
+ dependencies,
27
+ highlight_color_sym: :exception_color_detail,
28
+ plain_color_sym: :menu_chrome_color,
29
+ label: 'Dependencies:',
30
+ highlight: [],
31
+ line_prefix: ' ',
32
+ line_postfix: '',
33
+ detail_sep: ' '
34
+ )
35
+ formatted_deps = dependencies&.map do |dep_name, sub_items|
36
+ formatted_sub_items = sub_items.map do |item|
37
+ color_sym = highlight.include?(item) ? highlight_color_sym : plain_color_sym
38
+ string_send_color(item, color_sym)
39
+ end.join(detail_sep)
40
+
41
+ "#{line_prefix}- #{string_send_color(dep_name,
42
+ highlight.include?(dep_name) ? highlight_color_sym : plain_color_sym)}: #{formatted_sub_items}#{line_postfix}"
43
+ end || []
44
+
45
+ "#{line_prefix}#{string_send_color(label,
46
+ highlight_color_sym)}#{line_postfix}\n" + formatted_deps.join("\n")
47
+ end
48
+ # warn menu_blocks.to_yaml.sub(/^(?:---\n)?/, "MenuBlocks:\n")
49
+
50
+ IndexedLine = Struct.new(:index, :line) do
51
+ def to_s
52
+ line
53
+ end
54
+ end
55
+
56
+ # Class DirectorySearcher
57
+ # This class provides methods to search for a specified pattern
58
+ # in directory names, file names, and contents of files within given paths.
59
+ class DirectorySearcher
60
+ attr_reader :pattern, :paths, :include_subdirectories, :filename_glob
61
+
62
+ # Constructor
63
+ # @param pattern [Regexp] The regular expression pattern to search for.
64
+ # @param paths [Array<String>] List of directories to search in.
65
+ # @param include_subdirectories [Boolean] Whether to search in subdirectories.
66
+ # @param filename_glob [String, nil] Glob pattern for file names.
67
+ def initialize(pattern, paths, include_subdirectories: true, filename_glob: '*.[Mm][Dd]') #'*.md'
68
+ @pattern = pattern
69
+ @paths = paths
70
+ @include_subdirectories = include_subdirectories
71
+ @filename_glob = filename_glob
72
+ end
73
+
74
+ # Searches for the pattern in directory names.
75
+ # @return [Array<String>] List of matching directory names.
76
+ def search_in_directory_names
77
+ match_dirs = []
78
+ @paths.each do |path|
79
+ Find.find(path) do |p|
80
+ # p 'search_in_directory_names', p
81
+ # Find.prune unless @include_subdirectories || path == p
82
+ match_dirs << p if File.directory?(p) && p.match?(@pattern)
83
+ end
84
+ end
85
+ match_dirs
86
+ end
87
+
88
+ # Searches for the pattern in file names.
89
+ # @return [Array<String>] List of matching file names.
90
+ def search_in_file_names
91
+ match_files = []
92
+ @paths.each do |path|
93
+ Find.find(path) do |p|
94
+ # Find.prune unless @include_subdirectories || path == p
95
+ next unless File.file?(p)
96
+
97
+ file_name = File.basename(p)
98
+ next if @filename_glob && !File.fnmatch(@filename_glob, file_name)
99
+
100
+ begin
101
+ match_files << p if file_name.encode('UTF-8', invalid: :replace, undef: :replace,
102
+ replace: '').match?(@pattern)
103
+ rescue EncodingError
104
+ # Optionally log the file with encoding issues
105
+ # puts "Encoding error in file: #{p}"
106
+ end
107
+ end
108
+ end
109
+ match_files
110
+ end
111
+
112
+ # Searches for the pattern in the contents of the files and returns matches along with their file paths and line numbers.
113
+ # @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.
114
+ def search_in_file_contents
115
+ match_details = {}
116
+
117
+ @paths.each do |path|
118
+ Find.find(path) do |p|
119
+ Find.prune unless @include_subdirectories || path == p
120
+ next unless File.file?(p)
121
+
122
+ next if @filename_glob && !File.fnmatch(@filename_glob, File.basename(p))
123
+
124
+ begin
125
+ File.foreach(p).with_index(1) do |line, line_num| # Index starts from 1 for line numbers
126
+ line_utf8 = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
127
+ if line_utf8.match?(@pattern)
128
+ match_details[p] ||= []
129
+ # match_details[p] << { number: line_num, line: line_utf8.chomp }
130
+ match_details[p] << IndexedLine.new(line_num, line_utf8.chomp)
131
+ end
132
+ end
133
+ rescue EncodingError
134
+ # Optionally log the file with encoding issues
135
+ # puts "Encoding error in file: #{p}"
136
+ end
137
+ end
138
+ end
139
+
140
+ match_details
141
+ end
142
+
143
+ # # Searches for the pattern in the contents of the files.
144
+ # # @return [Array<String>] List of matching lines from files.
145
+ # def search_in_file_contents
146
+ # match_lines = []
147
+ # @paths.each do |path|
148
+ # Find.find(path) do |p|
149
+ # Find.prune unless @include_subdirectories || path == p
150
+ # next unless File.file?(p)
151
+
152
+ # next if @filename_glob && !File.fnmatch(@filename_glob, File.basename(p))
153
+
154
+ # begin
155
+ # File.foreach(p).with_index do |line, _line_num|
156
+ # line_utf8 = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
157
+ # match_lines << line_utf8.chomp if line_utf8.match?(@pattern)
158
+ # end
159
+ # rescue EncodingError
160
+ # # Optionally log the file with encoding issues
161
+ # # puts "Encoding error in file: #{p}"
162
+ # end
163
+ # end
164
+ # end
165
+ # match_lines
166
+ # end
167
+ end
168
+
169
+ if $PROGRAM_NAME == __FILE__
170
+ require 'bundler/setup'
171
+ Bundler.require(:default)
172
+
173
+ require 'minitest/autorun'
174
+ # require 'mocha/minitest'
175
+ # require_relative 'directory_searcher'
176
+
177
+ # Test class for DirectorySearcher
178
+ class DirectorySearcherTest < Minitest::Test
179
+ # Setup method to initialize common test data
180
+ def setup
181
+ @pattern = /test_pattern/
182
+ @paths = ['./spec']
183
+ @searcher = DirectorySearcher.new(@pattern, @paths)
184
+ end
185
+
186
+ # Test search_in_directory_names method
187
+ def test_search_in_directory_names
188
+ # Add assertions based on your test directory structure and expected results
189
+ assert_equal [], @searcher.search_in_directory_names
190
+ end
191
+
192
+ # Test search_in_file_names method
193
+ def test_search_in_file_names
194
+ # Add assertions based on your test directory structure and expected results
195
+ assert_equal [], @searcher.search_in_file_names
196
+ end
197
+
198
+ # Test search_in_file_contents method
199
+ def test_search_in_file_contents
200
+ # Add assertions based on your test directory structure and expected results
201
+ assert_equal ({}), @searcher.search_in_file_contents
202
+ end
203
+ end
204
+
205
+ # Test class for DirectorySearcher
206
+ class DirectorySearcherTest2 < Minitest::Test
207
+ # Setup method to initialize common test data
208
+ def setup
209
+ @pattern_spec = /spec/
210
+ @paths = ['./spec']
211
+ @filename_glob = nil
212
+ @searcher_spec = DirectorySearcher.new(@pattern_spec, @paths,
213
+ filename_glob: @filename_glob)
214
+ end
215
+
216
+ # Test search_in_directory_names method for 'spec'
217
+ def test_search_in_directory_names_for_spec
218
+ # Replace with actual expected directory names containing 'spec'
219
+ expected_dirs = ['./spec']
220
+ assert_equal expected_dirs, @searcher_spec.search_in_directory_names
221
+ end
222
+
223
+ # Test search_in_file_names method for 'spec'
224
+ def test_search_in_file_names_for_spec
225
+ # Replace with actual expected file names containing 'spec'
226
+ expected_files = ['./spec/cli_spec.rb', './spec/env_spec.rb',
227
+ './spec/markdown_exec_spec.rb', './spec/tap_spec.rb']
228
+ assert_equal expected_files, @searcher_spec.search_in_file_names
229
+ end
230
+
231
+ # # Test search_in_file_contents method for 'spec'
232
+ # def test_search_in_file_contents_for_spec
233
+ # # Replace with actual expected lines containing 'spec'
234
+ # expected_lines = {['Line with spec 1', 'Line with spec 2']}
235
+ # assert_equal expected_lines, @searcher_spec.search_in_file_contents
236
+ # end
237
+ end
238
+
239
+ end
data/lib/exceptions.rb ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ module Exceptions
7
+ def self.error_handler(name = '', opts = {}, format_string: "\nError: %{name} -- %{message}", color_symbol: :red, take_count: 16)
8
+ warn(error = format(format_string,
9
+ { name: name, message: $! }).send(color_symbol))
10
+ warn($@.select do |s|
11
+ s.include? 'markdown_exec'
12
+ end.reject { |s| s.include? 'vendor' }.take(take_count).map.with_index { |line, ind| " * #{ind}: #{line}" })
13
+
14
+ binding.pry if $tap_enable
15
+ raise ArgumentError, error unless opts.fetch(:abort, true)
16
+
17
+ exit 1
18
+ end
19
+
20
+ def self.warn_format(message = '', opts = {})
21
+ warn(
22
+ error = format(
23
+ opts.fetch(:format_string, "\nError: %{error}"),
24
+ { error: message }
25
+ ).send(opts.fetch(:color_symbol, :yellow))
26
+ )
27
+ # warn(caller.take(4).map.with_index { |line, ind| " * #{ind}: #{line}" })
28
+
29
+ binding.pry if $tap_enable
30
+ raise ArgumentError, error unless opts.fetch(:abort, false)
31
+
32
+ exit 1
33
+ end
34
+ end
data/lib/fcb.rb CHANGED
@@ -19,6 +19,7 @@ module MarkdownExec
19
19
  call: nil,
20
20
  headings: [],
21
21
  dname: nil,
22
+ indent: '',
22
23
  name: nil,
23
24
  oname: nil,
24
25
  reqs: [],
@@ -29,6 +30,39 @@ module MarkdownExec
29
30
  }.merge(options)
30
31
  end
31
32
 
33
+ def title=(value)
34
+ @attrs[:title] = value
35
+ end
36
+
37
+ # Derives a title from the body of an FCB object.
38
+ # @param fcb [Object] The FCB object whose title is to be derived.
39
+ # @return [String] The derived title.
40
+ def derive_title_from_body
41
+ body_content = @attrs[:body]
42
+ unless body_content
43
+ @attrs[:title] = ''
44
+ return
45
+ end
46
+
47
+ @attrs[:title] = if body_content.count == 1
48
+ body_content.first
49
+ else
50
+ format_multiline_body_as_title(body_content)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # Formats multiline body content as a title string.
57
+ # indents all but first line with two spaces so it displays correctly in menu
58
+ # @param body_lines [Array<String>] The lines of body content.
59
+ # @return [String] Formatted title.
60
+ def format_multiline_body_as_title(body_lines)
61
+ body_lines.map.with_index do |line, index|
62
+ index.zero? ? line : " #{line}"
63
+ end.join("\n") << "\n"
64
+ end
65
+
32
66
  # :reek:ManualDispatch
33
67
  def method_missing(method, *args, &block)
34
68
  method_name = method.to_s
@@ -48,7 +82,9 @@ module MarkdownExec
48
82
  raise err # Here, we simply propagate the original error instead of wrapping it in a StandardError.
49
83
  end
50
84
 
51
- def respond_to_missing?(method_name, _include_private = false)
85
+ public
86
+
87
+ def respond_to_missing?(method_name, include_private = false)
52
88
  @attrs.key?(method_name.to_sym) || super
53
89
  end
54
90
 
@@ -63,6 +99,9 @@ module MarkdownExec
63
99
  end
64
100
 
65
101
  if $PROGRAM_NAME == __FILE__
102
+ require 'bundler/setup'
103
+ Bundler.require(:default)
104
+
66
105
  require 'minitest/autorun'
67
106
  require 'yaml'
68
107
 
@@ -73,6 +112,7 @@ if $PROGRAM_NAME == __FILE__
73
112
  call: 'Sample call',
74
113
  headings: %w[Header1 Header2],
75
114
  dname: 'Sample name',
115
+ indent: '',
76
116
  name: 'Sample name',
77
117
  oname: 'Sample name',
78
118
  reqs: %w[req1 req2],
data/lib/filter.rb CHANGED
@@ -38,14 +38,18 @@ module MarkdownExec
38
38
  name = fcb.oname
39
39
  shell = fcb.fetch(:shell, '')
40
40
 
41
+ ### filter in menu, not in source code
42
+ filters[:depth] =
43
+ fcb.fetch(:depth,
44
+ 0).positive? && !options[:menu_include_imported_blocks]
41
45
  apply_name_filters(options, filters, name)
42
46
  apply_shell_filters(options, filters, shell)
43
47
  apply_other_filters(options, filters, fcb)
44
48
 
45
49
  evaluate_filters(options, filters)
46
- rescue StandardError => err
47
- warn("ERROR ** Filter::fcb_select?(); #{err.inspect}")
48
- raise err
50
+ rescue StandardError
51
+ warn("ERROR ** Filter::fcb_select?(); #{$!.inspect}")
52
+ raise ArgumentError, $!
49
53
  end
50
54
 
51
55
  # Applies name-based filters to determine whether to include or
@@ -68,14 +72,16 @@ module MarkdownExec
68
72
  end
69
73
 
70
74
  if name.present? && filters[:name_select].nil? && options[:select_by_name_regex].present?
71
- filters[:name_select] = !!(name =~ /#{options[:select_by_name_regex]}/)
75
+ filters[:name_select] =
76
+ !!(name =~ /#{options[:select_by_name_regex]}/)
72
77
  end
73
78
 
74
79
  unless name.present? && filters[:name_exclude].nil? && options[:exclude_by_name_regex].present?
75
80
  return
76
81
  end
77
82
 
78
- filters[:name_exclude] = !!(name =~ /#{options[:exclude_by_name_regex]}/)
83
+ filters[:name_exclude] =
84
+ !!(name =~ /#{options[:exclude_by_name_regex]}/)
79
85
  end
80
86
 
81
87
  # Applies shell-based filters to determine whether to include or
@@ -90,12 +96,16 @@ module MarkdownExec
90
96
  filters[:shell_expect] = shell == 'expect'
91
97
 
92
98
  if shell.present? && options[:select_by_shell_regex].present?
93
- filters[:shell_select] = !!(shell =~ /#{options[:select_by_shell_regex]}/)
99
+ filters[:shell_select] =
100
+ !!(shell =~ /#{options[:select_by_shell_regex]}/)
94
101
  end
95
102
 
96
- return unless shell.present? && options[:exclude_by_shell_regex].present?
103
+ unless shell.present? && options[:exclude_by_shell_regex].present?
104
+ return
105
+ end
97
106
 
98
- filters[:shell_exclude] = !!(shell =~ /#{options[:exclude_by_shell_regex]}/)
107
+ filters[:shell_exclude] =
108
+ !!(shell =~ /#{options[:exclude_by_shell_regex]}/)
99
109
  end
100
110
 
101
111
  # Applies additional filters to determine whether to include or
@@ -138,7 +148,9 @@ module MarkdownExec
138
148
  # if it should be excluded.
139
149
  #
140
150
  def self.evaluate_filters(options, filters)
141
- if filters[:fcb_chrome] == true
151
+ if filters[:depth] == true
152
+ false
153
+ elsif filters[:fcb_chrome] == true
142
154
  !options[:no_chrome]
143
155
  elsif options[:exclude_expect_blocks] && filters[:shell_expect] == true
144
156
  false
@@ -160,15 +172,18 @@ module MarkdownExec
160
172
  end
161
173
  end
162
174
 
163
- # blocks for menu, without missing exit and back chrome
164
- # remove hidden blocks
175
+ # check if a block is not in the menu based on multiple match patterns
165
176
  #
166
- def self.prepared_not_in_menu?(options, fcb)
167
- fcb[:shell] == BlockType::BASH &&
168
- ((options[:block_name_include_match].present? &&
169
- fcb[:oname] =~ /#{options[:block_name_include_match]}/) ||
170
- (options[:block_name_wrapper_match].present? &&
171
- fcb[:oname] =~ /#{options[:block_name_wrapper_match]}/))
177
+ # @param options [Hash] Options hash containing various settings
178
+ # @param fcb [Hash] Hash representing a file code block
179
+ # @param match_patterns [Array<String>] Array of regular expression patterns for matching
180
+ # @return [Boolean] True if the block should not be in the menu, false otherwise
181
+ def self.prepared_not_in_menu?(options, fcb, match_patterns)
182
+ return false unless fcb[:shell] == BlockType::BASH
183
+
184
+ match_patterns.any? do |pattern|
185
+ options[pattern].present? && fcb[:oname] =~ /#{options[pattern]}/
186
+ end
172
187
  end
173
188
  end
174
189
  end
data/lib/fout.rb ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ # stdout manager
7
+ #
8
+ # module FOut
9
+ class FOut
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def approved_fout?(level)
15
+ level <= fetch_display_level
16
+ end
17
+
18
+ # integer value for comparison
19
+ #
20
+ def fetch_display_level
21
+ @config.fetch(:display_level, 1)
22
+ end
23
+
24
+ # integer value for comparison
25
+ #
26
+ def fetch_display_level_xbase_prefix
27
+ @config.fetch(:level_xbase_prefix, '')
28
+ end
29
+
30
+ # standard output; not for debug
31
+ #
32
+ def fout(str)
33
+ puts str
34
+ end
35
+
36
+ def fout_list(str)
37
+ puts str
38
+ end
39
+
40
+ def fout_section(name, data)
41
+ puts "# #{name}"
42
+ puts data.to_yaml
43
+ end
44
+
45
+ # display output at level or lower than filter (DISPLAY_LEVEL_DEFAULT)
46
+ #
47
+ def lout(str, level: DISPLAY_LEVEL_BASE)
48
+ return unless approved_fout?(level)
49
+
50
+ fout level == DISPLAY_LEVEL_BASE ? str : fetch_display_level_xbase_prefix + str
51
+ end
52
+ end
data/lib/hash.rb ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ # hash with keys sorted by name
7
+ # add Hash.sym_keys
8
+ #
9
+ class Hash
10
+ unless defined?(sort_by_key)
11
+ def sort_by_key
12
+ keys.sort.to_h { |key| [key, self[key]] }
13
+ end
14
+ end
15
+
16
+ unless defined?(sym_keys)
17
+ def sym_keys
18
+ transform_keys(&:to_sym)
19
+ end
20
+ end
21
+ end