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 +4 -4
- data/Rakefile +8 -1
- data/bin/h-plugin +1 -1
- data/exe/h +1 -1
- data/hiiro.gemspec +3 -0
- data/lib/hiiro/prefix_matcher.rb +169 -38
- data/lib/hiiro/version.rb +1 -1
- data/plugins/pins.rb +10 -6
- data/plugins/tasks.rb +20 -20
- data/script/test +4 -0
- metadata +30 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f183baf860848362b475e46afc9f6519e244e341cc2c63e07a5917dee51603a
|
|
4
|
+
data.tar.gz: de0fde039502136565942f159e499f0bb99eb76cb6a4bc25990174131b0b8779
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8871dc00189d5e8a640ac91b0b7ac28759ead113adf8675e848fffa79282524547140b93f2ad51a9b2b7ecea69a6dd78b290306288440303129bdb8ae03817f9
|
|
7
|
+
data.tar.gz: 3b2a9793aea6c1d54d52141c98549f7fc01d017d4e11c5fecca424383430f917252b62b8aa62545cd4aced9771c3429d306fdbf6c7a4b1c1e590b829f9285736
|
data/Rakefile
CHANGED
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
data/hiiro.gemspec
CHANGED
data/lib/hiiro/prefix_matcher.rb
CHANGED
|
@@ -34,69 +34,69 @@ class Hiiro
|
|
|
34
34
|
@block = block
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
46
|
-
|
|
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
|
-
|
|
65
|
+
search(prefix, key, &block)
|
|
51
66
|
end
|
|
52
67
|
|
|
53
68
|
def find_all(prefix, key = nil, &block)
|
|
54
|
-
|
|
69
|
+
search(prefix, key, &block)
|
|
55
70
|
end
|
|
56
71
|
|
|
57
72
|
def resolve(prefix, key = nil, &block)
|
|
58
|
-
|
|
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
|
-
|
|
77
|
+
search_path(prefix, key, &block)
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
def find_all_paths(prefix, key = nil, &block)
|
|
72
|
-
|
|
81
|
+
search_path(prefix, key, &block)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
84
|
def resolve_path(prefix, key = nil, &block)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return matches.first.first if matches.one?
|
|
85
|
+
search_path(prefix, key, &block)
|
|
86
|
+
end
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
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
data/plugins/pins.rb
CHANGED
|
@@ -53,23 +53,27 @@ module Pins
|
|
|
53
53
|
value
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def
|
|
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
|
-
|
|
65
|
+
search(partial).matches.map(&:item)
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
def remove(name)
|
|
65
|
-
|
|
69
|
+
result = search(name)
|
|
66
70
|
|
|
67
|
-
if
|
|
68
|
-
puts "Unable to remove pin. Multiple matches: #{
|
|
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 =
|
|
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
|
-
|
|
552
|
+
result = environment.app_matcher.find_all(app_name)
|
|
553
553
|
|
|
554
|
-
case
|
|
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
|
|
561
|
+
print result.first.item.resolve(tree_root)
|
|
562
562
|
else
|
|
563
563
|
puts "Multiple matches found:"
|
|
564
|
-
matches.each { |
|
|
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
|
-
|
|
626
|
+
result = environment.app_matcher.find_all(app_name)
|
|
627
627
|
|
|
628
|
-
case
|
|
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 =
|
|
641
|
+
app = result.first.item
|
|
642
642
|
[app.name, app.resolve(tree_root)]
|
|
643
643
|
else
|
|
644
|
-
exact = matches.find { |
|
|
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 { |
|
|
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
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.
|
|
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:
|