howzit 2.1.27 → 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 +24 -0
- data/lib/howzit/buildnote.rb +112 -38
- data/lib/howzit/stringutils.rb +36 -2
- data/lib/howzit/topic.rb +2 -1
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +1 -1
- 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,27 @@
|
|
|
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
|
+
|
|
1
25
|
### 2.1.27
|
|
2
26
|
|
|
3
27
|
2025-12-26 08:59
|
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/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}'
|
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
|