hiiro 0.1.236 → 0.1.237
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/bin/h-branch +117 -1
- data/bin/h-pr +90 -22
- data/lib/hiiro/tags.rb +71 -0
- data/lib/hiiro/tasks.rb +86 -6
- data/lib/hiiro/version.rb +1 -1
- data/lib/hiiro.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b4988e7f5ab4be2b903187c815ec22bea49c3817c69b9520390758d5eb16ee0
|
|
4
|
+
data.tar.gz: d16582776302c496a387791be6dfb3424fb6c669af6efbcf38f25c555feb3e88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd23321769a10e3181d1e88ef9f6a953ff86fab5d4037e03058a55760ae22f64082e20a779f372a06d1f588909733a82ead732ce0d305f05873846913bc01937
|
|
7
|
+
data.tar.gz: a67d8dbd70576be90258660e17708ecb86b0d823cbc10be838f05ddeb1fb1de307ec72b8d9d1247f96cbbbca2a0a5734beb89b0e39e53547663fbdd48ebdc570
|
data/bin/h-branch
CHANGED
|
@@ -116,14 +116,130 @@ class BranchManager
|
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
BRANCH_TAG_OPTS = Proc.new {
|
|
120
|
+
option(:tag, short: 't', desc: 'filter by tag (OR when multiple)', multi: true)
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
Hiiro.run(*ARGV) do
|
|
120
|
-
manager
|
|
124
|
+
manager = BranchManager.new(self)
|
|
125
|
+
tag_store = Hiiro::Tags.new(:branch)
|
|
121
126
|
|
|
122
127
|
add_subcmd(:edit) { edit_files(__FILE__) }
|
|
123
128
|
add_subcmd(:save) { manager.save }
|
|
124
129
|
add_subcmd(:current) { print `git branch --show-current` }
|
|
125
130
|
add_subcmd(:info) { manager.current }
|
|
126
131
|
|
|
132
|
+
add_subcmd(:ls) do |*ls_args|
|
|
133
|
+
opts = Hiiro::Options.parse(ls_args, &BRANCH_TAG_OPTS)
|
|
134
|
+
branches = git.branches(sort_by: 'authordate', ignore_case: true)
|
|
135
|
+
current = git.branch
|
|
136
|
+
tags_all = tag_store.all # { branch_name => [tags] }
|
|
137
|
+
|
|
138
|
+
tag_filter = Array(opts.tag).reject(&:empty?)
|
|
139
|
+
if tag_filter.any?
|
|
140
|
+
branches = branches.select { |b| (Array(tags_all[b]) & tag_filter).any? }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if branches.empty?
|
|
144
|
+
puts tag_filter.any? ? "No branches match tags: #{tag_filter.join(', ')}" : "No branches found."
|
|
145
|
+
next
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
branches.each do |b|
|
|
149
|
+
marker = b == current ? "* " : " "
|
|
150
|
+
tags = Array(tags_all[b])
|
|
151
|
+
tag_str = tags.any? ? " " + Hiiro::Tags.badges(tags) : ""
|
|
152
|
+
puts "#{marker}#{b}#{tag_str}"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
add_subcmd(:tag) do |*raw_args|
|
|
157
|
+
opts = Hiiro::Options.parse(raw_args) { flag(:edit, short: 'e', desc: 'open YAML editor to bulk-tag branches') }
|
|
158
|
+
branches = git.branches(sort_by: 'authordate', ignore_case: true)
|
|
159
|
+
|
|
160
|
+
if opts.edit
|
|
161
|
+
branch_lines = branches.map { |b| "- #{b}" }
|
|
162
|
+
yaml_content = <<~YAML
|
|
163
|
+
# Select branches to tag and list the tags to apply.
|
|
164
|
+
# All listed branches will receive all listed tags.
|
|
165
|
+
|
|
166
|
+
branches:
|
|
167
|
+
#{branch_lines.join("\n")}
|
|
168
|
+
|
|
169
|
+
tags:
|
|
170
|
+
-
|
|
171
|
+
YAML
|
|
172
|
+
|
|
173
|
+
require 'tempfile'
|
|
174
|
+
tmpfile = Tempfile.new(['branch-tag-', '.yml'])
|
|
175
|
+
tmpfile.write(yaml_content)
|
|
176
|
+
tmpfile.close
|
|
177
|
+
edit_files(tmpfile.path)
|
|
178
|
+
|
|
179
|
+
parsed = YAML.safe_load(File.read(tmpfile.path)) rescue nil
|
|
180
|
+
tmpfile.unlink
|
|
181
|
+
|
|
182
|
+
unless parsed.is_a?(Hash)
|
|
183
|
+
puts "Aborted: could not parse YAML"
|
|
184
|
+
next
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
selected = Array(parsed['branches']).map(&:to_s).reject(&:empty?)
|
|
188
|
+
new_tags = Array(parsed['tags']).map(&:to_s).reject(&:empty?)
|
|
189
|
+
|
|
190
|
+
if selected.empty? || new_tags.empty?
|
|
191
|
+
puts "Aborted: need at least one branch and one tag"
|
|
192
|
+
next
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
selected.each do |b|
|
|
196
|
+
tag_store.add(b, *new_tags)
|
|
197
|
+
puts "Tagged #{b} with: #{new_tags.join(', ')}"
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
branch = opts.args[0] || git.branch
|
|
201
|
+
tag_names = opts.args[1..]
|
|
202
|
+
|
|
203
|
+
if tag_names.empty?
|
|
204
|
+
puts "Usage: h branch tag [branch] <tag> [tag2 ...]"
|
|
205
|
+
puts " h branch tag -e (bulk edit mode)"
|
|
206
|
+
next
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
result = tag_store.add(branch, *tag_names)
|
|
210
|
+
puts "Tagged #{branch} with: #{tag_names.join(', ')}"
|
|
211
|
+
puts " Tags now: #{result.join(', ')}"
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
add_subcmd(:untag) do |*raw_args|
|
|
216
|
+
branch = raw_args[0] || git.branch
|
|
217
|
+
tag_names = raw_args[1..]
|
|
218
|
+
|
|
219
|
+
tag_store.remove(branch, *tag_names)
|
|
220
|
+
if tag_names.empty?
|
|
221
|
+
puts "Cleared all tags from #{branch}"
|
|
222
|
+
else
|
|
223
|
+
puts "Removed #{tag_names.join(', ')} from #{branch}"
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
add_subcmd(:tags) do
|
|
228
|
+
all = tag_store.all
|
|
229
|
+
if all.empty?
|
|
230
|
+
puts "No tagged branches."
|
|
231
|
+
next
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
by_tag = Hash.new { |h, k| h[k] = [] }
|
|
235
|
+
all.each { |branch, tags| tags.each { |t| by_tag[t] << branch } }
|
|
236
|
+
|
|
237
|
+
by_tag.sort.each do |tag, brs|
|
|
238
|
+
puts "#{Hiiro::Tags.badges([tag])} (#{brs.length})"
|
|
239
|
+
brs.each { |b| puts " #{b}" }
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
127
243
|
add_subcmd(:select) do |*select_args|
|
|
128
244
|
branches = git.branches(sort_by: 'authordate', ignore_case: true)
|
|
129
245
|
|
data/bin/h-pr
CHANGED
|
@@ -392,7 +392,7 @@ class PinnedPRManager
|
|
|
392
392
|
|
|
393
393
|
def filter_active?(opts)
|
|
394
394
|
FILTER_PREDICATES.keys.any? { |f| opts.respond_to?(f) && opts.send(f) } ||
|
|
395
|
-
(opts.respond_to?(:tag) && opts.tag)
|
|
395
|
+
(opts.respond_to?(:tag) && Array(opts.tag).any?)
|
|
396
396
|
end
|
|
397
397
|
|
|
398
398
|
def apply_filters(prs, opts, forced: [])
|
|
@@ -401,10 +401,10 @@ class PinnedPRManager
|
|
|
401
401
|
|
|
402
402
|
results = active.empty? ? prs : prs.select { |pr| active.any? { |f| FILTER_PREDICATES[f]&.call(pr) } }
|
|
403
403
|
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
results = results.select { |pr| Array(pr['tags']).
|
|
404
|
+
# Tags are an AND post-filter; multiple tags are OR'd among themselves
|
|
405
|
+
tag_filter = Array(opts.respond_to?(:tag) ? opts.tag : nil).map(&:to_s).reject(&:empty?)
|
|
406
|
+
unless tag_filter.empty?
|
|
407
|
+
results = results.select { |pr| (Array(pr['tags']) & tag_filter).any? }
|
|
408
408
|
end
|
|
409
409
|
|
|
410
410
|
results
|
|
@@ -488,7 +488,7 @@ FILTER_OPTS = Proc.new {
|
|
|
488
488
|
flag(:merged, short: 'm', desc: 'filter: merged PRs')
|
|
489
489
|
flag(:active, short: 'o', desc: 'filter: open (non-merged) PRs')
|
|
490
490
|
flag(:numbers, short: 'n', desc: 'output PR numbers only (no #)')
|
|
491
|
-
option(:tag, short: 't', desc: 'filter by tag (AND with
|
|
491
|
+
option(:tag, short: 't', desc: 'filter by tag (OR when multiple; AND with flag filters)', multi: true)
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
@@ -1332,26 +1332,94 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
|
|
|
1332
1332
|
|
|
1333
1333
|
# === Tags ===
|
|
1334
1334
|
|
|
1335
|
-
add_subcmd(:tag) do |
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
next
|
|
1335
|
+
add_subcmd(:tag) do |*raw_args|
|
|
1336
|
+
opts = Hiiro::Options.parse(raw_args) do
|
|
1337
|
+
flag(:edit, short: 'e', desc: 'open YAML editor to bulk-tag multiple PRs')
|
|
1339
1338
|
end
|
|
1340
1339
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1340
|
+
if opts.edit
|
|
1341
|
+
pinned = pinned_manager.load_pinned
|
|
1343
1342
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
next
|
|
1349
|
-
end
|
|
1343
|
+
if pinned.empty?
|
|
1344
|
+
puts "No tracked PRs to tag"
|
|
1345
|
+
next
|
|
1346
|
+
end
|
|
1350
1347
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1348
|
+
pr_lines = pinned.map do |pr|
|
|
1349
|
+
branch = pr['headRefName'] ? "[#{pr['headRefName']}]" : "[##{pr['number']}]"
|
|
1350
|
+
"- #{pr['number']} # #{branch} #{pr['title']}"
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
yaml_content = <<~YAML
|
|
1354
|
+
# Select PRs to tag and list the tags to apply.
|
|
1355
|
+
# All listed PRs will receive all listed tags.
|
|
1356
|
+
# Lines starting with # are comments and are ignored.
|
|
1357
|
+
|
|
1358
|
+
prs:
|
|
1359
|
+
#{pr_lines.join("\n")}
|
|
1360
|
+
|
|
1361
|
+
tags:
|
|
1362
|
+
-
|
|
1363
|
+
YAML
|
|
1364
|
+
|
|
1365
|
+
tmpfile = Tempfile.new(['pr-tag-', '.yml'])
|
|
1366
|
+
tmpfile.write(yaml_content)
|
|
1367
|
+
tmpfile.close
|
|
1368
|
+
edit_files(tmpfile.path)
|
|
1369
|
+
|
|
1370
|
+
parsed = YAML.safe_load(File.read(tmpfile.path)) rescue nil
|
|
1371
|
+
tmpfile.unlink
|
|
1372
|
+
|
|
1373
|
+
unless parsed.is_a?(Hash)
|
|
1374
|
+
puts "Aborted: could not parse YAML"
|
|
1375
|
+
next
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
selected_numbers = Array(parsed['prs']).map(&:to_s).reject(&:empty?)
|
|
1379
|
+
new_tags = Array(parsed['tags']).map(&:to_s).reject(&:empty?)
|
|
1380
|
+
|
|
1381
|
+
if selected_numbers.empty? || new_tags.empty?
|
|
1382
|
+
puts "Aborted: need at least one PR and one tag"
|
|
1383
|
+
next
|
|
1384
|
+
end
|
|
1385
|
+
|
|
1386
|
+
pinned = pinned_manager.load_pinned
|
|
1387
|
+
updated = 0
|
|
1388
|
+
selected_numbers.each do |num|
|
|
1389
|
+
pr = pinned.find { |p| p['number'].to_s == num }
|
|
1390
|
+
next unless pr
|
|
1391
|
+
pr['tags'] = (Array(pr['tags']) + new_tags).uniq
|
|
1392
|
+
updated += 1
|
|
1393
|
+
puts "Tagged ##{num} with: #{new_tags.join(', ')}"
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
pinned_manager.save_pinned(pinned)
|
|
1397
|
+
puts "Updated #{updated} PR(s)."
|
|
1398
|
+
else
|
|
1399
|
+
ref = opts.args[0]
|
|
1400
|
+
tag_names = opts.args[1..]
|
|
1401
|
+
|
|
1402
|
+
if ref.nil? || tag_names.empty?
|
|
1403
|
+
puts "Usage: h pr tag <ref> <tag> [tag2 ...]"
|
|
1404
|
+
puts " h pr tag -e (bulk edit mode)"
|
|
1405
|
+
next
|
|
1406
|
+
end
|
|
1407
|
+
|
|
1408
|
+
pr_number = resolve_pr.call(ref)
|
|
1409
|
+
next unless pr_number
|
|
1410
|
+
|
|
1411
|
+
pinned = pinned_manager.load_pinned
|
|
1412
|
+
pr = pinned.find { |p| p['number'].to_s == pr_number.to_s }
|
|
1413
|
+
unless pr
|
|
1414
|
+
puts "PR ##{pr_number} not in tracked list"
|
|
1415
|
+
next
|
|
1416
|
+
end
|
|
1417
|
+
|
|
1418
|
+
pr['tags'] = (Array(pr['tags']) + tag_names).uniq
|
|
1419
|
+
pinned_manager.save_pinned(pinned)
|
|
1420
|
+
puts "Tagged ##{pr_number} with: #{tag_names.join(', ')}"
|
|
1421
|
+
puts " Tags now: #{pr['tags'].join(', ')}"
|
|
1422
|
+
end
|
|
1355
1423
|
end
|
|
1356
1424
|
|
|
1357
1425
|
add_subcmd(:untag) do |ref = nil, *tag_names|
|
data/lib/hiiro/tags.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
class Hiiro
|
|
2
|
+
# Shared tag store, keyed by namespace (e.g. :branch, :task).
|
|
3
|
+
# Stored in ~/.config/hiiro/tags.yml as { namespace => { key => [tags] } }.
|
|
4
|
+
class Tags
|
|
5
|
+
FILE = Hiiro::Config.path('tags.yml')
|
|
6
|
+
|
|
7
|
+
def initialize(namespace)
|
|
8
|
+
@namespace = namespace.to_s
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Returns the tag array for a given key ([] if none).
|
|
12
|
+
def get(key)
|
|
13
|
+
Array(load.dig(@namespace, key.to_s))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Adds tags to a key (idempotent). Returns the new tag array.
|
|
17
|
+
def add(key, *tags)
|
|
18
|
+
data = load
|
|
19
|
+
data[@namespace] ||= {}
|
|
20
|
+
current = Array(data.dig(@namespace, key.to_s))
|
|
21
|
+
data[@namespace][key.to_s] = (current + tags.map(&:to_s)).uniq
|
|
22
|
+
save(data)
|
|
23
|
+
data[@namespace][key.to_s]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Removes specific tags from a key. Pass no tags to clear all.
|
|
27
|
+
def remove(key, *tags)
|
|
28
|
+
data = load
|
|
29
|
+
data[@namespace] ||= {}
|
|
30
|
+
current = Array(data.dig(@namespace, key.to_s))
|
|
31
|
+
updated = tags.empty? ? [] : (current - tags.map(&:to_s))
|
|
32
|
+
if updated.empty?
|
|
33
|
+
data[@namespace].delete(key.to_s)
|
|
34
|
+
else
|
|
35
|
+
data[@namespace][key.to_s] = updated
|
|
36
|
+
end
|
|
37
|
+
save(data)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the full { key => [tags] } hash for this namespace.
|
|
41
|
+
def all
|
|
42
|
+
load[@namespace] || {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns all distinct tag values used in this namespace.
|
|
46
|
+
def known_tags
|
|
47
|
+
all.values.flatten.uniq.sort
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Formats a tag array as colored badges for terminal output.
|
|
51
|
+
def self.badges(tags)
|
|
52
|
+
Array(tags).map { |t| "\e[30;104m#{t}\e[0m" }.join(' ')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def load
|
|
58
|
+
return {} unless File.exist?(FILE)
|
|
59
|
+
YAML.safe_load(File.read(FILE)) || {}
|
|
60
|
+
rescue
|
|
61
|
+
{}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def save(data)
|
|
65
|
+
data.each { |_, v| v.reject! { |_, tags| tags.nil? || tags.empty? } if v.is_a?(Hash) }
|
|
66
|
+
data.reject! { |_, v| v.nil? || v.empty? }
|
|
67
|
+
FileUtils.mkdir_p(File.dirname(FILE))
|
|
68
|
+
File.write(FILE, data.to_yaml)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/hiiro/tasks.rb
CHANGED
|
@@ -182,11 +182,21 @@ class Hiiro
|
|
|
182
182
|
puts "Stopped task '#{task.name}' (worktree available for reuse)"
|
|
183
183
|
end
|
|
184
184
|
|
|
185
|
-
def list
|
|
185
|
+
def list(tags_filter: [])
|
|
186
|
+
tag_store = Hiiro::Tags.new(:task)
|
|
186
187
|
items = tasks
|
|
188
|
+
|
|
189
|
+
if tags_filter.any?
|
|
190
|
+
items = items.select { |t| (tag_store.get(t.name) & tags_filter).any? }
|
|
191
|
+
end
|
|
192
|
+
|
|
187
193
|
if items.empty?
|
|
188
|
-
|
|
189
|
-
|
|
194
|
+
if tags_filter.any?
|
|
195
|
+
puts "No #{scope == :subtask ? 'subtasks' : 'tasks'} match tags: #{tags_filter.join(', ')}"
|
|
196
|
+
else
|
|
197
|
+
puts scope == :subtask ? "No subtasks found" : "No tasks found"
|
|
198
|
+
puts "Use 'h #{scope} start NAME' to create one."
|
|
199
|
+
end
|
|
190
200
|
return
|
|
191
201
|
end
|
|
192
202
|
|
|
@@ -227,9 +237,11 @@ class Hiiro
|
|
|
227
237
|
|
|
228
238
|
rows.each do |r|
|
|
229
239
|
name_pad = name_col - r[:prefix].length
|
|
240
|
+
tags = tag_store.get(r[:name])
|
|
241
|
+
tag_str = tags.any? ? " " + Hiiro::Tags.badges(tags) : ""
|
|
230
242
|
print r[:prefix]
|
|
231
243
|
puts format("%-#{name_pad}s %-#{tree_col}s %-#{branch_col}s %s",
|
|
232
|
-
r[:name], r[:tree], r[:branch], r[:session])
|
|
244
|
+
r[:name], r[:tree], r[:branch], r[:session]) + tag_str
|
|
233
245
|
end
|
|
234
246
|
|
|
235
247
|
available = environment.all_trees.reject { |t|
|
|
@@ -649,8 +661,76 @@ class Hiiro
|
|
|
649
661
|
module Tasks
|
|
650
662
|
def self.build_hiiro(parent_hiiro, tm)
|
|
651
663
|
task_hiiro = parent_hiiro.make_child do |h|
|
|
652
|
-
h.add_subcmd(:list)
|
|
653
|
-
|
|
664
|
+
h.add_subcmd(:list) do |*args|
|
|
665
|
+
opts = Hiiro::Options.parse(args) { option(:tag, short: 't', desc: 'filter by tag (OR when multiple)', multi: true) }
|
|
666
|
+
tm.list(tags_filter: Array(opts.tag).reject(&:empty?))
|
|
667
|
+
end
|
|
668
|
+
h.add_subcmd(:ls) do |*args|
|
|
669
|
+
opts = Hiiro::Options.parse(args) { option(:tag, short: 't', desc: 'filter by tag (OR when multiple)', multi: true) }
|
|
670
|
+
tm.list(tags_filter: Array(opts.tag).reject(&:empty?))
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
h.add_subcmd(:tag) do |*raw_args|
|
|
674
|
+
tag_store = Hiiro::Tags.new(:task)
|
|
675
|
+
opts = Hiiro::Options.parse(raw_args) { flag(:edit, short: 'e', desc: 'open YAML editor to bulk-tag tasks') }
|
|
676
|
+
|
|
677
|
+
if opts.edit
|
|
678
|
+
task_names = tm.tasks.map(&:name)
|
|
679
|
+
task_lines = task_names.map { |n| "- #{n}" }
|
|
680
|
+
yaml_content = "# Select tasks to tag.\n\ntasks:\n#{task_lines.join("\n")}\n\ntags:\n-\n"
|
|
681
|
+
require 'tempfile'
|
|
682
|
+
tmpfile = Tempfile.new(['task-tag-', '.yml'])
|
|
683
|
+
tmpfile.write(yaml_content)
|
|
684
|
+
tmpfile.close
|
|
685
|
+
h.edit_files(tmpfile.path)
|
|
686
|
+
parsed = YAML.safe_load(File.read(tmpfile.path)) rescue nil
|
|
687
|
+
tmpfile.unlink
|
|
688
|
+
selected = Array(parsed&.dig('tasks')).map(&:to_s).reject(&:empty?)
|
|
689
|
+
new_tags = Array(parsed&.dig('tags')).map(&:to_s).reject(&:empty?)
|
|
690
|
+
if selected.empty? || new_tags.empty?
|
|
691
|
+
puts "Aborted: need at least one task and one tag"
|
|
692
|
+
next
|
|
693
|
+
end
|
|
694
|
+
selected.each { |n| tag_store.add(n, *new_tags); puts "Tagged #{n} with: #{new_tags.join(', ')}" }
|
|
695
|
+
else
|
|
696
|
+
task_name = opts.args[0] || tm.current_task&.name
|
|
697
|
+
tag_names = opts.args[1..]
|
|
698
|
+
if task_name.nil? || tag_names.empty?
|
|
699
|
+
puts "Usage: h task tag [task_name] <tag> [tag2 ...]"
|
|
700
|
+
next
|
|
701
|
+
end
|
|
702
|
+
result = tag_store.add(task_name, *tag_names)
|
|
703
|
+
puts "Tagged #{task_name} with: #{tag_names.join(', ')}"
|
|
704
|
+
puts " Tags now: #{result.join(', ')}"
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
h.add_subcmd(:untag) do |*raw_args|
|
|
709
|
+
tag_store = Hiiro::Tags.new(:task)
|
|
710
|
+
task_name = raw_args[0] || tm.current_task&.name
|
|
711
|
+
tag_names = raw_args[1..]
|
|
712
|
+
if task_name.nil?
|
|
713
|
+
puts "Usage: h task untag [task_name] [tag ...]"
|
|
714
|
+
next
|
|
715
|
+
end
|
|
716
|
+
tag_store.remove(task_name, *tag_names)
|
|
717
|
+
puts tag_names.empty? ? "Cleared all tags from #{task_name}" : "Removed #{tag_names.join(', ')} from #{task_name}"
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
h.add_subcmd(:tags) do
|
|
721
|
+
tag_store = Hiiro::Tags.new(:task)
|
|
722
|
+
all = tag_store.all
|
|
723
|
+
if all.empty?
|
|
724
|
+
puts "No tagged tasks."
|
|
725
|
+
next
|
|
726
|
+
end
|
|
727
|
+
by_tag = Hash.new { |h2, k| h2[k] = [] }
|
|
728
|
+
all.each { |task, tags| tags.each { |t| by_tag[t] << task } }
|
|
729
|
+
by_tag.sort.each do |tag, tasks|
|
|
730
|
+
puts "#{Hiiro::Tags.badges([tag])} (#{tasks.length})"
|
|
731
|
+
tasks.each { |t| puts " #{t}" }
|
|
732
|
+
end
|
|
733
|
+
end
|
|
654
734
|
|
|
655
735
|
h.add_subcmd(:start) do |*raw_args|
|
|
656
736
|
opts = Hiiro::Options.parse(raw_args) do
|
data/lib/hiiro/version.rb
CHANGED
data/lib/hiiro.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "hiiro/matcher"
|
|
|
14
14
|
require_relative "hiiro/notification"
|
|
15
15
|
require_relative "hiiro/options"
|
|
16
16
|
require_relative "hiiro/paths"
|
|
17
|
+
require_relative "hiiro/tags"
|
|
17
18
|
require_relative "hiiro/queue"
|
|
18
19
|
require_relative "hiiro/tasks"
|
|
19
20
|
require_relative "hiiro/tmux"
|
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.237
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Toyota
|
|
@@ -158,6 +158,7 @@ files:
|
|
|
158
158
|
- lib/hiiro/runner_tool.rb
|
|
159
159
|
- lib/hiiro/service_manager.rb
|
|
160
160
|
- lib/hiiro/shell.rb
|
|
161
|
+
- lib/hiiro/tags.rb
|
|
161
162
|
- lib/hiiro/tasks.rb
|
|
162
163
|
- lib/hiiro/tmux.rb
|
|
163
164
|
- lib/hiiro/tmux/buffer.rb
|