hiiro 0.1.42 → 0.1.44

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: e1a957e724383a7df39d3cfb3697cd364839cc46cb38349cdcb67bf17bd56fd7
4
- data.tar.gz: 9e54741f54b9074cb94e5325c8baa030c62b500de13bd735c2e064931dcc98fe
3
+ metadata.gz: 905c984e32273b1a0c950252f7a7380ca84edfdea8293161a19537b638087b7b
4
+ data.tar.gz: 9597fa067608d4afda704464685aeb1839a3e6664dade67a95609afc4d6c47d2
5
5
  SHA512:
6
- metadata.gz: '098472ad16ff1d6c5522c8d04d4de49ea798c7ed676329ed55e7674b8fdb5b9a93381555027aecb2cd286497632640121d3fd8b9da5552805417e218aa4ab01c'
7
- data.tar.gz: 48642a4e5f8c32be7ad500d19bb832d448ebb58adb7470b22476bff4ddbf4d1b2fb5f323a605ad1bb90cc0c64a94d117c60e0f39dc0ddd7e155c43454636c5bd
6
+ metadata.gz: 0e2d3891085e9ba9ed96405fe8ce94b51ea74967df558ab8dd8fc77b7135a59047041b8fe3edae7b5dcef841761afdee4288d91b33c916c64d499a2a7a541429
7
+ data.tar.gz: 1514c66640b2c1aba783740110abd6b341e1abe9dbc5beb2c675190823c7963e6d6013538b6ceaf435b83cadc86d7649e4e170004b0499cf7831d86ca19fd6c6
data/bin/h-app ADDED
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'pathname'
5
+ require 'hiiro'
6
+
7
+ APPS_FILE = File.join(Dir.home, '.config', 'hiiro', 'apps.yml')
8
+
9
+ def load_apps
10
+ return {} unless File.exist?(APPS_FILE)
11
+ YAML.safe_load_file(APPS_FILE) || {}
12
+ end
13
+
14
+ def git_root
15
+ root = `git rev-parse --show-toplevel 2>/dev/null`.strip
16
+ root.empty? ? nil : root
17
+ end
18
+
19
+ def task_root
20
+ # Fall back to current task's tree path if not in a git repo
21
+ env = Environment.current rescue nil
22
+ return nil unless env
23
+
24
+ task = env.task
25
+ return nil unless task
26
+
27
+ tree = env.find_tree(task.tree_name)
28
+ tree&.path
29
+ end
30
+
31
+ def resolve_app_path(app_relative_path)
32
+ root = git_root || task_root
33
+ return nil unless root
34
+
35
+ File.join(root, app_relative_path)
36
+ end
37
+
38
+ def relative_cd_path(target_path)
39
+ pwd = Pathname.new(Dir.pwd)
40
+ target = Pathname.new(target_path)
41
+ target.relative_path_from(pwd).to_s
42
+ end
43
+
44
+ def send_cd(path)
45
+ pane = ENV['TMUX_PANE']
46
+ if pane
47
+ system('tmux', 'send-keys', '-t', pane, "cd #{path}\n")
48
+ else
49
+ system('tmux', 'send-keys', "cd #{path}\n")
50
+ end
51
+ end
52
+
53
+ Hiiro.run(*ARGV, plugins: [:Tasks]) {
54
+ add_subcmd(:config) {
55
+ editor = ENV['EDITOR'] || 'nvim'
56
+ system(editor, APPS_FILE)
57
+ }
58
+
59
+ add_subcmd(:cd) { |app_name|
60
+ apps = load_apps
61
+ match = apps.find { |name, _| name.start_with?(app_name) }
62
+
63
+ if match
64
+ name, app_relative_path = match
65
+ target = resolve_app_path(app_relative_path)
66
+
67
+ if target
68
+ cd_path = relative_cd_path(target)
69
+ send_cd(cd_path)
70
+ else
71
+ puts "Not in a git repo or task - cannot resolve app path"
72
+ end
73
+ else
74
+ puts "App '#{app_name}' not found"
75
+ puts
76
+ puts "Available apps:"
77
+ apps.each { |name, path| puts format(" %-20s => %s", name, path) }
78
+ end
79
+ }
80
+
81
+ add_subcmd(:ls) {
82
+ apps = load_apps
83
+
84
+ if apps.empty?
85
+ puts "No apps configured."
86
+ puts "Create #{APPS_FILE} with format:"
87
+ puts " app_name: /path/to/app"
88
+ else
89
+ puts "Configured apps:"
90
+ puts
91
+ apps.each { |name, path| puts format(" %-20s => %s", name, path) }
92
+ end
93
+ }
94
+ }
data/bin/h-config ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hiiro'
4
+
5
+ def open_config(dir:, file:)
6
+ editor = ENV['EDITOR'] || 'nvim'
7
+ full_path = File.expand_path(File.join(dir, file))
8
+ Dir.chdir(File.expand_path(dir))
9
+ system(editor, full_path)
10
+ end
11
+
12
+ Hiiro.run(*ARGV) {
13
+ add_subcmd(:vim) {
14
+ dir = '~/.config/nvim'
15
+ file = File.exist?(File.expand_path('~/.config/nvim/init.lua')) ? 'init.lua' : 'init.vim'
16
+ open_config(dir: dir, file: file)
17
+ }
18
+
19
+ add_subcmd(:tmux) {
20
+ open_config(dir: '~', file: '.tmux.conf')
21
+ }
22
+
23
+ add_subcmd(:zsh) {
24
+ open_config(dir: '~', file: '.zshrc')
25
+ }
26
+
27
+ add_subcmd(:profile) {
28
+ open_config(dir: '~', file: '.zprofile')
29
+ }
30
+
31
+ add_subcmd(:starship) {
32
+ open_config(dir: '~/.config/starship', file: 'starship.toml')
33
+ }
34
+
35
+ add_subcmd(:claude) {
36
+ open_config(dir: '~/.claude', file: 'settings.json')
37
+ }
38
+ }
@@ -0,0 +1,168 @@
1
+ class Hiiro
2
+ class Options
3
+ class Definition
4
+ attr_reader :name, :short, :type, :default, :desc, :multi
5
+
6
+ def initialize(name, short: nil, type: :string, default: nil, desc: nil, multi: false)
7
+ @name = name.to_sym
8
+ @short = short&.to_s
9
+ @type = type
10
+ @default = default
11
+ @desc = desc
12
+ @multi = multi
13
+ end
14
+
15
+ def flag?
16
+ type == :flag
17
+ end
18
+
19
+ def long_form
20
+ "--#{name.to_s.tr('_', '-')}"
21
+ end
22
+
23
+ def short_form
24
+ short ? "-#{short}" : nil
25
+ end
26
+
27
+ def match?(arg)
28
+ arg == long_form || arg == short_form
29
+ end
30
+
31
+ def coerce(value)
32
+ case type
33
+ when :integer then value.to_i
34
+ when :float then value.to_f
35
+ else value
36
+ end
37
+ end
38
+ end
39
+
40
+ attr_reader :definitions, :values, :remaining_args
41
+ alias args remaining_args
42
+
43
+ def initialize(raw_args = [], &block)
44
+ @raw_args = raw_args.dup
45
+ @definitions = {}
46
+ @values = {}
47
+ @remaining_args = []
48
+ @parsed = false
49
+
50
+ instance_eval(&block) if block
51
+ parse! if block
52
+ end
53
+
54
+ # DSL: define a toggle flag (default false, presence inverts)
55
+ def flag(name, short: nil, default: false, desc: nil)
56
+ defn = Definition.new(name, short: short, type: :flag, default: default, desc: desc)
57
+ @definitions[name.to_sym] = defn
58
+ self
59
+ end
60
+
61
+ # DSL: define an option that takes a value
62
+ def option(name, short: nil, type: :string, default: nil, desc: nil, multi: false)
63
+ defn = Definition.new(name, short: short, type: type, default: default, desc: desc, multi: multi)
64
+ @definitions[name.to_sym] = defn
65
+ self
66
+ end
67
+
68
+ def parse!
69
+ return self if @parsed
70
+
71
+ @definitions.each do |name, defn|
72
+ @values[name] = defn.multi ? [] : defn.default
73
+ end
74
+
75
+ args = @raw_args.dup
76
+ while args.any?
77
+ arg = args.shift
78
+
79
+ if arg == '--'
80
+ @remaining_args.concat(args)
81
+ break
82
+ elsif arg.start_with?('--')
83
+ parse_long_option(arg, args)
84
+ elsif arg.start_with?('-') && arg.length > 1
85
+ parse_short_options(arg, args)
86
+ else
87
+ @remaining_args << arg
88
+ end
89
+ end
90
+
91
+ @parsed = true
92
+ self
93
+ end
94
+
95
+ def [](name)
96
+ @values[name.to_sym]
97
+ end
98
+
99
+ def method_missing(name, *args, &block)
100
+ name_str = name.to_s
101
+ if name_str.end_with?('?')
102
+ key = name_str.chomp('?').to_sym
103
+ return !!@values[key] if @definitions.key?(key)
104
+ else
105
+ return @values[name] if @definitions.key?(name)
106
+ end
107
+ super
108
+ end
109
+
110
+ def respond_to_missing?(name, include_private = false)
111
+ key = name.to_s.chomp('?').to_sym
112
+ @definitions.key?(key) || super
113
+ end
114
+
115
+ def to_h
116
+ @values.dup
117
+ end
118
+
119
+ private
120
+
121
+ def parse_long_option(arg, args)
122
+ if arg.include?('=')
123
+ opt, value = arg.split('=', 2)
124
+ name = opt.sub(/^--/, '').tr('-', '_').to_sym
125
+ else
126
+ name = arg.sub(/^--/, '').tr('-', '_').to_sym
127
+ value = nil
128
+ end
129
+
130
+ defn = @definitions[name]
131
+ return unless defn
132
+
133
+ if defn.flag?
134
+ @values[name] = !defn.default
135
+ else
136
+ value ||= args.shift
137
+ store_value(defn, value)
138
+ end
139
+ end
140
+
141
+ def parse_short_options(arg, args)
142
+ chars = arg.sub(/^-/, '').chars
143
+
144
+ chars.each_with_index do |char, idx|
145
+ defn = @definitions.values.find { |d| d.short == char }
146
+ next unless defn
147
+
148
+ if defn.flag?
149
+ @values[defn.name] = !defn.default
150
+ elsif idx == chars.length - 1
151
+ store_value(defn, args.shift)
152
+ else
153
+ store_value(defn, chars[(idx + 1)..].join)
154
+ break
155
+ end
156
+ end
157
+ end
158
+
159
+ def store_value(defn, value)
160
+ coerced = defn.coerce(value)
161
+ if defn.multi
162
+ @values[defn.name] << coerced
163
+ else
164
+ @values[defn.name] = coerced
165
+ end
166
+ end
167
+ end
168
+ end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.42"
2
+ VERSION = "0.1.44"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -4,6 +4,7 @@ require "shellwords"
4
4
 
