markdown_exec 2.2.0 → 2.3.0

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: 32532369e9eec3574b8bff6d6c79097f547e120c7b113c10a765364e20a311a4
4
- data.tar.gz: 3773bab7197401361c06bace1921663679bbf53d77f2cbe5daa1c2975c091e87
3
+ metadata.gz: 40813cfbaca2f9b2e8b865e2f574ab71211c56120d5706d2f8ebc4fbb103d254
4
+ data.tar.gz: 880d121d18747d2370dff0caa90f84da9fca51633ec6e00919f690ea4821d1f0
5
5
  SHA512:
6
- metadata.gz: e6ad41fdae333916ef13870498b6b1b9be7338af8ae39238b4a16a475b42430cb9282c59dbcecd124f422ab218848a70611ce6120e03074660a213a89e054df1
7
- data.tar.gz: f9ef27a86c8a7cb4e15b4eb59705d1fdad6a15b8c6af176978b09f1108599569e0f74423b89c1baaa2cfaed249a4ff37fb18c8e7c6c87220d0ecb48a5e4cdd45
6
+ metadata.gz: df8c99ddc62b271b1fa74036ffcb485411a1461195d68c2e1825ae3ba606e4b20de456636d0094606c4579548d4532dd46287c5c486b4ab613217ab708144709
7
+ data.tar.gz: 4b812fa4808161beaa06c3de9b0639a031446294bb8a171d599e5baa4db157660233701c1068a130c93dfbd4cd0469853867cf4c751dfda4197ffb13ee81899a
data/.rubocop.yml CHANGED
@@ -15,8 +15,8 @@ Layout/LineContinuationLeadingSpace:
15
15
 
16
16
  Layout/LineLength: # 2024-01-21 temp disable
17
17
  # Enabled: false
18
- # Max: 80
19
- Max: 96
18
+ Max: 80
19
+ # Max: 96
20
20
  # Max: 120
21
21
 
22
22
  Lint/Debugger:
@@ -88,6 +88,9 @@ Style/FormatStringToken:
88
88
  Style/GlobalVars:
89
89
  Enabled: false
90
90
 
91
+ Style/IfUnlessModifier: # 2024-08 suggests lines that are too long
92
+ Enabled: false
93
+
91
94
  Style/Lambda:
92
95
  Enabled: false
93
96
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.0] - 2024-08-05
4
+
5
+ ### Added
6
+
7
+ - In-line decoration of text
8
+ - ANSI graphics modes for text decoration.
9
+ - Translation from text to ANSI graphics.
10
+ - Options for decorations in three stages, with default (Main) in the middle.
11
+
3
12
  ## [2.2.0] - 2024-07-27
4
13
 
5
14
  ### Added
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- markdown_exec (2.2.0)
4
+ markdown_exec (2.3.0)
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.2.0"
16
+ echo "2.3.0"
17
17
  }
18
18
 
