markdown_exec 1.5 → 1.7

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: 41787e8352174177ca182e0376cabec7aee5923af43536a81fc2a9e61ee109e3
4
- data.tar.gz: 6a6c1fa34bf57aad286892772dcdbec19abb41fdb0385a69e264fee70747f742
3
+ metadata.gz: 97045160c53b7dca3998f6d700bf136f500826141d226f9f2cc650d15e6d1ba7
4
+ data.tar.gz: c69c23b07fcb3304d0209f79596619fbe54d0c2bb9a03f94c8598e3358c25ed7
5
5
  SHA512:
6
- metadata.gz: a4b9377c62c5054d077974b2056d7f89acd54088cd064d9c495b10eda49f64750154dc6618e6f7719dee93aaf3a0e0ca3cafa8d8fd7ca3ccbdb970e2c433b23c
7
- data.tar.gz: c9ed40b8c0df7b4b41e2db40d495a420ae1a294bbdd0c7df7702f5bfd99c6e9931a89e197e0fb5b3109fbec3403ef2b7c28126b1f0d000c6aea7c0426cde9a34
6
+ metadata.gz: a525dc3c727c38aba2d52382871650b6d2636891028a6def27f8958fcd78ae46afcb8fbbeb0f1bfdb0d550beb22c7cc90c67744c52143490f0a27c75c9158442
7
+ data.tar.gz: ae304dc41d17d2763a17b518da50956f0271ca401a4bc78929200eeb17ab3d19dfd148db76f104aed8216120c38673d19c9e56af018d3c9d83534c8901cc0915
data/.rubocop.yml CHANGED
@@ -14,7 +14,7 @@ Layout/LineContinuationLeadingSpace:
14
14
  Enabled: false
15
15
 
16
16
  Layout/LineLength:
17
- Max: 72
17
+ Max: 78
18
18
  # Max: 80
19
19
  # Max: 96
20
20
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6] - 2023-11-13
4
+
5
+ ### Added
6
+
7
+ - Options to remember a block's indentation in the source document and to display with same indentation in the menu.
8
+
9
+ ### Changed
10
+
11
+ - Note option matches the remaining lines in the document and they are displayed in the menu.
12
+
3
13
  ## [1.5] - 2023-11-08
4
14
 
5
15
  ### Added
data/Gemfile CHANGED
@@ -22,4 +22,7 @@ gem 'rubocop-rake', require: false
22
22
  gem 'rubocop-rspec', require: false
23
23
  gem 'shellwords'
24
24
  gem 'uri'
25
+
26
+ gem 'visual_call_graph'
27
+
25
28
  gem 'yaml', '~> 0.2.1'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- markdown_exec (1.5)
4
+ markdown_exec (1.7)
5
5
  clipboard (~> 1.3.6)
6
6
  open3 (~> 0.1.1)
7
7
  optparse (~> 0.1.1)
@@ -139,6 +139,8 @@ GEM
139
139
  rubocop (~> 1.33)
140
140
  rubocop-capybara (~> 2.17)
141
141
  rubocop-factory_bot (~> 2.22)
142
+ ruby-graphviz (1.2.5)
143
+ rexml
142
144
  ruby-progressbar (1.13.0)
143
145
  ruby2_keywords (0.0.5)
144
146
  shellwords (0.1.0)
@@ -157,6 +159,8 @@ GEM
157
159
  concurrent-ruby (~> 1.0)
158
160
  unicode-display_width (2.5.0)
159
161
  uri (0.12.2)
162
+ visual_call_graph (0.4.0)
163
+ ruby-graphviz (~> 1.2, >= 1.2.0)
160
164
  wisper (2.0.1)
161
165
  yaml (0.2.1)
162
166
  zeitwerk (2.6.12)
@@ -182,6 +186,7 @@ DEPENDENCIES
182
186
  rubocop-rspec
183
187
  shellwords
184
188
  uri
189
+ visual_call_graph
185
190
  yaml (~> 0.2.1)
186
191
 
187
192
  BUNDLED WITH
data/Rakefile CHANGED
@@ -81,6 +81,7 @@ task :minitest do
81
81
  './lib/cached_nested_file_reader.rb',
82
82
  './lib/fcb.rb',
83
83
  './lib/filter.rb',
