hiiro 0.1.146 → 0.1.147

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: ad86f3f872fa785b1c5268a8b15f3b3ce94f3c25b4e938b2c45e69c799893e27
4
- data.tar.gz: f017456d5902a1cf64b35984c8f16d2d621cbc94565dcc913e52937b697439fd
3
+ metadata.gz: a6548d2f79ee793b4c4855565db3b0695c1fb4cc7d677f49359107900af3c3dd
4
+ data.tar.gz: 1d4bc53e4f8cb004c96faa3c1f7940a5754f816b8006faaa7ee0fc0a045168e1
5
5
  SHA512:
6
- metadata.gz: f3115e6d6e558d8f8b1f3bbb0a3d7d82d919d7b2f66d9856d75c726af5f4b0ebf1dae756e7380c6ef851e48f30ac29529c4ee924d50d848821380d6fd10f1a09
7
- data.tar.gz: 224aca8d29cc9e0c4618a98b4b095192f8d32e4a22ea1de068cacb662b50bf66b500eb43f9a443b53beadd0eba00be468a5da59583728ebb7b3cf08978dc4a29
6
+ metadata.gz: 230c9aee91c69295fb32f1d66aef7dbc05210ec9ed08c6dcf25a9300de3b424ccf297cc8efd16b11e856809f898830a7dfb9491142bec1bd66aa89f0ec5c55a2
7
+ data.tar.gz: 59b47e377f278fdde3ca2f74a0bbce190509522e1097b82a735ed0e36eeeee338e2ca115a433b309c2f48edb1fa40a5ce1e95562698fe2b6ea90fcf35a149c75
@@ -0,0 +1,148 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ class Hiiro
5
+ class AppFiles
6
+ CONFIG_FILE = File.join(Dir.home, '.config', 'hiiro', 'app_files.yml')
7
+
8
+ attr_reader :config_file
9
+
10
+ def initialize(config_file: CONFIG_FILE)
11
+ @config_file = config_file
12
+ end
13
+
14
+ def files_for(app_name)
15
+ data = load_config
16
+ data[app_name] || []
17
+ end
18
+
19
+ def add(app_name, *filenames)
20
+ data = load_config
21
+ data[app_name] ||= []
22
+ filenames.flatten.each do |f|
23
+ data[app_name] << f unless data[app_name].include?(f)
24
+ end
25
+ save_config(data)
26
+ puts "Added #{filenames.length} file(s) to '#{app_name}'"
27
+ end
28
+
29
+ def remove(app_name, *filenames)
30
+ data = load_config
31
+ unless data.key?(app_name)
32
+ puts "App '#{app_name}' not found in app files"
33
+ return
34
+ end
35
+
36
+ filenames.flatten.each do |f|
37
+ data[app_name].delete(f)
38
+ end
39
+
40
+ data.delete(app_name) if data[app_name].empty?
41
+ save_config(data)
42
+ puts "Removed #{filenames.length} file(s) from '#{app_name}'"
43
+ end
44
+
45
+ def list(app_name: nil)
46
+ data = load_config
47
+
48
+ if app_name
49
+ files = data[app_name]
50
+ if files && files.any?
51
+ puts "Files for '#{app_name}':"
52
+ files.each { |f| puts " #{f}" }
53
+ else
54
+ puts "No files tracked for '#{app_name}'"
55
+ end
56
+ else
57
+ if data.empty?
58
+ puts "No app files configured."
59
+ puts "Use 'file add <app> <file>' to track files."
60
+ return
61
+ end
62
+
63
+ data.each do |name, files|
64
+ puts "#{name}:"
65
+ files.each { |f| puts " #{f}" }
66
+ puts
67
+ end
68
+ end
69
+ end
70
+
71
+ def self.build_hiiro(parent_hiiro, af, environment: nil)
72
+ parent_hiiro.make_child(:file) do |h|
73
+ h.add_subcmd(:ls) do |app_name=nil|
74
+ af.list(app_name: app_name)
75
+ end
76
+
77
+ h.add_subcmd(:add) do |app_name=nil, *filenames|
78
+ unless app_name
79
+ puts "Usage: file add <app_name> <file1> [file2 ...]"
80
+ next
81
+ end
82
+
83
+ if filenames.empty?
84
+ puts "Usage: file add <app_name> <file1> [file2 ...]"
85
+ next
86
+ end
87
+
88
+ af.add(app_name, *filenames)
89
+ end
90
+
91
+ h.add_subcmd(:rm) do |app_name=nil, *filenames|
92
+ unless app_name
93
+ puts "Usage: file rm <app_name> <file1> [file2 ...]"
94
+ next
95
+ end
96
+
97
+ if filenames.empty?
98
+ puts "Usage: file rm <app_name> <file1> [file2 ...]"
99
+ next
100
+ end
101
+
102
+ af.remove(app_name, *filenames)
103
+ end
104
+
105
+ h.add_subcmd(:edit) do |app_name=nil|
106
+ unless app_name
107
+ puts "Usage: file edit <app_name>"
108
+ next
109
+ end
110
+
111
+ files = af.files_for(app_name)
112
+ if files.empty?
113
+ puts "No files tracked for '#{app_name}'"
114
+ next
115
+ end
116
+
117
+ # Resolve files relative to tree root if environment available
118
+ resolved = files
119
+ if environment
120
+ tree = environment.tree
121
+ if tree
122
+ resolved = files.map { |f| File.join(tree.path, f) }
123
+ end
124
+ end
125
+
126
+ editor = ENV['EDITOR'] || 'nvim'
127
+ if editor.match?(/vim/i)
128
+ system(editor, '-O', *resolved)
129
+ else
130
+ system(editor, *resolved)
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def load_config
139
+ return {} unless File.exist?(config_file)
140
+ YAML.safe_load_file(config_file, permitted_classes: [Symbol]) || {}
141
+ end
142
+
143
+ def save_config(data)
144
+ FileUtils.mkdir_p(File.dirname(config_file))
145
+ File.write(config_file, YAML.dump(data))
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,265 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ class Hiiro
5
+ class RunnerTool
6
+ CONFIG_FILE = File.join(Dir.home, '.config', 'hiiro', 'tools.yml')
7
+
8
+ KNOWN_CHANGE_SETS = %w[dirty branch all].freeze
9
+ KNOWN_TOOL_TYPES = %w[lint test format].freeze
10
+
11
+ attr_reader :config_file
12
+
13
+ def initialize(config_file: CONFIG_FILE)
14
+ @config_file = config_file
15
+ end
16
+
17
+ def tools
18
+ load_config
19
+ end
20
+
21
+ def find_tool(name)
22
+ configs = tools
23
+ names = configs.keys.map { |k| OpenStruct.new(name: k) }
24
+ result = Hiiro::Matcher.new(names, :name).by_prefix(name)
25
+ match = result.resolved || result.first
26
+ return nil unless match
27
+
28
+ tool_name = match.item.name
29
+ { name: tool_name, **symbolize_keys(configs[tool_name]) }
30
+ end
31
+
32
+ def find_tools(tool_type: nil, file_type_group: nil)
33
+ configs = tools
34
+ configs.select { |_, cfg|
35
+ (tool_type.nil? || cfg['tool_type'] == tool_type) &&
36
+ (file_type_group.nil? || cfg['file_type_group'] == file_type_group)
37
+ }
38
+ end
39
+
40
+ def changed_files(change_set, git: nil)
41
+ case change_set
42
+ when 'dirty'
43
+ output = `git status --porcelain 2>/dev/null`
44
+ output.lines.map { |l| l.strip.sub(/^.{3}/, '') }.reject(&:empty?)
45
+ when 'branch'
46
+ base = `git merge-base HEAD main 2>/dev/null`.chomp
47
+ base = 'main' if base.empty?
48
+ output = `git diff --name-only #{base}...HEAD 2>/dev/null`
49
+ output.lines.map(&:chomp).reject(&:empty?)
50
+ when 'all'
51
+ nil
52
+ else
53
+ nil
54
+ end
55
+ end
56
+
57
+ def run(change_set: 'dirty', tool_type: nil, file_type_group: nil, variation: nil, git: nil)
58
+ matching = find_tools(tool_type: tool_type, file_type_group: file_type_group)
59
+
60
+ if matching.empty?
61
+ puts "No tools found matching criteria"
62
+ puts " tool_type: #{tool_type || '(any)'}"
63
+ puts " file_type_group: #{file_type_group || '(any)'}"
64
+ return false
65
+ end
66
+
67
+ files = changed_files(change_set, git: git)
68
+
69
+ matching.each do |name, cfg|
70
+ tool_files = filter_files_for_tool(files, cfg)
71
+
72
+ if files && tool_files.empty?
73
+ puts "No matching files for '#{name}' (#{cfg['file_extensions']})"
74
+ next
75
+ end
76
+
77
+ cmd = if variation && cfg['variations'] && cfg['variations'][variation]
78
+ cfg['variations'][variation]
79
+ else
80
+ cfg['command']
81
+ end
82
+
83
+ filenames = tool_files ? tool_files.join(' ') : ''
84
+ cmd = cmd.gsub('[FILENAMES]', filenames)
85
+
86
+ puts "Running #{name}: #{cmd}"
87
+ system(cmd)
88
+ end
89
+
90
+ true
91
+ end
92
+
93
+ def add_tool(config_hash)
94
+ name = config_hash.delete('name') || config_hash.delete(:name)
95
+ unless name
96
+ puts "Tool name required"
97
+ return false
98
+ end
99
+
100
+ configs = load_config
101
+ if configs.key?(name)
102
+ puts "Tool '#{name}' already exists"
103
+ return false
104
+ end
105
+
106
+ configs[name] = config_hash.transform_keys(&:to_s)
107
+ save_config(configs)
108
+ puts "Added tool '#{name}'"
109
+ true
110
+ end
111
+
112
+ def remove_tool(name)
113
+ configs = load_config
114
+ unless configs.key?(name)
115
+ puts "Tool '#{name}' not found"
116
+ return false
117
+ end
118
+
119
+ configs.delete(name)
120
+ save_config(configs)
121
+ puts "Removed tool '#{name}'"
122
+ true
123
+ end
124
+
125
+ def edit_config
126
+ editor = ENV['EDITOR'] || 'nvim'
127
+ system(editor, config_file)
128
+ end
129
+
130
+ def self.build_hiiro(parent_hiiro, rt, git: nil)
131
+ parent_hiiro.make_child(:run) do |h|
132
+ h.add_default do |*run_args|
133
+ change_set = nil
134
+ tool_type = nil
135
+ file_type_group = nil
136
+ variation = nil
137
+
138
+ positional = []
139
+ args_iter = run_args.each
140
+ loop do
141
+ arg = args_iter.next
142
+ case arg
143
+ when '--variation', '-v'
144
+ variation = args_iter.next
145
+ else
146
+ positional << arg
147
+ end
148
+ end
149
+
150
+ positional.each do |arg|
151
+ if KNOWN_CHANGE_SETS.include?(arg)
152
+ change_set = arg
153
+ elsif KNOWN_TOOL_TYPES.include?(arg)
154
+ tool_type = arg
155
+ else
156
+ # Treat as file_type_group
157
+ file_type_group ||= arg
158
+ end
159
+ end
160
+
161
+ change_set ||= 'dirty'
162
+
163
+ rt.run(
164
+ change_set: change_set,
165
+ tool_type: tool_type,
166
+ file_type_group: file_type_group,
167
+ variation: variation,
168
+ git: git,
169
+ )
170
+ end
171
+
172
+ h.add_subcmd(:ls) do
173
+ configs = rt.tools
174
+ if configs.empty?
175
+ puts "No tools configured."
176
+ puts "Use 'run add' to add one, or edit #{rt.config_file}"
177
+ next
178
+ end
179
+
180
+ puts "Configured tools:"
181
+ puts
182
+ configs.each do |name, cfg|
183
+ variations = cfg['variations'] ? " (#{cfg['variations'].keys.join(', ')})" : ""
184
+ puts format(" %-15s [%s] %-10s exts: %s%s",
185
+ name,
186
+ cfg['tool_type'] || '?',
187
+ cfg['file_type_group'] || '?',
188
+ cfg['file_extensions'] || '*',
189
+ variations)
190
+ end
191
+ end
192
+
193
+ h.add_subcmd(:add) do |*add_args|
194
+ template = {
195
+ 'tool_type' => 'lint',
196
+ 'command' => 'echo [FILENAMES]',
197
+ 'variations' => {},
198
+ 'file_type_group' => '',
199
+ 'file_extensions' => '',
200
+ }
201
+
202
+ require 'tempfile'
203
+ tmpfile = Tempfile.new(['tool', '.yml'])
204
+ tmpfile.write(YAML.dump({ 'new_tool' => template }))
205
+ tmpfile.close
206
+
207
+ editor = ENV['EDITOR'] || 'nvim'
208
+ system(editor, tmpfile.path)
209
+
210
+ begin
211
+ data = YAML.safe_load_file(tmpfile.path, permitted_classes: [Symbol]) || {}
212
+ data.each do |name, cfg|
213
+ rt.add_tool({ 'name' => name }.merge(cfg || {}))
214
+ end
215
+ rescue => e
216
+ puts "Error parsing config: #{e.message}"
217
+ ensure
218
+ tmpfile.unlink
219
+ end
220
+ end
221
+
222
+ h.add_subcmd(:rm) do |tool_name=nil|
223
+ unless tool_name
224
+ puts "Usage: run rm <name>"
225
+ next
226
+ end
227
+
228
+ rt.remove_tool(tool_name)
229
+ end
230
+
231
+ h.add_subcmd(:config) do
232
+ rt.edit_config
233
+ end
234
+ end
235
+ end
236
+
237
+ private
238
+
239
+ def load_config
240
+ return {} unless File.exist?(config_file)
241
+ YAML.safe_load_file(config_file, permitted_classes: [Symbol]) || {}
242
+ end
243
+
244
+ def save_config(data)
245
+ FileUtils.mkdir_p(File.dirname(config_file))
246
+ File.write(config_file, YAML.dump(data))
247
+ end
248
+
249
+ def filter_files_for_tool(files, cfg)
250
+ return files unless files
251
+
252
+ extensions = (cfg['file_extensions'] || '').split(',').map(&:strip)
253
+ return files if extensions.empty?
254
+
255
+ files.select { |f|
256
+ ext = File.extname(f).delete('.')
257
+ extensions.include?(ext)
258
+ }
259
+ end
260
+
261
+ def symbolize_keys(hash)
262
+ hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,465 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+ require 'time'
4
+
5
+ class Hiiro
6
+ class ServiceManager
7
+ CONFIG_FILE = File.join(Dir.home, '.config', 'hiiro', 'services.yml')
8
+ STATE_DIR = File.join(Dir.home, '.config', 'hiiro', 'services')
9
+ STATE_FILE = File.join(STATE_DIR, 'running.yml')
10
+
11
+ attr_reader :config_file, :state_file
12
+
13
+ def initialize(config_file: CONFIG_FILE, state_file: STATE_FILE)
14
+ @config_file = config_file
15
+ @state_file = state_file
16
+ end
17
+
18
+ def services
19
+ load_config
20
+ end
21
+
22
+ def find_service(name)
23
+ configs = services
24
+ names = configs.keys.map { |k| OpenStruct.new(name: k) }
25
+ result = Hiiro::Matcher.new(names, :name).by_prefix(name)
26
+ match = result.resolved || result.first
27
+ return nil unless match
28
+
29
+ svc_name = match.item.name
30
+ { name: svc_name, **symbolize_keys(configs[svc_name]) }
31
+ end
32
+
33
+ def running?(name)
34
+ state = load_state
35
+ state.key?(name)
36
+ end
37
+
38
+ def running_services
39
+ load_state
40
+ end
41
+
42
+ def start(name, tmux_info: {}, task_info: {})
43
+ svc = find_service(name)
44
+ unless svc
45
+ puts "Service '#{name}' not found"
46
+ return false
47
+ end
48
+
49
+ svc_name = svc[:name]
50
+
51
+ if running?(svc_name)
52
+ info = running_services[svc_name]
53
+ puts "Service '#{svc_name}' is already running (pid: #{info['pid']}, pane: #{info['tmux_pane']})"
54
+ return false
55
+ end
56
+
57
+ # Run init commands
58
+ if svc[:init]
59
+ svc[:init].each do |cmd|
60
+ base_dir = svc[:base_dir]
61
+ if base_dir
62
+ system("cd #{base_dir} && #{cmd}")
63
+ else
64
+ system(cmd)
65
+ end
66
+ end
67
+ end
68
+
69
+ # Start the service
70
+ start_cmd = svc[:start]
71
+ unless start_cmd
72
+ puts "No start command configured for '#{svc_name}'"
73
+ return false
74
+ end
75
+
76
+ base_dir = svc[:base_dir]
77
+ pane_id = tmux_info[:pane] || ENV['TMUX_PANE']
78
+
79
+ if pane_id
80
+ if base_dir
81
+ system('tmux', 'send-keys', '-t', pane_id, "cd #{base_dir} && #{start_cmd}", 'Enter')
82
+ else
83
+ system('tmux', 'send-keys', '-t', pane_id, start_cmd, 'Enter')
84
+ end
85
+ else
86
+ if base_dir
87
+ system("cd #{base_dir} && #{start_cmd} &")
88
+ else
89
+ system("#{start_cmd} &")
90
+ end
91
+ end
92
+
93
+ # Record state
94
+ state = load_state
95
+ state[svc_name] = {
96
+ 'pid' => nil,
97
+ 'tmux_session' => tmux_info[:session],
98
+ 'tmux_window' => tmux_info[:window],
99
+ 'tmux_pane' => pane_id,
100
+ 'task' => task_info[:task_name],
101
+ 'tree' => task_info[:tree],
102
+ 'branch' => task_info[:branch],
103
+ 'started_at' => Time.now.iso8601,
104
+ }
105
+ save_state(state)
106
+
107
+ puts "Started service '#{svc_name}'"
108
+ true
109
+ end
110
+
111
+ def stop(name)
112
+ svc = find_service(name)
113
+ unless svc
114
+ puts "Service '#{name}' not found"
115
+ return false
116
+ end
117
+
118
+ svc_name = svc[:name]
119
+
120
+ unless running?(svc_name)
121
+ puts "Service '#{svc_name}' is not running"
122
+ return false
123
+ end
124
+
125
+ info = running_services[svc_name]
126
+ pane_id = info['tmux_pane']
127
+
128
+ if svc[:stop]
129
+ stop_cmd = svc[:stop]
130
+ if info['pid']
131
+ stop_cmd = stop_cmd.gsub('$PID', info['pid'].to_s)
132
+ end
133
+ system(stop_cmd)
134
+ elsif pane_id
135
+ system('tmux', 'send-keys', '-t', pane_id, 'C-c')
136
+ end
137
+
138
+ # Run cleanup commands
139
+ if svc[:cleanup]
140
+ svc[:cleanup].each { |cmd| system(cmd) }
141
+ end
142
+
143
+ # Remove from state
144
+ state = load_state
145
+ state.delete(svc_name)
146
+ save_state(state)
147
+
148
+ puts "Stopped service '#{svc_name}'"
149
+ true
150
+ end
151
+
152
+ def attach(name)
153
+ svc = find_service(name)
154
+ unless svc
155
+ puts "Service '#{name}' not found"
156
+ return false
157
+ end
158
+
159
+ svc_name = svc[:name]
160
+ unless running?(svc_name)
161
+ puts "Service '#{svc_name}' is not running"
162
+ return false
163
+ end
164
+
165
+ info = running_services[svc_name]
166
+ pane_id = info['tmux_pane']
167
+ session = info['tmux_session']
168
+
169
+ if session
170
+ system('tmux', 'switch-client', '-t', session)
171
+ end
172
+
173
+ if pane_id
174
+ system('tmux', 'select-pane', '-t', pane_id)
175
+ end
176
+
177
+ true
178
+ end
179
+
180
+ def url(name)
181
+ svc = find_service(name)
182
+ return nil unless svc
183
+
184
+ host = svc[:host] || 'localhost'
185
+ port = svc[:port]
186
+ return nil unless port
187
+
188
+ "http://#{host}:#{port}"
189
+ end
190
+
191
+ def port(name)
192
+ svc = find_service(name)
193
+ svc&.[](:port)
194
+ end
195
+
196
+ def host(name)
197
+ svc = find_service(name)
198
+ svc&.[](:host) || 'localhost'
199
+ end
200
+
201
+ def status(name)
202
+ svc = find_service(name)
203
+ unless svc
204
+ puts "Service '#{name}' not found"
205
+ return
206
+ end
207
+
208
+ svc_name = svc[:name]
209
+ puts "Service: #{svc_name}"
210
+ puts "Base dir: #{svc[:base_dir] || '(none)'}"
211
+ puts "URL: #{url(svc_name) || '(none)'}"
212
+
213
+ if running?(svc_name)
214
+ info = running_services[svc_name]
215
+ puts "Status: running"
216
+ puts "PID: #{info['pid'] || '(unknown)'}"
217
+ puts "Pane: #{info['tmux_pane'] || '(unknown)'}"
218
+ puts "Task: #{info['task'] || '(none)'}"
219
+ puts "Started: #{info['started_at']}"
220
+ else
221
+ puts "Status: stopped"
222
+ end
223
+ end
224
+
225
+ def services_for_dir(base_dir)
226
+ configs = services
227
+ configs.select { |_, v| v['base_dir'] == base_dir }
228
+ end
229
+
230
+ def add_service(config_hash)
231
+ name = config_hash.delete('name') || config_hash.delete(:name)
232
+ unless name
233
+ puts "Service name required"
234
+ return false
235
+ end
236
+
237
+ configs = load_config
238
+ if configs.key?(name)
239
+ puts "Service '#{name}' already exists"
240
+ return false
241
+ end
242
+
243
+ configs[name] = config_hash.transform_keys(&:to_s)
244
+ save_config(configs)
245
+ puts "Added service '#{name}'"
246
+ true
247
+ end
248
+
249
+ def remove_service(name)
250
+ configs = load_config
251
+ unless configs.key?(name)
252
+ puts "Service '#{name}' not found"
253
+ return false
254
+ end
255
+
256
+ configs.delete(name)
257
+ save_config(configs)
258
+ puts "Removed service '#{name}'"
259
+ true
260
+ end
261
+
262
+ def self.build_hiiro(parent_hiiro, sm, task_manager: nil)
263
+ parent_hiiro.make_child(:service) do |h|
264
+ h.add_subcmd(:ls) do
265
+ configs = sm.services
266
+ if configs.empty?
267
+ puts "No services configured."
268
+ puts "Use 'service add' to add one, or edit #{sm.config_file}"
269
+ next
270
+ end
271
+
272
+ running = sm.running_services
273
+ puts "Services:"
274
+ puts
275
+ configs.each do |name, cfg|
276
+ status = running.key?(name) ? "running" : "stopped"
277
+ port_str = cfg['port'] ? ":#{cfg['port']}" : ""
278
+ puts format(" %-20s [%s]%s %s", name, status, port_str, cfg['base_dir'] || '')
279
+ end
280
+ end
281
+
282
+ h.add_subcmd(:list) do
283
+ h.run_subcmd(:ls)
284
+ end
285
+
286
+ h.add_subcmd(:start) do |svc_name=nil|
287
+ unless svc_name
288
+ puts "Usage: service start <name>"
289
+ next
290
+ end
291
+
292
+ tmux_info = {
293
+ session: ENV['TMUX'] ? `tmux display-message -p '#S'`.chomp : nil,
294
+ window: ENV['TMUX'] ? `tmux display-message -p '#I'`.chomp : nil,
295
+ pane: ENV['TMUX_PANE'],
296
+ }
297
+
298
+ task_info = {}
299
+ if task_manager
300
+ task = task_manager.current_task
301
+ if task
302
+ task_info = {
303
+ task_name: task.name,
304
+ tree: task.tree_name,
305
+ branch: task.branch,
306
+ session: task.session_name,
307
+ }
308
+ end
309
+ end
310
+
311
+ sm.start(svc_name, tmux_info: tmux_info, task_info: task_info)
312
+ end
313
+
314
+ h.add_subcmd(:stop) do |svc_name=nil|
315
+ unless svc_name
316
+ puts "Usage: service stop <name>"
317
+ next
318
+ end
319
+
320
+ sm.stop(svc_name)
321
+ end
322
+
323
+ h.add_subcmd(:attach) do |svc_name=nil|
324
+ unless svc_name
325
+ puts "Usage: service attach <name>"
326
+ next
327
+ end
328
+
329
+ sm.attach(svc_name)
330
+ end
331
+
332
+ h.add_subcmd(:open) do |svc_name=nil|
333
+ unless svc_name
334
+ puts "Usage: service open <name>"
335
+ next
336
+ end
337
+
338
+ svc_url = sm.url(svc_name)
339
+ if svc_url
340
+ system('open', svc_url)
341
+ else
342
+ puts "No URL configured for '#{svc_name}'"
343
+ end
344
+ end
345
+
346
+ h.add_subcmd(:url) do |svc_name=nil|
347
+ unless svc_name
348
+ puts "Usage: service url <name>"
349
+ next
350
+ end
351
+
352
+ svc_url = sm.url(svc_name)
353
+ if svc_url
354
+ puts svc_url
355
+ else
356
+ puts "No URL configured for '#{svc_name}'"
357
+ end
358
+ end
359
+
360
+ h.add_subcmd(:port) do |svc_name=nil|
361
+ unless svc_name
362
+ puts "Usage: service port <name>"
363
+ next
364
+ end
365
+
366
+ p = sm.port(svc_name)
367
+ if p
368
+ puts p
369
+ else
370
+ puts "No port configured for '#{svc_name}'"
371
+ end
372
+ end
373
+
374
+ h.add_subcmd(:status) do |svc_name=nil|
375
+ unless svc_name
376
+ puts "Usage: service status <name>"
377
+ next
378
+ end
379
+
380
+ sm.status(svc_name)
381
+ end
382
+
383
+ h.add_subcmd(:add) do |*add_args|
384
+ template = {
385
+ 'base_dir' => '',
386
+ 'host' => 'localhost',
387
+ 'port' => '',
388
+ 'init' => [],
389
+ 'start' => '',
390
+ 'stop' => '',
391
+ 'cleanup' => [],
392
+ }
393
+
394
+ require 'tempfile'
395
+ tmpfile = Tempfile.new(['service', '.yml'])
396
+ tmpfile.write(YAML.dump({ 'new_service' => template }))
397
+ tmpfile.close
398
+
399
+ editor = ENV['EDITOR'] || 'nvim'
400
+ system(editor, tmpfile.path)
401
+
402
+ begin
403
+ data = YAML.safe_load_file(tmpfile.path, permitted_classes: [Symbol]) || {}
404
+ data.each do |name, cfg|
405
+ sm.add_service({ 'name' => name }.merge(cfg || {}))
406
+ end
407
+ rescue => e
408
+ puts "Error parsing config: #{e.message}"
409
+ ensure
410
+ tmpfile.unlink
411
+ end
412
+ end
413
+
414
+ h.add_subcmd(:rm) do |svc_name=nil|
415
+ unless svc_name
416
+ puts "Usage: service rm <name>"
417
+ next
418
+ end
419
+
420
+ sm.remove_service(svc_name)
421
+ end
422
+
423
+ h.add_subcmd(:remove) do |svc_name=nil|
424
+ unless svc_name
425
+ puts "Usage: service remove <name>"
426
+ next
427
+ end
428
+
429
+ sm.remove_service(svc_name)
430
+ end
431
+
432
+ h.add_subcmd(:config) do
433
+ editor = ENV['EDITOR'] || 'nvim'
434
+ system(editor, sm.config_file)
435
+ end
436
+ end
437
+ end
438
+
439
+ private
440
+
441
+ def load_config
442
+ return {} unless File.exist?(config_file)
443
+ YAML.safe_load_file(config_file, permitted_classes: [Symbol]) || {}
444
+ end
445
+
446
+ def save_config(data)
447
+ FileUtils.mkdir_p(File.dirname(config_file))
448
+ File.write(config_file, YAML.dump(data))
449
+ end
450
+
451
+ def load_state
452
+ return {} unless File.exist?(state_file)
453
+ YAML.safe_load_file(state_file, permitted_classes: [Symbol]) || {}
454
+ end
455
+
456
+ def save_state(data)
457
+ FileUtils.mkdir_p(File.dirname(state_file))
458
+ File.write(state_file, YAML.dump(data))
459
+ end
460
+
461
+ def symbolize_keys(hash)
462
+ hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }
463
+ end
464
+ end
465
+ end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.146"
2
+ VERSION = "0.1.147"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -16,7 +16,7 @@ require_relative "hiiro/queue"
16
16
  require_relative "hiiro/tasks"
17
17
  require_relative "hiiro/tmux"
18
18
  require_relative "hiiro/todo"
19
- require_relative "hiiro/service"
19
+ require_relative "hiiro/service_manager"
20
20
  require_relative "hiiro/runner_tool"
21
21
  require_relative "hiiro/app_files"
22
22
 
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.146
4
+ version: 0.1.147
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -118,6 +118,7 @@ files:
118
118
  - h-tmux-plugins.tmux
119
119
  - hiiro.gemspec
120
120
  - lib/hiiro.rb
121
+ - lib/hiiro/app_files.rb
121
122
  - lib/hiiro/bins.rb
122
123
  - lib/hiiro/fuzzyfind.rb
123
124
  - lib/hiiro/git.rb
@@ -133,6 +134,8 @@ files:
133
134
  - lib/hiiro/options.rb
134
135
  - lib/hiiro/paths.rb
135
136
  - lib/hiiro/queue.rb
137
+ - lib/hiiro/runner_tool.rb
138
+ - lib/hiiro/service_manager.rb
136
139
  - lib/hiiro/shell.rb
137
140
  - lib/hiiro/tasks.rb
138
141
  - lib/hiiro/tmux.rb