19
19
  _mde() {
@@ -1,8 +1,32 @@
1
1
  ## Demonstrate handling of special characters in block names
2
- The hidden block "(success)" is required below. It prints "Success".
3
- ```bash :(success)
4
- echo "Success"
5
- ```
2
+
3
+ ::: Click below to trigger. If it prints "1","2","3","4", the Link blocks were required.
4
+ Long block names can be required by a Bash block.
5
+ ```bash :calling-block +long_block_name_12345678901234567890123456789012345678901234567890 +(long_block_name_12345678901234567890123456789012345678901234567890) +[long_block_name_12345678901234567890123456789012345678901234567890]
6
+ echo '1'
7
+ ```
8
+ Long block names can be used in Link blocks.
9
+ ```link
10
+ block: long_block_name_12345678901234567890123456789012345678901234567890
11
+ ```
12
+ ```link
13
+ block: "(long_block_name_12345678901234567890123456789012345678901234567890)"
14
+ ```
15
+ ```link
16
+ block: "[long_block_name_12345678901234567890123456789012345678901234567890]"
17
+ ```
18
+
19
+ Do not call these blocks directly.
20
+ ```bash :long_block_name_12345678901234567890123456789012345678901234567890
21
+ echo '2'
22
+ ```
23
+ ```bash :(long_block_name_12345678901234567890123456789012345678901234567890)
24
+ echo '3'
25
+ ```
26
+ ```bash :[long_block_name_12345678901234567890123456789012345678901234567890]
27
+ echo '4'
28
+ ```
29
+
6
30
  Block names with all chars.
7
31
  / UTF-8
8
32
  / !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
@@ -25,3 +49,14 @@ The un-named block should display correctly.
25
49
  vars:
26
50
  page2_var_via_environment: for_page2_from_page1_via_current_environment
27
51
  ```
52
+ The hidden block "(success)" is required above. It prints "Success".
53
+ ```bash :(success)
54
+ echo "Success"
55
+ ```
56
+
57
+ ```opts :(document_options)
58
+ execute_in_own_window: false
59
+ output_execution_report: false
60
+ output_execution_summary: false
61
+ pause_after_script_execution: true
62
+ ```
data/lib/colorize.rb CHANGED
@@ -101,4 +101,17 @@ class String
101
101
  def red; fg_rgbh_FF_00_00; end
102
102
  def violet; fg_rgbh_94_00_D3; end
103
103
  def yellow; fg_rgbh_FF_FF_00; end
104
+
105
+ # graphics modes
106
+ def bold; "\033[1m#{self}\033[22m"; end
107
+ def bold_italic; "\033[1m\033[3m#{self}\033[22m\033[23m"; end
108
+ def bold_underline; "\033[1m\033[4m#{self}\033[22m\033[24m"; end
109
+ def dim; "\033[2m#{self}\033[22m"; end
110
+ def italic; "\033[3m#{self}\033[23m"; end
111
+ def underline; "\033[4m#{self}\033[24m"; end
112
+ def underline_italic; "\033[4m\033[3m#{self}\033[23m\033[24m"; end
113
+ def blinking; "\033[5m#{self}\033[25m"; end
114
+ def inverse; "\033[7m#{self}\033[27m"; end
115
+ def hidden; "\033[8m#{self}\033[28m"; end
116
+ def strikethrough; "\033[9m#{self}\033[29m"; end
104
117
  end
@@ -29,6 +29,7 @@ require_relative 'fcb'
29
29
  require_relative 'filter'
30
30
  require_relative 'fout'
31
31
  require_relative 'hash'
32
+ require_relative 'hierarchy_string'
32
33
  require_relative 'link_history'
33
34
  require_relative 'mdoc'
34
35
  require_relative 'namer'
@@ -37,6 +38,7 @@ require_relative 'resize_terminal'
37
38
  require_relative 'std_out_err_logger'
38
39
  require_relative 'streams_out'
39
40
  require_relative 'string_util'
41
+ require_relative 'text_analyzer'
40
42
 
41
43
  $pd = false unless defined?($pd)
42
44
 
@@ -482,6 +484,7 @@ module MarkdownExec
482
484
 
483
485
  extend HashDelegatorSelf
484
486
  include CompactionHelpers
487
+ include TextAnalyzer
485
488
 
486
489
  def initialize(delegate_object = {})
487
490
  @delegate_object = delegate_object
@@ -668,6 +671,16 @@ module MarkdownExec
668
671
  end
669
672
  end
670
673
 
674
+ def apply_tree_decorations(text, color_method, decor_patterns)
675
+ tree = HierarchyString.new([{ text: text, color: color_method }])
676
+ decor_patterns.each do |pc|
677
+ analyzed_hierarchy = TextAnalyzer.analyze_hierarchy(tree.substrings, pc[:pattern],
678
+ color_method, pc[:color_method])
679
+ tree = HierarchyString.new(analyzed_hierarchy)
680
+ end
681
+ tree.decorate
682
+ end
683
+
671
684
  def assign_key_value_in_bash(key, value)
672
685
  if value =~ /["$\\`]/
673
686
  # requiring ShellWords to write into Bash scripts
@@ -685,6 +698,7 @@ module MarkdownExec
685
698
  # @return [Array<FCB>] An array of FCB objects representing the blocks.
686
699
  def blocks_from_nested_files
687
700
  register_console_attributes(@delegate_object)
701
+ @decor_patterns_from_delegate_object_for_block_create = collect_line_decor_patterns(@delegate_object)
688
702
 
689
703
  blocks = []
690
704
  iter_blocks_from_nested_files do |btype, fcb|
