hiiro 0.1.24 → 0.1.26

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: 6a46d6691bc65a663bdbcb63dfe0fe6643572ee377c5f1448bbd34a5e9efe3f1
4
- data.tar.gz: 2edd95e23a40d0910b88c0844e70f7839da22dbce3f86c2f2518b238abf68706
3
+ metadata.gz: 73b9be67839d84fe5bf4166509623e58aee1067d2176f2f984b71b3bd3d0832b
4
+ data.tar.gz: 4fd46fe1da0f037a14bf35262fdaa36e7ed7b1271a044072db9a2b76b4d2244a
5
5
  SHA512:
6
- metadata.gz: 8d402b962e9e2c51870a586a8f83ea0209b43109b92135d468ac33336d15e37e882b564a1848e1c8efcf0fd6acf1739d6b356a8cf06bfc7069ea1f251457f280
7
- data.tar.gz: df17f16075ceff456b6ee512424b962f207a25b72a1eaa2867fabbccaf91a64a9c015bdb5706f0a37bce3882d7c6ec927e0325984bfebc5828f6810a03562092
6
+ metadata.gz: 6b7dbaa0d37759fa8a12aa2b8d47c9bffc7fcb441f44970cd622ddc751144b20144b5ddd25f9dc6b0496df0cee1d5c70d3c2b24115948a9e1b16a26f5395616b
7
+ data.tar.gz: cf1827be5abebc173856ef9a098db8ca2cca0cc2c3cbe46da03633f42e31c473a20f81c11096a62c267fa7ead37c56bb1f482e619b6edd861beedfb7a4babfa9
data/bin/g-pr ADDED
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "hiiro"
4
+ require "time"
5
+ require "fileutils"
6
+ require "yaml"
7
+ require "json"
8
+
9
+ Hiiro.load_env
10
+ hiiro = Hiiro.init(*ARGV, plugins: [Task, Tmux, Pins])
11
+
12
+ class PRManager
13
+ attr_reader :hiiro
14
+
15
+ def initialize(hiiro)
16
+ @hiiro = hiiro
17
+ end
18
+
19
+ def help
20
+ puts "Usage: h pr <subcommand> [args]"
21
+ puts
22
+ puts "Subcommands:"
23
+ puts " save [PR_NUMBER] Record PR for this task (auto-detects if omitted)"
24
+ puts " history List PR history (oldest to newest)"
25
+ puts " history --worktree=X Filter by worktree"
26
+ puts " history --task=X Filter by task"
27
+ puts " history --session=X Filter by tmux session"
28
+ puts " current Show current branch's PR info"
29
+ puts " open [PR_NUMBER] Open PR in browser"
30
+ puts " view [PR_NUMBER] View PR details in terminal"
31
+ end
32
+
33
+ def save(pr_number = nil)
34
+ pr_info = fetch_pr_info(pr_number)
35
+ unless pr_info
36
+ puts "ERROR: Could not find PR"
37
+ puts "Make sure you have the gh CLI installed and authenticated."
38
+ return false
39
+ end
40
+
41
+ entry = build_entry(pr_info)
42
+ unless entry[:task]
43
+ puts "WARNING: Not in a task session, saving without task info"
44
+ end
45
+
46
+ data = load_data
47
+ data['prs'] ||= []
48
+
49
+ # Check if this PR is already recorded for this task
50
+ existing = data['prs'].find do |p|
51
+ p['number'] == pr_info['number'] && p['task'] == entry[:task]
52
+ end
53
+
54
+ if existing
55
+ # Update existing entry
56
+ existing.merge!(entry.transform_keys(&:to_s))
57
+ existing['updated_at'] = Time.now.iso8601
58
+ puts "Updated PR ##{pr_info['number']} for task '#{entry[:task]}'"
59
+ else
60
+ # Add new entry
61
+ data['prs'] << entry.transform_keys(&:to_s).merge('created_at' => Time.now.iso8601)
62
+ puts "Saved PR ##{pr_info['number']} for task '#{entry[:task]}'"
63
+ end
64
+
65
+ save_data(data)
66
+ show_entry(entry)
67
+ true
68
+ end
69
+
70
+ def history(args = [])
71
+ filters = parse_filters(args)
72
+ data = load_data
73
+ prs = data['prs'] || []
74
+
75
+ if prs.empty?
76
+ puts "No PRs recorded."
77
+ puts "Use 'h pr save' to record the current PR."
78
+ return
79
+ end
80
+
81
+ # Apply filters
82
+ prs = filter_entries(prs, filters)
83
+
84
+ if prs.empty?
85
+ puts "No PRs match the given filters."
86
+ return
87
+ end
88
+
89
+ # Sort by created_at (oldest first)
90
+ prs = prs.sort_by { |p| p['created_at'] || '' }
91
+
92
+ puts "PR history (oldest to newest):"
93
+ puts
94
+ prs.each_with_index do |pr, idx|
95
+ puts format_entry(pr, idx + 1)
96
+ end
97
+ end
98
+
99
+ def current
100
+ pr_info = fetch_pr_info
101
+ unless pr_info
102
+ puts "No PR found for current branch."
103
+ puts "Create one with 'gh pr create' or specify a PR number."
104
+ return false
105
+ end
106
+
107
+ entry = build_entry(pr_info)
108
+ puts "Current PR info:"
109
+ puts
110
+ show_entry(entry)
111
+ end
112
+
113
+ def open(pr_number = nil)
114
+ if pr_number
115
+ system('gh', 'pr', 'view', pr_number.to_s, '--web')
116
+ else
117
+ system('gh', 'pr', 'view', '--web')
118
+ end
119
+ end
120
+
121
+ def view(pr_number = nil)
122
+ if pr_number
123
+ system('gh', 'pr', 'view', pr_number.to_s)
124
+ else
125
+ system('gh', 'pr', 'view')
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def fetch_pr_info(pr_number = nil)
132
+ cmd = if pr_number
133
+ ['gh', 'pr', 'view', pr_number.to_s, '--json', 'number,title,url,headRefName,state']
134
+ else
135
+ ['gh', 'pr', 'view', '--json', 'number,title,url,headRefName,state']
136
+ end
137
+
138
+ output = `#{cmd.join(' ')} 2>/dev/null`
139
+ return nil if output.empty?
140
+
141
+ JSON.parse(output)
142
+ rescue JSON::ParserError
143
+ nil
144
+ end
145
+
146
+ def build_entry(pr_info)
147
+ task_info = hiiro.task_manager.send(:current_task)
148
+ tmux_info = capture_tmux_info
149
+
150
+ {
151
+ number: pr_info['number'],
152
+ title: pr_info['title'],
153
+ url: pr_info['url'],
154
+ branch: pr_info['headRefName'],
155
+ state: pr_info['state'],
156
+ worktree: task_info&.[](:tree),
157
+ task: task_info&.[](:task),
158
+ tmux: tmux_info
159
+ }
160
+ end
161
+
162
+ def capture_tmux_info
163
+ return nil unless ENV['TMUX']
164
+
165
+ {
166
+ 'session' => `tmux display-message -p '#S'`.strip,
167
+ 'window' => `tmux display-message -p '#W'`.strip,
168
+ 'pane' => ENV['TMUX_PANE']
169
+ }
170
+ end
171
+
172
+ def parse_filters(args)
173
+ filters = {}
174
+ args.each do |arg|
175
+ case arg
176
+ when /^--worktree=(.+)$/
177
+ filters[:worktree] = $1
178
+ when /^--task=(.+)$/
179
+ filters[:task] = $1
180
+ when /^--session=(.+)$/
181
+ filters[:session] = $1
182
+ when /^-w(.+)$/
183
+ filters[:worktree] = $1
184
+ when /^-t(.+)$/
185
+ filters[:task] = $1
186
+ when /^-s(.+)$/
187
+ filters[:session] = $1
188
+ end
189
+ end
190
+ filters
191
+ end
192
+
193
+ def filter_entries(entries, filters)
194
+ entries.select do |entry|
195
+ next false if filters[:worktree] && !entry['worktree']&.include?(filters[:worktree])
196
+ next false if filters[:task] && !entry['task']&.include?(filters[:task])
197
+ next false if filters[:session] && entry.dig('tmux', 'session') != filters[:session]
198
+ true
199
+ end
200
+ end
201
+
202
+ def format_entry(entry, num)
203
+ lines = []
204
+ lines << "#{num}. PR ##{entry['number']}: #{entry['title']}"
205
+ lines << " Branch: #{entry['branch']}"
206
+ lines << " State: #{entry['state']}"
207
+ lines << " Task: #{entry['task'] || '(none)'}"
208
+ lines << " Worktree: #{entry['worktree'] || '(none)'}"
209
+ if entry['tmux']
210
+ lines << " Tmux: #{entry['tmux']['session']}/#{entry['tmux']['window']}"
211
+ end
212
+ lines << " Created: #{entry['created_at']}"
213
+ lines << " URL: #{entry['url']}"
214
+ lines.join("\n")
215
+ end
216
+
217
+ def show_entry(entry)
218
+ puts " PR: ##{entry[:number]}"
219
+ puts " Title: #{entry[:title]}"
220
+ puts " Branch: #{entry[:branch]}"
221
+ puts " State: #{entry[:state]}"
222
+ puts " URL: #{entry[:url]}"
223
+ puts " Task: #{entry[:task] || '(none)'}"
224
+ puts " Worktree: #{entry[:worktree] || '(none)'}"
225
+ if entry[:tmux]
226
+ puts " Tmux session: #{entry[:tmux]['session']}"
227
+ puts " Tmux window: #{entry[:tmux]['window']}"
228
+ puts " Tmux pane: #{entry[:tmux]['pane']}"
229
+ end
230
+ end
231
+
232
+ def data_file
233
+ File.join(Dir.home, '.config', 'hiiro', 'prs.yml')
234
+ end
235
+
236
+ def load_data
237
+ return {} unless File.exist?(data_file)
238
+ YAML.safe_load_file(data_file) || {}
239
+ end
240
+
241
+ def save_data(data)
242
+ FileUtils.mkdir_p(File.dirname(data_file))
243
+ File.write(data_file, YAML.dump(data))
244
+ end
245
+ end
246
+
247
+ WATCH_BLOCK = lambda { |original_pr_number=nil, *args|
248
+ watch = o.get_value(:watch)
249
+ fail_fast = o.get_value(:fail_fast)
250
+
251
+ pr_valid = false
252
+ pr_number = nil
253
+ if !original_pr_number.nil? && pin_value = o.pins.get(original_pr_number)
254
+ pr_valid = true
255
+ pr_number = pin_value
256
+ end
257
+
258
+ pr_args = [pr_valid ? pr_number : original_pr_number].compact
259
+ args = [*pr_args, *args]
260
+
261
+ base_cmd = %w[gh pr checks]
262
+ base_cmd << '--watch' if watch
263
+ base_cmd << '--fail-fast' if fail_fast
264
+
265
+ command = [
266
+ *base_cmd,
267
+ *args,
268
+ ]
269
+
270
+ puts command: command, base_cmd: base_cmd, args: args;
271
+
272
+ result = system(*command)
273
+
274
+ pr_info = JSON.parse(`gh pr view #{pr_args.first} --json url,number,title`)
275
+ pr_url = pr_info['url']
276
+ pr_number = pr_info['number']
277
+ pr_title = pr_info['title']
278
+
279
+ if result
280
+ system('say', 'pr good')
281
+ system('terminal-notifier', '-title', 'PR Good', '-message', "##{pr_number}: #{pr_title}", '-open', pr_url)
282
+ else
283
+ system('say', 'pr bad')
284
+ system('terminal-notifier', '-title', 'PR Bad', '-message', "##{pr_number}: #{pr_title}", '-open', pr_url)
285
+ end
286
+ }
287
+
288
+ manager = PRManager.new(hiiro)
289
+
290
+ hiiro.add_subcmd(:check, &WATCH_BLOCK)
291
+ hiiro.add_subcmd(:watch, watch: true, &WATCH_BLOCK)
292
+ hiiro.add_subcmd(:fwatch, watch: true, fail_fast: true, &WATCH_BLOCK)
293
+
294
+ hiiro.add_subcmd(:number) { |*args|
295
+ stdout = `gh pr view`
296
+
297
+ number = stdout[/number:\s*(\d+)/, 1]
298
+
299
+ if number
300
+ print number
301
+ else
302
+ puts stdout
303
+ end
304
+ }
305
+
306
+ hiiro.add_subcmd(:link) { |*args|
307
+ stdout = `gh pr view`
308
+
309
+ number = stdout[/number:\s*(\d+)/, 1]
310
+
311
+ if number
312
+ print ['https://github.com/instacart/carrot/pull/', number].join
313
+ else
314
+ puts stdout
315
+ end
316
+ }
317
+
318
+ hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
319
+ hiiro.add_subcmd(:save) { |pr_number=nil| manager.save(pr_number) }
320
+ hiiro.add_subcmd(:history) { |*args| manager.history(args) }
321
+ hiiro.add_subcmd(:current) { manager.current }
322
+ hiiro.add_subcmd(:open) { |pr_number=nil| manager.open(pr_number) }
323
+ hiiro.add_subcmd(:view) { |pr_number=nil| manager.view(pr_number) }
324
+
325
+ hiiro.add_default { manager.help }
326
+
327
+ hiiro.run
data/bin/h-branch CHANGED
@@ -207,6 +207,24 @@ hiiro.add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
207
207
  hiiro.add_subcmd(:save) { manager.save }
