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 +4 -4
- data/bin/h-app +94 -0
- data/bin/h-config +38 -0
- data/lib/hiiro/options.rb +168 -0
- data/lib/hiiro/version.rb +1 -1
- data/lib/hiiro.rb +8 -1
- data/plugins/tasks.rb +5 -1
- data/script/publish +78 -3
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 905c984e32273b1a0c950252f7a7380ca84edfdea8293161a19537b638087b7b
|
|
4
|
+
data.tar.gz: 9597fa067608d4afda704464685aeb1839a3e6664dade67a95609afc4d6c47d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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
|
-
|
|
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 =
|
|
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 "
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
# Parse -t flag for pre-release
|
|
7
|
+
pre_release = ARGV.include?('-t')
|
|
6
8
|
|
|
7
|
-
|
|
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.
|
|
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
|