@@ -700,9 +714,7 @@ module MarkdownExec
700
714
  # if matched, the block returned has properties that it is from cli and not ui
701
715
  def block_state_for_name_from_cli(block_name)
702
716
  SelectedBlockMenuState.new(
703
- @dml_blocks_in_file.find do |item|
704
- block_name == item.pub_name
705
- end,
717
+ blocks_find_by_block_name(@dml_blocks_in_file, block_name),
706
718
  OpenStruct.new(
707
719
  block_name_from_cli: true,
708
720
  block_name_from_ui: false
@@ -711,6 +723,14 @@ module MarkdownExec
711
723
  )
712
724
  end
713
725
 
726
+ def blocks_find_by_block_name(blocks, block_name)
727
+ @dml_blocks_in_file.find do |item|
728
+ # 2024-08-04 match oname for long block names
729
+ # 2024-08-04 match nickname for long block names
730
+ block_name == item.pub_name || block_name == item.nickname || block_name == item.oname
731
+ end
732
+ end
733
+
714
734
  # private
715
735
 
716
736
  def calc_logged_stdout_filename(block_name:)
@@ -752,6 +772,23 @@ module MarkdownExec
752
772
  true
753
773
  end
754
774
 
775
+ def collect_line_decor_patterns(delegate_object)
776
+ extract_patterns = lambda do |key|
777
+ return [] unless delegate_object[key].present?
778
+
779
+ HashDelegator.safeval(delegate_object[key]).map do |pc|
780
+ {
781
+ color_method: pc[:color_method].to_sym,
782
+ pattern: Regexp.new(pc[:pattern])
783
+ }
784
+ end
785
+ end
786
+
787
+ %i[line_decor_pre line_decor_main line_decor_post].flat_map do |key|
788
+ extract_patterns.call(key)
789
+ end
790
+ end
791
+
755
792
  # Collects required code lines based on the selected block and the delegate object's configuration.
756
793
  # If the block type is VARS, it also sets environment variables based on the block's content.
757
794
  #
@@ -920,6 +957,7 @@ module MarkdownExec
920
957
  format_option:, color_method:,
921
958
  case_conversion: nil,
922
959
  center: nil,
960
+ decor_patterns: [],
923
961
  wrap: nil)
924
962
  line_cap = match_data.named_captures.transform_keys(&:to_sym)
925
963
 
@@ -970,11 +1008,14 @@ module MarkdownExec
970
1008
  # format expects :line to be text only
971
1009
  line_obj[:line] = line_obj[:text]
972
1010
  oname = format(format_option, line_obj)
1011
+
1012
+ decorated = apply_tree_decorations(oname, color_method, decor_patterns)
1013
+
973
1014
  line_obj[:line] = line_obj[:indent] + line_obj[:text]
974
1015
  blocks.push FCB.new(
975
1016
  chrome: true,
976
1017
  disabled: '',
977
- dname: line_obj[:indent] + oname.send(color_method),
1018
+ dname: line_obj[:indent] + decorated,
978
1019
  oname: line_obj[:text]
979
1020
  )
980
1021
  end
@@ -988,6 +1029,7 @@ module MarkdownExec
988
1029
  # @param opts [Hash] Options containing configuration for line processing.
989
1030
  # @param use_chrome [Boolean] Indicates if the chrome styling should be applied.
990
1031
  def create_and_add_chrome_blocks(blocks, fcb)
1032
+ # rubocop:disable Layout/LineLength
991
1033
  match_criteria = [
992
1034
  { color: :menu_heading1_color, format: :menu_heading1_format, match: :heading1_match, center: true, case_conversion: :upcase, wrap: true },
993
1035
  { color: :menu_heading2_color, format: :menu_heading2_format, match: :heading2_match, center: true, wrap: true },
@@ -996,6 +1038,7 @@ module MarkdownExec
996
1038
  { color: :menu_note_color, format: :menu_note_format, match: :menu_note_match, wrap: true },
997
1039
  { color: :menu_task_color, format: :menu_task_format, match: :menu_task_match, wrap: true }
998
1040
  ]
1041
+ # rubocop:enable Layout/LineLength
999
1042
  # rubocop:enable Style/UnlessElse
1000
1043
  match_criteria.each do |criteria|
1001
1044
  unless @delegate_object[criteria[:match]].present? &&
@@ -1008,6 +1051,7 @@ module MarkdownExec
1008
1051
  case_conversion: criteria[:case_conversion],
1009
1052
  center: criteria[:center],
1010
1053
  color_method: @delegate_object[criteria[:color]].to_sym,
1054
+ decor_patterns: @decor_patterns_from_delegate_object_for_block_create,
1011
1055
  format_option: @delegate_object[criteria[:format]],
1012
1056
  match_data: mbody,
1013
1057
  wrap: criteria[:wrap]
@@ -1224,9 +1268,8 @@ module MarkdownExec
1224
1268
  when :user_choice
1225
1269
  if @dml_link_state.block_name.present?
1226
1270
  # @prior_block_was_link = true
1227
- @dml_block_state.block = @dml_blocks_in_file.find do |item|
1228
- item.pub_name == @dml_link_state.block_name || item.oname == @dml_link_state.block_name
1229
- end
1271
+ @dml_block_state.block = blocks_find_by_block_name(@dml_blocks_in_file,
1272
+ @dml_link_state.block_name)
1230
1273
  @dml_link_state.block_name = nil
1231
1274
  else
1232
1275
  # puts "? - Select a block to execute (or type #{$texit} to exit):"
@@ -2433,8 +2476,7 @@ module MarkdownExec
2433
2476
  %i[blocks line]
2434
2477
  when :line
2435
2478
  unless @delegate_object[:no_chrome]
2436
- create_and_add_chrome_blocks(blocks,
2437
- fcb)
2479
+ create_and_add_chrome_blocks(blocks, fcb)
2438
2480
  end
2439
2481
  end
2440
2482
  end
@@ -3496,7 +3538,8 @@ module MarkdownExec
3496
3538
 
3497
3539
  def test_block_find_with_default
3498
3540
  blocks = [FCB.new(text: 'value1'), FCB.new(text: 'value2')]
3499
- result = HashDelegator.block_find(blocks, :text, 'missing_value', 'default')
3541
+ result = HashDelegator.block_find(blocks, :text, 'missing_value',
3542
+ 'default')
3500
3543
  assert_equal 'default', result
3501
3544
  end
3502
3545
  end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Class representing a hierarchy of substrings stored as Hash nodes
4
+ class HierarchyString
5
+ attr_accessor :substrings
6
+
7
+ # Initialize with a single hash or an array of hashes
8
+ def initialize(substrings)
9
+ @substrings = parse_substrings(substrings)
10
+ end
11
+
12
+ def map_substring_text_yield(tree, &block)
13
+ case tree
14
+ when Array
15
+ tree.each.with_index do |node, ind|
16
+ case node
17
+ when String
18
+ tree[ind] = yield node
19
+ else
20
+ map_substring_text_yield(node, &block)
21
+ end
22
+ end
23
+ when Hash
24
+ text = yield tree[:text]
25
+ tree[:text] = text
26
+
27
+ tree
28
+ when String
29
+ yield tree
30
+ else
31
+ raise ArgumentError, 'Invalid type.'
32
+ end
33
+ end
34
+
35
+ # operate on substring
36
+ def replace_text!
37
+ map_substring_text_yield(@substrings) do |node|
38
+ case node
39
+ when Hash
40
+ text = yield node[:text]
41
+ node[:text] = text
42
+ when String
43
+ yield node
44
+ end
45
+ end
46
+ end
47
+
48
+ # Method to concatenate all substrings into a single string
49
+ def concatenate
50
+ concatenate_substrings(@substrings)
51
+ end
52
+
53
+ # Method to decorate all substrings into a single string
54
+ def decorate
55
+ decorate_substrings(@substrings)
56
+ end
57
+
58
+ # Handle string inspection methods and pass them to the concatenated string
59
+ def method_missing(method_name, *arguments, &block)
60
+ if ''.respond_to?(method_name)
61
+ concatenate.send(method_name, *arguments, &block)
62
+ else
63
+ super
64
+ end
65
+ end
66
+
67
+ # Ensure proper handling of method checks
68
+ def respond_to_missing?(method_name, include_private = false)
69
+ ''.respond_to?(method_name) || super
70
+ end
71
+
72
+ private
73
+
74
+ # Parse the input substrings into a nested array of hashes structure
75
+ def parse_substrings(substrings)
76
+ case substrings
77
+ when Hash
78
+ [substrings]
79
+ when Array
80
+ substrings.map { |s| parse_substrings(s) }
81
+ else
82
+ substrings
83
+ # raise ArgumentError, 'Invalid input type. Expected Hash or Array.'
84
+ end
85
+ end
86
+
87
+ # Recursively concatenate substrings
88
+ def concatenate_substrings(substrings)
89
+ substrings.map do |s|
90
+ case s
91
+ when Hash
92
+ s[:text]
93
+ when Array
94
+ concatenate_substrings(s)
95
+ end
96
+ end.join
97
+ end
98
+
99
+ # Recursively decorate substrings
100
+ def decorate_substrings(substrings, prior_color = '')
101
+ substrings.map do |s|
102
+ case s
103
+ when Hash
104
+ if s[:color]
105
+ s[:text].send(s[:color]) + prior_color
106
+ else
107
+ s[:text]
108
+ end
109
+ when Array
110
+ decorate_substrings(s, prior_color)
111
+ end
112
+ end.join
113
+ end
114
+ end
115
+
116
+ return if $PROGRAM_NAME != __FILE__
117
+
118
+ # require 'bundler/setup'
119
+ # Bundler.require(:default)
120
+
121
+ # require 'fcb'
122
+ # require 'minitest/autorun'
123
+
124
+ # Usage
125
+ hierarchy = HierarchyString.new([{ text: 'Hello ', color: :red },
126
+ [{ text: 'World', color: :upcase },
127
+ { text: '!' }]])
128
+ puts hierarchy.decorate
129
+ puts hierarchy.length
130
+ # puts hierarchy.concatenate # Outputs: Hello World!
131
+ # puts hierarchy.upcase # Outputs: HELLO WORLD!
132
+ # puts hierarchy.length # Outputs: 12
133
+ # puts hierarchy.gsub('World', 'Ruby') # Outputs: Hello Ruby!
@@ -79,7 +79,8 @@ class InputSequencer
79
79
  )
80
80
  exit_when_bq_empty = !bq_is_empty? # true when running blocks from cli; unless "stay" is used
81
81
  loop do
82
- break if run_yield(:parse_document, now_menu.document_filename, &block) == :break
82
+ break if run_yield(:parse_document, now_menu.document_filename,
83
+ &block) == :break
83
84
 
84
85
  # self.imw_ins now_menu, 'now_menu'
85
86
 
@@ -92,7 +93,8 @@ class InputSequencer
92
93
  choice = run_yield :user_choice, &block
93
94
 
94
95
  raise 'Block not recognized.' if choice.nil?
95
- break if run_yield(:exit?, choice&.downcase, &block) # Exit loop and method to terminate the app
96
+ # Exit loop and method to terminate the app
97
+ break if run_yield(:exit?, choice&.downcase, &block)
96
98
 
97
99
  next_state = run_yield :execute_block, choice, &block
98
100
  # imw_ins next_state, 'next_state'
@@ -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.2.0'
10
+ VERSION = '2.3.0'
11
11
  end
data/lib/markdown_exec.rb CHANGED
@@ -125,7 +125,10 @@ module MarkdownExec
125
125
  return if max <= min # Ensure the range is valid
126
126
 
127
127
  # Normalize the value within the range 0 to 1
128
- normalized_value = [0, [(integer_value - min).to_f / (max - min), 1].min].max
128
+ normalized_value = [
129
+ 0,
130
+ [(integer_value - min).to_f / (max - min), 1].min
131
+ ].max
129
132
 
130
133
  # Calculate how many characters should be filled
131
134
  filled_length = (normalized_value * width).round
@@ -146,11 +149,13 @@ module MarkdownExec
146
149
  @o_color = :red
147
150
  end
148
151
 
149
- def build_menu(file_names, directory_names, found_in_block_names, files_in_directories, vbn)
152
+ def build_menu(file_names, directory_names, found_in_block_names,
153
+ files_in_directories, vbn)
150
154
  choices = []
151
155
 
152
156
  # Adding section title and data for file names
153
- choices << { disabled: '', name: "in #{file_names[:section_title]}".send(@chrome_color) }
157
+ choices << { disabled: '',
158
+ name: "in #{file_names[:section_title]}".send(@chrome_color) }
154
159
  choices += file_names[:data].map { |str| FileInMenu.for_menu(str) }
155
160
 
156
161
  # Conditionally add directory names if data is present
@@ -464,7 +469,8 @@ module MarkdownExec
464
469
  :menu_chrome_color)}"
465
470
  searcher = SearchResultsReport.new(value, [find_path])
466
471
  file_names = searcher.file_names(options, value)
467
- found_in_block_names = searcher.found_in_block_names(options, value, formspec: '%<line>s')
472
+ found_in_block_names = searcher.found_in_block_names(options, value,
473
+ formspec: '%<line>s')
468
474
  directory_names = searcher.directory_names(options, value)
469
475
 
470
476
  ### search in file contents (block names, chrome, or text)
@@ -498,9 +504,10 @@ module MarkdownExec
498
504
  details,
499
505
  highlight: [value]
500
506
  )
501
- [FileInMenu.for_menu(filename)] + nexo.map do |str|
502
- { disabled: '', name: (' ' * 20) + str }
503
- end
507
+ [FileInMenu.for_menu(filename)] +
508
+ nexo.map do |str|
509
+ { disabled: '', name: (' ' * 20) + str }
510
+ end
504
511
  end.flatten
505
512
 
