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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +143 -0
- data/.gitignore +8 -0
- data/.gitmodules +3 -0
- data/Gemfile +17 -0
- data/README.adoc +295 -0
- data/Rakefile +8 -0
- data/lib/ukiryu/cli.rb +348 -0
- data/lib/ukiryu/errors.rb +30 -0
- data/lib/ukiryu/executor.rb +716 -0
- data/lib/ukiryu/platform.rb +93 -0
- data/lib/ukiryu/registry.rb +246 -0
- data/lib/ukiryu/schema_validator.rb +103 -0
- data/lib/ukiryu/shell/base.rb +79 -0
- data/lib/ukiryu/shell/bash.rb +60 -0
- data/lib/ukiryu/shell/cmd.rb +75 -0
- data/lib/ukiryu/shell/fish.rb +16 -0
- data/lib/ukiryu/shell/powershell.rb +60 -0
- data/lib/ukiryu/shell/sh.rb +16 -0
- data/lib/ukiryu/shell/zsh.rb +16 -0
- data/lib/ukiryu/shell.rb +164 -0
- data/lib/ukiryu/tool.rb +439 -0
- data/lib/ukiryu/type.rb +254 -0
- data/lib/ukiryu/version.rb +5 -0
- data/lib/ukiryu.rb +54 -0
- data/ukiryu.gemspec +33 -0
- metadata +72 -0
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
|