208
208
  hiiro.add_subcmd(:history) { |*args| manager.history(args) }
209
209
  hiiro.add_subcmd(:current) { manager.current }
210
+ hiiro.add_subcmd(:select) do |*args|
211
+ branches = `git branch -i --sort=authordate`.lines(chomp: true)
212
+
213
+ lines = branches.each_with_object({}) do |ln,h|
214
+ h[ln] = ln.sub(/../, '')
215
+ end
216
+
217
+ if args.any?
218
+ lines = hash_matches?(lines, *args)
219
+ end
220
+
221
+ require 'open3'
222
+ selected, status = Open3.capture2('sk', '--no-sort', '--tac', stdin_data: lines.keys.join("\n"))
223
+
224
+ if status.success? && !selected.strip.empty?
225
+ puts lines[selected.chomp]
226
+ end
227
+ end
210
228
 
211
229
  hiiro.add_default { manager.help }
212
230
 
data/bin/h-dot ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ hiiro = Hiiro.init(*ARGV)
6
+
7
+ hiiro.add_subcmd(:compare) do |*args|
8
+ left = File.join(Dir.home, 'proj/home/bin')
9
+ right = File.join(Dir.home, 'bin')
10
+ diff = `diff -qrs #{left} #{right}`.lines(chomp: true)
11
+
12
+ diff.each do |line|
13
+ next if line.match?(/[.]DS_Store/)
14
+
15
+ case line
16
+ when /^Only in #{left}:/
17
+ match = line.match(/^Only in ([^:]+):\s*(\S.*)/)
18
+ _, fname = match.captures
19
+ puts "ln -v #{File.join(left, fname).inspect} #{File.join(right, fname).inspect}"
20
+ when /^Only in #{right}:/
21
+ match = line.match(/^Only in ([^:]+):\s*(\S.*)/)
22
+ _, fname = match.captures
23
+ puts "ln -v #{File.join(right, fname).inspect} #{File.join(left, fname).inspect}"
24
+ when / differ/
25
+ match = line.match(/^Files (.*) and (.*) differ/)
26
+ lname, rname = match.captures
27
+ puts "vim -d #{lname.inspect} #{rname.inspect}"
28
+ end
29
+ puts
30
+ end
31
+ end
32
+
33
+
34
+ hiiro.run
35
+
data/bin/h-dotfiles ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ EDITOR = ENV.fetch('EDITOR', 'nvim')
6
+ BASE_DIR = File.join(Dir.home, 'proj', 'home')
7
+
8
+ o = Hiiro.init(*ARGV)
9
+
10
+ o.add_subcmd(:edit) { |*args|
11
+ system(EDITOR, __FILE__)
12
+ }
13
+
14
+ o.add_subcmd(:path) { |*args|
15
+ print BASE_DIR
16
+ }
17
+
18
+ o.add_subcmd(:skf) { |*args|
19
+ Dir.chdir(BASE_DIR)
20
+ system('skf', *args)
21
+ }
22
+
23
+ o.add_subcmd(:rg) { |*args|
24
+ Dir.chdir(BASE_DIR)
25
+ system('rg', '-S', *args)
26
+ }
27
+
28
+ o.add_subcmd(:rgall) { |*args|
29
+ Dir.chdir(BASE_DIR)
30
+ system('rg', '-S', '--no-ignore-vcs', *args)
31
+ }
32
+
33
+ o.add_subcmd(:ebin) { |*args|
34
+ system(EDITOR, *args)
35
+ }
36
+
37
+ if o.runnable?
38
+ o.run
39
+ else
40
+ puts :no_runnable_found
41
+ end
42
+
data/bin/h-home ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ EDITOR = ENV.fetch('EDITOR', 'nvim')
6
+ BASE_DIR = Dir.home
7
+
8
+ o = Hiiro.init(*ARGV)
9
+
10
+ o.add_subcmd(:edit) { |*args|
11
+ system(EDITOR, __FILE__)
12
+ }
13
+
14
+ o.add_subcmd(:path) { |*args|
15
+ print BASE_DIR
16
+ }
17
+
18
+ o.add_subcmd(:skf) { |*args|
19
+ Dir.chdir(BASE_DIR)
20
+ system('skf', *args)
21
+ }
22
+
23
+ o.add_subcmd(:rg) { |*args|
24
+ Dir.chdir(BASE_DIR)
25
+ system('rg', '-S', *args)
26
+ }
27
+
28
+ o.add_subcmd(:rgall) { |*args|
29
+ Dir.chdir(BASE_DIR)
30
+ system('rg', '-S', '--no-ignore-vcs', *args)
31
+ }
32
+
33
+ o.add_subcmd(:ebin) { |*args|
34
+ Dir.chdir(File.join(Dir.home, 'bin'))
35
+
36
+ glob_patterns = []
37
+ globs = []
38
+
39
+ files = args.filter_map { |arg|
40
+ matches =
41
+ if arg.end_with?('*')
42
+ glob_patterns << arg
43
+ Dir.glob(arg)
44
+ elsif arg.is_a?(String)
45
+ glob_patterns << '*' + arg.to_s + '*'
46
+ Dir.glob('*' + arg.to_s + '*')
47
+ end
48
+
49
+ globs << matches
50
+
51
+ matches unless matches.empty?
52
+ }.flatten
53
+
54
+ system(EDITOR, *files.flatten)
55
+ }
56
+
57
+ if o.runnable?
58
+ o.run
59
+ else
60
+ puts :no_runnable_found
61
+ end
62
+