5
5
  require_relative "hiiro/version"
6
6
  require_relative "hiiro/history"
7
+ require_relative "hiiro/options"
7
8
 
8
9
  class String
9
10
  def underscore(camel_cased_word=self)
@@ -39,7 +40,13 @@ class Hiiro
39
40
  system(ENV['EDITOR'] || 'nvim', hiiro.bin)
40
41
  }
41
42
 
42
- block.call(hiiro) if block
43
+ if block
44
+ if block.arity == 1
45
+ block.call(hiiro)
46
+ else
47
+ hiiro.instance_eval(&block)
48
+ end
49
+ end
43
50
  end
44
51
  end
45
52
 
data/plugins/tasks.rb CHANGED
@@ -672,7 +672,11 @@ class TaskManager
672
672
  # --- Interactive selection with sk ---
673
673
 
674
674
  def select_task_interactive(prompt = nil)
675
- names = tasks.map { |t| scope == :subtask ? t.short_name : t.name }
675
+ names = if scope == :subtask
676
+ tasks.map(&:short_name)
677
+ else
678
+ environment.all_tasks.map(&:name)
679
+ end
676
680
  return nil if names.empty?
677
681
 
678
682
  sk_select(names)
data/script/publish CHANGED
@@ -1,10 +1,72 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "hiiro"
3
+ require "net/http"
4
+ require "json"
4
5
 