84
+ './lib/hash_delegator.rb',
84
85
  './lib/markdown_exec.rb',
85
86
  './lib/mdoc.rb',
86
87
  './lib/object_present.rb',
@@ -13,7 +13,7 @@ __filedirs_all()
13
13
  }
14
14
 
15
15
  _mde_echo_version() {
16
- echo "1.5"
16
+ echo "1.7"
17
17
  }
18
18
 
19
19
  _mde() {
@@ -138,4 +138,4 @@ _mde() {
138
138
 
139
139
  complete -o filenames -o nospace -F _mde mde
140
140
  # _mde_echo_version
141
- # echo "Updated: 2023-11-08 15:34:04 UTC"
141
+ # echo "Updated: 2023-12-01 18:34:38 UTC"
data/examples/import0.md CHANGED
@@ -1,8 +1,44 @@
1
- ```bash :01
2
- 01
3
- ```
1
+ This is Page 0
2
+
4
3
  @import import1.md
5
- ```bash :03
6
- 03
4
+
5
+ ```opts :(document_options)
6
+ user_must_approve: true
7
+ ```
8
+
9
+ ::: Page 0 code blocks
10
+
11
+ ```bash :page0_block1 +(page0_block2) +(page1_block2)
12
+ echo "page 0 block 1 visible"
13
+ # requires page 0 block 2
14
+ # imports page 1
15
+ # requires page 1 block 2
16
+ ```
17
+
18
+ ```bash :(page0_block2)
19
+ echo "page 0 block 2 hidden"
7
20
  ```
8
21
 
22
+ ::: Control display of code blocks
23
+
24
+ ```opts :hide_imported_blocks
25
+ menu_include_imported_blocks: false
26
+ ```
27
+
28
+ ```opts :show_imported_blocks
29
+ menu_include_imported_blocks: true
30
+ ```
31
+
32
+ ::: Control display of notes
33
+
34
+ ```opts :hide_imported_notes
35
+ menu_include_imported_notes: false
36
+ ```
37
+
38
+ ```opts :show_imported_notes
39
+ menu_include_imported_notes: true
40
+ ```
41
+
42
+ ```link :page_1
43
+ file: examples/import1.md
44
+ ```
data/examples/import1.md CHANGED
@@ -1,10 +1,11 @@
1
- ```bash :11
2
- 11
3
- ```
4
- ```bash :12
5
- 12
6
- ```
7
- ```bash:13
8
- 13
1
+ This is Page 1
2
+
3
+ ::: Page 1 code blocks
4
+
5
+ ```bash :page1_block1
6
+ echo "page 1 block 1 visible"
9
7
  ```
10
8
 
9
+ ```bash :(page1_block2)
10
+ echo "page 1 block 2 hidden"
11
+ ```
data/examples/linked1.md CHANGED
@@ -11,16 +11,20 @@ colorize_env_vars 'vars for page2' PAGE2_VAR_VIA_INHERIT page2_var_via_environme
11
11
  colorize_env_vars 'vars for page3' PAGE3_VAR_VIA_INHERIT page3_var_via_environment
12
12
  ```
13
13
 
14
+ ```bash :(vars2)
15
+ PAGE2_VAR_VIA_INHERIT=for_page2_from_page1_via_inherited_code_file
16
+ ```
17
+
18
+ ```link :(linked2)
19
+ file: examples/linked2.md
20
+ ```
21
+
14
22
  ::: This Link block
15
23
  ::: 1. requires a block that sets environment variable PAGE2_VAR_VIA_INHERIT,
16
24
  ::: 2. navigates to document 2, and
17
25
  ::: 3. executes block "show_vars" to display the imported PAGE2_VAR_VIA_INHERIT.
18
26
  ::: Any script generated by page 2 will contain the inherited code.
19
27
 
20
- ```bash :(vars2)
21
- PAGE2_VAR_VIA_INHERIT=for_page2_from_page1_via_inherited_code_file
22
- ```
23
-
24
28
  ```link :linked2_import_vars +(vars2)
25
29
  file: examples/linked2.md
26
30
  block: show_vars
data/examples/opts.md CHANGED
@@ -1,23 +1,20 @@
1
1
  # Demo configuring options
2
- + note1
3
- a
2
+
4
3
  ::: These Opts blocks set the color of a couple of menu options to demonstrate the live update of options.
5
- b
6
- + note2
7
- c
4
+
8
5
  ```opts :opts1
9
6
  menu_divider_color: yellow
10
7
  menu_note_match:
11
8
  menu_task_color: fg_rgb_255_63_255
12
9
  ```
13
- + note3
10
+
14
11
  ```opts :opts2
15
12
  menu_divider_color: red
16
13
  menu_note_color: yellow
17
14
  menu_note_match: "^\\+ +(?<line>.+?) *$"
18
15
  menu_task_color: fg_rgb_127_127_255
19
16
  ```
20
- + note4
17
+
21
18
  ```opts :(document_options)
22
19
  menu_divider_color: green
23
20
  menu_link_color: fg_rgbh_88_cc_66
data/lib/array.rb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ class Array
7
+ # Processes each element of the array, yielding the previous, current, and next elements to the given block.
8
+ # Deletes the current element if the block returns true.
9
+ # @return [Array] The modified array after conditional deletions.
10
+ def process_and_conditionally_delete!
11
+ i = 0
12
+ while i < length
13
+ prev_item = self[i - 1] unless i.zero?
14
+ current_item = self[i]
15
+ next_item = self[i + 1]
16
+
17
+ should_delete = yield prev_item, current_item, next_item
18
+ if should_delete
19
+ delete_at(i)
20
+ else
21
+ i += 1
22
+ end
23
+ end
24
+
25
+ self
26
+ end
27
+ end
data/lib/array_util.rb ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # encoding=utf-8
5
+
6
+ module ArrayUtil
7
+ def self.partition_by_predicate(arr)
8
+ true_list = []
9
+ false_list = []
10
+
11
+ arr.each do |element|
12
+ if yield(element)
13
+ true_list << element
14
+ else
15
+ false_list << element
16
+ end
17
+ end
18
+
19
+ [true_list, false_list]
20
+ end
21
+ end
@@ -5,6 +5,16 @@
5
5
 
6
6
  # version 2023-10-03
7
7
 
8
+ require 'fileutils'
9
+ require_relative 'exceptions'
10
+
11
+ # a struct to hold the data for a single line
12
+ NestedLine = Struct.new(:text, :depth) do
13
+ def to_s
14
+ text
15
+ end
16
+ end
17
+
8
18
  ##
9
19
  # The CachedNestedFileReader class provides functionality to read file lines with the ability
10
20
  # to process '#import filename' directives. When such a directive is encountered in a file,
@@ -13,42 +23,58 @@
13
23
  # It allows clients to read lines with or without providing a block.
14
24
  #
15
25
  class CachedNestedFileReader
26
+ include Exceptions
27
+
16
28
  def initialize(import_pattern: /^ *#import (.+)$/)
17
29
  @file_cache = {}
18
30
  @import_pattern = import_pattern
19
31
  end
20
32
 
21
- def readlines(filename, &block)
33
+ def error_handler(name = '', opts = {})
34
+ Exceptions.error_handler(
35
+ "CachedNestedFileReader.#{name} -- #{$!}",
36
+ opts
37
+ )
38
+ end
39
+
40
+ def warn_format(name, message, opts = {})
41
+ Exceptions.warn_format(
42
+ "CachedNestedFileReader.#{name} -- #{message}",
43
+ opts
44
+ )
45
+ end
46
+
47
+ def readlines(filename, depth = 0, &block)
22
48
  if @file_cache.key?(filename)
23
- @file_cache[filename].each(&block) if block_given?
49
+ @file_cache[filename].each(&block) if block
24
50
  return @file_cache[filename]
25
51
  end
26
52
 
27
53
  directory_path = File.dirname(filename)
28
- lines = File.readlines(filename, chomp: true)
54
+ # lines = File.readlines(filename, chomp: true)
29
55
  processed_lines = []
30
56
 
31
- lines.each do |line|
32
- if (match = line.match(@import_pattern))
33
- included_file_path = if match[1].strip.match %r{^/}
34
- match[1].strip
57
+ File.readlines(filename, chomp: true).each do |line|
58
+ if Regexp.new(@import_pattern) =~ line
59
+ name_strip = $~[:name].strip
60
+ included_file_path = if name_strip =~ %r{^/}
61
+ name_strip
35
62
  else
36
- File.join(directory_path, match[1].strip)
63
+ File.join(directory_path, name_strip)
37
64
  end
38
- processed_lines += readlines(included_file_path, &block)
65
+ processed_lines += readlines(included_file_path, depth + 1,
66
+ &block)
39
67
  else
40
- processed_lines.push(line)
41
- yield line if block_given?
68
+ nested_line = NestedLine.new(line, depth)
69
+ processed_lines.push(nested_line)
70
+ block&.call(nested_line)
42
71
  end
43
72
  end
44
73
 
45
74
  @file_cache[filename] = processed_lines
46
- end
47
-
48
- private
49
-
50
- def fetch_lines(filename)
51
- @fetch_lines_cache[filename] ||= File.readlines(filename, chomp: true)
75
+ rescue Errno::ENOENT
76
+ # Exceptions.error_handler('readlines', { abort: true })
77
+ warn_format('readlines', "No such file -- #{filename}", { abort: true })
52
78
  end
53
79
  end
54
80
 
@@ -56,10 +82,6 @@ if $PROGRAM_NAME == __FILE__
56
82
  require 'minitest/autorun'
57
83
  require 'tempfile'
58
84
 
59
- ##
60
- # The CachedNestedFileReaderTest class provides testing for
61
- # the CachedNestedFileReader class.
62
- #
63
85
  class CachedNestedFileReaderTest < Minitest::Test
64
86
  def setup
65
87
  @file2 = Tempfile.new('test2.txt')
@@ -69,7 +91,7 @@ if $PROGRAM_NAME == __FILE__
69
91
  @file1 = Tempfile.new('test1.txt')
70
92
  @file1.write("Line1\nLine2\n #insert #{@file2.path}\nLine3")
71
93
  @file1.rewind
72
- @reader = CachedNestedFileReader.new(import_pattern: /^ *#insert (.+)$/)
94
+ @reader = CachedNestedFileReader.new(import_pattern: /^ *#insert (?'name'.+)$/)
73
95
  end
74
96
 
75
97
  def teardown
@@ -81,28 +103,26 @@ if $PROGRAM_NAME == __FILE__
81
103
  end
82
104
 
83
105
  def test_readlines_without_imports
84
- result = []
85
- @reader.readlines(@file2.path) { |line| result << line }
106
+ result = @reader.readlines(@file2.path).map(&:to_s)
86
107
  assert_equal %w[ImportedLine1 ImportedLine2], result
87
108
  end
88
109
 
89
110
  def test_readlines_with_imports
90
- result = []
91
- @reader.readlines(@file1.path) { |line| result << line }
92
- assert_equal %w[Line1 Line2 ImportedLine1 ImportedLine2 Line3], result
111
+ result = @reader.readlines(@file1.path).map(&:to_s)
112
+ assert_equal %w[Line1 Line2 ImportedLine1 ImportedLine2 Line3],
113
+ result
93
114
  end
94
115
 
95
116
  def test_caching_functionality
96
117
  # First read
97
- result1 = []
98
- @reader.readlines(@file2.path) { |line| result1 << line }
118
+
119
+ result1 = @reader.readlines(@file2.path).map(&:to_s)
99
120
 
100
121
  # Simulate file content change
101
122
  @file2.reopen(@file2.path, 'w') { |f| f.write('ChangedLine') }
102
123
 
103
124
  # Second read (should read from cache, not the changed file)
104
- result2 = []
105
- @reader.readlines(@file2.path) { |line| result2 << line }
125
+ result2 = @reader.readlines(@file2.path).map(&:to_s)
106
126
 
107
127
  assert_equal result1, result2
108
128
  assert_equal %w[ImportedLine1 ImportedLine2], result2
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
data/lib/env.rb CHANGED
@@ -18,7 +18,8 @@ module Env
18
18
 
19
19
  # :reek:UtilityFunction
20
20
  def env_bool_false(name)
21
- !(val = (name && ENV.fetch(name, nil))).nil? && !(val.empty? || val == '0')
21
+ !(val = (name && ENV.fetch(name,
22
+ nil))).nil? && !(val.empty? || val == '0')
22
23
  end
23
24
 
24
25
  # skip :reek:DataClump
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],