hiiro 0.1.56 → 0.1.58

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: 0db0a8fd9d54e542cb2b5951197e2250a1d199a66278b31bab5c003b50d8f39f
4
- data.tar.gz: bee3a32c0dc9e68379aee3963bb11b15041a9cd21715379dbbc086bff59eba17
3
+ metadata.gz: 0f183baf860848362b475e46afc9f6519e244e341cc2c63e07a5917dee51603a
4
+ data.tar.gz: de0fde039502136565942f159e499f0bb99eb76cb6a4bc25990174131b0b8779
5
5
  SHA512:
6
- metadata.gz: b59060b23668b9f6fc2818884628dccae6a95e84a5554d9666b9ce79d9bfc45f231c486039121c6a33cc8e97b144e1579cbd637131d8ca5cd0f196bfcdb4e9ff
7
- data.tar.gz: 5918af1c725ce23f22a17d273def7f656118c1a54f9fd13ee7fe0be708e82f8c8146c7a4b4c7860482c5fb85a5c8a86910c5412062a96d9c37ce9901b527bee7
6
+ metadata.gz: 8871dc00189d5e8a640ac91b0b7ac28759ead113adf8675e848fffa79282524547140b93f2ad51a9b2b7ecea69a6dd78b290306288440303129bdb8ae03817f9
7
+ data.tar.gz: 3b2a9793aea6c1d54d52141c98549f7fc01d017d4e11c5fecca424383430f917252b62b8aa62545cd4aced9771c3429d306fdbf6c7a4b1c1e590b829f9285736
data/Rakefile CHANGED
@@ -1,3 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
2
3
 
3
- task default: :build
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task default: :test
data/bin/h-plugin CHANGED
@@ -24,7 +24,7 @@ o.add_subcmd(:edit) { |*args|
24
24
  system(ENV['EDITOR'] || 'safe_nvim', __FILE__)
25
25
  else
26
26
  pm = Hiiro::PrefixMatcher.new(plugin_files) { |f| File.basename(f) }
27
- plugins = args.flat_map { |arg| pm.find_all(arg) }.uniq
27
+ plugins = args.flat_map { |arg| pm.find_all(arg).matches.map(&:item) }.uniq
28
28
 
29
29
  if plugins.none?
30
30
  puts "No matching plugins found for: #{args.map(&:inspect).join(' ')}"
data/exe/h CHANGED
@@ -4,7 +4,7 @@ require "hiiro"
4
4
  require "fileutils"
5
5
  require "pry"
6
6
 
7
- Hiiro.run(*ARGV, cwd: Dir.pwd, plugins: [:Tasks]) do
7
+ Hiiro.run(*ARGV, cwd: Dir.pwd, plugins: [Tasks]) do
8
8
  add_subcommand(:version) { |*args|
9
9
  puts Hiiro::VERSION
10
10
  }
data/hiiro.gemspec CHANGED
@@ -27,4 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_dependency "pry", "~> 0.14"
30
+
31
+ spec.add_development_dependency "minitest", "~> 5.0"
32
+ spec.add_development_dependency "rake", "~> 13.0"
30
33
  end
@@ -34,69 +34,69 @@ class Hiiro
34
34
  @block = block
35
35
  end
36
36
 
37
- def items(key = nil, &block)
38
- if key.nil? && !block_given?
39
- @items ||= original_items.map { |item| extract(item, @key, &@block) }
40
- else
41
- original_items.map { |item| extract(item, key, &block) }
42
- end
37
+ def all_items(key = nil, &block)
38
+ use_key = key.nil? && !block_given? ? @key : key
39
+ use_block = key.nil? && !block_given? ? @block : block
40
+
41
+ @all_items_cache ||= {}
42
+ cache_key = [use_key, use_block].hash
43
+
44
+ @all_items_cache[cache_key] ||= original_items.map { |item|
45
+ Item.new(
46
+ item: item,
47
+ extracted_item: extract(item, use_key, &use_block),
48
+ key: use_key,
49
+ block: use_block
50
+ )
51
+ }
43
52
  end
44
53
 
45
- def extracted_items(key = nil, &block)
46
- original_items.zip(items(key, &block))
54
+ def search(prefix, key = nil, &block)
55
+ Result.new(
56
+ matcher: self,
57
+ all_items: all_items(key, &block),
58
+ prefix: prefix,
59
+ key: key || @key,
60
+ block: block || @block
61
+ )
47
62
  end
48
63
 
49
64
  def find(prefix, key = nil, &block)
50
- extracted_items(key, &block).find { |_, extracted| matches?(extracted, prefix) }&.first
65
+ search(prefix, key, &block)
51
66
  end
52
67
 
53
68
  def find_all(prefix, key = nil, &block)
54
- extracted_items(key, &block).select { |_, extracted| matches?(extracted, prefix) }.map(&:first)
69
+ search(prefix, key, &block)
55
70
  end
56
71
 
57
72
  def resolve(prefix, key = nil, &block)
58
- pairs = extracted_items(key, &block)
59
-
60
- exact = pairs.find { |_, extracted| extracted == prefix }
61
- return exact.first if exact
62
-
63
- matches = pairs.select { |_, extracted| matches?(extracted, prefix) }
64
- matches.one? ? matches.first.first : nil
73
+ search(prefix, key, &block)
65
74
  end
66
75
 
67
76
  def find_path(prefix, key = nil, &block)
68
- matching_path_pairs(prefix, key, &block).first&.first
77
+ search_path(prefix, key, &block)
69
78
  end
70
79
 
71
80
  def find_all_paths(prefix, key = nil, &block)
72
- matching_path_pairs(prefix, key, &block).map(&:first)
81
+ search_path(prefix, key, &block)
73
82
  end
74
83
 
75
84
  def resolve_path(prefix, key = nil, &block)
76
- matches = matching_path_pairs(prefix, key, &block)
77
- return nil if matches.empty?
78
- return matches.first.first if matches.one?
85
+ search_path(prefix, key, &block)
86
+ end
79
87
 
80
- exact = matches.find { |_, path| path == prefix }
81
- exact&.first
88
+ def search_path(prefix, key = nil, &block)
89
+ PathResult.new(
90
+ matcher: self,
91
+ all_items: all_items(key, &block),
92
+ prefix: prefix,
93
+ key: key || @key,
94
+ block: block || @block
95
+ )
82
96
  end
83
97
 
84
98
  private
85
99
 
86
- def matching_path_pairs(prefix, key = nil, &block)
87
- prefixes = prefix.to_s.split('/')
88
-
89
- pairs = extracted_items(key, &block).map { |item, extracted|
90
- [item, extracted.to_s.split('/')]
91
- }
92
-
93
- prefixes.each_with_index do |seg, i|
94
- pairs = pairs.select { |_, path| path[i]&.start_with?(seg) }
95
- end
96
-
97
- pairs.map { |item, path| [item, path.join('/')] }
98
- end
99
-
100
100
  def matches?(item, prefix)
101
101
  item.to_s.start_with?(prefix.to_s)
102
102
  end
@@ -106,5 +106,136 @@ class Hiiro
106
106
  return item.send(key) if key
107
107
  item
108
108
  end
109
+
110
+ class Item
111
+ attr_reader :item, :extracted_item, :key, :block
112
+
113
+ def initialize(item:, extracted_item:, key: nil, block: nil)
114
+ @item = item
115
+ @extracted_item = extracted_item
116
+ @key = key
117
+ @block = block
118
+ end
119
+ end
120
+
121
+ class Result
122
+ attr_reader :matcher, :all_items, :key, :block, :prefix
123
+
124
+ def initialize(matcher:, all_items:, prefix:, key: nil, block: nil)
125
+ @matcher = matcher
126
+ @all_items = all_items
127
+ @prefix = prefix
128
+ @key = key
129
+ @block = block
130
+ end
131
+
132
+ def matches
133
+ @matches ||= all_items.select { |item| item.extracted_item.to_s.start_with?(prefix.to_s) }
134
+ end
135
+
136
+ def count
137
+ matches.count
138
+ end
139
+
140
+ def ambiguous?
141
+ count > 1
142
+ end
143
+
144
+ def exact_match
145
+ all_items.find { |item| item.extracted_item == prefix }
146
+ end
147
+
148
+ def match
149
+ one? ? matches.first : nil
150
+ end
151
+
152
+ def match?
153
+ matches.any?
154
+ end
155
+
156
+ def exact?
157
+ !exact_match.nil?
158
+ end
159
+
160
+ def one?
161
+ count == 1
162
+ end
163
+
164
+ # Returns item for resolve semantics: exact match, or single match, otherwise nil
165
+ def resolved
166
+ exact_match || match
167
+ end
168
+
169
+ # Returns the first matching item (for find semantics)
170
+ def first
171
+ matches.first
172
+ end
173
+ end
174
+
175
+ class PathResult
176
+ attr_reader :matcher, :all_items, :key, :block, :prefix
177
+
178
+ def initialize(matcher:, all_items:, prefix:, key: nil, block: nil)
179
+ @matcher = matcher
180
+ @all_items = all_items
181
+ @prefix = prefix
182
+ @key = key
183
+ @block = block
184
+ end
185
+
186
+ def matches
187
+ @matches ||= begin
188
+ prefixes = prefix.to_s.split('/')
189
+
190
+ items_with_paths = all_items.map { |item|
191
+ [item, item.extracted_item.to_s.split('/')]
192
+ }
193
+
194
+ prefixes.each_with_index do |seg, i|
195
+ items_with_paths = items_with_paths.select { |_, path| path[i]&.start_with?(seg) }
196
+ end
197
+
198
+ items_with_paths.map(&:first)
199
+ end
200
+ end
201
+
202
+ def count
203
+ matches.count
204
+ end
205
+
206
+ def ambiguous?
207
+ count > 1
208
+ end
209
+
210
+ def exact_match
211
+ all_items.find { |item| item.extracted_item == prefix }
212
+ end
213
+
214
+ def match
215
+ one? ? matches.first : nil
216
+ end
217
+
218
+ def match?
219
+ matches.any?
220
+ end
221
+
222
+ def exact?
223
+ !exact_match.nil?
224
+ end
225
+
226
+ def one?
227
+ count == 1
228
+ end
229
+
230
+ # Returns item for resolve semantics: exact match, or single match, otherwise nil
231
+ def resolved
232
+ exact_match || match
233
+ end
234
+
235
+ # Returns the first matching item (for find semantics)
236
+ def first
237
+ matches.first
238
+ end
239
+ end
109
240
  end
110
241
  end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.56"
2
+ VERSION = "0.1.58"
3
3
  end
data/plugins/pins.rb CHANGED
@@ -53,23 +53,27 @@ module Pins
53
53
  value
54
54
  end
55
55
 
56
- def find(partial)
56
+ def search(partial)
57
57
  Hiiro::PrefixMatcher.find(pins.keys.map(&:to_s), partial)
58
58
  end
59
59
 
60
+ def find(partial)
61
+ search(partial).match&.item
62
+ end
63
+
60
64
  def find_all(partial)
61
- Hiiro::PrefixMatcher.find_all(pins.keys.map(&:to_s), partial)
65
+ search(partial).matches.map(&:item)
62
66
  end
63
67
 
64
68
  def remove(name)
65
- all_matches = find_all(name)
69
+ result = search(name)
66
70
 
67
- if all_matches.count > 1
68
- puts "Unable to remove pin. Multiple matches: #{all_matches.inspect}"
71
+ if result.ambiguous?
72
+ puts "Unable to remove pin. Multiple matches: #{result.matches.map(&:item).inspect}"
69
73
  return
70
74
  end
71
75
 
72
- pin_name = all_matches.first
76
+ pin_name = result.match&.item
73
77
 
74
78
  pins.delete(pin_name.to_s)
75
79
  end
data/plugins/tasks.rb CHANGED
@@ -209,33 +209,33 @@ class Environment
209
209
  # Try path-based matching first (handles "parent/child" patterns)
210
210
  if abbreviated.include?('/')
211
211
  result = task_matcher.resolve_path(abbreviated)
212
- return result if result
212
+ return result.resolved&.item if result.match?
213
213
 
214
214
  # "main" refers to the parent task itself
215
215
  parent_prefix, child_prefix = abbreviated.split('/', 2)
216
216
  if 'main'.start_with?(child_prefix)
217
- return task_matcher.find(parent_prefix)
217
+ return task_matcher.find(parent_prefix).first&.item
218
218
  end
219
219
 
220
220
  nil
221
221
  else
222
- task_matcher.find(abbreviated)
222
+ task_matcher.find(abbreviated).first&.item
223
223
  end
224
224
  end
225
225
 
226
226
  def find_tree(abbreviated)
227
227
  return nil if abbreviated.nil?
228
- tree_matcher.find(abbreviated)
228
+ tree_matcher.find(abbreviated).first&.item
229
229
  end
230
230
 
231
231
  def find_session(abbreviated)
232
232
  return nil if abbreviated.nil?
233
- session_matcher.find(abbreviated)
233
+ session_matcher.find(abbreviated).first&.item
234
234
  end
235
235
 
236
236
  def find_app(abbreviated)
237
237
  return nil if abbreviated.nil?
238
- app_matcher.find(abbreviated)
238
+ app_matcher.find(abbreviated).first&.item
239
239
  end
240
240
  end
241
241
 
@@ -277,15 +277,15 @@ class TaskManager
277
277
  return slash_lookup(name) if name.include?('/')
278
278
 
279
279
  key = (scope == :subtask) ? :short_name : :name
280
- Hiiro::PrefixMatcher.new(tasks, key).find(name)
280
+ Hiiro::PrefixMatcher.new(tasks, key).find(name).first&.item
281
281
  end
282
282
 
283
283
  def task_by_tree(tree_name)
284
- environment.task_matcher.resolve(tree_name, :tree_name)
284
+ environment.task_matcher.resolve(tree_name, :tree_name).resolved&.item
285
285
  end
286
286
 
287
287
  def task_by_session(session_name)
288
- environment.task_matcher.resolve(session_name, :session_name)
288
+ environment.task_matcher.resolve(session_name, :session_name).resolved&.item
289
289
  end
290
290
 
291
291
  def current_task
@@ -549,19 +549,19 @@ class TaskManager
549
549
  return
550
550
  end
551
551
 
552
- matches = environment.app_matcher.find_all(app_name)
552
+ result = environment.app_matcher.find_all(app_name)
553
553
 
554
- case matches.count
554
+ case result.count
555
555
  when 0
556
556
  puts "ERROR: No matches found"
557
557
  puts
558
558
  puts "Possible Apps:"
559
559
  environment.all_apps.each { |a| puts format(" %-20s => %s", a.name, a.relative_path) }
560
560
  when 1
561
- print matches.first.resolve(tree_root)
561
+ print result.first.item.resolve(tree_root)
562
562
  else
563
563
  puts "Multiple matches found:"
564
- matches.each { |a| puts format(" %-20s => %s", a.name, a.relative_path) }
564
+ result.matches.each { |m| puts format(" %-20s => %s", m.item.name, m.item.relative_path) }
565
565
  end
566
566
  end
567
567
 
@@ -623,9 +623,9 @@ class TaskManager
623
623
  tree = environment.find_tree(task.tree_name)
624
624
  tree_root = tree ? tree.path : File.join(WORK_DIR, task.tree_name)
625
625
 
626
- matches = environment.app_matcher.find_all(app_name)
626
+ result = environment.app_matcher.find_all(app_name)
627
627
 
628
- case matches.count
628
+ case result.count
629
629
  when 0
630
630
  # Fallback: directory discovery
631
631
  exact = File.join(tree_root, app_name)
@@ -638,15 +638,15 @@ class TaskManager
638
638
  list_apps
639
639
  nil
640
640
  when 1
641
- app = matches.first
641
+ app = result.first.item
642
642
  [app.name, app.resolve(tree_root)]
643
643
  else
644
- exact = matches.find { |a| a.name == app_name }
644
+ exact = result.matches.find { |m| m.item.name == app_name }
645
645
  if exact
646
- [exact.name, exact.resolve(tree_root)]
646
+ [exact.item.name, exact.item.resolve(tree_root)]
647
647
  else
648
648
  puts "ERROR: '#{app_name}' matches multiple apps:"
649
- matches.each { |a| puts " #{a.name}" }
649
+ result.matches.each { |m| puts " #{m.item.name}" }
650
650
  nil
651
651
  end
652
652
  end
@@ -696,7 +696,7 @@ class TaskManager
696
696
  data = load_tasks
697
697
  data['tasks'] ||= []
698
698
  data['tasks'].reject! { |t| t['name'] == task.name }
699
- data['tasks'] << task.to_h
699
+ data['tasks'] << task.to_h.transform_keys(&:to_s)
700
700
  save_tasks(data)
701
701
  end
702
702
 
data/script/test ADDED
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ bundle exec rake test "$@"
4
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiiro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.56
4
+ version: 0.1.58
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
27
55
  description: Build multi-command CLI tools with subcommand dispatch, abbreviation
28
56
  matching, and a plugin system. Similar to git or docker command structure.
29
57
  email:
@@ -92,6 +120,7 @@ files:
92
120
  - script/install
93
121
  - script/publish
94
122
  - script/sync
123
+ - script/test
95
124
  - script/update
96
125
  homepage: https://github.com/unixsuperhero/hiiro
97
126
  licenses: