markdown_exec 2.2.0 → 2.3.0

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: 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