hiiro 0.1.55 → 0.1.57
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-app +12 -18
- data/bin/h-plugin +2 -1
- data/exe/h +50 -47
- data/hiiro.gemspec +3 -0
- data/lib/hiiro/prefix_matcher.rb +241 -0
- data/lib/hiiro/version.rb +1 -1
- data/lib/hiiro.rb +1 -0
- data/plugins/pins.rb +10 -10
- data/plugins/tasks.rb +136 -114
- data/script/test +4 -0
- metadata +32 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f55785ca9b54838e9b0e0eefe8e1e829f85ad1b1d9a1ed6b961305b847f1828
|
|
4
|
+
data.tar.gz: cda5ec5bdc86b4210e4870d88bca437c9cc7dbac3857ae3e264a66ba0b054675
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d8e719178da0355245154216264edb4c292fb5ccc62dc561e8529bf7b67f4a94d85a5db3516e1406f5cf7464f61454dc3c07f7dc26530a44a895577b7082c12
|
|
7
|
+
data.tar.gz: 28a570aba442cb90419feaf3edd4ef4135773fc2f971401df942bb09617f17c7466c3ba32dbfc2edd1849895d973cdada07f80b8edd2932685e8f21bc00f29d4
|
data/Rakefile
CHANGED
data/bin/h-app
CHANGED
|
@@ -62,23 +62,21 @@ Hiiro.run(*ARGV, plugins: [:Tasks]) {
|
|
|
62
62
|
next
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
match = apps.find { |name, _| name.start_with?(app_name) }
|
|
65
|
+
app = environment.app_matcher.find(app_name)
|
|
67
66
|
|
|
68
|
-
if
|
|
69
|
-
|
|
70
|
-
target = File.join(root, app_relative_path)
|
|
67
|
+
if app
|
|
68
|
+
target = File.join(root, app.relative_path)
|
|
71
69
|
send_cd(relative_cd_path(target))
|
|
72
70
|
else
|
|
73
71
|
puts "App '#{app_name}' not found"
|
|
74
72
|
puts
|
|
75
73
|
puts "Available apps:"
|
|
76
|
-
|
|
74
|
+
environment.all_apps.each { |a| puts format(" %-20s => %s", a.name, a.relative_path) }
|
|
77
75
|
end
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
add_subcmd(:ls) {
|
|
81
|
-
apps =
|
|
79
|
+
apps = environment.all_apps
|
|
82
80
|
|
|
83
81
|
if apps.empty?
|
|
84
82
|
puts "No apps configured."
|
|
@@ -87,7 +85,7 @@ Hiiro.run(*ARGV, plugins: [:Tasks]) {
|
|
|
87
85
|
else
|
|
88
86
|
puts "Configured apps:"
|
|
89
87
|
puts
|
|
90
|
-
apps.each { |
|
|
88
|
+
apps.each { |a| puts format(" %-20s => %s", a.name, a.relative_path) }
|
|
91
89
|
end
|
|
92
90
|
}
|
|
93
91
|
|
|
@@ -104,12 +102,10 @@ Hiiro.run(*ARGV, plugins: [:Tasks]) {
|
|
|
104
102
|
next
|
|
105
103
|
end
|
|
106
104
|
|
|
107
|
-
|
|
108
|
-
match = apps.find { |name, _| name.start_with?(app_name) }
|
|
105
|
+
app = environment.app_matcher.find(app_name)
|
|
109
106
|
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
target = File.join(root, app_relative_path)
|
|
107
|
+
if app
|
|
108
|
+
target = File.join(root, app.relative_path)
|
|
113
109
|
puts relative_cd_path(target)
|
|
114
110
|
else
|
|
115
111
|
puts "App '#{app_name}' not found"
|
|
@@ -129,12 +125,10 @@ Hiiro.run(*ARGV, plugins: [:Tasks]) {
|
|
|
129
125
|
next
|
|
130
126
|
end
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
match = apps.find { |name, _| name.start_with?(app_name) }
|
|
128
|
+
app = environment.app_matcher.find(app_name)
|
|
134
129
|
|
|
135
|
-
if
|
|
136
|
-
|
|
137
|
-
puts File.join(root, app_relative_path)
|
|
130
|
+
if app
|
|
131
|
+
puts File.join(root, app.relative_path)
|
|
138
132
|
else
|
|
139
133
|
puts "App '#{app_name}' not found"
|
|
140
134
|
end
|
data/bin/h-plugin
CHANGED
|
@@ -23,7 +23,8 @@ o.add_subcmd(:edit) { |*args|
|
|
|
23
23
|
if args.none?
|
|
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).matches.map(&:item) }.uniq
|
|
27
28
|
|
|
28
29
|
if plugins.none?
|
|
29
30
|
puts "No matching plugins found for: #{args.map(&:inspect).join(' ')}"
|
data/exe/h
CHANGED
|
@@ -2,52 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
require "hiiro"
|
|
4
4
|
require "fileutils"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
plugin_files
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
bin_files.
|
|
45
|
-
|
|
46
|
-
|
|
5
|
+
require "pry"
|
|
6
|
+
|
|
7
|
+
Hiiro.run(*ARGV, cwd: Dir.pwd, plugins: [Tasks]) do
|
|
8
|
+
add_subcommand(:version) { |*args|
|
|
9
|
+
puts Hiiro::VERSION
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
add_subcommand(:pry) { |*args|
|
|
13
|
+
binding.pry
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add_subcommand(:ping) { |*args|
|
|
17
|
+
puts "pong"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
add_subcommand(:setup) do |*args|
|
|
21
|
+
gem_root = File.expand_path("../..", __FILE__)
|
|
22
|
+
source_plugins = File.join(gem_root, "plugins")
|
|
23
|
+
source_bins = File.join(gem_root, "bin")
|
|
24
|
+
dest_plugins = File.expand_path("~/.config/hiiro/plugins")
|
|
25
|
+
dest_bin = File.expand_path("~/bin")
|
|
26
|
+
|
|
27
|
+
FileUtils.mkdir_p(dest_plugins)
|
|
28
|
+
FileUtils.mkdir_p(dest_bin)
|
|
29
|
+
|
|
30
|
+
# Copy plugins
|
|
31
|
+
plugin_files = Dir["#{source_plugins}/*.rb"]
|
|
32
|
+
if plugin_files.any?
|
|
33
|
+
FileUtils.cp(plugin_files, dest_plugins)
|
|
34
|
+
puts "Installed #{plugin_files.size} plugins to #{dest_plugins}"
|
|
35
|
+
plugin_files.each { |f| puts " - #{File.basename(f)}" }
|
|
36
|
+
else
|
|
37
|
+
puts "No plugins found in #{source_plugins}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
puts
|
|
41
|
+
|
|
42
|
+
# Copy bin scripts
|
|
43
|
+
bin_files = Dir["#{source_bins}/h-*"]
|
|
44
|
+
if bin_files.any?
|
|
45
|
+
FileUtils.cp(bin_files, dest_bin)
|
|
46
|
+
bin_files.each { |f| FileUtils.chmod(0755, File.join(dest_bin, File.basename(f))) }
|
|
47
|
+
puts "Installed #{bin_files.size} subcommands to #{dest_bin}"
|
|
48
|
+
bin_files.each { |f| puts " - #{File.basename(f)}" }
|
|
49
|
+
else
|
|
50
|
+
puts "No subcommands found in #{source_bins}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
puts
|
|
54
|
+
puts "Setup complete! Make sure ~/bin is in your PATH."
|
|
47
55
|
end
|
|
48
|
-
|
|
49
|
-
puts
|
|
50
|
-
puts "Setup complete! Make sure ~/bin is in your PATH."
|
|
51
56
|
end
|
|
52
|
-
|
|
53
|
-
hiiro.run
|
data/hiiro.gemspec
CHANGED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
class Hiiro
|
|
2
|
+
class PrefixMatcher
|
|
3
|
+
class Item
|
|
4
|
+
attr_reader :item, :extracted_item, :key, :block
|
|
5
|
+
|
|
6
|
+
def initialize(item:, extracted_item:, key: nil, block: nil)
|
|
7
|
+
@item = item
|
|
8
|
+
@extracted_item = extracted_item
|
|
9
|
+
@key = key
|
|
10
|
+
@block = block
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Result
|
|
15
|
+
attr_reader :matcher, :all_items, :key, :block, :prefix
|
|
16
|
+
|
|
17
|
+
def initialize(matcher:, all_items:, prefix:, key: nil, block: nil)
|
|
18
|
+
@matcher = matcher
|
|
19
|
+
@all_items = all_items
|
|
20
|
+
@prefix = prefix
|
|
21
|
+
@key = key
|
|
22
|
+
@block = block
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def matches
|
|
26
|
+
@matches ||= all_items.select { |item| item.extracted_item.to_s.start_with?(prefix.to_s) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def count
|
|
30
|
+
matches.count
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def ambiguous?
|
|
34
|
+
count > 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def exact_match
|
|
38
|
+
all_items.find { |item| item.extracted_item == prefix }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def match
|
|
42
|
+
one? ? matches.first : nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def match?
|
|
46
|
+
matches.any?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def exact?
|
|
50
|
+
!exact_match.nil?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def one?
|
|
54
|
+
count == 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns item for resolve semantics: exact match, or single match, otherwise nil
|
|
58
|
+
def resolved
|
|
59
|
+
exact_match || match
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns the first matching item (for find semantics)
|
|
63
|
+
def first
|
|
64
|
+
matches.first
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class PathResult
|
|
69
|
+
attr_reader :matcher, :all_items, :key, :block, :prefix
|
|
70
|
+
|
|
71
|
+
def initialize(matcher:, all_items:, prefix:, key: nil, block: nil)
|
|
72
|
+
@matcher = matcher
|
|
73
|
+
@all_items = all_items
|
|
74
|
+
@prefix = prefix
|
|
75
|
+
@key = key
|
|
76
|
+
@block = block
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def matches
|
|
80
|
+
@matches ||= begin
|
|
81
|
+
prefixes = prefix.to_s.split('/')
|
|
82
|
+
|
|
83
|
+
items_with_paths = all_items.map { |item|
|
|
84
|
+
[item, item.extracted_item.to_s.split('/')]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
prefixes.each_with_index do |seg, i|
|
|
88
|
+
items_with_paths = items_with_paths.select { |_, path| path[i]&.start_with?(seg) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
items_with_paths.map(&:first)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def count
|
|
96
|
+
matches.count
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def ambiguous?
|
|
100
|
+
count > 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def exact_match
|
|
104
|
+
all_items.find { |item| item.extracted_item == prefix }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def match
|
|
108
|
+
one? ? matches.first : nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def match?
|
|
112
|
+
matches.any?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def exact?
|
|
116
|
+
!exact_match.nil?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def one?
|
|
120
|
+
count == 1
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Returns item for resolve semantics: exact match, or single match, otherwise nil
|
|
124
|
+
def resolved
|
|
125
|
+
exact_match || match
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns the first matching item (for find semantics)
|
|
129
|
+
def first
|
|
130
|
+
matches.first
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class << self
|
|
135
|
+
def find(items, prefix, key: nil, &block)
|
|
136
|
+
new(items, key, &block).find(prefix)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def find_all(items, prefix, key: nil, &block)
|
|
140
|
+
new(items, key, &block).find_all(prefix)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def resolve(items, prefix, key: nil, &block)
|
|
144
|
+
new(items, key, &block).resolve(prefix)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def find_path(items, prefix, key: nil, &block)
|
|
148
|
+
new(items, key, &block).find_path(prefix)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def find_all_paths(items, prefix, key: nil, &block)
|
|
152
|
+
new(items, key, &block).find_all_paths(prefix)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def resolve_path(items, prefix, key: nil, &block)
|
|
156
|
+
new(items, key, &block).resolve_path(prefix)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
attr_reader :original_items, :key, :block
|
|
161
|
+
|
|
162
|
+
def initialize(items, key = nil, &block)
|
|
163
|
+
@original_items = items
|
|
164
|
+
@key = key
|
|
165
|
+
@block = block
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def all_items(key = nil, &block)
|
|
169
|
+
use_key = key.nil? && !block_given? ? @key : key
|
|
170
|
+
use_block = key.nil? && !block_given? ? @block : block
|
|
171
|
+
|
|
172
|
+
@all_items_cache ||= {}
|
|
173
|
+
cache_key = [use_key, use_block].hash
|
|
174
|
+
|
|
175
|
+
@all_items_cache[cache_key] ||= original_items.map { |item|
|
|
176
|
+
Item.new(
|
|
177
|
+
item: item,
|
|
178
|
+
extracted_item: extract(item, use_key, &use_block),
|
|
179
|
+
key: use_key,
|
|
180
|
+
block: use_block
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def search(prefix, key = nil, &block)
|
|
186
|
+
Result.new(
|
|
187
|
+
matcher: self,
|
|
188
|
+
all_items: all_items(key, &block),
|
|
189
|
+
prefix: prefix,
|
|
190
|
+
key: key || @key,
|
|
191
|
+
block: block || @block
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def find(prefix, key = nil, &block)
|
|
196
|
+
search(prefix, key, &block)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def find_all(prefix, key = nil, &block)
|
|
200
|
+
search(prefix, key, &block)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def resolve(prefix, key = nil, &block)
|
|
204
|
+
search(prefix, key, &block)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def find_path(prefix, key = nil, &block)
|
|
208
|
+
search_path(prefix, key, &block)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def find_all_paths(prefix, key = nil, &block)
|
|
212
|
+
search_path(prefix, key, &block)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def resolve_path(prefix, key = nil, &block)
|
|
216
|
+
search_path(prefix, key, &block)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def search_path(prefix, key = nil, &block)
|
|
220
|
+
PathResult.new(
|
|
221
|
+
matcher: self,
|
|
222
|
+
all_items: all_items(key, &block),
|
|
223
|
+
prefix: prefix,
|
|
224
|
+
key: key || @key,
|
|
225
|
+
block: block || @block
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
def matches?(item, prefix)
|
|
232
|
+
item.to_s.start_with?(prefix.to_s)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def extract(item, key = nil, &block)
|
|
236
|
+
return block.call(item) if block
|
|
237
|
+
return item.send(key) if key
|
|
238
|
+
item
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
data/lib/hiiro/version.rb
CHANGED
data/lib/hiiro.rb
CHANGED
data/plugins/pins.rb
CHANGED
|
@@ -53,27 +53,27 @@ module Pins
|
|
|
53
53
|
value
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
def search(partial)
|
|
57
|
+
Hiiro::PrefixMatcher.find(pins.keys.map(&:to_s), partial)
|
|
58
|
+
end
|
|
59
|
+
|
|
56
60
|
def find(partial)
|
|
57
|
-
|
|
58
|
-
pin_name.start_with?(partial)
|
|
59
|
-
end
|
|
61
|
+
search(partial).match&.item
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
def find_all(partial)
|
|
63
|
-
|
|
64
|
-
pin_name.start_with?(partial)
|
|
65
|
-
end
|
|
65
|
+
search(partial).matches.map(&:item)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def remove(name)
|
|
69
|
-
|
|
69
|
+
result = search(name)
|
|
70
70
|
|
|
71
|
-
if
|
|
72
|
-
puts "Unable to remove pin. Multiple matches: #{
|
|
71
|
+
if result.ambiguous?
|
|
72
|
+
puts "Unable to remove pin. Multiple matches: #{result.matches.map(&:item).inspect}"
|
|
73
73
|
return
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
pin_name =
|
|
76
|
+
pin_name = result.match&.item
|
|
77
77
|
|
|
78
78
|
pins.delete(pin_name.to_s)
|
|
79
79
|
end
|
data/plugins/tasks.rb
CHANGED
|
@@ -75,6 +75,8 @@ class Tree
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
class Task
|
|
78
|
+
HASH_METHODS = %i[name parent_name short_name session_name tree_name top_level? subtask?]
|
|
79
|
+
|
|
78
80
|
attr_reader :name, :tree_name, :session_name
|
|
79
81
|
|
|
80
82
|
def initialize(name:, tree: nil, session: nil, **_)
|
|
@@ -109,10 +111,7 @@ class Task
|
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
def to_h
|
|
112
|
-
|
|
113
|
-
h['tree'] = tree_name if tree_name
|
|
114
|
-
h['session'] = session_name if session_name != name
|
|
115
|
-
h
|
|
114
|
+
HASH_METHODS.zip(HASH_METHODS.map{|m| send(m) }).to_h
|
|
116
115
|
end
|
|
117
116
|
end
|
|
118
117
|
|
|
@@ -169,6 +168,22 @@ class Environment
|
|
|
169
168
|
@all_apps ||= config.apps
|
|
170
169
|
end
|
|
171
170
|
|
|
171
|
+
def tree_matcher
|
|
172
|
+
@tree_matcher ||= Hiiro::PrefixMatcher.new(all_trees, :name)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def session_matcher
|
|
176
|
+
@session_matcher ||= Hiiro::PrefixMatcher.new(all_sessions, :name)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def app_matcher
|
|
180
|
+
@app_matcher ||= Hiiro::PrefixMatcher.new(all_apps, :name)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def task_matcher
|
|
184
|
+
@task_matcher ||= Hiiro::PrefixMatcher.new(all_tasks, :name)
|
|
185
|
+
end
|
|
186
|
+
|
|
172
187
|
def task
|
|
173
188
|
@task ||= begin
|
|
174
189
|
s = session
|
|
@@ -191,36 +206,36 @@ class Environment
|
|
|
191
206
|
def find_task(abbreviated)
|
|
192
207
|
return nil if abbreviated.nil?
|
|
193
208
|
|
|
209
|
+
# Try path-based matching first (handles "parent/child" patterns)
|
|
194
210
|
if abbreviated.include?('/')
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return nil unless parent
|
|
198
|
-
|
|
199
|
-
subtask = all_tasks.select { |t| t.parent_name == parent.name }.find { |t| t.short_name.start_with?(child_prefix) }
|
|
200
|
-
return subtask if subtask
|
|
211
|
+
result = task_matcher.resolve_path(abbreviated)
|
|
212
|
+
return result.resolved&.item if result.match?
|
|
201
213
|
|
|
202
214
|
# "main" refers to the parent task itself
|
|
203
|
-
|
|
215
|
+
parent_prefix, child_prefix = abbreviated.split('/', 2)
|
|
216
|
+
if 'main'.start_with?(child_prefix)
|
|
217
|
+
return task_matcher.find(parent_prefix).first&.item
|
|
218
|
+
end
|
|
204
219
|
|
|
205
220
|
nil
|
|
206
221
|
else
|
|
207
|
-
|
|
222
|
+
task_matcher.find(abbreviated).first&.item
|
|
208
223
|
end
|
|
209
224
|
end
|
|
210
225
|
|
|
211
226
|
def find_tree(abbreviated)
|
|
212
227
|
return nil if abbreviated.nil?
|
|
213
|
-
|
|
228
|
+
tree_matcher.find(abbreviated).first&.item
|
|
214
229
|
end
|
|
215
230
|
|
|
216
231
|
def find_session(abbreviated)
|
|
217
232
|
return nil if abbreviated.nil?
|
|
218
|
-
|
|
233
|
+
session_matcher.find(abbreviated).first&.item
|
|
219
234
|
end
|
|
220
235
|
|
|
221
236
|
def find_app(abbreviated)
|
|
222
237
|
return nil if abbreviated.nil?
|
|
223
|
-
|
|
238
|
+
app_matcher.find(abbreviated).first&.item
|
|
224
239
|
end
|
|
225
240
|
end
|
|
226
241
|
|
|
@@ -228,89 +243,6 @@ class TaskManager
|
|
|
228
243
|
TASKS_DIR = File.join(Dir.home, '.config', 'hiiro', 'tasks')
|
|
229
244
|
APPS_FILE = File.join(Dir.home, '.config', 'hiiro', 'apps.yml')
|
|
230
245
|
|
|
231
|
-
class Config
|
|
232
|
-
attr_reader :tasks_file, :apps_file
|
|
233
|
-
|
|
234
|
-
def initialize(tasks_file: nil, apps_file: nil)
|
|
235
|
-
@tasks_file = tasks_file || File.join(TASKS_DIR, 'tasks.yml')
|
|
236
|
-
@apps_file = apps_file || APPS_FILE
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def tasks
|
|
240
|
-
data = load_tasks
|
|
241
|
-
(data['tasks'] || []).map { |h| Task.new(**h.transform_keys(&:to_sym)) }
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def apps
|
|
245
|
-
return [] unless File.exist?(apps_file)
|
|
246
|
-
data = YAML.safe_load_file(apps_file) || {}
|
|
247
|
-
data.map { |name, path| App.new(name: name, path: path) }
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def save_task(task)
|
|
251
|
-
data = load_tasks
|
|
252
|
-
data['tasks'] ||= []
|
|
253
|
-
data['tasks'].reject! { |t| t['name'] == task.name }
|
|
254
|
-
data['tasks'] << task.to_h
|
|
255
|
-
save_tasks(data)
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
def remove_task(name)
|
|
259
|
-
data = load_tasks
|
|
260
|
-
data['tasks'] ||= []
|
|
261
|
-
data['tasks'].reject! { |t| t['name'] == name }
|
|
262
|
-
save_tasks(data)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
private
|
|
266
|
-
|
|
267
|
-
def load_tasks
|
|
268
|
-
if File.exist?(tasks_file)
|
|
269
|
-
return YAML.safe_load_file(tasks_file) || { 'tasks' => [] }
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# Load from individual task_*.yml files
|
|
273
|
-
task_files = Dir.glob(File.join(File.dirname(tasks_file), 'task_*.yml'))
|
|
274
|
-
if task_files.any?
|
|
275
|
-
tasks = task_files.map do |file|
|
|
276
|
-
short_name = File.basename(file, '.yml').sub(/^task_/, '')
|
|
277
|
-
data = YAML.safe_load_file(file) || {}
|
|
278
|
-
# Support parent key for subtasks, or infer from tree path
|
|
279
|
-
parent = data['parent']
|
|
280
|
-
if parent.nil? && data['tree']&.include?('/')
|
|
281
|
-
parent = data['tree'].split('/').first
|
|
282
|
-
end
|
|
283
|
-
name = parent ? "#{parent}/#{short_name}" : short_name
|
|
284
|
-
h = { 'name' => name }
|
|
285
|
-
h['tree'] = data['tree'] if data['tree']
|
|
286
|
-
h['session'] = data['session'] if data['session']
|
|
287
|
-
h
|
|
288
|
-
end
|
|
289
|
-
return { 'tasks' => tasks }
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
assignments_file = File.join(File.dirname(tasks_file), 'assignments.yml')
|
|
293
|
-
if File.exist?(assignments_file)
|
|
294
|
-
raw = YAML.safe_load_file(assignments_file) || {}
|
|
295
|
-
tasks = raw.map do |tree_path, task_name|
|
|
296
|
-
h = { 'name' => task_name, 'tree' => tree_path }
|
|
297
|
-
h['session'] = task_name if task_name.include?('/')
|
|
298
|
-
h
|
|
299
|
-
end
|
|
300
|
-
data = { 'tasks' => tasks }
|
|
301
|
-
save_tasks(data)
|
|
302
|
-
return data
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
{ 'tasks' => [] }
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
def save_tasks(data)
|
|
309
|
-
FileUtils.mkdir_p(File.dirname(tasks_file))
|
|
310
|
-
File.write(tasks_file, YAML.dump(data))
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
|
|
314
246
|
attr_reader :hiiro, :scope, :environment
|
|
315
247
|
|
|
316
248
|
def initialize(hiiro, scope: :task, environment: nil)
|
|
@@ -344,18 +276,16 @@ class TaskManager
|
|
|
344
276
|
def task_by_name(name)
|
|
345
277
|
return slash_lookup(name) if name.include?('/')
|
|
346
278
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
match_name.start_with?(name)
|
|
350
|
-
}
|
|
279
|
+
key = (scope == :subtask) ? :short_name : :name
|
|
280
|
+
Hiiro::PrefixMatcher.new(tasks, key).find(name).first&.item
|
|
351
281
|
end
|
|
352
282
|
|
|
353
283
|
def task_by_tree(tree_name)
|
|
354
|
-
|
|
284
|
+
environment.task_matcher.resolve(tree_name, :tree_name).resolved&.item
|
|
355
285
|
end
|
|
356
286
|
|
|
357
287
|
def task_by_session(session_name)
|
|
358
|
-
|
|
288
|
+
environment.task_matcher.resolve(session_name, :session_name).resolved&.item
|
|
359
289
|
end
|
|
360
290
|
|
|
361
291
|
def current_task
|
|
@@ -619,19 +549,19 @@ class TaskManager
|
|
|
619
549
|
return
|
|
620
550
|
end
|
|
621
551
|
|
|
622
|
-
|
|
552
|
+
result = environment.app_matcher.find_all(app_name)
|
|
623
553
|
|
|
624
|
-
case
|
|
554
|
+
case result.count
|
|
625
555
|
when 0
|
|
626
556
|
puts "ERROR: No matches found"
|
|
627
557
|
puts
|
|
628
558
|
puts "Possible Apps:"
|
|
629
559
|
environment.all_apps.each { |a| puts format(" %-20s => %s", a.name, a.relative_path) }
|
|
630
560
|
when 1
|
|
631
|
-
print
|
|
561
|
+
print result.first.item.resolve(tree_root)
|
|
632
562
|
else
|
|
633
563
|
puts "Multiple matches found:"
|
|
634
|
-
matches.each { |
|
|
564
|
+
result.matches.each { |m| puts format(" %-20s => %s", m.item.name, m.item.relative_path) }
|
|
635
565
|
end
|
|
636
566
|
end
|
|
637
567
|
|
|
@@ -693,9 +623,9 @@ class TaskManager
|
|
|
693
623
|
tree = environment.find_tree(task.tree_name)
|
|
694
624
|
tree_root = tree ? tree.path : File.join(WORK_DIR, task.tree_name)
|
|
695
625
|
|
|
696
|
-
|
|
626
|
+
result = environment.app_matcher.find_all(app_name)
|
|
697
627
|
|
|
698
|
-
case
|
|
628
|
+
case result.count
|
|
699
629
|
when 0
|
|
700
630
|
# Fallback: directory discovery
|
|
701
631
|
exact = File.join(tree_root, app_name)
|
|
@@ -708,15 +638,15 @@ class TaskManager
|
|
|
708
638
|
list_apps
|
|
709
639
|
nil
|
|
710
640
|
when 1
|
|
711
|
-
app =
|
|
641
|
+
app = result.first.item
|
|
712
642
|
[app.name, app.resolve(tree_root)]
|
|
713
643
|
else
|
|
714
|
-
exact = matches.find { |
|
|
644
|
+
exact = result.matches.find { |m| m.item.name == app_name }
|
|
715
645
|
if exact
|
|
716
|
-
[exact.name, exact.resolve(tree_root)]
|
|
646
|
+
[exact.item.name, exact.item.resolve(tree_root)]
|
|
717
647
|
else
|
|
718
648
|
puts "ERROR: '#{app_name}' matches multiple apps:"
|
|
719
|
-
matches.each { |
|
|
649
|
+
result.matches.each { |m| puts " #{m.item.name}" }
|
|
720
650
|
nil
|
|
721
651
|
end
|
|
722
652
|
end
|
|
@@ -742,14 +672,106 @@ class TaskManager
|
|
|
742
672
|
def sk_select(items)
|
|
743
673
|
Hiiro::Sk.select(items)
|
|
744
674
|
end
|
|
675
|
+
|
|
676
|
+
class Config
|
|
677
|
+
attr_reader :tasks_file, :apps_file
|
|
678
|
+
|
|
679
|
+
def initialize(tasks_file: nil, apps_file: nil)
|
|
680
|
+
@tasks_file = tasks_file || File.join(TASKS_DIR, 'tasks.yml')
|
|
681
|
+
@apps_file = apps_file || APPS_FILE
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def tasks
|
|
685
|
+
data = load_tasks
|
|
686
|
+
(data['tasks'] || []).map { |h| Task.new(**h.transform_keys(&:to_sym)) }
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def apps
|
|
690
|
+
return [] unless File.exist?(apps_file)
|
|
691
|
+
data = YAML.safe_load_file(apps_file) || {}
|
|
692
|
+
data.map { |name, path| App.new(name: name, path: path) }
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
def save_task(task)
|
|
696
|
+
data = load_tasks
|
|
697
|
+
data['tasks'] ||= []
|
|
698
|
+
data['tasks'].reject! { |t| t['name'] == task.name }
|
|
699
|
+
data['tasks'] << task.to_h.transform_keys(&:to_s)
|
|
700
|
+
save_tasks(data)
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def remove_task(name)
|
|
704
|
+
data = load_tasks
|
|
705
|
+
data['tasks'] ||= []
|
|
706
|
+
data['tasks'].reject! { |t| t['name'] == name }
|
|
707
|
+
save_tasks(data)
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
private
|
|
711
|
+
|
|
712
|
+
def load_tasks
|
|
713
|
+
if File.exist?(tasks_file)
|
|
714
|
+
return YAML.safe_load_file(tasks_file) || { 'tasks' => [] }
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
# Load from individual task_*.yml files
|
|
718
|
+
task_files = Dir.glob(File.join(File.dirname(tasks_file), 'task_*.yml'))
|
|
719
|
+
if task_files.any?
|
|
720
|
+
tasks = task_files.map do |file|
|
|
721
|
+
short_name = File.basename(file, '.yml').sub(/^task_/, '')
|
|
722
|
+
data = YAML.safe_load_file(file) || {}
|
|
723
|
+
# Support parent key for subtasks, or infer from tree path
|
|
724
|
+
parent = data['parent']
|
|
725
|
+
if parent.nil? && data['tree']&.include?('/')
|
|
726
|
+
parent = data['tree'].split('/').first
|
|
727
|
+
end
|
|
728
|
+
name = parent ? "#{parent}/#{short_name}" : short_name
|
|
729
|
+
h = { 'name' => name }
|
|
730
|
+
h['tree'] = data['tree'] if data['tree']
|
|
731
|
+
h['session'] = data['session'] if data['session']
|
|
732
|
+
h
|
|
733
|
+
end
|
|
734
|
+
return { 'tasks' => tasks }
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
assignments_file = File.join(File.dirname(tasks_file), 'assignments.yml')
|
|
738
|
+
if File.exist?(assignments_file)
|
|
739
|
+
raw = YAML.safe_load_file(assignments_file) || {}
|
|
740
|
+
tasks = raw.map do |tree_path, task_name|
|
|
741
|
+
h = { 'name' => task_name, 'tree' => tree_path }
|
|
742
|
+
h['session'] = task_name if task_name.include?('/')
|
|
743
|
+
h
|
|
744
|
+
end
|
|
745
|
+
data = { 'tasks' => tasks }
|
|
746
|
+
save_tasks(data)
|
|
747
|
+
return data
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
{ 'tasks' => [] }
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
def save_tasks(data)
|
|
754
|
+
FileUtils.mkdir_p(File.dirname(tasks_file))
|
|
755
|
+
File.write(tasks_file, YAML.dump(data))
|
|
756
|
+
end
|
|
757
|
+
end
|
|
745
758
|
end
|
|
746
759
|
|
|
747
760
|
module Tasks
|
|
748
761
|
def self.load(hiiro)
|
|
749
762
|
hiiro.load_plugin(Tmux)
|
|
763
|
+
attach_methods(hiiro)
|
|
750
764
|
add_subcommands(hiiro)
|
|
751
765
|
end
|
|
752
766
|
|
|
767
|
+
def self.attach_methods(hiiro)
|
|
768
|
+
hiiro.instance_eval do
|
|
769
|
+
def environment
|
|
770
|
+
@environment ||= Environment.current
|
|
771
|
+
end
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
|
|
753
775
|
def self.add_subcommands(hiiro)
|
|
754
776
|
hiiro.add_subcmd(:task) do |*args|
|
|
755
777
|
mgr = TaskManager.new(hiiro, scope: :task)
|
data/script/test
ADDED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.57
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Toyota
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pry
|
|
@@ -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:
|
|
@@ -79,6 +107,7 @@ files:
|
|
|
79
107
|
- lib/hiiro/history.rb
|
|
80
108
|
- lib/hiiro/history/entry.rb
|
|
81
109
|
- lib/hiiro/options.rb
|
|
110
|
+
- lib/hiiro/prefix_matcher.rb
|
|
82
111
|
- lib/hiiro/sk.rb
|
|
83
112
|
- lib/hiiro/version.rb
|
|
84
113
|
- notes
|
|
@@ -91,6 +120,7 @@ files:
|
|
|
91
120
|
- script/install
|
|
92
121
|
- script/publish
|
|
93
122
|
- script/sync
|
|
123
|
+
- script/test
|
|
94
124
|
- script/update
|
|
95
125
|
homepage: https://github.com/unixsuperhero/hiiro
|
|
96
126
|
licenses:
|