markdown_exec 1.6 → 1.8

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