hiiro 0.1.125 → 0.1.126

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: 7bbd061adf047bc495716f4499a6018c90563f605d3f7b51536c0df208a27ef3
4
- data.tar.gz: a5d4ed3954dded40eba5af8a686972eef5c2cc0ac77e084de0f4d2fb8c94c68b
3
+ metadata.gz: 0f321444255d4b160d34528a803115bb6f9197c790fb719bf135b45ffcdd8125
4
+ data.tar.gz: ca269e97bfede7c97d19e01c5e73706d0b2abf63af0aa93272cba1e8b130ed2a
5
5
  SHA512:
6
- metadata.gz: 20575a6c9652705985a3354efe441cb7bd3a4567e0946ddaf67c1cd3a7fde429e707b4d5c9e707b703f9df75396ce8c37d5716db4a64bc90a87fcf17b981c9a4
7
- data.tar.gz: 3446a6a497ecda1dd2d3068ab917943d180fd6240c68bb123b99ea04255218ed30ea8bd59a6b88c7518be91064ffa0d3d3938f2ab97b78ef06c1df6f52b38892
6
+ metadata.gz: 7716e761376c53d15e0519ccc8eea8ebadd3a62004a634f66e275ac56e73f56b7051d12774a962f95f43ffe509c6e6912b258e022fcf06ed21689217053c8404
7
+ data.tar.gz: c6f00c587b31ebc02d5abf112c55cc0635d05b5125162af68d1d8e998e5f63f9e4d6130b5c6d821b7e36ed08d45b8da78123f789153e34ca60ae4af5ed479030
data/bin/h-app CHANGED
@@ -43,7 +43,7 @@ def send_cd(path)
43
43
  end
44
44
  end
45
45
 
46
- Hiiro.run(*ARGV, plugins: [:Tasks]) {
46
+ Hiiro.run(*ARGV) {
47
47
  add_subcmd(:config) {
48
48
  editor = ENV['EDITOR'] || 'nvim'
49
49
  system(editor, APPS_FILE)
data/bin/h-bin ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ Hiiro.run do
6
+ add_subcmd(:list) do |*subcmd_names|
7
+ patterns = subcmd_names.flatten.compact
8
+ .map { |sc|
9
+ sc.sub(/^(h-)?/, '{h-,}')
10
+ }
11
+
12
+ all_bins = Hiiro::Bins.glob(patterns)
13
+ bins = all_bins.each_with_object({}) { |bin, h|
14
+ basename = File.basename(bin)
15
+ next if h.key?(basename)
16
+
17
+ h[basename] = bin
18
+ }.values
19
+
20
+ puts bins
21
+ end
22
+
23
+ add_subcmd(:edit) do |*subcmd_names|
24
+ patterns = subcmd_names.flatten.compact.map { |sc|
25
+ sc.sub(/^(h-)?/, '{h-,}')
26
+ }
27
+
28
+ all_bins = Hiiro::Bins.glob(patterns)
29
+ bins = all_bins.each_with_object({}) { |bin, h|
30
+ basename = File.basename(bin)
31
+ next if h.key?(basename)
32
+
33
+ h[basename] = bin
34
+ }.values
35
+
36
+ edit_files(*bins)
37
+ end
38
+ end
data/bin/h-branch CHANGED
@@ -116,7 +116,7 @@ class BranchManager
116
116
  end
117
117
  end
118
118
 
119
- Hiiro.run(*ARGV, plugins: [Tasks]) do
119
+ Hiiro.run(*ARGV) do
120
120
  manager = BranchManager.new(self)
121
121
 
122
122
  add_subcmd(:edit) { system(ENV['EDITOR'] || 'nvim', __FILE__) }
data/bin/h-link CHANGED
@@ -7,6 +7,10 @@ require 'pry'
7
7
  require 'tempfile'
8
8
  require "hiiro"
9
9
 
10
+ opts = Hiiro::Options.setup {
11
+ option(:shorthand, short: :s)
12
+ }
13
+
10
14
  class LinkManager
11
15
  LINKS_FILE = File.join(Dir.home, '.config/hiiro/links.yml')
12
16
  LINK_TEMPLATE = {
@@ -215,10 +219,12 @@ Hiiro.run(*ARGV, plugins: [Pins], links_file: lm.links_file) do
215
219
  url = add_args.shift
216
220
  description = add_args.join(' ')
217
221
 
222
+ vals = opts.parse!(add_args)
223
+
218
224
  new_link = LinkManager::Link.new(
219
225
  url: url,
220
226
  description: description,
221
- shorthand: nil,
227
+ shorthand: vals.shorthand,
222
228
  created_at: Time.now.iso8601
223
229
  )
224
230
 
data/bin/h-pr CHANGED
@@ -16,16 +16,6 @@ class PRManager
16
16
  @hiiro = hiiro
17
17
  end
18
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 " current Show current branch's PR info"
25
- puts " open [PR_NUMBER] Open PR in browser"
26
- puts " view [PR_NUMBER] View PR details in terminal"
27
- end
28
-
29
19
  def save(pr_number = nil)
30
20
  pr_info = fetch_pr_info(pr_number)
31
21
  unless pr_info
@@ -266,7 +256,131 @@ class PinnedPRManager
266
256
  (authored_prs + assigned_prs).uniq { |pr| pr['number'] }
267
257
  end
268
258
 
269
- def refresh_status(pr)
259
+ def needs_refresh?(pr, force: false)
260
+ return true if force
261
+ return true unless pr['last_checked']
262
+
263
+ last_check_time = Time.parse(pr['last_checked']) rescue nil
264
+ return true unless last_check_time
265
+
266
+ # Refresh if last check was more than 2 minutes ago
267
+ (Time.now - last_check_time) > 120
268
+ end
269
+
270
+
271
+ def batch_fetch_pr_info(pr_numbers)
272
+ return {} if pr_numbers.empty?
273
+
274
+ # Build GraphQL query to fetch multiple PRs at once
275
+ pr_queries = pr_numbers.map.with_index do |num, idx|
276
+ <<~GRAPHQL.strip
277
+ pr#{idx}: pullRequest(number: #{num}) {
278
+ number
279
+ title
280
+ url
281
+ headRefName
282
+ state
283
+ isDraft
284
+ mergeable
285
+ reviewDecision
286
+ statusCheckRollup {
287
+ contexts {
288
+ ... on CheckRun {
289
+ conclusion
290
+ status
291
+ }
292
+ ... on StatusContext {
293
+ state
294
+ }
295
+ }
296
+ }
297
+ reviews(last: 50) {
298
+ nodes {
299
+ author {
300
+ login
301
+ }
302
+ state
303
+ }
304
+ }
305
+ }
306
+ GRAPHQL
307
+ end
308
+
309
+ query = <<~GRAPHQL
310
+ query {
311
+ repository(owner: "instacart", name: "carrot") {
312
+ #{pr_queries.join("\n")}
313
+ }
314
+ }
315
+ GRAPHQL
316
+
317
+ result = `gh api graphql -f query='#{query.gsub("'", "'\\''")}' 2>/dev/null`
318
+ return {} if result.empty?
319
+
320
+ data = JSON.parse(result)
321
+ repo_data = data.dig('data', 'repository')
322
+ return {} unless repo_data
323
+
324
+ # Convert GraphQL response to hash keyed by PR number
325
+ pr_info_by_number = {}
326
+ pr_numbers.each_with_index do |num, idx|
327
+ pr_data = repo_data["pr#{idx}"]
328
+ next unless pr_data
329
+
330
+ # Transform GraphQL response to match gh pr view format
331
+ pr_info_by_number[num] = {
332
+ 'number' => pr_data['number'],
333
+ 'title' => pr_data['title'],
334
+ 'url' => pr_data['url'],
335
+ 'headRefName' => pr_data['headRefName'],
336
+ 'state' => pr_data['state'],
337
+ 'isDraft' => pr_data['isDraft'],
338
+ 'mergeable' => pr_data['mergeable'],
339
+ 'reviewDecision' => pr_data['reviewDecision'],
340
+ 'statusCheckRollup' => pr_data['statusCheckRollup'],
341
+ 'reviews' => pr_data.dig('reviews', 'nodes') || []
342
+ }
343
+ end
344
+
345
+ pr_info_by_number
346
+ rescue JSON::ParserError, StandardError
347
+ {}
348
+ end
349
+
350
+ def refresh_all_status(prs, force: false)
351
+ # Determine which PRs need refreshing
352
+ prs_to_refresh = prs.select { |pr| needs_refresh?(pr, force: force) }
353
+
354
+ if prs_to_refresh.empty?
355
+ puts "All PRs recently checked (within last 2 minutes). Use -U to force update." unless force
356
+ return prs
357
+ end
358
+
359
+ # Batch fetch all PR info at once
360
+ pr_numbers = prs_to_refresh.map { |pr| pr['number'] }
361
+ fetched_info = batch_fetch_pr_info(pr_numbers)
362
+
363
+ # Update each PR with fetched info
364
+ prs_to_refresh.each do |pr|
365
+ info = fetched_info[pr['number']]
366
+ next unless info
367
+
368
+ pr['state'] = info['state']
369
+ pr['title'] = info['title']
370
+ pr['checks'] = summarize_checks(info['statusCheckRollup'])
371
+ pr['reviews'] = summarize_reviews(info['reviews'])
372
+ pr['review_decision'] = info['reviewDecision']
373
+ pr['is_draft'] = info['isDraft']
374
+ pr['mergeable'] = info['mergeable']
375
+ pr['last_checked'] = Time.now.iso8601
376
+ end
377
+
378
+ prs
379
+ end
380
+
381
+ def refresh_status(pr, force: false)
382
+ return pr unless needs_refresh?(pr, force: force)
383
+
270
384
  info = fetch_pr_info(pr['number'])
271
385
  return pr unless info
272
386
 
@@ -416,7 +530,7 @@ class PinnedPRManager
416
530
  end
417
531
  end
418
532
 
419
- Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
533
+ Hiiro.run(*ARGV, plugins: [Pins]) do
420
534
  manager = PRManager.new(self)
421
535
  pinned_manager = PinnedPRManager.new
422
536
 
@@ -661,12 +775,14 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
661
775
  end
662
776
 
663
777
  compact = status_args.include?('-c') || status_args.include?('--compact')
778
+ force = status_args.include?('-U') || status_args.include?('--force-update')
664
779
 
665
780
  puts "Refreshing status for #{pinned.length} pinned PR(s)..."
666
781
  puts
667
782
 
783
+ pinned_manager.refresh_all_status(pinned, force: force)
784
+
668
785
  pinned.each_with_index do |pr, idx|
669
- pinned_manager.refresh_status(pr)
670
786
  if compact
671
787
  puts pinned_manager.display_pinned(pr, idx)
672
788
  else
@@ -680,7 +796,7 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
680
796
  puts "Status updated at #{Time.now.strftime('%H:%M:%S')}"
681
797
  end
682
798
 
683
- add_subcmd(:update) do
799
+ add_subcmd(:update) do |*update_args|
684
800
  pinned = pinned_manager.load_pinned
685
801
 
686
802
  if pinned.empty?
@@ -688,15 +804,19 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
688
804
  next
689
805
  end
690
806
 
807
+ force = update_args.include?('-U') || update_args.include?('--force-update')
808
+
691
809
  puts "Updating status for #{pinned.length} PR(s)..."
692
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
810
+ pinned_manager.refresh_all_status(pinned, force: force)
693
811
  pinned_manager.save_pinned(pinned)
694
812
  puts "Done."
695
813
  end
696
814
 
697
- add_subcmd(:green) do
815
+ add_subcmd(:green) do |*green_args|
698
816
  pinned = pinned_manager.load_pinned
699
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
817
+ force = green_args.include?('-U') || green_args.include?('--force-update')
818
+
819
+ pinned_manager.refresh_all_status(pinned, force: force)
700
820
  pinned_manager.save_pinned(pinned)
701
821
 
702
822
  filtered = pinned.select { |pr|
@@ -714,9 +834,11 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
714
834
  filtered.each_with_index { |pr, i| puts pinned_manager.display_pinned(pr, i) }
715
835
  end
716
836
 
717
- add_subcmd(:red) do
837
+ add_subcmd(:red) do |*red_args|
718
838
  pinned = pinned_manager.load_pinned
719
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
839
+ force = red_args.include?('-U') || red_args.include?('--force-update')
840
+
841
+ pinned_manager.refresh_all_status(pinned, force: force)
720
842
  pinned_manager.save_pinned(pinned)
721
843
 
722
844
  filtered = pinned.select { |pr|
@@ -734,9 +856,11 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
734
856
  filtered.each_with_index { |pr, i| puts pinned_manager.display_pinned(pr, i) }
735
857
  end
736
858
 
737
- add_subcmd(:old) do
859
+ add_subcmd(:old) do |*old_args|
738
860
  pinned = pinned_manager.load_pinned
739
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
861
+ force = old_args.include?('-U') || old_args.include?('--force-update')
862
+
863
+ pinned_manager.refresh_all_status(pinned, force: force)
740
864
  pinned_manager.save_pinned(pinned)
741
865
 
742
866
  filtered = pinned.select { |pr| pr['state'] == 'MERGED' }
@@ -760,7 +884,8 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
760
884
  next
761
885
  end
762
886
 
763
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
887
+ force = args.include?('-U') || args.include?('--force-update')
888
+ pinned_manager.refresh_all_status(pinned, force: force)
764
889
 
765
890
  merged_or_closed = pinned.select { |pr| pr['state'] == 'MERGED' || pr['state'] == 'CLOSED' }
766
891
 
@@ -782,9 +907,11 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
782
907
  puts "Done. #{pinned_manager.load_pinned.length} PR(s) still tracked."
783
908
  end
784
909
 
785
- add_subcmd(:draft) do
910
+ add_subcmd(:draft) do |*draft_args|
786
911
  pinned = pinned_manager.load_pinned
787
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
912
+ force = draft_args.include?('-U') || draft_args.include?('--force-update')
913
+
914
+ pinned_manager.refresh_all_status(pinned, force: force)
788
915
  pinned_manager.save_pinned(pinned)
789
916
 
790
917
  filtered = pinned.select { |pr| pr['is_draft'] == true }
@@ -827,7 +954,14 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
827
954
  missing_numbers = missing.map { |pr| pr['number'] }
828
955
 
829
956
  tmpfile = Tempfile.new(['missing-prs-', '.yml'])
830
- tmpfile.write(missing_numbers.to_yaml)
957
+ lines = ['---']
958
+ missing.each do |pr|
959
+ lines << ''
960
+ lines << "# [#{pr['headRefName']}] #{pr['title']}"
961
+ lines << "# url: #{pr['url']}"
962
+ lines << "- #{pr['number']}"
963
+ end
964
+ tmpfile.write(lines.join("\n"))
831
965
  tmpfile.close
832
966
 
833
967
  system(ENV['EDITOR'] || 'nvim', tmpfile.path)
@@ -944,7 +1078,8 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
944
1078
  end
945
1079
 
946
1080
  # Refresh all statuses first
947
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
1081
+ force = args.include?('-U') || args.include?('--force-update')
1082
+ pinned_manager.refresh_all_status(pinned, force: force)
948
1083
  pinned_manager.save_pinned(pinned)
949
1084
 
950
1085
  merged = pinned.select { |pr| pr['state'] == 'MERGED' }
@@ -975,7 +1110,8 @@ Hiiro.run(*ARGV, plugins: [Tasks, Tmux, Pins]) do
975
1110
  end
976
1111
 
977
1112
  # Refresh all statuses first
978
- pinned.each { |pr| pinned_manager.refresh_status(pr) }
1113
+ force = args.include?('-U') || args.include?('--force-update')
1114
+ pinned_manager.refresh_all_status(pinned, force: force)
979
1115
 
980
1116
  merged_or_closed = pinned.select { |pr| pr['state'] == 'MERGED' || pr['state'] == 'CLOSED' }
981
1117
 
data/bin/h-session CHANGED
@@ -4,7 +4,7 @@ require 'hiiro'
4
4
  require 'tempfile'
5
5
  require 'yaml'
6
6
 
7
- Hiiro.run(*ARGV, plugins: [Pins, Tasks]) do
7
+ Hiiro.run(*ARGV, tasks: true, plugins: [Pins]) do
8
8
  tmux = tmux_client
9
9
 
10
10
  add_subcmd(:ls, :list) { |*args|
@@ -130,7 +130,6 @@ Hiiro.run(*ARGV, plugins: [Pins, Tasks]) do
130
130
  end
131
131
 
132
132
  add_subcmd(:orphans) do
133
- env = Environment.current
134
133
  task_session_names = env.all_tasks.map(&:session_name)
135
134
  orphans = env.all_sessions.reject { |s| task_session_names.include?(s.name) }
136
135
 
@@ -144,7 +143,6 @@ Hiiro.run(*ARGV, plugins: [Pins, Tasks]) do
144
143
  end
145
144
 
146
145
  add_subcmd(:okill) do
147
- env = Environment.current
148
146
  task_session_names = env.all_tasks.map(&:session_name)
149
147
  orphans = env.all_sessions.reject { |s| task_session_names.include?(s.name) }
150
148
 
data/bin/h-window CHANGED
@@ -133,4 +133,48 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
133
133
  puts "Window not found"
134
134
  end
135
135
  }
136
+
137
+ add_subcmd(:vsplit) {
138
+ system('tmux', 'select-layout', 'even-horizontal')
139
+ }
140
+
141
+ add_subcmd(:hsplit) {
142
+ system('tmux', 'select-layout', 'even-vertical')
143
+ }
144
+
145
+ add_subcmd(:layout) { |q=nil|
146
+ layouts = {
147
+ mhorizontal: 'main-vertical',
148
+ main_horizontal: 'main-vertical',
149
+ ehorizontal: 'even-vertical',
150
+ horizontal: 'even-vertical',
151
+ even_horizontal: 'even-vertical',
152
+
153
+ even_vertical: 'even-horizontal',
154
+ evertical: 'even-horizontal',
155
+ vertical: 'even-horizontal',
156
+ main_vertical: 'main-horizontal',
157
+ mvertical: 'main-horizontal',
158
+ main_vertical_mirrored: 'main-horizontal-mirrored',
159
+ mmvertical: 'main-horizontal-mirrored',
160
+
161
+ tiled: 'tiled',
162
+ }
163
+
164
+ if q.nil?
165
+ layout = fuzzyfind(layouts)
166
+ end
167
+
168
+ if layouts.values.include?(q)
169
+ layout = q
170
+ end
171
+
172
+ if layout.nil?
173
+ begin
174
+ _, layout = Hiiro::Matcher.resolve(layouts, q) { |k,v| k }
175
+ end
176
+ end
177
+
178
+ system('tmux', 'select-layout', layout)
179
+ }
136
180
  end
data/exe/h CHANGED
@@ -3,7 +3,21 @@
3
3
  require "hiiro"
4
4
  require "fileutils"
5
5
 
6
- Hiiro.run(*ARGV, cwd: Dir.pwd, plugins: [Tasks]) do
6
+ def update_hiiro
7
+ if `gem list hiiro`.strip.empty?
8
+ system('gem', 'install', 'hiiro')
9
+ else
10
+ system('gem', 'update', 'hiiro')
11
+ end
12
+
13
+ system('h', 'setup')
14
+ end
15
+
16
+ def rbenv_versions
17
+ versions = `rbenv versions --bare`.lines(chomp: true)
18
+ end
19
+
20
+ Hiiro.run(*ARGV, cwd: Dir.pwd, tasks: true) do
7
21
  add_subcmd(:version) { |*args|
8
22
  puts Hiiro::VERSION
9
23
  }
@@ -12,6 +26,21 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, plugins: [Tasks]) do
12
26
  puts "pong"
13
27
  }
14
28
 
29
+ add_subcmd(:install) { |*args|
30
+ opts = Hiiro::Options.setup {
31
+ flag(:all, short: :a)
32
+ }.parse!(ARGV)
33
+
34
+ if opts.all
35
+ rbenv_versions.each do |ver|
36
+ ENV['RBENV_VERSION'] = ver
37
+ update_hiiro
38
+ end
39
+ else
40
+ update_hiiro
41
+ end
42
+ }
43
+
15
44
  add_subcmd(:setup) do |*args|
16
45
  # Detect how the command was invoked to determine prefix
17
46
  invoked_as = File.basename($0)
@@ -83,4 +112,8 @@ Hiiro.run(*ARGV, cwd: Dir.pwd, plugins: [Tasks]) do
83
112
  add_subcmd(:alert) do |*args|
84
113
  Hiiro::Notification.show(self)
85
114
  end
115
+
116
+ add_subcmd(:queue) do |*args|
117
+ Hiiro::Queue.build_hiiro(self).run
118
+ end
86
119
  end
data/hiiro.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_dependency "pry", "~> 0.14"
30
+ spec.add_dependency "front_matter_parser"
30
31
 
31
32
  spec.add_development_dependency "minitest", "~> 5.0"
32
33
  spec.add_development_dependency "rake", "~> 13.0"