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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfddcd9969d36eaa46a5441a912b60a0d4622c7536ffd34c6aebe83cc3c840a3
4
- data.tar.gz: 9e5d7fee17c7931fc03b455eebe411f1490e1c27d70545f5e5a3d62f492a2f50
3
+ metadata.gz: cd3edf8d90a9cf744655479a0d81d11453d30d4d32f1f43cd719b6133b7c18d1
4
+ data.tar.gz: d8c862edff83fb8a456057682bf5063ddd7dfca44e37e45a97faee14482a1fdd
5
5
  SHA512:
6
- metadata.gz: '019b3813e1f3ed8a79f70711a0f2dae38a83f0cfca205a32d9ab14794ba9815ce8d971d26f6bed0afbb90ce9d4ffd5d0d926cf50ef965cf806ffa45565203611'
7
- data.tar.gz: c77fdeea70957604027d8152517498e8fe6df7043f36e139f8a81b23daca958d701a2dda786385921a6d9bf5c01acaca068375e7d3b9bd680091a3e988275069
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
@@ -291,21 +291,17 @@ module Howzit
291
291
  title = File.basename(Dir.pwd)
292
292
  # prompt = TTY::Prompt.new
293
293
  if default
294
- input = title
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(project_title)
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
- new_topic = topic.is_a?(String) ? find_topic(topic)[0] : topic.dup
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
- topic_matches.concat(Prompt.choose(matches.map(&:title), height: :max, query: Howzit.options[:grep]))
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 { |title| topic_matches.push(find_topic(title)[0]) }
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
- else
1029
+ elsif Howzit.options[:run]
968
1030
  # No arguments
969
- if Howzit.options[:run]
970
- # Check for default metadata when running with no args
971
- if @metadata.key?('default')
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
- # Show all topics
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, output)
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
- old_matching = Howzit.options[:matching]
1043
- Howzit.options[:matching] = 'exact'
1044
- selected = res.flat_map { |title| find_topic(title) }
1045
- Howzit.options[:matching] = old_matching
1046
- selected
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! { |topic| topic.is_a?(String) ? find_topic(topic)[0] : topic }
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
- Howzit.arguments = args.split(/ *, */).map(&:render_arguments)
1112
- else
1113
- Howzit.arguments = []
1114
- end
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)
@@ -175,9 +175,43 @@ module Howzit
175
175
  when 'beginswith'
176
176
  /^#{self}/i
177
177
  when 'fuzzy'
178
- /#{split(//).join('.{0,3}?')}/i
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
- /#{self}/i
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}'
@@ -3,5 +3,5 @@
3
3
  # Primary module for this gem.
4
4
  module Howzit
5
5
  # Current Howzit version.
6
- VERSION = '2.1.27'
6
+ VERSION = '2.1.28'
7
7
  end
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: howzit
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.27
4
+ version: 2.1.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra