ukiryu 0.1.0

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.
data/lib/ukiryu/cli.rb ADDED
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "ukiryu"
5
+ require_relative "version"
6
+
7
+ module Ukiryu
8
+ # CLI for exploring and interacting with Ukiryu tool profiles
9
+ class Cli < Thor
10
+ package_name "ukiryu"
11
+
12
+ # Set default registry path if not configured
13
+ def self.exit_on_failure?
14
+ false
15
+ end
16
+
17
+ desc "list", "List all available tools in the registry"
18
+ method_option :registry, aliases: :r, desc: "Path to tool registry", type: :string
19
+ def list
20
+ setup_registry(options[:registry])
21
+
22
+ tools = Registry.tools
23
+ if tools.empty?
24
+ say "No tools found in registry", :red
25
+ return
26
+ end
27
+
28
+ say "Available tools (#{tools.count}):", :cyan
29
+ tools.sort.each do |name|
30
+ begin
31
+ tool = Tool.get(name)
32
+ version_info = tool.version ? "v#{tool.version}" : "version unknown"
33
+ available = tool.available? ? "[✓]" : "[✗]"
34
+ say " #{available.ljust(4)} #{name.ljust(20)} #{version_info}", tool.available? ? :green : :red
35
+ rescue Ukiryu::Error => e
36
+ say " [?] #{name.ljust(20)} error: #{e.message}", :red
37
+ end
38
+ end
39
+ end
40
+
41
+ desc "info TOOL", "Show detailed information about a tool"
42
+ method_option :registry, aliases: :r, desc: "Path to tool registry", type: :string
43
+ def info(tool_name)
44
+ setup_registry(options[:registry])
45
+
46
+ tool = Tool.get(tool_name)
47
+ profile = tool.profile
48
+
49
+ say "", :clear
50
+ say "Tool: #{profile[:name] || tool_name}", :cyan
51
+ say "Display Name: #{profile[:display_name] || 'N/A'}", :white
52
+ say "Version: #{profile[:version] || 'N/A'}", :white
53
+ say "Homepage: #{profile[:homepage] || 'N/A'}", :white
54
+
55
+ if profile[:aliases] && !profile[:aliases].empty?
56
+ say "Aliases: #{profile[:aliases].join(', ')}", :white
57
+ end
58
+
59
+ # Version detection
60
+ if profile[:version_detection]
61
+ vd = profile[:version_detection]
62
+ say "", :clear
63
+ say "Version Detection:", :yellow
64
+ say " Command: #{vd[:command]}", :white
65
+ say " Pattern: #{vd[:pattern]}", :white
66
+ if vd[:modern_threshold]
67
+ say " Modern Threshold: #{vd[:modern_threshold]}", :white
68
+ end
69
+ end
70
+
71
+ # Search paths
72
+ if profile[:search_paths]
73
+ say "", :clear
74
+ say "Search Paths:", :yellow
75
+ profile[:search_paths].each do |platform, paths|
76
+ next if paths.nil? || paths.empty?
77
+ say " #{platform}:", :white
78
+ Array(paths).each { |p| say " - #{p}", :white }
79
+ end
80
+ end
81
+
82
+ # Profiles
83
+ if profile[:profiles]
84
+ say "", :clear
85
+ say "Profiles (#{profile[:profiles].count}):", :yellow
86
+ profile[:profiles].each do |prof|
87
+ platforms = Array(prof[:platforms] || ['all']).join(', ')
88
+ shells = Array(prof[:shells] || ['all']).join(', ')
89
+ say " #{prof[:name] || 'unnamed'}:", :white
90
+ say " Platforms: #{platforms}", :white
91
+ say " Shells: #{shells}", :white
92
+ say " Version: #{prof[:version] || 'any'}", :white
93
+ end
94
+ end
95
+
96
+ # Availability
97
+ say "", :clear
98
+ if tool.available?
99
+ say "Status: INSTALLED", :green
100
+ say "Executable: #{tool.executable}", :white
101
+ say "Detected Version: #{tool.version || 'unknown'}", :white
102
+ else
103
+ say "Status: NOT FOUND", :red
104
+ end
105
+ end
106
+
107
+ desc "commands TOOL", "List all commands available for a tool"
108
+ method_option :registry, aliases: :r, desc: "Path to tool registry", type: :string
109
+ def commands(tool_name)
110
+ setup_registry(options[:registry])
111
+
112
+ tool = Tool.get(tool_name)
113
+ commands = tool.commands
114
+
115
+ unless commands
116
+ say "No commands defined for #{tool_name}", :red
117
+ return
118
+ end
119
+
120
+ say "Commands for #{tool_name}:", :cyan
121
+ commands.each do |cmd_name, cmd|
122
+ cmd_name = cmd_name || 'unnamed'
123
+ description = cmd[:description] || 'No description'
124
+ say " #{cmd_name.to_s.ljust(20)} #{description}", :white
125
+
126
+ # Show usage if available
127
+ if cmd[:usage]
128
+ say " Usage: #{cmd[:usage]}", :dim
129
+ end
130
+
131
+ # Show subcommand if exists
132
+ if cmd[:subcommand]
133
+ subcommand_info = cmd[:subcommand].nil? ? '(none)' : cmd[:subcommand]
134
+ say " Subcommand: #{subcommand_info}", :dim
135
+ end
136
+ end
137
+ end
138
+
139
+ desc "opts TOOL [COMMAND]", "Show options for a tool or specific command"
140
+ method_option :registry, aliases: :r, desc: "Path to tool registry", type: :string
141
+ def opts(tool_name, command_name = nil)
142
+ setup_registry(options[:registry])
143
+
144
+ tool = Tool.get(tool_name)
145
+ commands = tool.commands
146
+
147
+ unless commands
148
+ say "No commands defined for #{tool_name}", :red
149
+ return
150
+ end
151
+
152
+ # Find the command
153
+ cmds = if command_name
154
+ cmd = commands[command_name.to_sym] || commands[command_name]
155
+ cmd ? [cmd] : []
156
+ else
157
+ commands.values
158
+ end
159
+
160
+ cmds.each do |cmd|
161
+ cmd_title = command_name ? "#{tool_name} #{command_name}" : tool_name
162
+ say "", :clear
163
+ say "Options for #{cmd_title}:", :cyan
164
+ say "#{cmd[:description]}" if cmd[:description]
165
+
166
+ # Arguments
167
+ if cmd[:arguments] && !cmd[:arguments].empty?
168
+ say "", :clear
169
+ say "Arguments:", :yellow
170
+ cmd[:arguments].each do |arg|
171
+ name = arg[:name] || 'unnamed'
172
+ type = arg[:type] || 'unknown'
173
+ position = arg[:position] || 'default'
174
+ variadic = arg[:variadic] ? '(variadic)' : ''
175
+
176
+ say " #{name} (#{type}#{variadic})", :white
177
+ say " Position: #{position}", :dim if position != 'default'
178
+ say " Description: #{arg[:description]}", :dim if arg[:description]
179
+ end
180
+ end
181
+
182
+ # Options
183
+ if cmd[:options] && !cmd[:options].empty?
184
+ say "", :clear
185
+ say "Options:", :yellow
186
+ cmd[:options].each do |opt|
187
+ name = opt[:name] || 'unnamed'
188
+ cli = opt[:cli] || 'N/A'
189
+ type = opt[:type] || 'unknown'
190
+ description = opt[:description] || ''
191
+
192
+ say " --#{name.ljust(20)} #{cli}", :white
193
+ say " Type: #{type}", :dim
194
+ say " #{description}", :dim if description
195
+ if opt[:values]
196
+ say " Values: #{opt[:values].join(', ')}", :dim
197
+ end
198
+ if opt[:range]
199
+ say " Range: #{opt[:range].join('..')}", :dim
200
+ end
201
+ end
202
+ end
203
+
204
+ # Post-options (options between input and output)
205
+ if cmd[:post_options] && !cmd[:post_options].empty?
206
+ say "", :clear
207
+ say "Post-Options (between input and output):", :yellow
208
+ cmd[:post_options].each do |opt|
209
+ name = opt[:name] || 'unnamed'
210
+ cli = opt[:cli] || 'N/A'
211
+ type = opt[:type] || 'unknown'
212
+ description = opt[:description] || ''
213
+
214
+ say " --#{name.ljust(20)} #{cli}", :white
215
+ say " Type: #{type}", :dim
216
+ say " #{description}", :dim if description
217
+ end
218
+ end
219
+
220
+ # Flags
221
+ if cmd[:flags] && !cmd[:flags].empty?
222
+ say "", :clear
223
+ say "Flags:", :yellow
224
+ cmd[:flags].each do |flag|
225
+ name = flag[:name] || 'unnamed'
226
+ cli = flag[:cli] || 'N/A'
227
+ default = flag[:default]
228
+ default_str = default.nil? ? '' : " (default: #{default})"
229
+
230
+ say " #{cli.ljust(25)} #{name}#{default_str}", :white
231
+ say " #{flag[:description]}", :dim if flag[:description]
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ desc "execute TOOL COMMAND [OPTIONS]", "Execute a tool command with options"
238
+ method_option :registry, aliases: :r, desc: "Path to tool registry", type: :string
239
+ method_option :inputs, aliases: :i, desc: "Input files", type: :array
240
+ method_option :output, aliases: :o, desc: "Output file", type: :string
241
+ method_option :dry_run, aliases: :d, desc: "Show command without executing", type: :boolean, default: false
242
+ def execute(tool_name, command_name, **extra_opts)
243
+ setup_registry(options[:registry])
244
+
245
+ tool = Tool.get(tool_name)
246
+
247
+ # Build params from options
248
+ params = {}
249
+ params[:inputs] = options[:inputs] if options[:inputs]
250
+ params[:output] = options[:output] if options[:output]
251
+
252
+ # Add extra options as params
253
+ extra_opts.each do |key, value|
254
+ params[key.to_sym] = value
255
+ end
256
+
257
+ if options[:dry_run]
258
+ # Show what would be executed without actually running
259
+ say "DRY RUN - Would execute:", :yellow
260
+ say " Tool: #{tool_name}", :white
261
+ say " Command: #{command_name}", :white
262
+ say " Parameters:", :white
263
+ params.each do |k, v|
264
+ say " #{k}: #{v.inspect}", :dim
265
+ end
266
+ else
267
+ result = tool.execute(command_name.to_sym, params)
268
+
269
+ if result.success?
270
+ say "Command completed successfully", :green
271
+ say "Exit status: #{result.status}", :white
272
+ say "Duration: #{result.metadata.formatted_duration}", :white
273
+
274
+ if result.output.stdout && !result.output.stdout.empty?
275
+ say "", :clear
276
+ say "STDOUT:", :yellow
277
+ say result.output.stdout
278
+ end
279
+
280
+ if result.output.stderr && !result.output.stderr.empty?
281
+ say "", :clear
282
+ say "STDERR:", :yellow
283
+ say result.output.stderr
284
+ end
285
+ else
286
+ say "Command failed", :red
287
+ say "Exit status: #{result.status}", :white
288
+ say "Duration: #{result.metadata.formatted_duration}", :white
289
+
290
+ if result.output.stdout && !result.output.stdout.empty?
291
+ say "", :clear
292
+ say "STDOUT:", :yellow
293
+ say result.output.stdout
294
+ end
295
+
296
+ if result.output.stderr && !result.output.stderr.empty?
297
+ say "", :clear
298
+ say "STDERR:", :yellow
299
+ say result.output.stderr
300
+ end
301
+
302
+ exit 1
303
+ end
304
+ end
305
+ end
306
+
307
+ desc "version", "Show Ukiryu version"
308
+ def version
309
+ say "Ukiryu version #{Ukiryu::VERSION}", :cyan
310
+ end
311
+
312
+ private
313
+
314
+ def setup_registry(custom_path)
315
+ registry_path = custom_path ||
316
+ ENV['UKIRYU_REGISTRY'] ||
317
+ default_registry_path
318
+ if registry_path && Dir.exist?(registry_path)
319
+ Registry.default_registry_path = registry_path
320
+ end
321
+ end
322
+
323
+ def default_registry_path
324
+ # Try multiple approaches to find the registry
325
+ # 1. Check environment variable
326
+ env_path = ENV['UKIRYU_REGISTRY']
327
+ return env_path if env_path && Dir.exist?(env_path)
328
+
329
+ # 2. Try relative to gem location
330
+ # From lib/ukiryu/cli.rb, go up to gem root, then to sibling register/
331
+ gem_root = File.dirname(File.dirname(File.dirname(__FILE__)))
332
+ registry_path = File.join(gem_root, '..', 'register')
333
+ if Dir.exist?(registry_path)
334
+ return File.expand_path(registry_path)
335
+ end
336
+
337
+ # 3. Try from current directory (development setup)
338
+ current = File.expand_path('../register', Dir.pwd)
339
+ return current if Dir.exist?(current)
340
+
341
+ # 4. Try from parent directory
342
+ parent = File.expand_path('../../register', Dir.pwd)
343
+ return parent if Dir.exist?(parent)
344
+
345
+ nil
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ # Base error class
5
+ class Error < StandardError; end
6
+
7
+ # Shell detection errors
8
+ class UnknownShellError < Error; end
9
+
10
+ # Platform errors
11
+ class UnsupportedPlatformError < Error; end
12
+
13
+ # Type validation errors
14
+ class ValidationError < Error; end
15
+
16
+ # Profile errors
17
+ class ProfileNotFoundError < Error; end
18
+ class ProfileLoadError < Error; end
19
+
20
+ # Tool errors
21
+ class ToolNotFoundError < Error; end
22
+ class ExecutableNotFoundError < Error; end
23
+
24
+ # Execution errors
25
+ class ExecutionError < Error; end
26
+ class TimeoutError < Error; end
27
+
28
+ # Version errors
29
+ class VersionDetectionError < Error; end
30
+ end