hiiro 0.1.54 → 0.1.56

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: ed4284e83a1b52e665208b5e706de9017e9acfb3eedbcd0e1b265e3719292638
4
- data.tar.gz: 23dbed41838b15eeaa2f312669da58bd5a2546a0b1cd52b6149b44bf54d7316e
3
+ metadata.gz: 0db0a8fd9d54e542cb2b5951197e2250a1d199a66278b31bab5c003b50d8f39f
4
+ data.tar.gz: bee3a32c0dc9e68379aee3963bb11b15041a9cd21715379dbbc086bff59eba17
5
5
  SHA512:
6
- metadata.gz: 5ea8a092c971853fbf396ab63702dabf56e88abb09ca34ab170d45cf8cec0ae5a561ece441768136780f98b024657b93d9d3adb46174d15354fec6a9d3545d6b
7
- data.tar.gz: 3677c376b5a84c7da0ffe69c10cddceb9c3c9446bfbee8aa37282a1e1b33cbd20534d98e67d6deacbc62a78480393d7b2238b1bb992a714903d670d9014b14cf
6
+ metadata.gz: b59060b23668b9f6fc2818884628dccae6a95e84a5554d9666b9ce79d9bfc45f231c486039121c6a33cc8e97b144e1579cbd637131d8ca5cd0f196bfcdb4e9ff
7
+ data.tar.gz: 5918af1c725ce23f22a17d273def7f656118c1a54f9fd13ee7fe0be708e82f8c8146c7a4b4c7860482c5fb85a5c8a86910c5412062a96d9c37ce9901b527bee7
data/bin/h-app CHANGED
@@ -62,23 +62,21 @@ Hiiro.run(*ARGV, plugins: [:Tasks]) {
62
62
  next
63
63
  end
64
64
 
65
- apps = load_apps
66
- match = apps.find { |name, _| name.start_with?(app_name) }
65
+ app = environment.app_matcher.find(app_name)
67
66
 
68
- if match
69
- name, app_relative_path = match
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
- apps.each { |name, path| puts format(" %-20s => %s", name, path) }
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 = load_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 { |name, path| puts format(" %-20s => %s", name, path) }
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
- apps = load_apps
108
- match = apps.find { |name, _| name.start_with?(app_name) }
105
+ app = environment.app_matcher.find(app_name)
109
106
 
110
- if match
111
- name, app_relative_path = match
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
- apps = load_apps
133
- match = apps.find { |name, _| name.start_with?(app_name) }
128
+ app = environment.app_matcher.find(app_name)
134
129
 
135
- if match
136
- name, app_relative_path = match
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
- plugins = plugin_files.select{|f| args.any?{|arg| File.basename(f).start_with?(arg) } }
26
+ pm = Hiiro::PrefixMatcher.new(plugin_files) { |f| File.basename(f) }
27
+ plugins = args.flat_map { |arg| pm.find_all(arg) }.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
- hiiro = Hiiro.init(*ARGV, cwd: Dir.pwd, plugins: [Tasks])
7
-
8
- hiiro.add_subcommand(:version) { |*args|
9
- puts Hiiro::VERSION
10
- }
11
-
12
- hiiro.add_subcommand(:ping) { |*args|
13
- puts "pong"
14
- }
15
-
16
- hiiro.add_subcommand(:setup) do |*args|
17
- gem_root = File.expand_path("../..", __FILE__)
18
- source_plugins = File.join(gem_root, "plugins")
19
- source_bins = File.join(gem_root, "bin")
20
- dest_plugins = File.expand_path("~/.config/hiiro/plugins")
21
- dest_bin = File.expand_path("~/bin")
22
-
23
- FileUtils.mkdir_p(dest_plugins)
24
- FileUtils.mkdir_p(dest_bin)
25
-
26
- # Copy plugins
27
- plugin_files = Dir["#{source_plugins}/*.rb"]
28
- if plugin_files.any?
29
- FileUtils.cp(plugin_files, dest_plugins)
30
- puts "Installed #{plugin_files.size} plugins to #{dest_plugins}"
31
- plugin_files.each { |f| puts " - #{File.basename(f)}" }
32
- else
33
- puts "No plugins found in #{source_plugins}"
34
- end
35
-
36
- puts
37
-
38
- # Copy bin scripts
39
- bin_files = Dir["#{source_bins}/h-*"]
40
- if bin_files.any?
41
- FileUtils.cp(bin_files, dest_bin)
42
- bin_files.each { |f| FileUtils.chmod(0755, File.join(dest_bin, File.basename(f))) }
43
- puts "Installed #{bin_files.size} subcommands to #{dest_bin}"
44
- bin_files.each { |f| puts " - #{File.basename(f)}" }
45
- else
46
- puts "No subcommands found in #{source_bins}"
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
@@ -85,12 +85,13 @@ class Hiiro
85
85
  def oneline(index = nil)
86
86
  time_str = timestamp ? Time.parse(timestamp).strftime('%m/%d %H:%M') : ''
87
87
  prefix = index ? format('%3d ', index) : ''
88
+ sha_str = git_sha ? git_sha[0..6] : '-------'
88
89
  branch_str = git_branch ? "[#{git_branch}]" : ''
89
90
  task_str = task ? "(#{task})" : ''
90
91
  cmd_str = cmd || description || ''
91
- cmd_str = cmd_str[0..40] + '...' if cmd_str.length > 43
92
+ cmd_str = cmd_str[0..35] + '...' if cmd_str.length > 38
92
93
 
93
- "#{prefix}#{time_str} #{branch_str.ljust(20)} #{task_str.ljust(15)} #{cmd_str}"
94
+ "#{prefix}#{time_str} #{sha_str} #{branch_str.ljust(20)} #{task_str.ljust(15)} #{cmd_str}"
94
95
  end
95
96
 
96
97
  def full_display
data/lib/hiiro/history.rb CHANGED
@@ -382,13 +382,13 @@ class Hiiro
382
382
  end
383
383
 
384
384
  def current_git_sha
385
- git_helper.commit('HEAD', short: true)
385
+ git_helper.commit('HEAD')
386
386
  end
387
387
 
388
388
  def current_git_origin_sha
389
389
  branch = current_git_branch
390
390
  return nil unless branch
391
- git_helper.commit("origin/#{branch}", short: true)
391
+ git_helper.commit("origin/#{branch}")
392
392
  end
393
393
 
394
394
  def current_git_worktree
@@ -0,0 +1,110 @@
1
+ class Hiiro
2
+ class PrefixMatcher
3
+ class << self
4
+ def find(items, prefix, key: nil, &block)
5
+ new(items, key, &block).find(prefix)
6
+ end
7
+
8
+ def find_all(items, prefix, key: nil, &block)
9
+ new(items, key, &block).find_all(prefix)
10
+ end
11
+
12
+ def resolve(items, prefix, key: nil, &block)
13
+ new(items, key, &block).resolve(prefix)
14
+ end
15
+
16
+ def find_path(items, prefix, key: nil, &block)
17
+ new(items, key, &block).find_path(prefix)
18
+ end
19
+
20
+ def find_all_paths(items, prefix, key: nil, &block)
21
+ new(items, key, &block).find_all_paths(prefix)
22
+ end
23
+
24
+ def resolve_path(items, prefix, key: nil, &block)
25
+ new(items, key, &block).resolve_path(prefix)
26
+ end
27
+ end
28
+
29
+ attr_reader :original_items, :key, :block
30
+
31
+ def initialize(items, key = nil, &block)
32
+ @original_items = items
33
+ @key = key
34
+ @block = block
35
+ end
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
43
+ end
44
+
45
+ def extracted_items(key = nil, &block)
46
+ original_items.zip(items(key, &block))
47
+ end
48
+
49
+ def find(prefix, key = nil, &block)
50
+ extracted_items(key, &block).find { |_, extracted| matches?(extracted, prefix) }&.first
51
+ end
52
+
53
+ def find_all(prefix, key = nil, &block)
54
+ extracted_items(key, &block).select { |_, extracted| matches?(extracted, prefix) }.map(&:first)
55
+ end
56
+
57
+ 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
65
+ end
66
+
67
+ def find_path(prefix, key = nil, &block)
68
+ matching_path_pairs(prefix, key, &block).first&.first
69
+ end
70
+
71
+ def find_all_paths(prefix, key = nil, &block)
72
+ matching_path_pairs(prefix, key, &block).map(&:first)
73
+ end
74
+
75
+ 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?
79
+
80
+ exact = matches.find { |_, path| path == prefix }
81
+ exact&.first
82
+ end
83
+
84
+ private
85
+
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
+ def matches?(item, prefix)
101
+ item.to_s.start_with?(prefix.to_s)
102
+ end
103
+
104
+ def extract(item, key = nil, &block)
105
+ return block.call(item) if block
106
+ return item.send(key) if key
107
+ item
108
+ end
109
+ end
110
+ end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.54"
2
+ VERSION = "0.1.56"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -3,6 +3,7 @@ require "yaml"
3
3
  require "shellwords"
4
4
 
5
5
  require_relative "hiiro/version"
6
+ require_relative "hiiro/prefix_matcher"
6
7
  require_relative "hiiro/git"
7
8
  require_relative "hiiro/history"
8
9
  require_relative "hiiro/options"
data/plugins/pins.rb CHANGED
@@ -54,15 +54,11 @@ module Pins
54
54
  end
55
55
 
56
56
  def find(partial)
57
- pins.keys.map(&:to_s).find do |pin_name|
58
- pin_name.start_with?(partial)
59
- end
57
+ Hiiro::PrefixMatcher.find(pins.keys.map(&:to_s), partial)
60
58
  end
61
59
 
62
60
  def find_all(partial)
63
- pins.keys.map(&:to_s).select do |pin_name|
64
- pin_name.start_with?(partial)
65
- end
61
+ Hiiro::PrefixMatcher.find_all(pins.keys.map(&:to_s), partial)
66
62
  end
67
63
 
68
64
  def remove(name)
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
- h = { 'name' => name }
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
- parent_prefix, child_prefix = abbreviated.split('/', 2)
196
- parent = all_tasks.select(&:top_level?).find { |t| t.name.start_with?(parent_prefix) }
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 if result
201
213
 
202
214
  # "main" refers to the parent task itself
203
- return parent if 'main'.start_with?(child_prefix)
215
+ parent_prefix, child_prefix = abbreviated.split('/', 2)
216
+ if 'main'.start_with?(child_prefix)
217
+ return task_matcher.find(parent_prefix)
218
+ end
204
219
 
205
220
  nil
206
221
  else
207
- all_tasks.find { |t| t.name.start_with?(abbreviated) }
222
+ task_matcher.find(abbreviated)
208
223
  end
209
224
  end
210
225
 
211
226
  def find_tree(abbreviated)
212
227
  return nil if abbreviated.nil?
213
- all_trees.find { |t| t.name.start_with?(abbreviated) }
228
+ tree_matcher.find(abbreviated)
214
229
  end
215
230
 
216
231
  def find_session(abbreviated)
217
232
  return nil if abbreviated.nil?
218
- all_sessions.find { |s| s.name.start_with?(abbreviated) }
233
+ session_matcher.find(abbreviated)
219
234
  end
220
235
 
221
236
  def find_app(abbreviated)
222
237
  return nil if abbreviated.nil?
223
- all_apps.find { |a| a.name.start_with?(abbreviated) }
238
+ app_matcher.find(abbreviated)
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
- tasks.find { |t|
348
- match_name = (scope == :subtask) ? t.short_name : t.name
349
- match_name.start_with?(name)
350
- }
279
+ key = (scope == :subtask) ? :short_name : :name
280
+ Hiiro::PrefixMatcher.new(tasks, key).find(name)
351
281
  end
352
282
 
353
283
  def task_by_tree(tree_name)
354
- tasks.find { |t| t.tree_name == tree_name }
284
+ environment.task_matcher.resolve(tree_name, :tree_name)
355
285
  end
356
286
 
357
287
  def task_by_session(session_name)
358
- tasks.find { |t| t.session_name == session_name }
288
+ environment.task_matcher.resolve(session_name, :session_name)
359
289
  end
360
290
 
361
291
  def current_task
@@ -619,7 +549,7 @@ class TaskManager
619
549
  return
620
550
  end
621
551
 
622
- matches = environment.all_apps.select { |a| a.name.start_with?(app_name) }
552
+ matches = environment.app_matcher.find_all(app_name)
623
553
 
624
554
  case matches.count
625
555
  when 0
@@ -693,7 +623,7 @@ 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
- matches = environment.all_apps.select { |a| a.name.start_with?(app_name) }
626
+ matches = environment.app_matcher.find_all(app_name)
697
627
 
698
628
  case matches.count
699
629
  when 0
@@ -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
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)
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.54
4
+ version: 0.1.56
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-07 00:00:00.000000000 Z
11
+ date: 2026-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -79,6 +79,7 @@ files:
79
79
  - lib/hiiro/history.rb
80
80
  - lib/hiiro/history/entry.rb
81
81
  - lib/hiiro/options.rb
82
+ - lib/hiiro/prefix_matcher.rb
82
83
  - lib/hiiro/sk.rb
83
84
  - lib/hiiro/version.rb
84
85
  - notes