506
513
  choices = MenuBuilder.new.build_menu(file_names, directory_names, found_in_block_names,
data/lib/mdoc.rb CHANGED
@@ -87,11 +87,11 @@ module MarkdownExec
87
87
  all_dependency_names = collect_unique_names(dependencies).push(nickname).uniq
88
88
  # &bt all_dependency_names.count
89
89
 
90
- # select non-chrome blocks in order of appearance in source documents
90
+ # select blocks in order of appearance in source documents
91
91
  #
92
92
  blocks = @table.select do |fcb|
93
- # !fcb.fetch(:chrome, false) && all_dependency_names.include?(fcb.pub_name)
94
- all_dependency_names.include?(fcb.pub_name)
93
+ # 2024-08-04 match nickname
94
+ all_dependency_names.include?(fcb.pub_name) || all_dependency_names.include?(fcb.nickname) || all_dependency_names.include?(fcb.oname)
95
95
  end
96
96
  # &bt blocks.count
97
97
 
@@ -99,7 +99,9 @@ module MarkdownExec
99
99
  #
100
100
  unmet_dependencies = all_dependency_names.dup
101
101
  blocks = blocks.map do |fcb|
102
- unmet_dependencies.delete(fcb.pub_name) # may not exist if block name is duplicated
102
+ # 2024-08-04 match oname for long block names
103
+ # 2024-08-04 match nickname
104
+ unmet_dependencies.delete(fcb.pub_name) || unmet_dependencies.delete(fcb.nickname) || unmet_dependencies.delete(fcb.oname) # may not exist if block name is duplicated
103
105
  if (call = fcb.call)
104
106
  [get_block_by_anyname("[#{call.match(/^%\((\S+) |\)/)[1]}]")
105
107
  .merge({ cann: call })]
@@ -262,7 +264,8 @@ module MarkdownExec
262
264
  # and `label_format_below` is "End of %{block_name}", the method will return:
263
265
  # ["Start of Example_Block", "line1", "line2", "End of Example_Block"]
264
266
  #
265
- def generate_label_body_code(fcb, block_source, label_format_above, label_format_below)
267
+ def generate_label_body_code(fcb, block_source, label_format_above,
268
+ label_format_below)
266
269
  block_name_for_bash_comment = fcb.pub_name.gsub(/\s+/, '_')
267
270
 
268
271
  label_above = if label_format_above
@@ -387,7 +390,9 @@ module MarkdownExec
387
390
 
388
391
  memo[source] = block.reqs
389
392
 
390
- block.reqs.each { |req| collect_dependencies(req, memo) unless memo.key?(req) }
393
+ block.reqs.each do |req|
394
+ collect_dependencies(req, memo) unless memo.key?(req)
395
+ end
391
396
  memo
392
397
  end
393
398
 
@@ -456,7 +461,9 @@ if $PROGRAM_NAME == __FILE__
456
461
 
457
462
  if false # must raise error
458
463
  def test_collect_dependencies_with_nonexistent_source
459
- assert_raises(RuntimeError) { @mdoc.collect_dependencies('nonexistent') }
464
+ assert_raises(RuntimeError) do
465
+ @mdoc.collect_dependencies('nonexistent')
466
+ end
460
467
  end
461
468
  end
462
469
 
data/lib/menu.src.yml CHANGED
@@ -397,6 +397,43 @@
397
397
  :opt_name: import_pattern
398
398
  :procname: val_as_str
399
399
 
400
+ - :default:
401
+ - :color_method: :bold_underline
402
+ :pattern: '\*\*_([^_]{0,64})_\*\*'
403
+
404
+ - :color_method: :bold_italic
405
+ :pattern: '\*\*~([^~]{0,64})~\*\*'
406
+
407
+ - :color_method: :bold
408
+ :pattern: '\*\*([^*]{0,64})\*\*'
409
+ - :color_method: :bold
410
+ :pattern: '__([^_]{0,64})__'
411
+
412
+ - :color_method: :underline
413
+ :pattern: '\*([^*]{0,64})\*'
414
+
415
+ - :color_method: :underline_italic
416
+ :pattern: '_~([^_]{0,64})~_'
417
+
418
+ - :color_method: strikethrough
419
+ :pattern: '~~([^~]{0,64})~~'
420
+ :description: Line-oriented text decoration (Main)
421
+ :env_var: MDE_LINE_DECOR_MAIN
422
+ :opt_name: line_decor_main
423
+ :procname: val_as_str
424
+
425
+ - :default: []
426
+ :description: Line-oriented text decoration (Post)
427
+ :env_var: MDE_LINE_DECOR_POST
428
+ :opt_name: line_decor_post
429
+ :procname: val_as_str
430
+
431
+ - :default: []
432
+ :description: Line-oriented text decoration (Pre)
433
+ :env_var: MDE_LINE_DECOR_PRE
434
+ :opt_name: line_decor_pre
435
+ :procname: val_as_str
436
+
400
437
  - :description: List blocks
401
438
  :long_name: list-blocks
402
439
  :opt_name: list_blocks
data/lib/menu.yml CHANGED
@@ -339,6 +339,35 @@
339
339
  :env_var: MDE_IMPORT_PATTERN
340
340
  :opt_name: import_pattern
341
341
  :procname: val_as_str
342
+ - :default:
343
+ - :color_method: :bold_underline
344
+ :pattern: "\\*\\*_([^_]{0,64})_\\*\\*"
345
+ - :color_method: :bold_italic
346
+ :pattern: "\\*\\*~([^~]{0,64})~\\*\\*"
347
+ - :color_method: :bold
348
+ :pattern: "\\*\\*([^*]{0,64})\\*\\*"
349
+ - :color_method: :bold
350
+ :pattern: __([^_]{0,64})__
351
+ - :color_method: :underline
352
+ :pattern: "\\*([^*]{0,64})\\*"
353
+ - :color_method: :underline_italic
354
+ :pattern: _~([^_]{0,64})~_
355
+ - :color_method: strikethrough
356
+ :pattern: "~~([^~]{0,64})~~"
357
+ :description: Line-oriented text decoration (Main)
358
+ :env_var: MDE_LINE_DECOR_MAIN
359
+ :opt_name: line_decor_main
360
+ :procname: val_as_str
361
+ - :default: []
362
+ :description: Line-oriented text decoration (Post)
363
+ :env_var: MDE_LINE_DECOR_POST
364
+ :opt_name: line_decor_post
365
+ :procname: val_as_str
366
+ - :default: []
367
+ :description: Line-oriented text decoration (Pre)
368
+ :env_var: MDE_LINE_DECOR_PRE
369
+ :opt_name: line_decor_pre
370
+ :procname: val_as_str
342
371
  - :description: List blocks
343
372
  :long_name: list-blocks
344
373
  :opt_name: list_blocks
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TextAnalyzer
4
+ # Analyzes a hierarchical structure (String or Array) and highlights segments based on the pattern
5
+ #
6
+ # @param hierarchy [String, Array] the hierarchical structure to be analyzed
7
+ # @param pattern [Regexp] the pattern to match against the text
8
+ # @param default_color [String] the color for non-matching segments
9
+ # @param match_color [String] the color for matching segments
10
+ #
11
+ # @return [Array<Hash>, Array<Array<Hash>>] an array or nested arrays of highlighted segments
12
+ #
13
+ # @raise [ArgumentError] if the hierarchy structure is neither a String nor an Array
14
+ def self.analyze_hierarchy(hierarchy, pattern, default_color, match_color)
15
+ case hierarchy
16
+ when String
17
+ highlight_segments(hierarchy, pattern, default_color, match_color)
18
+ when Hash
19
+ decorated = highlight_segments(hierarchy[:text], pattern,
20
+ hierarchy[:color], match_color)
21
+
22
+ case decorated
23
+ when String
24
+ hierarchy
25
+ when Array
26
+ if decorated.length == 1
27
+ hierarchy
28
+ else
29
+ decorated
30
+ end
31
+ else
32
+ decorated
33
+ end
34
+ when Array
35
+ hierarchy.map do |element|
36
+ analyze_hierarchy(element, pattern, default_color, match_color)
37
+ end
38
+ when HierarchyString
39
+
40
+ hierarchy.replace_text! do |substring|
41
+ substring ### no change
42
+ end
43
+
44
+ else
45
+ binding.irb
46
+ raise ArgumentError, 'Invalid hierarchy structure'
47
+ end
48
+ end
49
+
50
+ # Highlights segments of the text based on the pattern
51
+ #
52
+ # @param text [String] the text to be analyzed
53
+ # @param pattern [Regexp] the pattern to match against the text
54
+ # @param default_color [String] the color for non-matching segments
55
+ # @param match_color [String] the color for matching segments
56
+ #
57
+ # @return [Array<Hash>] an array of hashes, each containing a segment of text and its corresponding color
58
+ def self.highlight_segments(text, pattern, default_color, match_color)
59
+ segments = []
60
+
61
+ yield_matches_and_non_matches(text, pattern) do |segment, is_match|
62
+ segments << if is_match
63
+ { text: segment, color: match_color }
64
+ else
65
+ { text: segment, color: default_color }
66
+ end
67
+ end
68
+
69
+ segments
70
+ end
71
+
72
+ # Yields matching and non-matching segments of the text based on the pattern
73
+ #
74
+ # @param text [String] the text to be analyzed
75
+ # @param pattern [Regexp] the pattern to match against the text
76
+ #
77
+ # @yieldparam segment [String] a segment of the text
78
+ # @yieldparam is_match [Boolean] true if the segment matches the pattern, false otherwise
79
+ def self.yield_matches_and_non_matches(text, pattern)
80
+ last_end = 0
81
+
82
+ text.scan(pattern) do |match|
83
+ match_start = Regexp.last_match.begin(0)
84
+ match_end = Regexp.last_match.end(0)
85
+
86
+ # Yield the non-matching segment before the match
87
+ yield text[last_end...match_start], false if match_start > last_end
88
+
89
+ # Yield the matching segment
90
+ yield match.first, true
91
+
92
+ last_end = match_end
93
+ end
94
+
95
+ # Yield any remaining non-matching segment after the last match
96
+ return unless last_end < text.length
97
+
98
+ yield text[last_end..-1], false
99
+ end
100
+ 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.2.0
4
+ version: 2.3.0
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-08-04 00:00:00.000000000 Z
11
+ date: 2024-08-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard
@@ -171,6 +171,7 @@ files:
171
171
  - lib/fout.rb
172
172
  - lib/hash.rb
173
173
  - lib/hash_delegator.rb
174
+ - lib/hierarchy_string.rb
174
175
  - lib/input_sequencer.rb
175
176
  - lib/instance_method_wrapper.rb
176
177
  - lib/link_history.rb
@@ -193,6 +194,7 @@ files:
193
194
  - lib/streams_out.rb
194
195
  - lib/string_util.rb
195
196
  - lib/tap.rb
197
+ - lib/text_analyzer.rb
196
198
  homepage: https://rubygems.org/gems/markdown_exec
197
199
  licenses:
198
200
  - MIT