ukiryu 0.1.0 → 0.1.1
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/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +99 -0
- data/.github/workflows/rake.yml +19 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +18 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +213 -0
- data/Gemfile +12 -8
- data/README.adoc +613 -0
- data/Rakefile +2 -2
- data/docs/assets/logo.svg +1 -0
- data/exe/ukiryu +11 -0
- data/lib/ukiryu/action/base.rb +77 -0
- data/lib/ukiryu/cache.rb +199 -0
- data/lib/ukiryu/cli.rb +133 -307
- data/lib/ukiryu/cli_commands/base_command.rb +155 -0
- data/lib/ukiryu/cli_commands/commands_command.rb +120 -0
- data/lib/ukiryu/cli_commands/commands_command.rb.fixed +40 -0
- data/lib/ukiryu/cli_commands/config_command.rb +249 -0
- data/lib/ukiryu/cli_commands/describe_command.rb +326 -0
- data/lib/ukiryu/cli_commands/describe_command.rb.fixed +254 -0
- data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +180 -0
- data/lib/ukiryu/cli_commands/extract_command.rb +84 -0
- data/lib/ukiryu/cli_commands/info_command.rb +156 -0
- data/lib/ukiryu/cli_commands/list_command.rb +70 -0
- data/lib/ukiryu/cli_commands/opts_command.rb +106 -0
- data/lib/ukiryu/cli_commands/opts_command.rb.fixed +105 -0
- data/lib/ukiryu/cli_commands/response_formatter.rb +240 -0
- data/lib/ukiryu/cli_commands/run_command.rb +375 -0
- data/lib/ukiryu/cli_commands/run_file_command.rb +215 -0
- data/lib/ukiryu/cli_commands/system_command.rb +90 -0
- data/lib/ukiryu/cli_commands/validate_command.rb +87 -0
- data/lib/ukiryu/cli_commands/version_command.rb +16 -0
- data/lib/ukiryu/cli_commands/which_command.rb +166 -0
- data/lib/ukiryu/command_builder.rb +205 -0
- data/lib/ukiryu/config/env_provider.rb +64 -0
- data/lib/ukiryu/config/env_schema.rb +63 -0
- data/lib/ukiryu/config/override_resolver.rb +68 -0
- data/lib/ukiryu/config/type_converter.rb +59 -0
- data/lib/ukiryu/config.rb +249 -0
- data/lib/ukiryu/errors.rb +3 -0
- data/lib/ukiryu/executable_locator.rb +114 -0
- data/lib/ukiryu/execution/command_info.rb +64 -0
- data/lib/ukiryu/execution/metadata.rb +97 -0
- data/lib/ukiryu/execution/output.rb +144 -0
- data/lib/ukiryu/execution/result.rb +194 -0
- data/lib/ukiryu/execution.rb +15 -0
- data/lib/ukiryu/execution_context.rb +251 -0
- data/lib/ukiryu/executor.rb +76 -493
- data/lib/ukiryu/extractors/base_extractor.rb +63 -0
- data/lib/ukiryu/extractors/extractor.rb +150 -0
- data/lib/ukiryu/extractors/help_parser.rb +188 -0
- data/lib/ukiryu/extractors/native_extractor.rb +47 -0
- data/lib/ukiryu/io.rb +196 -0
- data/lib/ukiryu/logger.rb +544 -0
- data/lib/ukiryu/models/argument.rb +28 -0
- data/lib/ukiryu/models/argument_definition.rb +119 -0
- data/lib/ukiryu/models/arguments.rb +113 -0
- data/lib/ukiryu/models/command_definition.rb +176 -0
- data/lib/ukiryu/models/command_info.rb +37 -0
- data/lib/ukiryu/models/components.rb +107 -0
- data/lib/ukiryu/models/env_var_definition.rb +30 -0
- data/lib/ukiryu/models/error_response.rb +41 -0
- data/lib/ukiryu/models/execution_metadata.rb +31 -0
- data/lib/ukiryu/models/execution_report.rb +236 -0
- data/lib/ukiryu/models/exit_codes.rb +74 -0
- data/lib/ukiryu/models/flag_definition.rb +67 -0
- data/lib/ukiryu/models/option_definition.rb +102 -0
- data/lib/ukiryu/models/output_info.rb +25 -0
- data/lib/ukiryu/models/platform_profile.rb +153 -0
- data/lib/ukiryu/models/routing.rb +211 -0
- data/lib/ukiryu/models/search_paths.rb +39 -0
- data/lib/ukiryu/models/success_response.rb +85 -0
- data/lib/ukiryu/models/tool_definition.rb +145 -0
- data/lib/ukiryu/models/tool_metadata.rb +82 -0
- data/lib/ukiryu/models/validation_result.rb +80 -0
- data/lib/ukiryu/models/version_compatibility.rb +152 -0
- data/lib/ukiryu/models/version_detection.rb +39 -0
- data/lib/ukiryu/models.rb +23 -0
- data/lib/ukiryu/options/base.rb +95 -0
- data/lib/ukiryu/options_builder/formatter.rb +87 -0
- data/lib/ukiryu/options_builder/validator.rb +43 -0
- data/lib/ukiryu/options_builder.rb +311 -0
- data/lib/ukiryu/platform.rb +6 -6
- data/lib/ukiryu/registry.rb +143 -183
- data/lib/ukiryu/response/base.rb +217 -0
- data/lib/ukiryu/runtime.rb +179 -0
- data/lib/ukiryu/schema_validator.rb +8 -10
- data/lib/ukiryu/shell/bash.rb +3 -3
- data/lib/ukiryu/shell/cmd.rb +4 -4
- data/lib/ukiryu/shell/fish.rb +1 -1
- data/lib/ukiryu/shell/powershell.rb +3 -3
- data/lib/ukiryu/shell/sh.rb +1 -1
- data/lib/ukiryu/shell/zsh.rb +1 -1
- data/lib/ukiryu/shell.rb +146 -39
- data/lib/ukiryu/thor_ext.rb +208 -0
- data/lib/ukiryu/tool.rb +649 -258
- data/lib/ukiryu/tool_index.rb +224 -0
- data/lib/ukiryu/tools/base.rb +381 -0
- data/lib/ukiryu/tools/class_generator.rb +132 -0
- data/lib/ukiryu/tools/executable_finder.rb +29 -0
- data/lib/ukiryu/tools/generator.rb +154 -0
- data/lib/ukiryu/tools.rb +109 -0
- data/lib/ukiryu/type.rb +28 -43
- data/lib/ukiryu/validation/constraints.rb +281 -0
- data/lib/ukiryu/validation/validator.rb +188 -0
- data/lib/ukiryu/validation.rb +21 -0
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu/version_detector.rb +51 -0
- data/lib/ukiryu.rb +31 -15
- data/ukiryu-proposal.md +2952 -0
- data/ukiryu.gemspec +18 -14
- metadata +137 -5
- data/.github/workflows/test.yml +0 -143
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
require_relative '../executor'
|
|
6
|
+
require 'yaml'
|
|
7
|
+
|
|
8
|
+
module Ukiryu
|
|
9
|
+
module CliCommands
|
|
10
|
+
# Execute a tool command inline (shorthand for run)
|
|
11
|
+
class ExecInlineCommand < BaseCommand
|
|
12
|
+
# Supported output formats
|
|
13
|
+
OUTPUT_FORMATS = %i[yaml json].freeze
|
|
14
|
+
|
|
15
|
+
# Execute the command
|
|
16
|
+
#
|
|
17
|
+
# @param tool_name [String] the tool name
|
|
18
|
+
# @param command_name [String] the command name
|
|
19
|
+
# @param params [Array<String>] key=value parameter pairs
|
|
20
|
+
def run(tool_name, command_name, *params)
|
|
21
|
+
setup_registry
|
|
22
|
+
|
|
23
|
+
# Parse key=value pairs into arguments hash
|
|
24
|
+
arguments = parse_inline_params(params)
|
|
25
|
+
|
|
26
|
+
# Build execution request
|
|
27
|
+
request = {
|
|
28
|
+
'tool' => tool_name,
|
|
29
|
+
'command' => command_name,
|
|
30
|
+
'arguments' => arguments
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Validate format option
|
|
34
|
+
format = (options[:format] || 'yaml').to_sym
|
|
35
|
+
error! "Invalid format: #{options[:format]}. Must be one of: #{OUTPUT_FORMATS.join(', ')}" unless OUTPUT_FORMATS.include?(format)
|
|
36
|
+
|
|
37
|
+
if options[:dry_run]
|
|
38
|
+
# Show dry run output
|
|
39
|
+
say_dry_run(request)
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Execute the request
|
|
44
|
+
response = execute_request(request)
|
|
45
|
+
|
|
46
|
+
# Output response
|
|
47
|
+
output_response(response, format, nil)
|
|
48
|
+
|
|
49
|
+
# Exit with error code if command failed
|
|
50
|
+
exit_code = response['exit_code'].to_i
|
|
51
|
+
exit(exit_code) if exit_code != 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Parse inline key=value params into a hash
|
|
57
|
+
def parse_inline_params(params_array)
|
|
58
|
+
arguments = {}
|
|
59
|
+
|
|
60
|
+
params_array.each do |param|
|
|
61
|
+
if param.include?('=')
|
|
62
|
+
key, value = param.split('=', 2)
|
|
63
|
+
|
|
64
|
+
# Try to parse value as YAML to handle types properly
|
|
65
|
+
begin
|
|
66
|
+
parsed_value = YAML.safe_load(value, permitted_classes: [Symbol])
|
|
67
|
+
value = parsed_value
|
|
68
|
+
rescue StandardError
|
|
69
|
+
# Keep as string if YAML parsing fails
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Convert key to symbol for consistency with API
|
|
73
|
+
arguments[key.to_sym] = value
|
|
74
|
+
else
|
|
75
|
+
error! "Invalid parameter format: #{param}. Use key=value"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
arguments
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Execute the request and build response
|
|
83
|
+
def execute_request(request)
|
|
84
|
+
tool_name = request['tool']
|
|
85
|
+
command_name = request['command']
|
|
86
|
+
arguments = stringify_keys(request['arguments'])
|
|
87
|
+
|
|
88
|
+
begin
|
|
89
|
+
# Get tool
|
|
90
|
+
tool = Tool.get(tool_name.to_sym)
|
|
91
|
+
return build_error_response("Tool not available: #{tool_name}") unless tool.available?
|
|
92
|
+
|
|
93
|
+
# Build options object (OOP approach)
|
|
94
|
+
options_class = tool.options_for(command_name.to_sym)
|
|
95
|
+
options = options_class.new
|
|
96
|
+
arguments.each { |key, value| options.send("#{key}=", value) }
|
|
97
|
+
|
|
98
|
+
# Execute command
|
|
99
|
+
result = tool.execute(command_name.to_sym, options)
|
|
100
|
+
|
|
101
|
+
# Build successful response
|
|
102
|
+
build_success_response(result)
|
|
103
|
+
rescue Ukiryu::ToolNotFoundError => e
|
|
104
|
+
build_error_response("Tool not found: #{e.message}")
|
|
105
|
+
rescue Ukiryu::ProfileNotFoundError => e
|
|
106
|
+
build_error_response("Profile not found: #{e.message}")
|
|
107
|
+
rescue Ukiryu::ExecutionError => e
|
|
108
|
+
build_error_response(e.message)
|
|
109
|
+
rescue Ukiryu::TimeoutError => e
|
|
110
|
+
build_error_response("Command timed out: #{e.message}")
|
|
111
|
+
rescue ArgumentError => e
|
|
112
|
+
build_error_response("Invalid arguments: #{e.message}")
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
build_error_response("Unexpected error: #{e.class}: #{e.message}")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Build success response from Result object
|
|
119
|
+
def build_success_response(result)
|
|
120
|
+
{
|
|
121
|
+
'status' => 'success',
|
|
122
|
+
'exit_code' => result.status,
|
|
123
|
+
'command' => {
|
|
124
|
+
'executable' => result.executable,
|
|
125
|
+
'arguments' => result.command_info.arguments,
|
|
126
|
+
'full_command' => result.command_info.full_command,
|
|
127
|
+
'shell' => result.command_info.shell.to_s
|
|
128
|
+
},
|
|
129
|
+
'output' => {
|
|
130
|
+
'stdout' => result.stdout,
|
|
131
|
+
'stderr' => result.error_output,
|
|
132
|
+
'stdout_lines' => result.stdout_lines,
|
|
133
|
+
'stderr_lines' => result.stderr_lines
|
|
134
|
+
},
|
|
135
|
+
'metadata' => {
|
|
136
|
+
'started_at' => result.started_at.iso8601,
|
|
137
|
+
'finished_at' => result.finished_at.iso8601,
|
|
138
|
+
'duration_seconds' => result.duration,
|
|
139
|
+
'formatted_duration' => result.metadata.formatted_duration
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Build error response
|
|
145
|
+
def build_error_response(message)
|
|
146
|
+
{
|
|
147
|
+
'status' => 'error',
|
|
148
|
+
'exit_code' => 1,
|
|
149
|
+
'error' => message
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Output response in specified format
|
|
154
|
+
def output_response(response, format, _output_file)
|
|
155
|
+
output_string = case format
|
|
156
|
+
when :yaml
|
|
157
|
+
response.to_yaml
|
|
158
|
+
when :json
|
|
159
|
+
require 'json'
|
|
160
|
+
JSON.pretty_generate(response)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
say output_string
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Show dry run output
|
|
167
|
+
def say_dry_run(request)
|
|
168
|
+
say 'DRY RUN - Ukiryu Structured Execution Request:', :yellow
|
|
169
|
+
say '', :clear
|
|
170
|
+
say "Tool: #{request['tool']}", :cyan
|
|
171
|
+
say "Command: #{request['command']}", :cyan
|
|
172
|
+
say 'Arguments:', :cyan
|
|
173
|
+
request['arguments'].each do |key, value|
|
|
174
|
+
say " #{key}: #{value.inspect}", :white
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../extractors/extractor'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Extract tool definition from an installed CLI tool
|
|
9
|
+
#
|
|
10
|
+
# This command attempts to extract a tool definition by:
|
|
11
|
+
# 1. Trying the tool's native --ukiryu-definition flag
|
|
12
|
+
# 2. Parsing the tool's --help output as a fallback
|
|
13
|
+
class ExtractCommand < BaseCommand
|
|
14
|
+
# Execute the extract command
|
|
15
|
+
#
|
|
16
|
+
# @param tool_name [String] the tool name to extract definition from
|
|
17
|
+
def run(tool_name)
|
|
18
|
+
result = Extractors::Extractor.extract(tool_name, extract_options)
|
|
19
|
+
|
|
20
|
+
if result[:success]
|
|
21
|
+
output_result(result)
|
|
22
|
+
else
|
|
23
|
+
handle_failure(result)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Build extraction options from CLI options
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash] extraction options
|
|
32
|
+
def extract_options
|
|
33
|
+
{
|
|
34
|
+
method: options[:method]&.to_sym || :auto,
|
|
35
|
+
verbose: options[:verbose]
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Output the extracted definition
|
|
40
|
+
#
|
|
41
|
+
# @param result [Hash] extraction result
|
|
42
|
+
def output_result(result)
|
|
43
|
+
yaml_content = result[:yaml]
|
|
44
|
+
|
|
45
|
+
# Write to file if output option specified
|
|
46
|
+
if options[:output]
|
|
47
|
+
File.write(options[:output], yaml_content)
|
|
48
|
+
say "Definition extracted to: #{options[:output]}", :green
|
|
49
|
+
say "Method: #{result[:method]}", :cyan if options[:verbose]
|
|
50
|
+
else
|
|
51
|
+
# Output to stdout
|
|
52
|
+
puts yaml_content
|
|
53
|
+
say "\n# Extracted using: #{result[:method]}", :cyan if options[:verbose]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Handle extraction failure
|
|
58
|
+
#
|
|
59
|
+
# @param result [Hash] extraction result
|
|
60
|
+
def handle_failure(result)
|
|
61
|
+
say "Failed to extract definition from '#{@tool_name}'", :red
|
|
62
|
+
say "Error: #{result[:error]}", :red
|
|
63
|
+
say '', :clear
|
|
64
|
+
say 'The tool may not be installed or may not support extraction.', :yellow
|
|
65
|
+
say '', :clear
|
|
66
|
+
say 'Extraction methods tried:', :yellow
|
|
67
|
+
|
|
68
|
+
if options[:method] && options[:method] != 'auto'
|
|
69
|
+
say " - #{options[:method]} (explicitly selected)", :white
|
|
70
|
+
else
|
|
71
|
+
say " - native (try --ukiryu-definition flag)", :white
|
|
72
|
+
say " - help (parse --help output)", :white
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
say '', :clear
|
|
76
|
+
say 'You can specify a method with --method:', :yellow
|
|
77
|
+
say ' ukiryu extract TOOL --method native', :white
|
|
78
|
+
say ' ukiryu extract TOOL --method help', :white
|
|
79
|
+
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Show detailed information about a tool
|
|
9
|
+
class InfoCommand < BaseCommand
|
|
10
|
+
# Execute the info command
|
|
11
|
+
#
|
|
12
|
+
# @param tool_name [String] the tool name
|
|
13
|
+
def run(tool_name)
|
|
14
|
+
setup_registry
|
|
15
|
+
|
|
16
|
+
# Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
|
|
17
|
+
tool = Tool.find_by(tool_name.to_sym)
|
|
18
|
+
error!("Tool not found: #{tool_name}\nAvailable tools: #{Registry.tools.sort.join(', ')}") unless tool
|
|
19
|
+
|
|
20
|
+
profile = tool.profile
|
|
21
|
+
show_all = options[:all]
|
|
22
|
+
|
|
23
|
+
say '', :clear
|
|
24
|
+
|
|
25
|
+
# Show interface information if queried name differs from actual tool name
|
|
26
|
+
if profile.name != tool_name.to_s && profile.implements
|
|
27
|
+
say "Interface: #{tool_name}", :cyan
|
|
28
|
+
say " This tool implements the '#{tool_name}' interface", :dim
|
|
29
|
+
say " Tool: #{profile.name}", :white
|
|
30
|
+
|
|
31
|
+
# Find other implementations of this interface
|
|
32
|
+
other_implementations = find_other_implementations(tool_name.to_s, profile.name)
|
|
33
|
+
say " Other implementations: #{other_implementations.join(', ')}", :dim if other_implementations.any?
|
|
34
|
+
|
|
35
|
+
if show_all
|
|
36
|
+
say '', :clear
|
|
37
|
+
say "All '#{tool_name}' implementations:", :yellow
|
|
38
|
+
all_implementations = [profile.name, *other_implementations].sort
|
|
39
|
+
all_implementations.each do |impl|
|
|
40
|
+
impl_tool = Tool.get(impl)
|
|
41
|
+
if impl_tool
|
|
42
|
+
status = impl_tool.available? ? '[✓]' : '[✗]'
|
|
43
|
+
color = impl_tool.available? ? :green : :red
|
|
44
|
+
say " #{status.ljust(4)} #{impl}", color
|
|
45
|
+
else
|
|
46
|
+
say " [?] #{impl}", :white
|
|
47
|
+
end
|
|
48
|
+
rescue Ukiryu::ToolNotFoundError, Ukiryu::ProfileNotFoundError
|
|
49
|
+
# Tool exists but no compatible profile for this platform
|
|
50
|
+
say " [ ] #{impl}", :dim
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
say "Tool: #{profile.name || tool_name}", :cyan
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
say "Display Name: #{profile.display_name || 'N/A'}", :white
|
|
58
|
+
say "Version: #{profile.version || 'N/A'}", :white
|
|
59
|
+
say "Homepage: #{profile.homepage || 'N/A'}", :white
|
|
60
|
+
|
|
61
|
+
say "Aliases: #{profile.aliases.join(', ')}", :white if profile.aliases && !profile.aliases.empty?
|
|
62
|
+
|
|
63
|
+
# Version detection
|
|
64
|
+
if profile.version_detection
|
|
65
|
+
vd = profile.version_detection
|
|
66
|
+
say '', :clear
|
|
67
|
+
say 'Version Detection:', :yellow
|
|
68
|
+
command_display = vd.command.is_a?(Array) ? vd.command.join(' ') : vd.command
|
|
69
|
+
say " Command: #{command_display}", :white
|
|
70
|
+
say " Pattern: #{vd.pattern}", :white
|
|
71
|
+
say " Modern Threshold: #{vd.modern_threshold}", :white if vd.modern_threshold
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Search paths
|
|
75
|
+
if profile.search_paths
|
|
76
|
+
say '', :clear
|
|
77
|
+
say 'Search Paths:', :yellow
|
|
78
|
+
search_paths = profile.search_paths
|
|
79
|
+
|
|
80
|
+
# Iterate over platform attributes
|
|
81
|
+
%i[macos linux windows freebsd openbsd netbsd].each do |platform|
|
|
82
|
+
paths = search_paths.send(platform)
|
|
83
|
+
next if paths.nil? || paths.empty?
|
|
84
|
+
|
|
85
|
+
say " #{platform}:", :white
|
|
86
|
+
Array(paths).each { |p| say " - #{p}", :white }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Profiles
|
|
91
|
+
if profile.profiles
|
|
92
|
+
say '', :clear
|
|
93
|
+
say "Profiles (#{profile.profiles.count}):", :yellow
|
|
94
|
+
profile.profiles.each do |prof|
|
|
95
|
+
platforms = Array(prof.platforms || ['all']).join(', ')
|
|
96
|
+
shells = Array(prof.shells || ['all']).join(', ')
|
|
97
|
+
option_style = prof.option_style || 'default'
|
|
98
|
+
say " #{prof.name || 'unnamed'}:", :white
|
|
99
|
+
say " Platforms: #{platforms}", :white
|
|
100
|
+
say " Shells: #{shells}", :white
|
|
101
|
+
say " Option Style: #{option_style}", :white
|
|
102
|
+
say " Inherits: #{prof.inherits || 'none'}", :white if prof.inherits
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Availability
|
|
107
|
+
say '', :clear
|
|
108
|
+
if tool.available?
|
|
109
|
+
say 'Status: INSTALLED', :green
|
|
110
|
+
say "Executable: #{tool.executable}", :white
|
|
111
|
+
say "Detected Version: #{tool.version || 'unknown'}", :white
|
|
112
|
+
else
|
|
113
|
+
say 'Status: NOT FOUND', :red
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
# Find other tools that implement the same interface
|
|
120
|
+
#
|
|
121
|
+
# @param interface_name [String] the interface name
|
|
122
|
+
# @param current_tool_name [String] the current tool name to exclude
|
|
123
|
+
# @return [Array<String>] list of other tool names
|
|
124
|
+
def find_other_implementations(interface_name, current_tool_name)
|
|
125
|
+
require_relative '../registry'
|
|
126
|
+
|
|
127
|
+
implementations = []
|
|
128
|
+
interface_sym = interface_name.to_sym
|
|
129
|
+
|
|
130
|
+
if config.debug
|
|
131
|
+
say " [DEBUG] Looking for tools implementing '#{interface_name}' (excluding '#{current_tool_name}')", :dim
|
|
132
|
+
say " [DEBUG] Registry tools: #{Registry.tools.inspect}", :dim
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Registry.tools.each do |tool_name|
|
|
136
|
+
next if tool_name == current_tool_name
|
|
137
|
+
|
|
138
|
+
begin
|
|
139
|
+
# Don't pass registry_path - let it use the default
|
|
140
|
+
tool_metadata = Registry.load_tool_metadata(tool_name.to_sym)
|
|
141
|
+
if config.debug
|
|
142
|
+
say " [DEBUG] #{tool_name} -> metadata: #{tool_metadata ? tool_metadata.implements : 'nil'}", :dim
|
|
143
|
+
end
|
|
144
|
+
implementations << tool_name if tool_metadata && tool_metadata.implements == interface_sym
|
|
145
|
+
rescue StandardError => e
|
|
146
|
+
# Skip tools that fail to load
|
|
147
|
+
say " [DEBUG] Failed to load #{tool_name}: #{e.message}", :dim if config.debug
|
|
148
|
+
next
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
implementations.sort
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
require_relative '../registry'
|
|
6
|
+
|
|
7
|
+
module Ukiryu
|
|
8
|
+
module CliCommands
|
|
9
|
+
# List all available tools in the registry
|
|
10
|
+
class ListCommand < BaseCommand
|
|
11
|
+
# Execute the list command
|
|
12
|
+
def run
|
|
13
|
+
setup_registry
|
|
14
|
+
|
|
15
|
+
tools = Registry.tools
|
|
16
|
+
error! 'No tools found in registry' if tools.empty?
|
|
17
|
+
|
|
18
|
+
say "Available tools (#{tools.count}):", :cyan
|
|
19
|
+
|
|
20
|
+
# Separate tools into interfaces and standalone tools
|
|
21
|
+
interfaces = {}
|
|
22
|
+
standalone_tools = []
|
|
23
|
+
|
|
24
|
+
tools.sort.each do |name|
|
|
25
|
+
metadata = Registry.load_tool_metadata(name.to_sym)
|
|
26
|
+
|
|
27
|
+
if metadata&.implements
|
|
28
|
+
# This tool implements an interface
|
|
29
|
+
interface_name = metadata.implements.to_s
|
|
30
|
+
interfaces[interface_name] ||= []
|
|
31
|
+
interfaces[interface_name] << name
|
|
32
|
+
elsif metadata&.aliases&.any?
|
|
33
|
+
# Tool has aliases but doesn't implement interface - treat as standalone
|
|
34
|
+
standalone_tools << name
|
|
35
|
+
else
|
|
36
|
+
# Regular standalone tool
|
|
37
|
+
standalone_tools << name
|
|
38
|
+
end
|
|
39
|
+
rescue Ukiryu::Error
|
|
40
|
+
# If we can't load metadata, treat as standalone
|
|
41
|
+
standalone_tools << name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Display interfaces first
|
|
45
|
+
interfaces.sort.each do |interface_name, impls|
|
|
46
|
+
say " #{interface_name}:", :cyan
|
|
47
|
+
|
|
48
|
+
impls.sort.each do |impl_name|
|
|
49
|
+
tool = Tool.get(impl_name)
|
|
50
|
+
version_info = tool.version ? "v#{tool.version}" : 'version unknown'
|
|
51
|
+
available = tool.available? ? '[✓]' : '[✗]'
|
|
52
|
+
say " #{available.ljust(4)} #{impl_name.ljust(20)} #{version_info}", tool.available? ? :green : :red
|
|
53
|
+
rescue Ukiryu::Error => e
|
|
54
|
+
say " [?] #{impl_name.ljust(20)} error: #{e.message}", :red
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Display standalone tools
|
|
59
|
+
standalone_tools.sort.each do |name|
|
|
60
|
+
tool = Tool.get(name)
|
|
61
|
+
version_info = tool.version ? "v#{tool.version}" : 'version unknown'
|
|
62
|
+
available = tool.available? ? '[✓]' : '[✗]'
|
|
63
|
+
say " #{available.ljust(4)} #{name.ljust(20)} #{version_info}", tool.available? ? :green : :red
|
|
64
|
+
rescue Ukiryu::Error => e
|
|
65
|
+
say " [?] #{name.ljust(20)} error: #{e.message}", :red
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Show options for a tool or specific command
|
|
9
|
+
class OptsCommand < BaseCommand
|
|
10
|
+
# Execute the opts command
|
|
11
|
+
#
|
|
12
|
+
# @param tool_name [String] the tool name
|
|
13
|
+
# @param command_name [String, nil] optional command name
|
|
14
|
+
def run(tool_name, command_name = nil)
|
|
15
|
+
setup_registry
|
|
16
|
+
|
|
17
|
+
# Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
|
|
18
|
+
tool = Tool.find_by(tool_name.to_sym)
|
|
19
|
+
error!("Tool not found: #{tool_name}\nAvailable tools: #{Registry.tools.sort.join(', ')}") unless tool
|
|
20
|
+
|
|
21
|
+
tool_commands = tool.commands
|
|
22
|
+
error! "No commands defined for #{tool_name}" unless tool_commands
|
|
23
|
+
|
|
24
|
+
# Find the command
|
|
25
|
+
cmds = if command_name
|
|
26
|
+
tool_commands.find { |c| c.name.to_s == command_name.to_s || c.name.to_sym == command_name.to_sym }
|
|
27
|
+
cmds ? [cmds] : []
|
|
28
|
+
else
|
|
29
|
+
tool_commands
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
cmds.each do |cmd|
|
|
33
|
+
cmd_title = command_name ? "#{tool_name} #{command_name}" : tool_name
|
|
34
|
+
say '', :clear
|
|
35
|
+
say "Options for #{cmd_title}:", :cyan
|
|
36
|
+
say cmd.description.to_s if cmd.description
|
|
37
|
+
|
|
38
|
+
# Arguments
|
|
39
|
+
if cmd.arguments && !cmd.arguments.empty?
|
|
40
|
+
say '', :clear
|
|
41
|
+
say 'Arguments:', :yellow
|
|
42
|
+
cmd.arguments.each do |arg|
|
|
43
|
+
name = arg.name || 'unnamed'
|
|
44
|
+
type = arg.type || 'unknown'
|
|
45
|
+
position = arg.position || 'default'
|
|
46
|
+
variadic = arg.variadic ? '(variadic)' : ''
|
|
47
|
+
|
|
48
|
+
say " #{name} (#{type}#{variadic})", :white
|
|
49
|
+
say " Position: #{position}", :dim if position != 'default'
|
|
50
|
+
say " Description: #{arg.description}", :dim if arg.description
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Options
|
|
55
|
+
if cmd.options && !cmd.options.empty?
|
|
56
|
+
say '', :clear
|
|
57
|
+
say 'Options:', :yellow
|
|
58
|
+
cmd.options.each do |opt|
|
|
59
|
+
name = opt.name || 'unnamed'
|
|
60
|
+
cli = opt.cli || 'N/A'
|
|
61
|
+
type = opt.type || 'unknown'
|
|
62
|
+
description = opt.description || ''
|
|
63
|
+
|
|
64
|
+
say " --#{name.ljust(20)} #{cli}", :white
|
|
65
|
+
say " Type: #{type}", :dim
|
|
66
|
+
say " #{description}", :dim if description
|
|
67
|
+
say " Values: #{opt.values.join(', ')}", :dim if opt.values
|
|
68
|
+
say " Range: #{opt.range.join('..')}", :dim if opt.range
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Post-options (options between input and output)
|
|
73
|
+
if cmd.post_options && !cmd.post_options.empty?
|
|
74
|
+
say '', :clear
|
|
75
|
+
say 'Post-Options (between input and output):', :yellow
|
|
76
|
+
cmd.post_options.each do |opt|
|
|
77
|
+
name = opt.name || 'unnamed'
|
|
78
|
+
cli = opt.cli || 'N/A'
|
|
79
|
+
type = opt.type || 'unknown'
|
|
80
|
+
description = opt.description || ''
|
|
81
|
+
|
|
82
|
+
say " --#{name.ljust(20)} #{cli}", :white
|
|
83
|
+
say " Type: #{type}", :dim
|
|
84
|
+
say " #{description}", :dim if description
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Flags
|
|
89
|
+
next unless cmd.flags && !cmd.flags.empty?
|
|
90
|
+
|
|
91
|
+
say '', :clear
|
|
92
|
+
say 'Flags:', :yellow
|
|
93
|
+
cmd.flags.each do |flag|
|
|
94
|
+
name = flag.name || 'unnamed'
|
|
95
|
+
cli = flag.cli || 'N/A'
|
|
96
|
+
default = flag.default
|
|
97
|
+
default_str = default.nil? ? '' : " (default: #{default})"
|
|
98
|
+
|
|
99
|
+
say " #{cli.ljust(25)} #{name}#{default_str}", :white
|
|
100
|
+
say " #{flag.description}", :dim if flag.description
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|