ukiryu 0.1.1 → 0.1.3
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/release.yml +58 -14
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +170 -79
- data/Gemfile +1 -1
- data/README.adoc +1603 -576
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +261 -0
- data/docs/_config.yml +180 -0
- data/docs/advanced/custom-tool-classes.adoc +581 -0
- data/docs/advanced/index.adoc +20 -0
- data/docs/features/configuration.adoc +657 -0
- data/docs/features/index.adoc +31 -0
- data/docs/features/platform-support.adoc +488 -0
- data/docs/getting-started/core-concepts.adoc +666 -0
- data/docs/getting-started/index.adoc +36 -0
- data/docs/getting-started/installation.adoc +216 -0
- data/docs/getting-started/quick-start.adoc +258 -0
- data/docs/guides/env-var-sets.adoc +388 -0
- data/docs/guides/index.adoc +20 -0
- data/docs/interfaces/cli.adoc +609 -0
- data/docs/interfaces/index.adoc +153 -0
- data/docs/interfaces/ruby-api.adoc +538 -0
- data/docs/lychee.toml +49 -0
- data/docs/reference/configuration-options.adoc +720 -0
- data/docs/reference/error-codes.adoc +634 -0
- data/docs/reference/index.adoc +20 -0
- data/docs/reference/ruby-api.adoc +1217 -0
- data/docs/understanding/index.adoc +20 -0
- data/lib/ukiryu/cli.rb +43 -58
- data/lib/ukiryu/cli_commands/base_command.rb +16 -27
- data/lib/ukiryu/cli_commands/cache_command.rb +100 -0
- data/lib/ukiryu/cli_commands/commands_command.rb +8 -8
- data/lib/ukiryu/cli_commands/commands_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/config_command.rb +49 -7
- data/lib/ukiryu/cli_commands/definitions_command.rb +254 -0
- data/lib/ukiryu/cli_commands/describe_command.rb +13 -7
- data/lib/ukiryu/cli_commands/describe_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/docs_command.rb +148 -0
- data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/extract_command.rb +2 -2
- data/lib/ukiryu/cli_commands/info_command.rb +7 -7
- data/lib/ukiryu/cli_commands/lint_command.rb +167 -0
- data/lib/ukiryu/cli_commands/list_command.rb +6 -6
- data/lib/ukiryu/cli_commands/opts_command.rb +2 -2
- data/lib/ukiryu/cli_commands/opts_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/register_command.rb +144 -0
- data/lib/ukiryu/cli_commands/resolve_command.rb +124 -0
- data/lib/ukiryu/cli_commands/run_command.rb +38 -14
- data/lib/ukiryu/cli_commands/run_file_command.rb +2 -2
- data/lib/ukiryu/cli_commands/system_command.rb +50 -32
- data/lib/ukiryu/cli_commands/validate_command.rb +452 -51
- data/lib/ukiryu/cli_commands/which_command.rb +5 -5
- data/lib/ukiryu/command_builder.rb +81 -23
- data/lib/ukiryu/config/env_provider.rb +3 -3
- data/lib/ukiryu/config/env_schema.rb +6 -6
- data/lib/ukiryu/config.rb +11 -11
- data/lib/ukiryu/definition/definition_cache.rb +238 -0
- data/lib/ukiryu/definition/definition_composer.rb +257 -0
- data/lib/ukiryu/definition/definition_linter.rb +460 -0
- data/lib/ukiryu/definition/definition_validator.rb +320 -0
- data/lib/ukiryu/definition/discovery.rb +239 -0
- data/lib/ukiryu/definition/documentation_generator.rb +429 -0
- data/lib/ukiryu/definition/lint_issue.rb +168 -0
- data/lib/ukiryu/definition/loader.rb +139 -0
- data/lib/ukiryu/definition/metadata.rb +159 -0
- data/lib/ukiryu/definition/source.rb +87 -0
- data/lib/ukiryu/definition/sources/file.rb +138 -0
- data/lib/ukiryu/definition/sources/string.rb +88 -0
- data/lib/ukiryu/definition/validation_result.rb +158 -0
- data/lib/ukiryu/definition/version_resolver.rb +194 -0
- data/lib/ukiryu/definition.rb +40 -0
- data/lib/ukiryu/errors.rb +6 -0
- data/lib/ukiryu/execution_context.rb +11 -11
- data/lib/ukiryu/executor.rb +6 -0
- data/lib/ukiryu/extractors/extractor.rb +6 -5
- data/lib/ukiryu/extractors/help_parser.rb +13 -19
- data/lib/ukiryu/logger.rb +3 -1
- data/lib/ukiryu/models/command_definition.rb +3 -3
- data/lib/ukiryu/models/command_info.rb +1 -1
- data/lib/ukiryu/models/components.rb +1 -3
- data/lib/ukiryu/models/env_var_definition.rb +11 -3
- data/lib/ukiryu/models/flag_definition.rb +15 -0
- data/lib/ukiryu/models/option_definition.rb +7 -7
- data/lib/ukiryu/models/platform_profile.rb +6 -3
- data/lib/ukiryu/models/routing.rb +1 -1
- data/lib/ukiryu/models/tool_definition.rb +2 -4
- data/lib/ukiryu/models/tool_metadata.rb +6 -6
- data/lib/ukiryu/models/validation_result.rb +1 -1
- data/lib/ukiryu/models/version_compatibility.rb +6 -3
- data/lib/ukiryu/models/version_detection.rb +10 -1
- data/lib/ukiryu/{registry.rb → register.rb} +54 -38
- data/lib/ukiryu/register_auto_manager.rb +268 -0
- data/lib/ukiryu/schema_validator.rb +31 -10
- data/lib/ukiryu/shell/base.rb +18 -0
- data/lib/ukiryu/shell/bash.rb +19 -1
- data/lib/ukiryu/shell/cmd.rb +11 -1
- data/lib/ukiryu/shell/powershell.rb +11 -1
- data/lib/ukiryu/shell.rb +1 -1
- data/lib/ukiryu/tool.rb +107 -95
- data/lib/ukiryu/tool_index.rb +22 -22
- data/lib/ukiryu/tools/base.rb +12 -25
- data/lib/ukiryu/tools/generator.rb +7 -7
- data/lib/ukiryu/tools.rb +3 -3
- data/lib/ukiryu/type.rb +20 -5
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu/version_detector.rb +21 -2
- data/lib/ukiryu.rb +6 -3
- data/ukiryu-proposal.md +41 -41
- data/ukiryu.gemspec +1 -0
- metadata +64 -8
- data/.gitmodules +0 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative '../definition/definition_linter'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Lint tool definitions for best practices
|
|
9
|
+
#
|
|
10
|
+
# The lint command checks tool definitions for best practices,
|
|
11
|
+
# deprecated patterns, and potential issues.
|
|
12
|
+
class LintCommand < Thor
|
|
13
|
+
class_option :verbose, type: :boolean, default: false
|
|
14
|
+
class_option :format, type: :string, default: 'text', enum: %w[text json]
|
|
15
|
+
class_option :severity, type: :string, enum: %w[error warning info style], desc: 'Minimum severity level'
|
|
16
|
+
|
|
17
|
+
desc 'file PATH', 'Lint a definition file'
|
|
18
|
+
def file(path)
|
|
19
|
+
lint_file(path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'all', 'Lint all definitions in register'
|
|
23
|
+
option :register, type: :string, desc: 'Register path'
|
|
24
|
+
def all
|
|
25
|
+
lint_all
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc 'rules', 'List all linting rules'
|
|
29
|
+
def rules
|
|
30
|
+
list_rules
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Lint a single file
|
|
36
|
+
#
|
|
37
|
+
# @param path [String] file path
|
|
38
|
+
def lint_file(path)
|
|
39
|
+
result = Ukiryu::Definition::DefinitionLinter.lint_file(path)
|
|
40
|
+
|
|
41
|
+
output_result(result, path)
|
|
42
|
+
|
|
43
|
+
# Exit with error if there are errors
|
|
44
|
+
exit 1 if result.has_errors?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Lint all definitions in register
|
|
48
|
+
def lint_all
|
|
49
|
+
register_path = options[:register] || Ukiryu::Register.default_register_path
|
|
50
|
+
return say_error("Register not found: #{register_path}") unless Dir.exist?(register_path)
|
|
51
|
+
|
|
52
|
+
tools_dir = File.join(register_path, 'tools')
|
|
53
|
+
return say_error("Tools directory not found: #{tools_dir}") unless Dir.exist?(tools_dir)
|
|
54
|
+
|
|
55
|
+
results = {}
|
|
56
|
+
total_issues = 0
|
|
57
|
+
|
|
58
|
+
Dir.glob(File.join(tools_dir, '*', '*', '*.yaml')).each do |file|
|
|
59
|
+
result = Ukiryu::Definition::DefinitionLinter.lint_file(file)
|
|
60
|
+
results[file] = result
|
|
61
|
+
total_issues += result.count
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Summary
|
|
65
|
+
total_files = results.length
|
|
66
|
+
total_errors = results.values.sum(&:error_count)
|
|
67
|
+
total_warnings = results.values.sum { |r| r.count_by_severity(Ukiryu::Definition::LintIssue::SEVERITY_WARNING) }
|
|
68
|
+
|
|
69
|
+
say "\nLint Summary:", :cyan
|
|
70
|
+
say " Files: #{total_files}", :white
|
|
71
|
+
say " Issues: #{total_issues}", total_issues.zero? ? :green : :white
|
|
72
|
+
say " Errors: #{total_errors}", total_errors.zero? ? :green : :red
|
|
73
|
+
say " Warnings: #{total_warnings}", :white
|
|
74
|
+
|
|
75
|
+
# Show files with issues
|
|
76
|
+
if total_issues.positive?
|
|
77
|
+
say '', :clear
|
|
78
|
+
say 'Files with Issues:', :yellow
|
|
79
|
+
|
|
80
|
+
results.each do |file, result|
|
|
81
|
+
next unless result.has_issues?
|
|
82
|
+
|
|
83
|
+
status = result.has_errors? ? '✗' : '⚠'
|
|
84
|
+
say " #{status} #{file} (#{result.count} issues)", :white
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
exit 1 if total_errors.positive?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# List all linting rules
|
|
92
|
+
def list_rules
|
|
93
|
+
say 'Linting Rules:', :cyan
|
|
94
|
+
say '', :clear
|
|
95
|
+
|
|
96
|
+
say 'Naming Convention Rules:', :white
|
|
97
|
+
say ' naming_tool_name_format - Tool name format', :dim
|
|
98
|
+
say ' naming_command_name_format - Command name format', :dim
|
|
99
|
+
say '', :clear
|
|
100
|
+
|
|
101
|
+
say 'Completeness Rules:', :white
|
|
102
|
+
say ' complete_missing_description - Missing description', :dim
|
|
103
|
+
say ' complete_missing_homepage - Missing homepage', :dim
|
|
104
|
+
say ' complete_missing_version_detection - Missing version detection', :dim
|
|
105
|
+
say '', :clear
|
|
106
|
+
|
|
107
|
+
say 'Security Rules:', :white
|
|
108
|
+
say ' security_suspicious_subcommand - Dangerous shell commands', :dim
|
|
109
|
+
say ' security_unvalidated_input - Arguments without type validation', :dim
|
|
110
|
+
say '', :clear
|
|
111
|
+
|
|
112
|
+
say 'Best Practice Rules:', :white
|
|
113
|
+
say ' style_redundant_default_profile - Redundant default profile', :dim
|
|
114
|
+
say ' complete_missing_platforms - Missing platforms specification', :dim
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Output linting result
|
|
118
|
+
#
|
|
119
|
+
# @param result [LintResult] linting result
|
|
120
|
+
# @param source [String] source identifier
|
|
121
|
+
def output_result(result, source)
|
|
122
|
+
case options[:format]
|
|
123
|
+
when 'json'
|
|
124
|
+
say result.to_h.to_json, :white
|
|
125
|
+
else
|
|
126
|
+
output_text(result, source)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Output as text
|
|
131
|
+
#
|
|
132
|
+
# @param result [LintResult] linting result
|
|
133
|
+
# @param source [String] source identifier
|
|
134
|
+
def output_text(result, source)
|
|
135
|
+
say "Linting: #{source}", :cyan
|
|
136
|
+
|
|
137
|
+
if result.has_issues?
|
|
138
|
+
say "\nFound #{result.count} issue(s):", :yellow
|
|
139
|
+
|
|
140
|
+
{
|
|
141
|
+
Ukiryu::Definition::LintIssue::SEVERITY_ERROR => 'Errors',
|
|
142
|
+
Ukiryu::Definition::LintIssue::SEVERITY_WARNING => 'Warnings',
|
|
143
|
+
Ukiryu::Definition::LintIssue::SEVERITY_INFO => 'Info',
|
|
144
|
+
Ukiryu::Definition::LintIssue::SEVERITY_STYLE => 'Style'
|
|
145
|
+
}.each do |severity, label|
|
|
146
|
+
issues = result.by_severity(severity)
|
|
147
|
+
next if issues.empty?
|
|
148
|
+
|
|
149
|
+
say '', :clear
|
|
150
|
+
say "#{label}:", severity == :error ? :red : :white
|
|
151
|
+
issues.each { |issue| say " #{issue}", :white }
|
|
152
|
+
end
|
|
153
|
+
else
|
|
154
|
+
say "\n✓ No issues found", :green
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Show error message
|
|
159
|
+
#
|
|
160
|
+
# @param message [String] error message
|
|
161
|
+
def say_error(message)
|
|
162
|
+
say message, :red
|
|
163
|
+
exit 1
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base_command'
|
|
4
4
|
require_relative '../tool'
|
|
5
|
-
require_relative '../
|
|
5
|
+
require_relative '../register'
|
|
6
6
|
|
|
7
7
|
module Ukiryu
|
|
8
8
|
module CliCommands
|
|
9
|
-
# List all available tools in the
|
|
9
|
+
# List all available tools in the register
|
|
10
10
|
class ListCommand < BaseCommand
|
|
11
11
|
# Execute the list command
|
|
12
12
|
def run
|
|
13
|
-
|
|
13
|
+
setup_register
|
|
14
14
|
|
|
15
|
-
tools =
|
|
16
|
-
error! 'No tools found in
|
|
15
|
+
tools = Register.tools
|
|
16
|
+
error! 'No tools found in register' if tools.empty?
|
|
17
17
|
|
|
18
18
|
say "Available tools (#{tools.count}):", :cyan
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ module Ukiryu
|
|
|
22
22
|
standalone_tools = []
|
|
23
23
|
|
|
24
24
|
tools.sort.each do |name|
|
|
25
|
-
metadata =
|
|
25
|
+
metadata = Register.load_tool_metadata(name.to_sym)
|
|
26
26
|
|
|
27
27
|
if metadata&.implements
|
|
28
28
|
# This tool implements an interface
|
|
@@ -12,11 +12,11 @@ module Ukiryu
|
|
|
12
12
|
# @param tool_name [String] the tool name
|
|
13
13
|
# @param command_name [String, nil] optional command name
|
|
14
14
|
def run(tool_name, command_name = nil)
|
|
15
|
-
|
|
15
|
+
setup_register
|
|
16
16
|
|
|
17
17
|
# Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
|
|
18
18
|
tool = Tool.find_by(tool_name.to_sym)
|
|
19
|
-
error!("Tool not found: #{tool_name}\nAvailable tools: #{
|
|
19
|
+
error!("Tool not found: #{tool_name}\nAvailable tools: #{Register.tools.sort.join(', ')}") unless tool
|
|
20
20
|
|
|
21
21
|
tool_commands = tool.commands
|
|
22
22
|
error! "No commands defined for #{tool_name}" unless tool_commands
|
|
@@ -12,7 +12,7 @@ module Ukiryu
|
|
|
12
12
|
# @param tool_name [String] the tool name
|
|
13
13
|
# @param command_name [String, nil] optional command name
|
|
14
14
|
def run(tool_name, command_name = nil)
|
|
15
|
-
|
|
15
|
+
setup_register
|
|
16
16
|
|
|
17
17
|
tool = Tool.get(tool_name)
|
|
18
18
|
tool_commands = tool.commands
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../register_auto_manager'
|
|
4
|
+
require_relative '../register'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Command to manage the register
|
|
9
|
+
class RegisterCommand < BaseCommand
|
|
10
|
+
# Run the register command
|
|
11
|
+
#
|
|
12
|
+
# @param subcommand [String, nil] the subcommand (info, update, etc.)
|
|
13
|
+
# @param options [Hash] command options
|
|
14
|
+
# @option options [Boolean] :force force re-clone
|
|
15
|
+
# @option options [Boolean] :verbose show verbose output
|
|
16
|
+
def run(subcommand = nil, options = {})
|
|
17
|
+
case subcommand
|
|
18
|
+
when 'info', nil
|
|
19
|
+
show_info(options)
|
|
20
|
+
when 'update'
|
|
21
|
+
update_register(options)
|
|
22
|
+
when 'path'
|
|
23
|
+
show_path
|
|
24
|
+
else
|
|
25
|
+
error!("Unknown subcommand: #{subcommand}. Valid subcommands: info, update, path")
|
|
26
|
+
end
|
|
27
|
+
rescue RegisterAutoManager::RegisterError => e
|
|
28
|
+
error!("Register error: #{e.message}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Show register information
|
|
34
|
+
#
|
|
35
|
+
# @param options [Hash] command options
|
|
36
|
+
def show_info(_options = {})
|
|
37
|
+
info = RegisterAutoManager.register_info
|
|
38
|
+
|
|
39
|
+
say 'Register Information', :cyan
|
|
40
|
+
say ''
|
|
41
|
+
|
|
42
|
+
case info[:status]
|
|
43
|
+
when :not_found
|
|
44
|
+
say ' Status: Not configured', :red
|
|
45
|
+
say ''
|
|
46
|
+
say ' No register found. Run: ukiryu register update', :yellow
|
|
47
|
+
when :not_cloned
|
|
48
|
+
say ' Status: Not cloned', :yellow
|
|
49
|
+
say " Expected path: #{info[:path]}", :dim
|
|
50
|
+
say ''
|
|
51
|
+
say ' Run: ukiryu register update', :yellow
|
|
52
|
+
when :invalid
|
|
53
|
+
say ' Status: Invalid', :red
|
|
54
|
+
say " Path: #{info[:path]}", :dim
|
|
55
|
+
say ''
|
|
56
|
+
say ' Register is corrupted. Run: ukiryu register update --force', :yellow
|
|
57
|
+
when :ok
|
|
58
|
+
say ' Status: OK', :green
|
|
59
|
+
say " Path: #{info[:path]}", :dim
|
|
60
|
+
say " Source: #{format_source(info[:source])}", :dim
|
|
61
|
+
|
|
62
|
+
say " Tools available: #{info[:tools_count]}", :dim if info[:tools_count]
|
|
63
|
+
|
|
64
|
+
say " Branch: #{info[:branch]}", :dim if info[:branch]
|
|
65
|
+
|
|
66
|
+
say " Commit: #{info[:commit]}", :dim if info[:commit]
|
|
67
|
+
|
|
68
|
+
say " Last updated: #{info[:last_update].strftime('%Y-%m-%d %H:%M:%S')}", :dim if info[:last_update]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
say ''
|
|
72
|
+
say 'Environment variable:', :cyan
|
|
73
|
+
env_path = ENV['UKIRYU_REGISTER']
|
|
74
|
+
if env_path
|
|
75
|
+
say " UKIRYU_REGISTER=#{env_path}", :dim
|
|
76
|
+
else
|
|
77
|
+
say ' UKIRYU_REGISTER (not set)', :dim
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
show_manual_setup_help if info[:status] != :ok
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Update the register
|
|
84
|
+
#
|
|
85
|
+
# @param options [Hash] command options
|
|
86
|
+
def update_register(options = {})
|
|
87
|
+
force = options[:force] || false
|
|
88
|
+
|
|
89
|
+
if force
|
|
90
|
+
say 'Force re-cloning register...', :yellow
|
|
91
|
+
else
|
|
92
|
+
say 'Updating register...', :cyan
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
RegisterAutoManager.update_register(force: force)
|
|
96
|
+
|
|
97
|
+
say 'Register updated successfully!', :green
|
|
98
|
+
show_info(options)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Show the register path
|
|
102
|
+
def show_path
|
|
103
|
+
path = RegisterAutoManager.register_path
|
|
104
|
+
|
|
105
|
+
if path
|
|
106
|
+
say path
|
|
107
|
+
else
|
|
108
|
+
error!('Register not available. Run: ukiryu register update')
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Format the source for display
|
|
113
|
+
#
|
|
114
|
+
# @param source [Symbol] the source symbol
|
|
115
|
+
# @return [String] formatted source
|
|
116
|
+
def format_source(source)
|
|
117
|
+
case source
|
|
118
|
+
when :env
|
|
119
|
+
'Environment variable (UKIRYU_REGISTER)'
|
|
120
|
+
when :dev
|
|
121
|
+
'Development mode (local submodule)'
|
|
122
|
+
when :user
|
|
123
|
+
'User local clone (~/.ukiryu/register)'
|
|
124
|
+
else
|
|
125
|
+
source.to_s
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Show manual setup help
|
|
130
|
+
def show_manual_setup_help
|
|
131
|
+
say ''
|
|
132
|
+
say 'Manual setup:', :cyan
|
|
133
|
+
say ''
|
|
134
|
+
say ' 1. Clone the register:'
|
|
135
|
+
say " git clone #{RegisterAutoManager::REGISTER_URL} ~/.ukiryu/register"
|
|
136
|
+
say ''
|
|
137
|
+
say ' 2. Or set environment variable:'
|
|
138
|
+
say ' export UKIRYU_REGISTER=/path/to/register'
|
|
139
|
+
say ''
|
|
140
|
+
say ' 3. Then run this command again.'
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../definition/discovery'
|
|
5
|
+
require_relative '../definition/version_resolver'
|
|
6
|
+
|
|
7
|
+
module Ukiryu
|
|
8
|
+
module CliCommands
|
|
9
|
+
# Resolve tool definitions
|
|
10
|
+
#
|
|
11
|
+
# The resolve command shows which definition would be used for a tool.
|
|
12
|
+
class ResolveCommand < BaseCommand
|
|
13
|
+
# Execute the resolve command
|
|
14
|
+
#
|
|
15
|
+
# @param tool_name [String] the tool name to resolve
|
|
16
|
+
# @param version_constraint [String, nil] optional version constraint
|
|
17
|
+
def run(tool_name, version_constraint = nil)
|
|
18
|
+
if tool_name.nil?
|
|
19
|
+
say 'Error: Tool name is required', :red
|
|
20
|
+
say '', :clear
|
|
21
|
+
say 'Usage: ukiryu resolve TOOL [VERSION_CONSTRAINT]', :white
|
|
22
|
+
exit 1
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Resolve the definition
|
|
26
|
+
resolve_definition(tool_name, version_constraint)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Resolve which definition would be used
|
|
32
|
+
#
|
|
33
|
+
# @param tool_name [String] the tool name
|
|
34
|
+
# @param version_constraint [String, nil] optional version constraint
|
|
35
|
+
def resolve_definition(tool_name, version_constraint)
|
|
36
|
+
# Get all available definitions for the tool
|
|
37
|
+
definitions = Ukiryu::Definition::Discovery.definitions_for(tool_name)
|
|
38
|
+
|
|
39
|
+
if definitions.empty?
|
|
40
|
+
say "Resolution for: #{tool_name}", :cyan
|
|
41
|
+
say '', :clear
|
|
42
|
+
say "✗ No definitions found for '#{tool_name}'", :red
|
|
43
|
+
say '', :clear
|
|
44
|
+
say 'To see available tools:', :cyan
|
|
45
|
+
say ' ukiryu list', :white
|
|
46
|
+
say '', :clear
|
|
47
|
+
say 'To search for definitions:', :cyan
|
|
48
|
+
say ' ukiryu definitions list', :white
|
|
49
|
+
exit 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
say "Resolution for: #{tool_name}", :cyan
|
|
53
|
+
say '', :clear
|
|
54
|
+
|
|
55
|
+
# Show all available definitions
|
|
56
|
+
say "Available Definitions (#{definitions.size}):", :white
|
|
57
|
+
definitions.each do |metadata|
|
|
58
|
+
priority_icon = priority_icon(metadata.priority)
|
|
59
|
+
say " #{priority_icon} #{metadata.version} (#{metadata.source_type})", :white
|
|
60
|
+
say " Path: #{metadata.path}", :dim
|
|
61
|
+
say " Mtime: #{metadata.mtime}", :dim
|
|
62
|
+
say " Priority: #{metadata.priority}", :dim
|
|
63
|
+
say '', :clear
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Determine which definition would be used
|
|
67
|
+
if version_constraint
|
|
68
|
+
# Resolve with version constraint
|
|
69
|
+
available_versions = definitions.map(&:version)
|
|
70
|
+
selected_version = Ukiryu::Definition::VersionResolver.resolve(
|
|
71
|
+
version_constraint,
|
|
72
|
+
available_versions
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if selected_version
|
|
76
|
+
selected_metadata = definitions.find { |d| d.version == selected_version }
|
|
77
|
+
say "Selected Definition (constraint: #{version_constraint}):", :cyan
|
|
78
|
+
say " ✓ #{selected_metadata.name}/#{selected_metadata.version}", :green
|
|
79
|
+
say " Source: #{selected_metadata.source_type}", :white
|
|
80
|
+
say " Path: #{selected_metadata.path}", :white
|
|
81
|
+
else
|
|
82
|
+
say "✗ No version satisfies constraint: #{version_constraint}", :red
|
|
83
|
+
say '', :clear
|
|
84
|
+
say 'Available versions:', :white
|
|
85
|
+
available_versions.each do |v|
|
|
86
|
+
say " - #{v}", :dim
|
|
87
|
+
end
|
|
88
|
+
exit 1
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
# Use highest priority definition
|
|
92
|
+
selected_metadata = definitions.first
|
|
93
|
+
|
|
94
|
+
say 'Selected Definition (highest priority):', :cyan
|
|
95
|
+
say " ✓ #{selected_metadata.name}/#{selected_metadata.version}", :green
|
|
96
|
+
say " Source: #{selected_metadata.source_type}", :white
|
|
97
|
+
say " Path: #{selected_metadata.path}", :white
|
|
98
|
+
say " Priority: #{selected_metadata.priority}", :white
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Check if definition exists and is valid
|
|
102
|
+
return unless selected_metadata && !selected_metadata.exists?
|
|
103
|
+
|
|
104
|
+
say '', :clear
|
|
105
|
+
say '⚠ Warning: Definition file does not exist!', :yellow
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Get priority icon
|
|
109
|
+
#
|
|
110
|
+
# @param priority [Integer] the priority value
|
|
111
|
+
# @return [String] icon character
|
|
112
|
+
def priority_icon(priority)
|
|
113
|
+
case priority
|
|
114
|
+
when 1 then '★' # User
|
|
115
|
+
when 2 then '◆' # Bundled
|
|
116
|
+
when 3 then '■' # Local system
|
|
117
|
+
when 4 then '□' # System
|
|
118
|
+
when 5 then '△' # Register
|
|
119
|
+
else '·'
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -22,7 +22,7 @@ module Ukiryu
|
|
|
22
22
|
# @param command_name [String, nil] the command name (optional, uses default if nil)
|
|
23
23
|
# @param params [Array<String>] key=value parameter pairs
|
|
24
24
|
def run(tool_name, command_name = nil, *params)
|
|
25
|
-
|
|
25
|
+
setup_register
|
|
26
26
|
|
|
27
27
|
# Handle the case where command_name is omitted and first param is a key=value pair
|
|
28
28
|
# When user types: ukiryu exec-inline ping host=127.0.0.1
|
|
@@ -80,7 +80,7 @@ module Ukiryu
|
|
|
80
80
|
debug: config.debug,
|
|
81
81
|
dry_run: config.dry_run,
|
|
82
82
|
output: config.output,
|
|
83
|
-
|
|
83
|
+
register: config.register,
|
|
84
84
|
stdin: !arguments[:stdin].nil?
|
|
85
85
|
}
|
|
86
86
|
logger.debug_section_ukiryu_options(ukiryu_options)
|
|
@@ -119,13 +119,19 @@ module Ukiryu
|
|
|
119
119
|
# @param tool_name [String] the tool name
|
|
120
120
|
# @return [String] the resolved command name
|
|
121
121
|
def resolve_default_command(tool_name)
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
# If --definition is provided, load from definition file
|
|
123
|
+
if options[:definition]
|
|
124
|
+
tool = Tool.load(options[:definition], validation: :strict)
|
|
125
|
+
metadata = tool.profile
|
|
126
|
+
else
|
|
127
|
+
# Use Register to load tool metadata without full resolution
|
|
128
|
+
# This avoids triggering debug output for "Tool Resolution" twice
|
|
129
|
+
require_relative '../register'
|
|
130
|
+
require_relative '../models/tool_metadata'
|
|
126
131
|
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
metadata = Register.load_tool_metadata(tool_name.to_sym, register_path: config.register)
|
|
133
|
+
error! "Tool not found: #{tool_name}" unless metadata
|
|
134
|
+
end
|
|
129
135
|
|
|
130
136
|
# Get the default command (checks YAML default_command, then implements, then tool name)
|
|
131
137
|
command = metadata.default_command
|
|
@@ -194,8 +200,20 @@ module Ukiryu
|
|
|
194
200
|
# Stage: Tool Resolution
|
|
195
201
|
execution_report.tool_resolution.start! if collect_metrics
|
|
196
202
|
|
|
197
|
-
#
|
|
198
|
-
|
|
203
|
+
# Load tool from definition file if --definition option provided
|
|
204
|
+
if options[:definition]
|
|
205
|
+
tool = Tool.load(options[:definition], validation: :strict)
|
|
206
|
+
# Verify that the tool name matches (if user specified one)
|
|
207
|
+
if tool_name && tool.name.to_sym != tool_name.to_sym
|
|
208
|
+
return Models::ErrorResponse.from_message(
|
|
209
|
+
"Tool name mismatch: definition file contains '#{tool.name}' but command specified '#{tool_name}'"
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
else
|
|
213
|
+
# Get tool - try find_by first for interface-based discovery, fallback to get
|
|
214
|
+
tool = Tool.find_by(tool_name.to_sym) || Tool.get(tool_name.to_sym)
|
|
215
|
+
end
|
|
216
|
+
|
|
199
217
|
return Models::ErrorResponse.from_message("Tool not available: #{tool_name}") unless tool
|
|
200
218
|
|
|
201
219
|
return Models::ErrorResponse.from_message("Tool found but not executable: #{tool_name}") unless tool.available?
|
|
@@ -324,11 +342,17 @@ module Ukiryu
|
|
|
324
342
|
# @param tool_name [String] the tool name
|
|
325
343
|
# @param params [Array<String>] additional parameters
|
|
326
344
|
def show_tool_help(tool_name, _params = [])
|
|
327
|
-
|
|
345
|
+
setup_register
|
|
346
|
+
|
|
347
|
+
# Load tool from definition file if --definition option provided
|
|
348
|
+
tool = if options[:definition]
|
|
349
|
+
Tool.load(options[:definition], validation: :strict)
|
|
350
|
+
else
|
|
351
|
+
# Use find_by for interface-based discovery
|
|
352
|
+
Tool.find_by(tool_name.to_sym)
|
|
353
|
+
end
|
|
328
354
|
|
|
329
|
-
#
|
|
330
|
-
tool = Tool.find_by(tool_name.to_sym)
|
|
331
|
-
error! "Tool not found: #{tool_name}\nAvailable tools: #{Registry.tools.sort.join(', ')}" unless tool
|
|
355
|
+
error! "Tool not found: #{tool_name}\nAvailable tools: #{Register.tools.sort.join(', ')}" unless tool
|
|
332
356
|
|
|
333
357
|
say '', :clear
|
|
334
358
|
say "Tool: #{tool.name}", :cyan
|
|
@@ -20,7 +20,7 @@ module Ukiryu
|
|
|
20
20
|
#
|
|
21
21
|
# @param request_file [String] path to the request YAML file
|
|
22
22
|
def run(request_file)
|
|
23
|
-
|
|
23
|
+
setup_register
|
|
24
24
|
|
|
25
25
|
# Output debug: Ukiryu CLI Options
|
|
26
26
|
if config.debug
|
|
@@ -30,7 +30,7 @@ module Ukiryu
|
|
|
30
30
|
debug: config.debug,
|
|
31
31
|
dry_run: config.dry_run,
|
|
32
32
|
output: config.output,
|
|
33
|
-
|
|
33
|
+
register: config.register,
|
|
34
34
|
request_file: request_file
|
|
35
35
|
}
|
|
36
36
|
logger.debug_section_ukiryu_options(ukiryu_options)
|