howzit 2.1.26 → 2.1.28
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 +4 -4
- data/CHANGELOG.md +36 -0
- data/lib/howzit/buildnote.rb +112 -38
- data/lib/howzit/colors.rb +2 -2
- data/lib/howzit/run_report.rb +10 -10
- data/lib/howzit/stringutils.rb +36 -2
- data/lib/howzit/topic.rb +11 -3
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +1 -1
- data/spec/run_report_spec.rb +20 -0
- data/spec/topic_spec.rb +29 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cd3edf8d90a9cf744655479a0d81d11453d30d4d32f1f43cd719b6133b7c18d1
|
|
4
|
+
data.tar.gz: d8c862edff83fb8a456057682bf5063ddd7dfca44e37e45a97faee14482a1fdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08ef313bfa86f22c700610a114bafbbcf6aa769667ada6c007dad65370fc90cefad1562df99227676bc4e70fdaf0c82d751a0ecb86321ef09f0955e8d5bc1c23'
|
|
7
|
+
data.tar.gz: 026b8782c3f889adbf18dd4841faa709fd460eed3710c1402c089f5926f50c0340b2b9ec471baaf268cf456256a7ee3de8ad35741bac7b57f2fe0f0a931d8442
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
### 2.1.28
|
|
2
|
+
|
|
3
|
+
2025-12-31 10:21
|
|
4
|
+
|
|
5
|
+
#### CHANGED
|
|
6
|
+
|
|
7
|
+
- Refactored code to use more concise unless statement syntax
|
|
8
|
+
|
|
9
|
+
#### NEW
|
|
10
|
+
|
|
11
|
+
- Added "token" matching mode for multi-word searches that matches words in order
|
|
12
|
+
|
|
13
|
+
#### IMPROVED
|
|
14
|
+
|
|
15
|
+
- Enhanced fuzzy matching to use token-based approach for multi-word searches
|
|
16
|
+
- Default partial matching now uses token-based matching for better multi-word search results
|
|
17
|
+
|
|
18
|
+
#### FIXED
|
|
19
|
+
|
|
20
|
+
- Improved error handling when topics are not found or invalid
|
|
21
|
+
- Fixed topic selection when using interactive choose mode with topics that have named arguments
|
|
22
|
+
- Added nil checks to prevent errors when arguments array is nil
|
|
23
|
+
- Improved handling of empty search terms to prevent matching all topics
|
|
24
|
+
|
|
25
|
+
### 2.1.27
|
|
26
|
+
|
|
27
|
+
2025-12-26 08:59
|
|
28
|
+
|
|
29
|
+
#### IMPROVED
|
|
30
|
+
|
|
31
|
+
- Task titles now support variable substitution using ${VAR} syntax, so titles like "@run(echo test) Title with ${var}" will replace ${var} with its value from topic metadata. This applies to all task types (run, copy, open, include) and code block titles.
|
|
32
|
+
|
|
33
|
+
#### FIXED
|
|
34
|
+
|
|
35
|
+
- Task and topic names containing dollar signs (like $text$ or ${VAR}) now display correctly in run report output without causing color code interpretation issues. Dollar signs are properly escaped during formatting and unescaped in the final output.
|
|
36
|
+
|
|
1
37
|
### 2.1.26
|
|
2
38
|
|
|
3
39
|
2025-12-26 04:53
|
data/lib/howzit/buildnote.rb
CHANGED
|
@@ -291,21 +291,17 @@ module Howzit
|
|
|
291
291
|
title = File.basename(Dir.pwd)
|
|
292
292
|
# prompt = TTY::Prompt.new
|
|
293
293
|
if default
|
|
294
|
-
|
|
294
|
+
title
|
|
295
295
|
else
|
|
296
296
|
title = Prompt.get_line('{bw}Project name{x}'.c, default: title)
|
|
297
297
|
end
|
|
298
298
|
summary = ''
|
|
299
|
-
unless default
|
|
300
|
-
summary = Prompt.get_line('{bw}Project summary{x}'.c)
|
|
301
|
-
end
|
|
299
|
+
summary = Prompt.get_line('{bw}Project summary{x}'.c) unless default
|
|
302
300
|
|
|
303
301
|
# Template selection
|
|
304
302
|
selected_templates = []
|
|
305
303
|
template_metadata = {}
|
|
306
|
-
unless default
|
|
307
|
-
selected_templates, template_metadata = select_templates_for_note(title)
|
|
308
|
-
end
|
|
304
|
+
selected_templates, template_metadata = select_templates_for_note(title) unless default
|
|
309
305
|
|
|
310
306
|
fname = 'buildnotes.md'
|
|
311
307
|
unless default
|
|
@@ -314,9 +310,7 @@ module Howzit
|
|
|
314
310
|
|
|
315
311
|
# Build metadata section
|
|
316
312
|
metadata_lines = []
|
|
317
|
-
unless selected_templates.empty?
|
|
318
|
-
metadata_lines << "template: #{selected_templates.join(',')}"
|
|
319
|
-
end
|
|
313
|
+
metadata_lines << "template: #{selected_templates.join(',')}" unless selected_templates.empty?
|
|
320
314
|
template_metadata.each do |key, value|
|
|
321
315
|
metadata_lines << "#{key}: #{value}"
|
|
322
316
|
end
|
|
@@ -386,7 +380,7 @@ module Howzit
|
|
|
386
380
|
##
|
|
387
381
|
## @return [Array<Array, Hash>] Array of [selected_template_names, required_vars_hash]
|
|
388
382
|
##
|
|
389
|
-
def select_templates_for_note(
|
|
383
|
+
def select_templates_for_note(_project_title)
|
|
390
384
|
template_dir = Howzit.config.template_folder
|
|
391
385
|
template_glob = File.join(template_dir, '*.md')
|
|
392
386
|
template_files = Dir.glob(template_glob)
|
|
@@ -879,7 +873,21 @@ module Howzit
|
|
|
879
873
|
## single topic
|
|
880
874
|
##
|
|
881
875
|
def process_topic(topic, run, single: false)
|
|
882
|
-
|
|
876
|
+
if topic.is_a?(String)
|
|
877
|
+
matches = find_topic(topic)
|
|
878
|
+
new_topic = matches.empty? ? nil : matches[0]
|
|
879
|
+
else
|
|
880
|
+
new_topic = begin
|
|
881
|
+
topic.dup
|
|
882
|
+
rescue StandardError
|
|
883
|
+
topic
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
if new_topic.nil?
|
|
888
|
+
Howzit.console.warn "{br}ERROR:{xr} Topic not found or invalid: {bw}#{topic.is_a?(String) ? topic : topic.inspect}{x}".c
|
|
889
|
+
return ''
|
|
890
|
+
end
|
|
883
891
|
|
|
884
892
|
output = if run
|
|
885
893
|
new_topic.run
|
|
@@ -946,13 +954,67 @@ module Howzit
|
|
|
946
954
|
when :all
|
|
947
955
|
topic_matches.concat(matches.sort_by(&:title))
|
|
948
956
|
else
|
|
949
|
-
|
|
957
|
+
selected_titles = Prompt.choose(matches.map(&:title), height: :max, query: Howzit.options[:grep])
|
|
958
|
+
# Convert selected titles back to topic objects
|
|
959
|
+
selected_titles.each do |title|
|
|
960
|
+
matched_topic = matches.find { |t| t.title == title }
|
|
961
|
+
topic_matches.push(matched_topic) if matched_topic
|
|
962
|
+
end
|
|
950
963
|
end
|
|
964
|
+
topic_matches.compact! # Remove any nil values
|
|
951
965
|
process_topic_matches(topic_matches, output)
|
|
952
966
|
elsif Howzit.options[:choose]
|
|
953
967
|
topic_matches = []
|
|
954
968
|
titles = Prompt.choose(list_topics, height: :max)
|
|
955
|
-
titles.each
|
|
969
|
+
titles.each do |selected_title|
|
|
970
|
+
selected_title = selected_title.strip
|
|
971
|
+
matched_topic = nil
|
|
972
|
+
|
|
973
|
+
# First, try to match by reconstructing the formatted title exactly as list_topics does
|
|
974
|
+
@topics.each do |topic|
|
|
975
|
+
formatted_title = topic.title.dup
|
|
976
|
+
formatted_title += "(#{topic.named_args.keys.join(', ')})" unless topic.named_args.empty?
|
|
977
|
+
formatted_title = formatted_title.strip
|
|
978
|
+
|
|
979
|
+
# Normalize both titles for comparison (remove extra whitespace, normalize case)
|
|
980
|
+
normalized_formatted = formatted_title.downcase.gsub(/\s+/, ' ')
|
|
981
|
+
normalized_selected = selected_title.downcase.gsub(/\s+/, ' ')
|
|
982
|
+
|
|
983
|
+
if formatted_title == selected_title || normalized_formatted == normalized_selected
|
|
984
|
+
matched_topic = topic
|
|
985
|
+
break
|
|
986
|
+
end
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
# If still not found, try matching by base title (without args)
|
|
990
|
+
unless matched_topic
|
|
991
|
+
clean_selected = selected_title.sub(/ *\([^)]*\) *$/, '').strip
|
|
992
|
+
matched_topic = @topics.find do |topic|
|
|
993
|
+
base_title = topic.title.strip
|
|
994
|
+
base_title.downcase == clean_selected.downcase || base_title == clean_selected
|
|
995
|
+
end
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
# Last resort: use find_topic with the cleaned title
|
|
999
|
+
unless matched_topic
|
|
1000
|
+
clean_selected = selected_title.sub(/ *\([^)]*\) *$/, '').strip
|
|
1001
|
+
matches = find_topic(clean_selected)
|
|
1002
|
+
matched_topic = matches[0] unless matches.empty?
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
if matched_topic
|
|
1006
|
+
topic_matches.push(matched_topic)
|
|
1007
|
+
else
|
|
1008
|
+
Howzit.console.warn "{br}WARNING:{xr} Could not find topic matching: {bw}#{selected_title}{x}".c
|
|
1009
|
+
end
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
topic_matches.compact! # Remove any nil values
|
|
1013
|
+
if topic_matches.empty?
|
|
1014
|
+
output.push(%({bR}ERROR:{xr} No valid topics found from selection{x}\n).c)
|
|
1015
|
+
Util.show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
|
|
1016
|
+
Process.exit 1
|
|
1017
|
+
end
|
|
956
1018
|
process_topic_matches(topic_matches, output)
|
|
957
1019
|
elsif !Howzit.cli_args.empty?
|
|
958
1020
|
# Check if first arg is "default"
|
|
@@ -964,23 +1026,21 @@ module Howzit
|
|
|
964
1026
|
topic_matches = collect_topic_matches(search, output)
|
|
965
1027
|
process_topic_matches(topic_matches, output)
|
|
966
1028
|
end
|
|
967
|
-
|
|
1029
|
+
elsif Howzit.options[:run]
|
|
968
1030
|
# No arguments
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
process_default_metadata(output)
|
|
973
|
-
else
|
|
974
|
-
Howzit.run_log = []
|
|
975
|
-
Howzit.multi_topic_run = topics.length > 1
|
|
976
|
-
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
977
|
-
finalize_output(output)
|
|
978
|
-
end
|
|
1031
|
+
# Check for default metadata when running with no args
|
|
1032
|
+
if @metadata.key?('default')
|
|
1033
|
+
process_default_metadata(output)
|
|
979
1034
|
else
|
|
980
|
-
|
|
1035
|
+
Howzit.run_log = []
|
|
1036
|
+
Howzit.multi_topic_run = topics.length > 1
|
|
981
1037
|
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
982
1038
|
finalize_output(output)
|
|
983
1039
|
end
|
|
1040
|
+
else
|
|
1041
|
+
# Show all topics
|
|
1042
|
+
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
1043
|
+
finalize_output(output)
|
|
984
1044
|
end
|
|
985
1045
|
end
|
|
986
1046
|
|
|
@@ -996,7 +1056,12 @@ module Howzit
|
|
|
996
1056
|
def collect_topic_matches(search_terms, output)
|
|
997
1057
|
all_matches = []
|
|
998
1058
|
|
|
1059
|
+
# If no search terms, return empty (don't match all topics)
|
|
1060
|
+
return [] if search_terms.nil? || search_terms.empty?
|
|
1061
|
+
|
|
999
1062
|
search_terms.each do |s|
|
|
1063
|
+
next if s.nil? || s.strip.empty?
|
|
1064
|
+
|
|
1000
1065
|
# First check for exact whole-word matches
|
|
1001
1066
|
exact_matches = find_topic_exact(s)
|
|
1002
1067
|
|
|
@@ -1024,7 +1089,7 @@ module Howzit
|
|
|
1024
1089
|
##
|
|
1025
1090
|
## @return [Array] Array of matched topics
|
|
1026
1091
|
##
|
|
1027
|
-
def resolve_fuzzy_matches(search_term,
|
|
1092
|
+
def resolve_fuzzy_matches(search_term, _output)
|
|
1028
1093
|
matches = find_topic(search_term)
|
|
1029
1094
|
|
|
1030
1095
|
return [] if matches.empty?
|
|
@@ -1039,11 +1104,12 @@ module Howzit
|
|
|
1039
1104
|
else
|
|
1040
1105
|
titles = matches.map(&:title)
|
|
1041
1106
|
res = Prompt.choose(titles, query: search_term)
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1107
|
+
# Convert selected titles back to topic objects from the original matches
|
|
1108
|
+
res.flat_map do |title|
|
|
1109
|
+
matched_topic = matches.find { |t| t.title == title }
|
|
1110
|
+
matched_topic || find_topic(title)[0]
|
|
1111
|
+
end.compact
|
|
1112
|
+
|
|
1047
1113
|
end
|
|
1048
1114
|
end
|
|
1049
1115
|
|
|
@@ -1065,7 +1131,15 @@ module Howzit
|
|
|
1065
1131
|
end
|
|
1066
1132
|
|
|
1067
1133
|
if !topic_matches.empty?
|
|
1068
|
-
topic_matches.map!
|
|
1134
|
+
topic_matches.map! do |topic|
|
|
1135
|
+
if topic.is_a?(String)
|
|
1136
|
+
matches = find_topic(topic)
|
|
1137
|
+
matches.empty? ? nil : matches[0]
|
|
1138
|
+
elsif topic.is_a?(Howzit::Topic)
|
|
1139
|
+
topic
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
topic_matches.compact! # Remove any nil values from failed matches
|
|
1069
1143
|
topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], single: true)) }
|
|
1070
1144
|
else
|
|
1071
1145
|
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
@@ -1107,11 +1181,11 @@ module Howzit
|
|
|
1107
1181
|
# Run each topic with its specific arguments
|
|
1108
1182
|
topic_specs.each do |topic_match, args|
|
|
1109
1183
|
# Set arguments if provided, otherwise clear them
|
|
1110
|
-
if args && !args.empty?
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1184
|
+
Howzit.arguments = if args && !args.empty?
|
|
1185
|
+
args.split(/ *, */).map(&:render_arguments)
|
|
1186
|
+
else
|
|
1187
|
+
[]
|
|
1188
|
+
end
|
|
1115
1189
|
output.push(process_topic(topic_match, Howzit.options[:run], single: true))
|
|
1116
1190
|
end
|
|
1117
1191
|
finalize_output(output)
|
data/lib/howzit/colors.rb
CHANGED
|
@@ -288,8 +288,8 @@ module Howzit
|
|
|
288
288
|
d: dark, b: bold, u: underline, i: italic, x: reset }
|
|
289
289
|
|
|
290
290
|
result = fmt.empty? ? input : format(fmt, colors)
|
|
291
|
-
# Unescape braces that were escaped to prevent color code interpretation
|
|
292
|
-
result.gsub(/\\\{/, '{').gsub(/\\\}/, '}')
|
|
291
|
+
# Unescape braces and dollar signs that were escaped to prevent color code interpretation
|
|
292
|
+
result.gsub(/\\\{/, '{').gsub(/\\\}/, '}').gsub(/\\\$/, '$')
|
|
293
293
|
end
|
|
294
294
|
end
|
|
295
295
|
|
data/lib/howzit/run_report.rb
CHANGED
|
@@ -30,13 +30,13 @@ module Howzit
|
|
|
30
30
|
symbol = entry[:success] ? '✅' : '❌'
|
|
31
31
|
parts = ["#{symbol} "]
|
|
32
32
|
if prefix_topic && entry[:topic] && !entry[:topic].empty?
|
|
33
|
-
# Escape braces in topic name to prevent color code interpretation
|
|
34
|
-
topic_escaped = entry[:topic].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
33
|
+
# Escape braces and dollar signs in topic name to prevent color code interpretation
|
|
34
|
+
topic_escaped = entry[:topic].gsub(/\{/, '\\{').gsub(/\}/, '\\}').gsub(/\$/, '\\$')
|
|
35
35
|
parts << "{bw}#{topic_escaped}{x}: "
|
|
36
36
|
end
|
|
37
|
-
# Escape braces in task name to prevent color code interpretation
|
|
38
|
-
task_escaped = entry[:task].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
39
|
-
parts << "{by}#{task_escaped}{x}"
|
|
37
|
+
# Escape braces and dollar signs in task name to prevent color code interpretation
|
|
38
|
+
task_escaped = entry[:task].gsub(/\{/, '\\{').gsub(/\}/, '\\}').gsub(/\$/, '\\$')
|
|
39
|
+
parts << "{by}#{task_escaped} {x}"
|
|
40
40
|
unless entry[:success]
|
|
41
41
|
reason = entry[:exit_status] ? "exit code #{entry[:exit_status]}" : 'failed'
|
|
42
42
|
parts << " {br}(#{reason}){x}"
|
|
@@ -82,15 +82,15 @@ module Howzit
|
|
|
82
82
|
task_parts_plain = []
|
|
83
83
|
|
|
84
84
|
if prefix_topic && entry[:topic] && !entry[:topic].empty?
|
|
85
|
-
# Escape braces in topic name to prevent color code interpretation
|
|
86
|
-
topic_escaped = entry[:topic].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
85
|
+
# Escape braces and dollar signs in topic name to prevent color code interpretation
|
|
86
|
+
topic_escaped = entry[:topic].gsub(/\{/, '\\{').gsub(/\}/, '\\}').gsub(/\$/, '\\$')
|
|
87
87
|
task_parts << "{bw}#{topic_escaped}{x}: "
|
|
88
88
|
task_parts_plain << "#{entry[:topic]}: "
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# Escape braces in task name to prevent color code interpretation
|
|
92
|
-
task_escaped = entry[:task].gsub(/\{/, '\\{').gsub(/\}/, '\\}')
|
|
93
|
-
task_parts << "{by}#{task_escaped}{x}"
|
|
91
|
+
# Escape braces and dollar signs in task name to prevent color code interpretation
|
|
92
|
+
task_escaped = entry[:task].gsub(/\{/, '\\{').gsub(/\}/, '\\}').gsub(/\$/, '\\$')
|
|
93
|
+
task_parts << "{by}#{task_escaped} {x}"
|
|
94
94
|
task_parts_plain << entry[:task]
|
|
95
95
|
|
|
96
96
|
unless entry[:success]
|
data/lib/howzit/stringutils.rb
CHANGED
|
@@ -175,9 +175,43 @@ module Howzit
|
|
|
175
175
|
when 'beginswith'
|
|
176
176
|
/^#{self}/i
|
|
177
177
|
when 'fuzzy'
|
|
178
|
-
|
|
178
|
+
# For fuzzy matching, use token-based matching where each word gets fuzzy character matching
|
|
179
|
+
# This allows "lst tst" to match "List Available Tests" by applying fuzzy matching to each token
|
|
180
|
+
words = split(/\s+/).reject(&:empty?)
|
|
181
|
+
if words.length > 1
|
|
182
|
+
# Multiple words: apply character-by-character fuzzy matching to each word token
|
|
183
|
+
# Then allow flexible matching between words
|
|
184
|
+
pattern = words.map do |w|
|
|
185
|
+
# Apply fuzzy matching to each word (character-by-character with up to 3 chars between)
|
|
186
|
+
w.split(//).map { |c| Regexp.escape(c) }.join('.{0,3}?')
|
|
187
|
+
end.join('.*')
|
|
188
|
+
/#{pattern}/i
|
|
189
|
+
else
|
|
190
|
+
# Single word: character-by-character fuzzy matching for flexibility
|
|
191
|
+
/#{split(//).join('.{0,3}?')}/i
|
|
192
|
+
end
|
|
193
|
+
when 'token'
|
|
194
|
+
# Token-based matching: match words in order with any text between them
|
|
195
|
+
# "list tests" matches "list available tests", "list of tests", etc.
|
|
196
|
+
words = split(/\s+/).reject(&:empty?)
|
|
197
|
+
if words.length > 1
|
|
198
|
+
pattern = words.map { |w| Regexp.escape(w) }.join('.*')
|
|
199
|
+
/#{pattern}/i
|
|
200
|
+
else
|
|
201
|
+
/#{Regexp.escape(self)}/i
|
|
202
|
+
end
|
|
179
203
|
else
|
|
180
|
-
|
|
204
|
+
# Default 'partial' mode: token-based matching for multi-word searches
|
|
205
|
+
# This allows "list tests" to match "list available tests"
|
|
206
|
+
words = split(/\s+/).reject(&:empty?)
|
|
207
|
+
if words.length > 1
|
|
208
|
+
# Token-based: match words in order with any text between
|
|
209
|
+
pattern = words.map { |w| Regexp.escape(w) }.join('.*')
|
|
210
|
+
/#{pattern}/i
|
|
211
|
+
else
|
|
212
|
+
# Single word: simple substring match
|
|
213
|
+
/#{Regexp.escape(self)}/i
|
|
214
|
+
end
|
|
181
215
|
end
|
|
182
216
|
end
|
|
183
217
|
|
data/lib/howzit/topic.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Howzit
|
|
|
37
37
|
args.each_with_index do |arg, idx|
|
|
38
38
|
arg_name, default = arg.split(/:/).map(&:strip)
|
|
39
39
|
|
|
40
|
-
@named_args[arg_name] = if Howzit.arguments.count >= idx + 1
|
|
40
|
+
@named_args[arg_name] = if Howzit.arguments && Howzit.arguments.count >= idx + 1
|
|
41
41
|
Howzit.arguments[idx]
|
|
42
42
|
else
|
|
43
43
|
default
|
|
@@ -175,6 +175,7 @@ module Howzit
|
|
|
175
175
|
return [] if matches.empty?
|
|
176
176
|
|
|
177
177
|
topic = matches[0]
|
|
178
|
+
return [] if topic.nil?
|
|
178
179
|
|
|
179
180
|
rule = '{kKd}'
|
|
180
181
|
color = '{Kyd}'
|
|
@@ -302,26 +303,31 @@ module Howzit
|
|
|
302
303
|
title: title.dup, # Make a copy to avoid reference issues
|
|
303
304
|
action: obj,
|
|
304
305
|
parent: self }
|
|
306
|
+
# Set named_arguments before processing titles for variable substitution
|
|
307
|
+
Howzit.named_arguments = @named_args
|
|
305
308
|
case cmd
|
|
306
309
|
when /include/i
|
|
307
310
|
if title =~ /\[(.*?)\] *$/
|
|
308
|
-
Howzit.named_arguments = @named_args
|
|
309
311
|
args = Regexp.last_match(1).split(/ *, */).map(&:render_arguments)
|
|
310
312
|
Howzit.arguments = args
|
|
311
313
|
arguments
|
|
312
314
|
title.sub!(/ *\[.*?\] *$/, '')
|
|
313
|
-
task_args[:title] = title
|
|
314
315
|
end
|
|
316
|
+
# Apply variable substitution to title after bracket processing
|
|
317
|
+
task_args[:title] = title.render_arguments
|
|
315
318
|
|
|
316
319
|
task_args[:type] = :include
|
|
317
320
|
task_args[:arguments] = Howzit.named_arguments
|
|
318
321
|
when /run/i
|
|
319
322
|
task_args[:type] = :run
|
|
323
|
+
task_args[:title] = title.render_arguments
|
|
320
324
|
when /copy/i
|
|
321
325
|
task_args[:type] = :copy
|
|
322
326
|
task_args[:action] = Shellwords.escape(obj)
|
|
327
|
+
task_args[:title] = title.render_arguments
|
|
323
328
|
when /open|url/i
|
|
324
329
|
task_args[:type] = :open
|
|
330
|
+
task_args[:title] = title.render_arguments
|
|
325
331
|
end
|
|
326
332
|
|
|
327
333
|
task_args
|
|
@@ -373,6 +379,8 @@ module Howzit
|
|
|
373
379
|
if c[:cmd].nil?
|
|
374
380
|
optional, default = define_optional(c[:optional2])
|
|
375
381
|
title = c[:title2].nil? ? '' : c[:title2].strip
|
|
382
|
+
# Apply variable substitution to block title
|
|
383
|
+
title = title.render_arguments if title && !title.empty?
|
|
376
384
|
block = c[:block]&.strip
|
|
377
385
|
runnable << Howzit::Task.new({ type: :block,
|
|
378
386
|
title: title,
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
|
@@ -13,7 +13,7 @@ COLOR_FILE = 'theme.yaml'
|
|
|
13
13
|
IGNORE_FILE = 'ignore.yaml'
|
|
14
14
|
|
|
15
15
|
# Available options for matching method
|
|
16
|
-
MATCHING_OPTIONS = %w[partial exact fuzzy beginswith].freeze
|
|
16
|
+
MATCHING_OPTIONS = %w[partial exact fuzzy beginswith token].freeze
|
|
17
17
|
|
|
18
18
|
# Available options for multiple_matches method
|
|
19
19
|
MULTIPLE_OPTIONS = %w[first best all choose].freeze
|
data/spec/run_report_spec.rb
CHANGED
|
@@ -47,5 +47,25 @@ describe Howzit::RunReport do
|
|
|
47
47
|
# Second line should be separator
|
|
48
48
|
expect(lines[1]).to match(/^\|[\s:-]+\|[\s:-]+\|$/)
|
|
49
49
|
end
|
|
50
|
+
|
|
51
|
+
it 'preserves dollar signs in task names without interfering with color codes' do
|
|
52
|
+
Howzit::RunReport.log({ topic: 'Test Topic', task: '$text$', success: true, exit_status: 0 })
|
|
53
|
+
Howzit::RunReport.log({ topic: 'Test', task: 'echo ${VAR}', success: true, exit_status: 0 })
|
|
54
|
+
plain = Howzit::RunReport.format.uncolor
|
|
55
|
+
expect(plain).to include('$text$')
|
|
56
|
+
expect(plain).to include('${VAR}')
|
|
57
|
+
# Verify dollar signs are preserved and not causing color code issues
|
|
58
|
+
expect(plain).not_to include('{x}')
|
|
59
|
+
expect(plain.scan(/\$\{x\}/).length).to eq(0)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'preserves dollar signs in topic names without interfering with color codes' do
|
|
63
|
+
Howzit.multi_topic_run = true
|
|
64
|
+
Howzit::RunReport.log({ topic: '$dollar$ Topic', task: 'Some Task', success: true, exit_status: 0 })
|
|
65
|
+
plain = Howzit::RunReport.format.uncolor
|
|
66
|
+
expect(plain).to include('$dollar$ Topic')
|
|
67
|
+
expect(plain).not_to include('{x}')
|
|
68
|
+
expect(plain.scan(/\$\{x\}/).length).to eq(0)
|
|
69
|
+
end
|
|
50
70
|
end
|
|
51
71
|
|
data/spec/topic_spec.rb
CHANGED
|
@@ -101,4 +101,33 @@ describe Howzit::Topic do
|
|
|
101
101
|
expect(topic.print_out({ single: true, header: true }).join("\n").uncolor).to match(/▶ ls -1/)
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
describe '.arguments' do
|
|
107
|
+
before do
|
|
108
|
+
Howzit.arguments = []
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'extracts named arguments from topic title with defaults' do
|
|
112
|
+
topic = Howzit::Topic.new('Test Topic (var1:default1, var2:default2)', 'Content')
|
|
113
|
+
expect(topic.named_args['var1']).to eq('default1')
|
|
114
|
+
expect(topic.named_args['var2']).to eq('default2')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'does nothing when title has no arguments' do
|
|
118
|
+
topic = Howzit::Topic.new('Test Topic', 'Content')
|
|
119
|
+
expect(topic.named_args).to eq({})
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'cleans title after extracting arguments' do
|
|
123
|
+
topic = Howzit::Topic.new('Test Topic (var:val)', 'Content')
|
|
124
|
+
expect(topic.title).to eq('Test Topic')
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'uses provided arguments over defaults' do
|
|
128
|
+
Howzit.arguments = ['provided_value']
|
|
129
|
+
topic = Howzit::Topic.new('Test Topic (var:default_value)', 'Content')
|
|
130
|
+
expect(topic.named_args['var']).to eq('provided_value')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
104
133
|
end
|