5
- major, minor, patch = Hiiro::VERSION.split(?.)
6
+ # Parse -t flag for pre-release
7
+ pre_release = ARGV.include?('-t')
6
8
 
7
- new_version = [major, minor, patch.to_i + 1].join(?.)
9
+ # Fetch all published versions from RubyGems
10
+ def fetch_versions
11
+ uri = URI("https://rubygems.org/api/v1/versions/hiiro.json")
12
+ response = Net::HTTP.get(uri)
13
+ JSON.parse(response).map { |v| v["number"] }
14
+ rescue => e
15
+ puts "Warning: Could not fetch versions from RubyGems: #{e.message}"
16
+ puts "Falling back to local version"
17
+ require "hiiro"
18
+ [Hiiro::VERSION]
19
+ end
20
+
21
+ # Parse version string into comparable components
22
+ def parse_version(version)
23
+ parts = version.split(?.)
24
+ is_pre = parts.length >= 5 && parts[-2] == 'pre'
25
+ if is_pre
26
+ { major: parts[0].to_i, minor: parts[1].to_i, patch: parts[2].to_i, pre: true, pre_num: parts[-1].to_i }
27
+ else
28
+ { major: parts[0].to_i, minor: parts[1].to_i, patch: parts[2].to_i, pre: false, pre_num: 0 }
29
+ end
30
+ end
31
+
32
+ # Find the latest version (prioritize release over pre for same patch)
33
+ def latest_version(versions)
34
+ versions.max_by do |v|
35
+ p = parse_version(v)
36
+ # Sort by major, minor, patch, then non-pre before pre, then pre_num
37
+ [p[:major], p[:minor], p[:patch], p[:pre] ? 0 : 1, p[:pre_num]]
38
+ end
39
+ end
40
+
41
+ versions = fetch_versions
42
+ current = latest_version(versions)
43
+ puts "Latest published version: #{current}"
44
+
45
+ parts = current.split(?.)
46
+ is_pre = parts.length >= 5 && parts[-2] == 'pre'
47
+
48
+ if is_pre
49
+ major, minor, patch = parts[0..2]
50
+ pre_num = parts[-1].to_i
51
+ if pre_release
52
+ # pre -> pre: increment pre number
53
+ new_version = [major, minor, patch, 'pre', pre_num + 1].join(?.)
54
+ else
55
+ # pre -> release: remove pre, keep same version number
56
+ new_version = [major, minor, patch].join(?.)
57
+ end
58
+ else
59
+ major, minor, patch = parts[0..2]
60
+ if pre_release
61
+ # release -> pre: increment patch and add pre.1
62
+ new_version = [major, minor, patch.to_i + 1, 'pre', 1].join(?.)
63
+ else
64
+ # release -> release: increment patch
65
+ new_version = [major, minor, patch.to_i + 1].join(?.)
66
+ end
67
+ end
68
+
69
+ puts "New version: #{new_version}"
8
70
 
9
71
  File.open('lib/hiiro/version.rb', 'w+') do |f|
10
72
  f.puts 'class Hiiro'
@@ -23,3 +85,16 @@ puts "\nERROR: unable to push\n" unless pushed
23
85
  system 'git', 'add', '--all'
24
86
  system 'git', 'commit', '-m', "publishing v#{new_version}"
25
87
 
88
+ # Try to push to origin main, fallback to a separate branch if it fails
89
+ if system('git', 'push', 'origin', 'main')
90
+ puts "Pushed to origin/main"
91
+ else
92
+ branch_name = "publish-v#{new_version}"
93
+ system 'git', 'checkout', '-b', branch_name
94
+ if system('git', 'push', 'origin', branch_name)
95
+ puts "Push to main failed. Pushed to origin/#{branch_name} instead"
96
+ else
97
+ puts "\nERROR: unable to push to origin\n"
98
+ end
99
+ system 'git', 'checkout', 'main'
100
+ end
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.42
4
+ version: 0.1.44
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -41,8 +41,10 @@ files:
41
41
  - TODO.md
42
42
  - bin/g-pr
43
43
  - bin/h
44
+ - bin/h-app
44
45
  - bin/h-branch
45
46
  - bin/h-buffer
47
+ - bin/h-config
46
48
  - bin/h-dot
47
49
  - bin/h-dotfiles
48
50
  - bin/h-home
@@ -77,6 +79,7 @@ files:
77
79
  - hiiro.gemspec
78
80
  - lib/hiiro.rb
79
81
  - lib/hiiro/history.rb
82
+ - lib/hiiro/options.rb
80
83
  - lib/hiiro/version.rb
81
84
  - notes
82
85
  - plugins/notify.rb