hiiro 0.1.105.pre.1 → 0.1.105.pre.2
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/lib/hiiro/version.rb +1 -1
- metadata +1 -2
- data/plugins/tasks.rb +0 -1070
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec412364c10d96baca34abfe06b466364e10bad8dd5ab79163282d8996631fee
|
|
4
|
+
data.tar.gz: 23961906f1965a27bdbb7250e7fe9c870bdf457e594fce98d7abbe81628442b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08961fbea3c8a8a19e9c213bdc09779cf8781a47f772a2baefdb255b4173a483981404a931ac546df1ae0739e73efa89cddf981d9d6873091996d977a8b13846'
|
|
7
|
+
data.tar.gz: 789aa50c54f4839567610a56c34879938d581d51834e913070254931b82bd24c8b8edccb776d464e3bd963b7288fa7f3e945b9bc0e6a89878cc7afa4e981bcfb
|
data/lib/hiiro/version.rb
CHANGED
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.105.pre.
|
|
4
|
+
version: 0.1.105.pre.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Toyota
|
|
@@ -141,7 +141,6 @@ files:
|
|
|
141
141
|
- plugins/notify.rb
|
|
142
142
|
- plugins/pins.rb
|
|
143
143
|
- plugins/project.rb
|
|
144
|
-
- plugins/tasks.rb
|
|
145
144
|
- scripts/compare
|
|
146
145
|
- scripts/install
|
|
147
146
|
- scripts/publish
|
data/plugins/tasks.rb
DELETED
|
@@ -1,1070 +0,0 @@
|
|
|
1
|
-
require 'yaml'
|
|
2
|
-
require 'fileutils'
|
|
3
|
-
|
|
4
|
-
class TmuxSession
|
|
5
|
-
attr_reader :name
|
|
6
|
-
|
|
7
|
-
def self.current
|
|
8
|
-
return nil unless ENV['TMUX']
|
|
9
|
-
|
|
10
|
-
name = `tmux display-message -p '#S'`.chomp
|
|
11
|
-
new(name)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def self.all
|
|
15
|
-
output = `tmux list-sessions -F '#S' 2>/dev/null`
|
|
16
|
-
output.lines(chomp: true).map { |name| new(name) }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def initialize(name)
|
|
20
|
-
@name = name
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def ==(other)
|
|
24
|
-
other.is_a?(TmuxSession) && name == other.name
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def to_s
|
|
28
|
-
name
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
class Tree
|
|
33
|
-
attr_reader :path, :head, :branch
|
|
34
|
-
|
|
35
|
-
def self.all(repo_path: Hiiro::REPO_PATH)
|
|
36
|
-
git = Hiiro::Git.new(nil, repo_path)
|
|
37
|
-
git.worktrees(repo_path: repo_path).filter_map do |wt|
|
|
38
|
-
next if wt.bare?
|
|
39
|
-
new(path: wt.path, head: wt.head, branch: wt.branch)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def initialize(path:, head: nil, branch: nil)
|
|
44
|
-
@path = path
|
|
45
|
-
@head = head
|
|
46
|
-
@branch = branch
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def name
|
|
50
|
-
@name ||= if path.start_with?(Hiiro::WORK_DIR + '/')
|
|
51
|
-
path.sub(Hiiro::WORK_DIR + '/', '')
|
|
52
|
-
else
|
|
53
|
-
File.basename(path)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def match?(pwd = Dir.pwd)
|
|
58
|
-
pwd == path || pwd.start_with?(path + '/')
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def detached?
|
|
62
|
-
branch.nil?
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def ==(other)
|
|
66
|
-
other.is_a?(Tree) && path == other.path
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def to_s
|
|
70
|
-
name
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
class Task
|
|
75
|
-
attr_reader :name, :tree_name, :session_name
|
|
76
|
-
|
|
77
|
-
def initialize(name:, tree: nil, session: nil, **_)
|
|
78
|
-
@name = name
|
|
79
|
-
@tree_name = tree
|
|
80
|
-
@session_name = session || name
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def parent_name
|
|
84
|
-
return nil unless subtask?
|
|
85
|
-
name.split('/').first
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def short_name
|
|
89
|
-
subtask? ? name.split('/', 2).last : name
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def subtask?
|
|
93
|
-
name.include?('/')
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def top_level?
|
|
97
|
-
!subtask?
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def tree
|
|
101
|
-
@tree ||= Environment.current&.find_tree(tree_name)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def branch
|
|
105
|
-
tree&.branch
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def ==(other)
|
|
109
|
-
other.is_a?(Task) && name == other.name
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def to_s
|
|
113
|
-
name
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def to_h
|
|
117
|
-
h = { name: name }
|
|
118
|
-
h[:tree] = tree_name if tree_name
|
|
119
|
-
h[:session] = session_name if session_name != name
|
|
120
|
-
h
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
class App
|
|
125
|
-
attr_reader :name, :relative_path
|
|
126
|
-
|
|
127
|
-
def initialize(name:, path:)
|
|
128
|
-
@name = name
|
|
129
|
-
@relative_path = path
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def resolve(tree_root)
|
|
133
|
-
File.join(tree_root, relative_path)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def ==(other)
|
|
137
|
-
other.is_a?(App) && name == other.name
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def to_s
|
|
141
|
-
name
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
class Environment
|
|
146
|
-
attr_reader :path
|
|
147
|
-
|
|
148
|
-
def self.current
|
|
149
|
-
new(path: Dir.pwd)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def initialize(path: Dir.pwd, config: nil)
|
|
153
|
-
@path = path
|
|
154
|
-
@config = config
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def config
|
|
158
|
-
@config ||= TaskManager::Config.new
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def all_tasks
|
|
162
|
-
@all_tasks ||= config.tasks
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def all_sessions
|
|
166
|
-
@all_sessions ||= TmuxSession.all
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def all_trees
|
|
170
|
-
@all_trees ||= Tree.all
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def all_apps
|
|
174
|
-
@all_apps ||= config.apps
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def tree_matcher
|
|
178
|
-
@tree_matcher ||= Hiiro::Matcher.new(all_trees, :name)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def session_matcher
|
|
182
|
-
@session_matcher ||= Hiiro::Matcher.new(all_sessions, :name)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def app_matcher
|
|
186
|
-
@app_matcher ||= Hiiro::Matcher.new(all_apps, :name)
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def task_matcher
|
|
190
|
-
@task_matcher ||= Hiiro::Matcher.new(all_tasks, :name)
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def task
|
|
194
|
-
@task ||= begin
|
|
195
|
-
s = session
|
|
196
|
-
t = tree
|
|
197
|
-
all_tasks.find { |task|
|
|
198
|
-
(s && task.session_name == s.name) ||
|
|
199
|
-
(t && task.tree_name == t.name)
|
|
200
|
-
}
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def session
|
|
205
|
-
@session ||= TmuxSession.current
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def tree
|
|
209
|
-
@tree ||= all_trees.find { |t| t.match?(path) }
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def find_task(abbreviated)
|
|
213
|
-
return nil if abbreviated.nil?
|
|
214
|
-
|
|
215
|
-
# Try path-based matching first (handles "parent/child" patterns)
|
|
216
|
-
if abbreviated.include?('/')
|
|
217
|
-
result = task_matcher.resolve_path(abbreviated)
|
|
218
|
-
return result.resolved&.item if result.match?
|
|
219
|
-
|
|
220
|
-
# "main" refers to the parent task itself
|
|
221
|
-
parent_prefix, child_prefix = abbreviated.split('/', 2)
|
|
222
|
-
if 'main'.start_with?(child_prefix)
|
|
223
|
-
return task_matcher.find(parent_prefix).first&.item
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
nil
|
|
227
|
-
else
|
|
228
|
-
task_matcher.find(abbreviated).first&.item
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def find_tree(abbreviated)
|
|
233
|
-
return nil if abbreviated.nil?
|
|
234
|
-
tree_matcher.find(abbreviated).first&.item
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def find_session(abbreviated)
|
|
238
|
-
return nil if abbreviated.nil?
|
|
239
|
-
session_matcher.find(abbreviated).first&.item
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def find_app(abbreviated)
|
|
243
|
-
return nil if abbreviated.nil?
|
|
244
|
-
app_matcher.find(abbreviated).first&.item
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
class TaskManager
|
|
249
|
-
TASKS_DIR = File.join(Dir.home, '.config', 'hiiro', 'tasks')
|
|
250
|
-
APPS_FILE = File.join(Dir.home, '.config', 'hiiro', 'apps.yml')
|
|
251
|
-
|
|
252
|
-
attr_reader :hiiro, :scope, :environment
|
|
253
|
-
|
|
254
|
-
def initialize(hiiro, scope: :task, environment: nil)
|
|
255
|
-
@hiiro = hiiro
|
|
256
|
-
@scope = scope
|
|
257
|
-
@environment = environment || Environment.current
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
def config
|
|
261
|
-
environment.config
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
# --- Scope-aware queries ---
|
|
265
|
-
|
|
266
|
-
def tasks
|
|
267
|
-
if scope == :subtask
|
|
268
|
-
parent = current_parent_task
|
|
269
|
-
return [] unless parent
|
|
270
|
-
main_task = Task.new(name: "#{parent.name}/main", tree: parent.tree_name, session: parent.session_name)
|
|
271
|
-
subtask_list = environment.all_tasks.select { |t| t.parent_name == parent.name }
|
|
272
|
-
[main_task, *subtask_list]
|
|
273
|
-
else
|
|
274
|
-
environment.all_tasks.select(&:top_level?)
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def subtasks(task)
|
|
279
|
-
environment.all_tasks.select { |t| t.parent_name == task.name }
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def task_by_name(name)
|
|
283
|
-
return slash_lookup(name) if name.include?('/')
|
|
284
|
-
|
|
285
|
-
key = (scope == :subtask) ? :short_name : :name
|
|
286
|
-
Hiiro::Matcher.new(tasks, key).by_prefix(name).first&.item
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
def task_by_tree(tree_name)
|
|
290
|
-
environment.task_matcher.resolve(tree_name, :tree_name).resolved&.item
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def task_by_session(session_name)
|
|
294
|
-
environment.task_matcher.resolve(session_name, :session_name).resolved&.item
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
def current_task
|
|
298
|
-
environment.task
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
def current_session
|
|
302
|
-
environment.session
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def current_tree
|
|
306
|
-
environment.tree
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# --- Actions ---
|
|
310
|
-
|
|
311
|
-
def start_task(name, app_name: nil)
|
|
312
|
-
existing = task_by_name(name)
|
|
313
|
-
if existing
|
|
314
|
-
puts "Task '#{existing.name}' already exists. Switching..."
|
|
315
|
-
switch_to_task(existing, app_name: app_name)
|
|
316
|
-
return
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
task_name = scope == :subtask ? "#{current_parent_task.name}/#{name}" : name
|
|
320
|
-
subtree_name = scope == :subtask ? "#{current_parent_task.name}/#{name}" : "#{name}/main"
|
|
321
|
-
|
|
322
|
-
target_path = File.join(Hiiro::WORK_DIR, subtree_name)
|
|
323
|
-
|
|
324
|
-
git = Hiiro::Git.new(nil, Hiiro::REPO_PATH)
|
|
325
|
-
available = find_available_tree
|
|
326
|
-
if available
|
|
327
|
-
puts "Renaming worktree '#{available.name}' to '#{subtree_name}'..."
|
|
328
|
-
FileUtils.mkdir_p(File.dirname(target_path))
|
|
329
|
-
unless git.move_worktree(available.path, target_path, repo_path: Hiiro::REPO_PATH)
|
|
330
|
-
puts "ERROR: Failed to rename worktree"
|
|
331
|
-
return
|
|
332
|
-
end
|
|
333
|
-
else
|
|
334
|
-
puts "Creating new worktree '#{subtree_name}'..."
|
|
335
|
-
FileUtils.mkdir_p(File.dirname(target_path))
|
|
336
|
-
unless git.add_worktree_detached(target_path, repo_path: Hiiro::REPO_PATH)
|
|
337
|
-
puts "ERROR: Failed to create worktree"
|
|
338
|
-
return
|
|
339
|
-
end
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
session_name = task_name
|
|
343
|
-
task = Task.new(name: task_name, tree: subtree_name, session: session_name)
|
|
344
|
-
config.save_task(task)
|
|
345
|
-
|
|
346
|
-
base_dir = target_path
|
|
347
|
-
if app_name
|
|
348
|
-
app = environment.find_app(app_name)
|
|
349
|
-
base_dir = app.resolve(target_path) if app
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
Dir.chdir(base_dir)
|
|
353
|
-
hiiro.start_tmux_session(session_name)
|
|
354
|
-
|
|
355
|
-
puts "Started task '#{task_name}' in worktree '#{subtree_name}'"
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
def switch_to_task(task, app_name: nil)
|
|
359
|
-
unless task
|
|
360
|
-
puts "Task not found"
|
|
361
|
-
return
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
tree = environment.find_tree(task.tree_name)
|
|
365
|
-
tree_path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
|
|
366
|
-
|
|
367
|
-
session_name = task.session_name
|
|
368
|
-
session_exists = system('tmux', 'has-session', '-t', session_name, err: File::NULL)
|
|
369
|
-
|
|
370
|
-
if session_exists
|
|
371
|
-
hiiro.start_tmux_session(session_name)
|
|
372
|
-
else
|
|
373
|
-
base_dir = tree_path
|
|
374
|
-
if app_name
|
|
375
|
-
app = environment.find_app(app_name)
|
|
376
|
-
base_dir = app.resolve(tree_path) if app
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
if Dir.exist?(base_dir)
|
|
380
|
-
Dir.chdir(base_dir)
|
|
381
|
-
hiiro.start_tmux_session(session_name)
|
|
382
|
-
else
|
|
383
|
-
puts "ERROR: Path '#{base_dir}' does not exist"
|
|
384
|
-
return
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
puts "Switched to '#{task.name}'"
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
def stop_task(task)
|
|
392
|
-
unless task
|
|
393
|
-
puts "Task not found"
|
|
394
|
-
return
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
config.remove_task(task.name)
|
|
398
|
-
# Also remove any subtasks
|
|
399
|
-
subtasks(task).each { |st| config.remove_task(st.name) }
|
|
400
|
-
|
|
401
|
-
puts "Stopped task '#{task.name}' (worktree available for reuse)"
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
def list
|
|
405
|
-
items = tasks
|
|
406
|
-
if items.empty?
|
|
407
|
-
puts scope == :subtask ? "No subtasks found" : "No tasks found"
|
|
408
|
-
puts "Use 'h #{scope} start NAME' to create one."
|
|
409
|
-
return
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
current = current_task
|
|
413
|
-
label = scope == :subtask ? "Subtasks" : "Tasks"
|
|
414
|
-
if scope == :subtask && current
|
|
415
|
-
parent = current_parent_task
|
|
416
|
-
label = "Subtasks of '#{parent&.name}'" if parent
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
puts "#{label}:"
|
|
420
|
-
puts
|
|
421
|
-
|
|
422
|
-
items.each do |task|
|
|
423
|
-
marker = (current && current.name == task.name) ? "*" : " "
|
|
424
|
-
tree = environment.find_tree(task.tree_name)
|
|
425
|
-
branch = tree&.branch || (tree&.detached? ? '(detached)' : nil)
|
|
426
|
-
branch_str = branch ? " [#{branch}]" : ""
|
|
427
|
-
|
|
428
|
-
display_name = scope == :subtask ? task.short_name : task.name
|
|
429
|
-
puts format("%s %-25s tree: %-20s%s", marker, display_name, task.tree_name || '(none)', branch_str)
|
|
430
|
-
|
|
431
|
-
# Show subtasks under parent
|
|
432
|
-
if scope == :task
|
|
433
|
-
subs = subtasks(task)
|
|
434
|
-
subs.each do |st|
|
|
435
|
-
sub_marker = (current && current.name == st.name) ? "*" : " "
|
|
436
|
-
sub_tree = environment.find_tree(st.tree_name)
|
|
437
|
-
sub_branch = sub_tree&.branch || (sub_tree&.detached? ? '(detached)' : nil)
|
|
438
|
-
sub_branch_str = sub_branch ? " [#{sub_branch}]" : ""
|
|
439
|
-
puts format("%s - %-23s tree: %-20s%s", sub_marker, st.short_name, st.tree_name || '(none)', sub_branch_str)
|
|
440
|
-
end
|
|
441
|
-
end
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
available = environment.all_trees.reject { |t|
|
|
445
|
-
environment.all_tasks.any? { |task| task.tree_name == t.name }
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if available.any?
|
|
449
|
-
puts
|
|
450
|
-
available.each do |tree|
|
|
451
|
-
branch_str = tree.branch ? " [#{tree.branch}]" : tree.detached? ? " [(detached)]" : ""
|
|
452
|
-
puts format(" %-25s (available)%s", tree.name, branch_str)
|
|
453
|
-
end
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
def status
|
|
458
|
-
task = current_task
|
|
459
|
-
unless task
|
|
460
|
-
puts "Not currently in a task session"
|
|
461
|
-
return
|
|
462
|
-
end
|
|
463
|
-
|
|
464
|
-
puts "Task: #{task.name}"
|
|
465
|
-
puts "Worktree: #{task.tree_name}"
|
|
466
|
-
tree = environment.find_tree(task.tree_name)
|
|
467
|
-
puts "Path: #{tree&.path || '(unknown)'}"
|
|
468
|
-
puts "Session: #{task.session_name}"
|
|
469
|
-
puts "Parent: #{task.parent_name}" if task.subtask?
|
|
470
|
-
end
|
|
471
|
-
|
|
472
|
-
def save
|
|
473
|
-
task = current_task
|
|
474
|
-
unless task
|
|
475
|
-
puts "ERROR: Not currently in a task session"
|
|
476
|
-
return
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
windows = capture_tmux_windows(task.session_name)
|
|
480
|
-
puts "Saved task '#{task.name}' state (#{windows.count} windows)"
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
def open_app(app_name)
|
|
484
|
-
task = current_task
|
|
485
|
-
unless task
|
|
486
|
-
puts "ERROR: Not currently in a task session"
|
|
487
|
-
return
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
result = resolve_app(app_name, task)
|
|
491
|
-
return unless result
|
|
492
|
-
|
|
493
|
-
resolved_name, app_path = result
|
|
494
|
-
system('tmux', 'new-window', '-n', resolved_name, '-c', app_path)
|
|
495
|
-
puts "Opened '#{resolved_name}' in new window (#{app_path})"
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
def list_apps
|
|
499
|
-
apps = environment.all_apps
|
|
500
|
-
if apps.any?
|
|
501
|
-
puts "Configured apps:"
|
|
502
|
-
puts
|
|
503
|
-
apps.each do |app|
|
|
504
|
-
puts format(" %-20s => %s", app.name, app.relative_path)
|
|
505
|
-
end
|
|
506
|
-
else
|
|
507
|
-
puts "No apps configured."
|
|
508
|
-
puts "Create #{APPS_FILE} with format:"
|
|
509
|
-
puts " app_name: relative/path/from/repo"
|
|
510
|
-
end
|
|
511
|
-
end
|
|
512
|
-
|
|
513
|
-
def branch(task_name = nil)
|
|
514
|
-
branch_name = nil
|
|
515
|
-
|
|
516
|
-
if task_name.nil?
|
|
517
|
-
branch = select_branch_interactive
|
|
518
|
-
return unless branch
|
|
519
|
-
print branch
|
|
520
|
-
return
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
task = task_by_name(task_name)
|
|
524
|
-
unless task
|
|
525
|
-
puts "Task not found: #{task_name}"
|
|
526
|
-
return
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
if task.branch
|
|
530
|
-
print task.branch
|
|
531
|
-
elsif task.tree&.detached?
|
|
532
|
-
puts "(detached HEAD)"
|
|
533
|
-
else
|
|
534
|
-
puts "(no branch)"
|
|
535
|
-
end
|
|
536
|
-
end
|
|
537
|
-
|
|
538
|
-
def cd_to_task(task)
|
|
539
|
-
unless task
|
|
540
|
-
puts "Task not found"
|
|
541
|
-
return
|
|
542
|
-
end
|
|
543
|
-
|
|
544
|
-
tree = environment.find_tree(task.tree_name)
|
|
545
|
-
path = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
|
|
546
|
-
send_cd(path)
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
def cd_to_app(app_name = nil)
|
|
550
|
-
task = current_task
|
|
551
|
-
unless task
|
|
552
|
-
puts "ERROR: Not currently in a task session"
|
|
553
|
-
return
|
|
554
|
-
end
|
|
555
|
-
|
|
556
|
-
if app_name.nil? || app_name.empty?
|
|
557
|
-
tree = environment.find_tree(task.tree_name)
|
|
558
|
-
send_cd(tree&.path || File.join(Hiiro::WORK_DIR, task.tree_name))
|
|
559
|
-
return
|
|
560
|
-
end
|
|
561
|
-
|
|
562
|
-
result = resolve_app(app_name, task)
|
|
563
|
-
return unless result
|
|
564
|
-
|
|
565
|
-
_resolved_name, app_path = result
|
|
566
|
-
send_cd(app_path)
|
|
567
|
-
end
|
|
568
|
-
|
|
569
|
-
def app_path(app_name = nil)
|
|
570
|
-
task = current_task
|
|
571
|
-
tree_root = if task
|
|
572
|
-
tree = environment.find_tree(task.tree_name)
|
|
573
|
-
tree&.path || File.join(Hiiro::WORK_DIR, task.tree_name)
|
|
574
|
-
else
|
|
575
|
-
Hiiro::Git.new(nil, Dir.pwd).root
|
|
576
|
-
end
|
|
577
|
-
|
|
578
|
-
if app_name.nil?
|
|
579
|
-
print tree_root
|
|
580
|
-
return
|
|
581
|
-
end
|
|
582
|
-
|
|
583
|
-
result = environment.app_matcher.find_all(app_name)
|
|
584
|
-
|
|
585
|
-
case result.count
|
|
586
|
-
when 0
|
|
587
|
-
puts "ERROR: No matches found"
|
|
588
|
-
puts
|
|
589
|
-
puts "Possible Apps:"
|
|
590
|
-
environment.all_apps.each { |a| puts format(" %-20s => %s", a.name, a.relative_path) }
|
|
591
|
-
when 1
|
|
592
|
-
print result.first.item.resolve(tree_root)
|
|
593
|
-
else
|
|
594
|
-
puts "Multiple matches found:"
|
|
595
|
-
result.matches.each { |m| puts format(" %-20s => %s", m.item.name, m.item.relative_path) }
|
|
596
|
-
end
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
def DELETEME_help
|
|
600
|
-
scope_name = scope.to_s
|
|
601
|
-
puts "Usage: h #{scope_name} <subcommand> [args]"
|
|
602
|
-
puts
|
|
603
|
-
puts "Subcommands:"
|
|
604
|
-
puts " list, ls List #{scope_name}s"
|
|
605
|
-
puts " start NAME [APP] Start a new #{scope_name}"
|
|
606
|
-
puts " switch [NAME] Switch to a #{scope_name} (interactive if no name)"
|
|
607
|
-
puts " branch [NAME] Print git branch for a #{scope_name} (interactive if no name)"
|
|
608
|
-
puts " app [APP_NAME] Open app in new tmux window (interactive if no name)"
|
|
609
|
-
puts " apps List configured apps"
|
|
610
|
-
puts " cd [APP_NAME] Change directory to app"
|
|
611
|
-
puts " path [APP_NAME] Print app path"
|
|
612
|
-
puts " status, st Show current #{scope_name} status"
|
|
613
|
-
puts " save Save current session state"
|
|
614
|
-
puts " stop [NAME] Stop a #{scope_name} (interactive if no name)"
|
|
615
|
-
end
|
|
616
|
-
|
|
617
|
-
# --- Interactive selection with sk ---
|
|
618
|
-
|
|
619
|
-
def select_task_interactive(prompt = nil)
|
|
620
|
-
names = if scope == :subtask
|
|
621
|
-
tasks.map(&:short_name)
|
|
622
|
-
else
|
|
623
|
-
environment.all_tasks.map(&:name)
|
|
624
|
-
end
|
|
625
|
-
return nil if names.empty?
|
|
626
|
-
|
|
627
|
-
hiiro.fuzzyfind(names.sort)
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
def value_for_task(task_name = nil, &block)
|
|
631
|
-
if task_name
|
|
632
|
-
task = task_by_name(task_name)
|
|
633
|
-
return block.call(task) if task
|
|
634
|
-
end
|
|
635
|
-
|
|
636
|
-
tasks = scope == :subtask ? tasks.sort_by(&:short_name) : environment.all_tasks.sort_by(&:name)
|
|
637
|
-
|
|
638
|
-
mapping = tasks.each_with_object({}) do |task,h|
|
|
639
|
-
name = scope == :subtask ? task.short_name : task.name
|
|
640
|
-
val = block.call(task)&.to_s
|
|
641
|
-
|
|
642
|
-
line = format("%-25s | %s", name, val)
|
|
643
|
-
h[line] = val
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
hiiro.fuzzyfind_from_map(mapping)
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
def select_branch_interactive(prompt = nil)
|
|
650
|
-
name_map = if scope == :subtask
|
|
651
|
-
tasks.sort_by(&:short_name).each_with_object({}) { |t,h| h[format('%-25s | %s', t.short_name, t.branch)] = t.branch }
|
|
652
|
-
else
|
|
653
|
-
environment.all_tasks.sort_by(&:name).each_with_object({}) { |t,h| h[format('%-25s | %s', t.name, t.branch)] = t.branch }
|
|
654
|
-
end
|
|
655
|
-
return nil if name_map.empty?
|
|
656
|
-
|
|
657
|
-
hiiro.fuzzyfind_from_map(name_map)
|
|
658
|
-
end
|
|
659
|
-
|
|
660
|
-
# --- Private helpers ---
|
|
661
|
-
|
|
662
|
-
private
|
|
663
|
-
|
|
664
|
-
def slash_lookup(input)
|
|
665
|
-
environment.find_task(input)
|
|
666
|
-
end
|
|
667
|
-
|
|
668
|
-
def current_parent_task
|
|
669
|
-
task = current_task
|
|
670
|
-
return nil unless task
|
|
671
|
-
|
|
672
|
-
if task.subtask?
|
|
673
|
-
environment.find_task(task.parent_name)
|
|
674
|
-
else
|
|
675
|
-
task
|
|
676
|
-
end
|
|
677
|
-
end
|
|
678
|
-
|
|
679
|
-
def find_available_tree
|
|
680
|
-
assigned_tree_names = environment.all_tasks.map(&:tree_name)
|
|
681
|
-
environment.all_trees.find { |tree| !assigned_tree_names.include?(tree.name) }
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
def resolve_app(app_name, task)
|
|
685
|
-
tree = environment.find_tree(task.tree_name)
|
|
686
|
-
tree_root = tree ? tree.path : File.join(Hiiro::WORK_DIR, task.tree_name)
|
|
687
|
-
|
|
688
|
-
result = environment.app_matcher.find_all(app_name)
|
|
689
|
-
|
|
690
|
-
case result.count
|
|
691
|
-
when 0
|
|
692
|
-
# Fallback: directory discovery
|
|
693
|
-
exact = File.join(tree_root, app_name)
|
|
694
|
-
return [app_name, exact] if Dir.exist?(exact)
|
|
695
|
-
|
|
696
|
-
nested = File.join(tree_root, app_name, app_name)
|
|
697
|
-
return [app_name, nested] if Dir.exist?(nested)
|
|
698
|
-
|
|
699
|
-
puts "ERROR: App '#{app_name}' not found"
|
|
700
|
-
list_apps
|
|
701
|
-
nil
|
|
702
|
-
when 1
|
|
703
|
-
app = result.first.item
|
|
704
|
-
[app.name, app.resolve(tree_root)]
|
|
705
|
-
else
|
|
706
|
-
exact = result.matches.find { |m| m.item.name == app_name }
|
|
707
|
-
if exact
|
|
708
|
-
[exact.item.name, exact.item.resolve(tree_root)]
|
|
709
|
-
else
|
|
710
|
-
puts "ERROR: '#{app_name}' matches multiple apps:"
|
|
711
|
-
result.matches.each { |m| puts " #{m.item.name}" }
|
|
712
|
-
nil
|
|
713
|
-
end
|
|
714
|
-
end
|
|
715
|
-
end
|
|
716
|
-
|
|
717
|
-
def send_cd(path)
|
|
718
|
-
pane = ENV['TMUX_PANE']
|
|
719
|
-
if pane
|
|
720
|
-
system('tmux', 'send-keys', '-t', pane, "cd #{path}\n")
|
|
721
|
-
else
|
|
722
|
-
system('tmux', 'send-keys', "cd #{path}\n")
|
|
723
|
-
end
|
|
724
|
-
end
|
|
725
|
-
|
|
726
|
-
def capture_tmux_windows(session)
|
|
727
|
-
output = `tmux list-windows -t #{session} -F '\#{window_index}:\#{window_name}:\#{pane_current_path}' 2>/dev/null`
|
|
728
|
-
output.lines.map(&:strip).map { |line|
|
|
729
|
-
idx, name, path = line.split(':')
|
|
730
|
-
{ 'index' => idx, 'name' => name, 'path' => path }
|
|
731
|
-
}
|
|
732
|
-
end
|
|
733
|
-
|
|
734
|
-
def sk_select(items)
|
|
735
|
-
hiiro.fuzzyfind(items) # Hiiro::Fuzzyfind.select(items)
|
|
736
|
-
end
|
|
737
|
-
|
|
738
|
-
class Config
|
|
739
|
-
attr_reader :tasks_file, :apps_file
|
|
740
|
-
|
|
741
|
-
def initialize(tasks_file: nil, apps_file: nil)
|
|
742
|
-
@tasks_file = tasks_file || File.join(TASKS_DIR, 'tasks.yml')
|
|
743
|
-
@apps_file = apps_file || APPS_FILE
|
|
744
|
-
end
|
|
745
|
-
|
|
746
|
-
def tasks
|
|
747
|
-
data = load_tasks
|
|
748
|
-
(data['tasks'] || []).map { |h| Task.new(**h.transform_keys(&:to_sym)) }
|
|
749
|
-
end
|
|
750
|
-
|
|
751
|
-
def apps
|
|
752
|
-
return [] unless File.exist?(apps_file)
|
|
753
|
-
data = YAML.safe_load_file(apps_file) || {}
|
|
754
|
-
data.map { |name, path| App.new(name: name, path: path) }
|
|
755
|
-
end
|
|
756
|
-
|
|
757
|
-
def save_task(task)
|
|
758
|
-
data = load_tasks
|
|
759
|
-
data['tasks'] ||= []
|
|
760
|
-
data['tasks'].reject! { |t| t['name'] == task.name }
|
|
761
|
-
data['tasks'] << task.to_h.transform_keys(&:to_s)
|
|
762
|
-
save_tasks(data)
|
|
763
|
-
end
|
|
764
|
-
|
|
765
|
-
def remove_task(name)
|
|
766
|
-
data = load_tasks
|
|
767
|
-
data['tasks'] ||= []
|
|
768
|
-
data['tasks'].reject! { |t| t['name'] == name }
|
|
769
|
-
save_tasks(data)
|
|
770
|
-
end
|
|
771
|
-
|
|
772
|
-
private
|
|
773
|
-
|
|
774
|
-
def load_tasks
|
|
775
|
-
if File.exist?(tasks_file)
|
|
776
|
-
return YAML.safe_load_file(tasks_file) || { 'tasks' => [] }
|
|
777
|
-
end
|
|
778
|
-
|
|
779
|
-
# Load from individual task_*.yml files
|
|
780
|
-
task_files = Dir.glob(File.join(File.dirname(tasks_file), 'task_*.yml'))
|
|
781
|
-
if task_files.any?
|
|
782
|
-
tasks = task_files.map do |file|
|
|
783
|
-
short_name = File.basename(file, '.yml').sub(/^task_/, '')
|
|
784
|
-
data = YAML.safe_load_file(file) || {}
|
|
785
|
-
# Support parent key for subtasks, or infer from tree path
|
|
786
|
-
parent = data['parent']
|
|
787
|
-
if parent.nil? && data['tree']&.include?('/')
|
|
788
|
-
parent = data['tree'].split('/').first
|
|
789
|
-
end
|
|
790
|
-
name = parent ? "#{parent}/#{short_name}" : short_name
|
|
791
|
-
h = { 'name' => name }
|
|
792
|
-
h['tree'] = data['tree'] if data['tree']
|
|
793
|
-
h['session'] = data['session'] if data['session']
|
|
794
|
-
h
|
|
795
|
-
end
|
|
796
|
-
return { 'tasks' => tasks }
|
|
797
|
-
end
|
|
798
|
-
|
|
799
|
-
assignments_file = File.join(File.dirname(tasks_file), 'assignments.yml')
|
|
800
|
-
if File.exist?(assignments_file)
|
|
801
|
-
raw = YAML.safe_load_file(assignments_file) || {}
|
|
802
|
-
tasks = raw.map do |tree_path, task_name|
|
|
803
|
-
h = { 'name' => task_name, 'tree' => tree_path }
|
|
804
|
-
h['session'] = task_name if task_name.include?('/')
|
|
805
|
-
h
|
|
806
|
-
end
|
|
807
|
-
data = { 'tasks' => tasks }
|
|
808
|
-
save_tasks(data)
|
|
809
|
-
return data
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
{ 'tasks' => [] }
|
|
813
|
-
end
|
|
814
|
-
|
|
815
|
-
def save_tasks(data)
|
|
816
|
-
FileUtils.mkdir_p(File.dirname(tasks_file))
|
|
817
|
-
File.write(tasks_file, YAML.dump(data))
|
|
818
|
-
end
|
|
819
|
-
end
|
|
820
|
-
end
|
|
821
|
-
|
|
822
|
-
module Tasks
|
|
823
|
-
def self.load(hiiro)
|
|
824
|
-
# hiiro.load_plugin(Tmux)
|
|
825
|
-
attach_methods(hiiro)
|
|
826
|
-
add_subcommands(hiiro)
|
|
827
|
-
end
|
|
828
|
-
|
|
829
|
-
def self.attach_methods(hiiro, task_manager=nil)
|
|
830
|
-
environment = Environment.current
|
|
831
|
-
|
|
832
|
-
hiiro.attach_method(:environment) { environment }
|
|
833
|
-
hiiro.attach_method(:env) { environment }
|
|
834
|
-
hiiro.attach_method(:task_manager) { task_manager }
|
|
835
|
-
hiiro.attach_method(:tm) { task_manager }
|
|
836
|
-
|
|
837
|
-
hiiro
|
|
838
|
-
end
|
|
839
|
-
|
|
840
|
-
def self.add_subcommands(hiiro)
|
|
841
|
-
hiiro.add_subcmd(:task) do |*args|
|
|
842
|
-
tm = TaskManager.new(hiiro, scope: :task)
|
|
843
|
-
build_hiiro(hiiro, tm).run
|
|
844
|
-
end
|
|
845
|
-
|
|
846
|
-
hiiro.add_subcmd(:subtask) do |*args|
|
|
847
|
-
tm = TaskManager.new(hiiro, scope: :subtask)
|
|
848
|
-
build_hiiro(hiiro, tm).run
|
|
849
|
-
end
|
|
850
|
-
end
|
|
851
|
-
|
|
852
|
-
def self.build_hiiro(parent_hiiro, tm)
|
|
853
|
-
bin_name = [parent_hiiro.bin, parent_hiiro.subcmd || ''].join('-')
|
|
854
|
-
|
|
855
|
-
task_hiiro = Hiiro.init(
|
|
856
|
-
bin_name:,
|
|
857
|
-
args: parent_hiiro.args,
|
|
858
|
-
) do |h|
|
|
859
|
-
h.add_subcmd(:list) { tm.list }
|
|
860
|
-
h.add_subcmd(:ls) { tm.list }
|
|
861
|
-
|
|
862
|
-
h.add_subcmd(:start) do |task_name, app_name=nil|
|
|
863
|
-
tm.start_task(task_name, app_name: app_name)
|
|
864
|
-
end
|
|
865
|
-
|
|
866
|
-
h.add_subcmd(:switch) do |task_name=nil, app_name=nil|
|
|
867
|
-
if task_name.nil?
|
|
868
|
-
task_name = tm.select_task_interactive
|
|
869
|
-
next unless task_name
|
|
870
|
-
end
|
|
871
|
-
task = tm.task_by_name(task_name)
|
|
872
|
-
tm.switch_to_task(task, app_name: app_name)
|
|
873
|
-
end
|
|
874
|
-
|
|
875
|
-
h.add_subcmd(:app) do |app_name=nil|
|
|
876
|
-
if app_name.nil?
|
|
877
|
-
names = tm.environment.all_apps.map(&:name)
|
|
878
|
-
app_name = h.fuzzyfind(names)
|
|
879
|
-
next unless app_name
|
|
880
|
-
end
|
|
881
|
-
tm.open_app(app_name)
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
h.add_subcmd(:apps) { tm.list_apps }
|
|
885
|
-
|
|
886
|
-
h.add_subcmd(:cd) do |app_name=nil|
|
|
887
|
-
tm.cd_to_app(app_name)
|
|
888
|
-
end
|
|
889
|
-
|
|
890
|
-
h.add_subcmd(:path) do |app_name=nil|
|
|
891
|
-
tm.app_path(app_name)
|
|
892
|
-
end
|
|
893
|
-
|
|
894
|
-
h.add_subcmd(:branch) do |task_name=nil|
|
|
895
|
-
print tm.value_for_task(task_name, &:branch)
|
|
896
|
-
end
|
|
897
|
-
|
|
898
|
-
h.add_subcmd(:tree) do |task_name=nil|
|
|
899
|
-
print tm.value_for_task(task_name, &:tree_name)
|
|
900
|
-
end
|
|
901
|
-
|
|
902
|
-
h.add_subcmd(:session) do |task_name=nil|
|
|
903
|
-
print tm.value_for_task(task_name, &:session_name)
|
|
904
|
-
end
|
|
905
|
-
|
|
906
|
-
h.add_subcmd(:current) do
|
|
907
|
-
task = tm.current_task
|
|
908
|
-
next STDERR.puts("Not in a task") unless task
|
|
909
|
-
print task.name
|
|
910
|
-
end
|
|
911
|
-
|
|
912
|
-
h.add_subcmd(:cbranch) do
|
|
913
|
-
task = tm.current_task
|
|
914
|
-
next STDERR.puts("Not in a task") unless task
|
|
915
|
-
print task.branch if task.branch
|
|
916
|
-
end
|
|
917
|
-
|
|
918
|
-
h.add_subcmd(:ctree) do
|
|
919
|
-
task = tm.current_task
|
|
920
|
-
next STDERR.puts("Not in a task") unless task
|
|
921
|
-
print task.tree_name if task.tree_name
|
|
922
|
-
end
|
|
923
|
-
|
|
924
|
-
h.add_subcmd(:csession) do
|
|
925
|
-
task = tm.current_task
|
|
926
|
-
next STDERR.puts("Not in a task") unless task
|
|
927
|
-
print task.session_name if task.session_name
|
|
928
|
-
end
|
|
929
|
-
|
|
930
|
-
h.add_subcmd(:status) { tm.status }
|
|
931
|
-
h.add_subcmd(:st) { tm.status }
|
|
932
|
-
|
|
933
|
-
h.add_subcmd(:save) { tm.save }
|
|
934
|
-
|
|
935
|
-
h.add_subcmd(:stop) do |task_name=nil|
|
|
936
|
-
if task_name.nil?
|
|
937
|
-
task_name = tm.select_task_interactive
|
|
938
|
-
next unless task_name
|
|
939
|
-
end
|
|
940
|
-
task = tm.task_by_name(task_name)
|
|
941
|
-
tm.stop_task(task)
|
|
942
|
-
end
|
|
943
|
-
|
|
944
|
-
h.add_subcmd(:edit) do
|
|
945
|
-
system(ENV['EDITOR'] || 'nvim', __FILE__)
|
|
946
|
-
end
|
|
947
|
-
|
|
948
|
-
h.add_subcmd(:todo) do |*todo_args|
|
|
949
|
-
todo_manager = Hiiro::TodoManager.new
|
|
950
|
-
task = tm.current_task
|
|
951
|
-
|
|
952
|
-
task_info = if task
|
|
953
|
-
{
|
|
954
|
-
task_name: task.subtask? ? task.parent_name : task.name,
|
|
955
|
-
subtask_name: task.subtask? ? task.short_name : nil,
|
|
956
|
-
tree: task.tree_name,
|
|
957
|
-
branch: task.branch,
|
|
958
|
-
session: task.session_name
|
|
959
|
-
}
|
|
960
|
-
end
|
|
961
|
-
|
|
962
|
-
todo_subcmd = todo_args.shift
|
|
963
|
-
case todo_subcmd
|
|
964
|
-
when 'ls', 'list', nil
|
|
965
|
-
show_all = todo_args.delete('-a') || todo_args.delete('--all')
|
|
966
|
-
# Default to filtering by current task unless -a or --all is used
|
|
967
|
-
items = if show_all
|
|
968
|
-
todo_manager.all
|
|
969
|
-
elsif task
|
|
970
|
-
todo_manager.filter_by_task(task.name).select { |i| %w[not_started started].include?(i.status) }
|
|
971
|
-
else
|
|
972
|
-
todo_manager.active
|
|
973
|
-
end
|
|
974
|
-
|
|
975
|
-
if items.empty?
|
|
976
|
-
puts task ? "No todo items for task '#{task.name}'." : "No todo items found."
|
|
977
|
-
else
|
|
978
|
-
puts todo_manager.list(items)
|
|
979
|
-
end
|
|
980
|
-
|
|
981
|
-
when 'add'
|
|
982
|
-
if todo_args.empty?
|
|
983
|
-
new_items = todo_manager.edit_items(task_info: task_info)
|
|
984
|
-
if new_items.empty?
|
|
985
|
-
puts "No items added."
|
|
986
|
-
next
|
|
987
|
-
end
|
|
988
|
-
todo_manager.add_items(new_items)
|
|
989
|
-
if new_items.length == 1
|
|
990
|
-
puts "Added: #{todo_manager.format_item(new_items.first)}"
|
|
991
|
-
else
|
|
992
|
-
puts "Added #{new_items.length} items:"
|
|
993
|
-
new_items.each { |item| puts " #{todo_manager.format_item(item)}" }
|
|
994
|
-
end
|
|
995
|
-
next
|
|
996
|
-
end
|
|
997
|
-
|
|
998
|
-
tags = nil
|
|
999
|
-
text_parts = []
|
|
1000
|
-
while todo_args.any?
|
|
1001
|
-
arg = todo_args.shift
|
|
1002
|
-
case arg
|
|
1003
|
-
when '-t', '--tags'
|
|
1004
|
-
tags = todo_args.shift
|
|
1005
|
-
else
|
|
1006
|
-
text_parts << arg
|
|
1007
|
-
end
|
|
1008
|
-
end
|
|
1009
|
-
text = text_parts.join(' ')
|
|
1010
|
-
item = todo_manager.add(text, tags: tags, task_info: task_info)
|
|
1011
|
-
puts "Added: #{todo_manager.format_item(item)}"
|
|
1012
|
-
|
|
1013
|
-
when 'rm', 'remove'
|
|
1014
|
-
id_or_index = todo_args.shift
|
|
1015
|
-
unless id_or_index
|
|
1016
|
-
puts "Usage: h #{tm.scope} todo rm <id|index>"
|
|
1017
|
-
next
|
|
1018
|
-
end
|
|
1019
|
-
item = todo_manager.remove(id_or_index)
|
|
1020
|
-
puts item ? "Removed: #{item.text}" : "Item not found: #{id_or_index}"
|
|
1021
|
-
|
|
1022
|
-
when 'start'
|
|
1023
|
-
id_or_index = todo_args.shift
|
|
1024
|
-
unless id_or_index
|
|
1025
|
-
puts "Usage: h #{tm.scope} todo start <id|index>"
|
|
1026
|
-
next
|
|
1027
|
-
end
|
|
1028
|
-
item = todo_manager.start(id_or_index)
|
|
1029
|
-
puts item ? "Started: #{todo_manager.format_item(item)}" : "Item not found: #{id_or_index}"
|
|
1030
|
-
|
|
1031
|
-
when 'done'
|
|
1032
|
-
id_or_index = todo_args.shift
|
|
1033
|
-
unless id_or_index
|
|
1034
|
-
puts "Usage: h #{tm.scope} todo done <id|index>"
|
|
1035
|
-
next
|
|
1036
|
-
end
|
|
1037
|
-
item = todo_manager.done(id_or_index)
|
|
1038
|
-
puts item ? "Done: #{todo_manager.format_item(item)}" : "Item not found: #{id_or_index}"
|
|
1039
|
-
|
|
1040
|
-
when 'skip'
|
|
1041
|
-
id_or_index = todo_args.shift
|
|
1042
|
-
unless id_or_index
|
|
1043
|
-
puts "Usage: h #{tm.scope} todo skip <id|index>"
|
|
1044
|
-
next
|
|
1045
|
-
end
|
|
1046
|
-
item = todo_manager.skip(id_or_index)
|
|
1047
|
-
puts item ? "Skipped: #{todo_manager.format_item(item)}" : "Item not found: #{id_or_index}"
|
|
1048
|
-
|
|
1049
|
-
when 'search'
|
|
1050
|
-
query = todo_args.join(' ')
|
|
1051
|
-
if query.empty?
|
|
1052
|
-
puts "Usage: h #{tm.scope} todo search <query>"
|
|
1053
|
-
next
|
|
1054
|
-
end
|
|
1055
|
-
items = todo_manager.search(query)
|
|
1056
|
-
if items.empty?
|
|
1057
|
-
puts "No items matching: #{query}"
|
|
1058
|
-
else
|
|
1059
|
-
puts todo_manager.list(items)
|
|
1060
|
-
end
|
|
1061
|
-
|
|
1062
|
-
else
|
|
1063
|
-
puts "Usage: h #{tm.scope} todo <ls|add|rm|start|done|skip|search> [args]"
|
|
1064
|
-
end
|
|
1065
|
-
end
|
|
1066
|
-
end
|
|
1067
|
-
|
|
1068
|
-
attach_methods task_hiiro, tm
|
|
1069
|
-
end
|
|
1070
|
-
end
|