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
|
@@ -28,51 +28,46 @@ module Ukiryu
|
|
|
28
28
|
require_relative '../shell'
|
|
29
29
|
|
|
30
30
|
all_shells = Shell.all_valid
|
|
31
|
-
available_shells = Shell.available_shells
|
|
32
31
|
platform_shells = Shell.valid_for_platform
|
|
32
|
+
available_shells = platform_shells.select { |shell| Shell.available?(shell) }
|
|
33
|
+
not_installed_shells = platform_shells.reject { |shell| Shell.available?(shell) }
|
|
34
|
+
not_supported_shells = all_shells - platform_shells
|
|
33
35
|
|
|
34
|
-
say 'Available
|
|
36
|
+
say 'Available shells', :cyan
|
|
37
|
+
say ''
|
|
38
|
+
say ' The following shells are installed and supported on this platform:'
|
|
35
39
|
say ''
|
|
36
40
|
|
|
37
41
|
if available_shells.empty?
|
|
38
|
-
say '
|
|
42
|
+
say ' No supported shells detected', :dim
|
|
39
43
|
else
|
|
40
44
|
available_shells.each do |shell|
|
|
41
|
-
|
|
42
|
-
say " #{status} #{shell}", :green
|
|
45
|
+
say " • #{shell_name_with_description(shell)}", :green
|
|
43
46
|
end
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
is_platform = platform_shells.include?(shell)
|
|
49
|
+
if not_installed_shells.any?
|
|
50
|
+
say ''
|
|
51
|
+
say 'Additional supported shells', :cyan
|
|
52
|
+
say ''
|
|
53
|
+
say ' These shells are supported but not currently installed:'
|
|
54
|
+
say ''
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
else
|
|
59
|
-
'-'
|
|
60
|
-
end
|
|
56
|
+
not_installed_shells.each do |shell|
|
|
57
|
+
say " • #{shell_name_with_description(shell)}", :dim
|
|
58
|
+
end
|
|
59
|
+
end
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
' (not supported on this platform)'
|
|
69
|
-
elsif !is_available
|
|
70
|
-
' (supported but not found)'
|
|
71
|
-
else
|
|
72
|
-
''
|
|
73
|
-
end
|
|
61
|
+
if not_supported_shells.any?
|
|
62
|
+
say ''
|
|
63
|
+
say 'Not available on this platform', :cyan
|
|
64
|
+
say ''
|
|
65
|
+
say ' These shells are not supported on this platform:'
|
|
66
|
+
say ''
|
|
74
67
|
|
|
75
|
-
|
|
68
|
+
not_supported_shells.each do |shell|
|
|
69
|
+
say " • #{shell_name_with_description(shell)}", :dim
|
|
70
|
+
end
|
|
76
71
|
end
|
|
77
72
|
|
|
78
73
|
say ''
|
|
@@ -85,6 +80,29 @@ module Ukiryu
|
|
|
85
80
|
|
|
86
81
|
say "Shell override: #{config_shell} (set via --shell, UKIRYU_SHELL, or config)", :yellow
|
|
87
82
|
end
|
|
83
|
+
|
|
84
|
+
# Get shell name with brief description
|
|
85
|
+
#
|
|
86
|
+
# @param shell_sym [Symbol] the shell symbol
|
|
87
|
+
# @return [String] formatted name with description
|
|
88
|
+
def shell_name_with_description(shell_sym)
|
|
89
|
+
case shell_sym
|
|
90
|
+
when :bash
|
|
91
|
+
'bash - GNU Bourne Again SHell'
|
|
92
|
+
when :zsh
|
|
93
|
+
'zsh - Z shell'
|
|
94
|
+
when :fish
|
|
95
|
+
'fish - Friendly interactive shell'
|
|
96
|
+
when :sh
|
|
97
|
+
'sh - POSIX shell'
|
|
98
|
+
when :powershell
|
|
99
|
+
'powershell - PowerShell command-line shell'
|
|
100
|
+
when :cmd
|
|
101
|
+
'cmd - Windows Command Prompt'
|
|
102
|
+
else
|
|
103
|
+
shell_sym.to_s
|
|
104
|
+
end
|
|
105
|
+
end
|
|
88
106
|
end
|
|
89
107
|
end
|
|
90
108
|
end
|
|
@@ -1,86 +1,487 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require_relative '../
|
|
5
|
-
require_relative '../
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative '../definition/definition_validator'
|
|
5
|
+
require_relative '../tool'
|
|
6
6
|
|
|
7
7
|
module Ukiryu
|
|
8
8
|
module CliCommands
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
# Validate tool definitions
|
|
10
|
+
#
|
|
11
|
+
# The validate command checks tool definitions against JSON Schema
|
|
12
|
+
# and structural validation rules.
|
|
13
|
+
class ValidateCommand < Thor
|
|
14
|
+
class_option :verbose, type: :boolean, default: false
|
|
15
|
+
class_option :format, type: :string, default: 'text', enum: %w[text json]
|
|
16
|
+
class_option :schema, type: :string, desc: 'Path to JSON Schema file'
|
|
17
|
+
class_option :strict, type: :boolean, default: false, desc: 'Treat warnings as errors'
|
|
18
|
+
class_option :executable, type: :boolean, default: false, desc: 'Test executable against actual tool'
|
|
19
|
+
class_option :all, type: :boolean, default: false, desc: 'Enable all validations (schema + executable + smoke tests)'
|
|
20
|
+
class_option :register, type: :string, desc: 'Path to tool register'
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
desc 'file PATH', 'Validate a definition file'
|
|
23
|
+
def file(path)
|
|
24
|
+
validate_file(path)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc 'all', 'Validate all definitions in register'
|
|
28
|
+
option :register, type: :string, desc: 'Register path'
|
|
29
|
+
def all
|
|
30
|
+
validate_all
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc 'string YAML', 'Validate a YAML string'
|
|
34
|
+
def string(yaml)
|
|
35
|
+
validate_string(yaml)
|
|
23
36
|
end
|
|
24
37
|
|
|
25
38
|
private
|
|
26
39
|
|
|
27
|
-
# Validate a single
|
|
40
|
+
# Validate a single file
|
|
28
41
|
#
|
|
29
|
-
# @param
|
|
30
|
-
def
|
|
31
|
-
|
|
42
|
+
# @param path [String] file path
|
|
43
|
+
def validate_file(path)
|
|
44
|
+
validate_file_impl(path)
|
|
45
|
+
end
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
# Validate a single file (implementation)
|
|
48
|
+
#
|
|
49
|
+
# @param path [String] file path
|
|
50
|
+
def validate_file_impl(path)
|
|
51
|
+
# First, validate the definition structure
|
|
52
|
+
result = Ukiryu::Definition::DefinitionValidator.validate_file(
|
|
53
|
+
path,
|
|
54
|
+
schema_path: options[:schema]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
output_result(result, path)
|
|
58
|
+
|
|
59
|
+
# If structural validation passed and --executable or --all flag is set, test the executable
|
|
60
|
+
test_executable(path) if (options[:executable] || options[:all]) && result.valid?
|
|
61
|
+
|
|
62
|
+
exit 1 if result.invalid? || (result.has_warnings? && options[:strict])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Test the executable against the actual tool
|
|
66
|
+
#
|
|
67
|
+
# @param path [String] path to definition file
|
|
68
|
+
def test_executable(path)
|
|
34
69
|
say '', :clear
|
|
70
|
+
say 'Testing executable...', :cyan
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
# Load the YAML profile to get smoke_tests
|
|
74
|
+
profile_data = YAML.safe_load(File.read(path), permitted_classes: [Symbol, Date, Time])
|
|
75
|
+
|
|
76
|
+
# Extract tool name from file path
|
|
77
|
+
file_name = File.basename(path, '.yaml')
|
|
78
|
+
tool_dir = File.basename(File.dirname(path))
|
|
79
|
+
tools_dir = File.basename(File.dirname(File.dirname(path)))
|
|
80
|
+
|
|
81
|
+
# The tool name is the directory name under tools/
|
|
82
|
+
tool_name = if tools_dir == 'tools'
|
|
83
|
+
tool_dir
|
|
84
|
+
else
|
|
85
|
+
file_name
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Don't hardcode register path - use options or let Tool.find handle it
|
|
89
|
+
tool_options = {}
|
|
90
|
+
tool_options[:register_path] = options[:register] if options[:register]
|
|
91
|
+
|
|
92
|
+
# Load the tool (Tool.get will find the right register path)
|
|
93
|
+
tool = Ukiryu::Tool.get(tool_name, **tool_options)
|
|
94
|
+
|
|
95
|
+
# Check if the tool is available
|
|
96
|
+
if tool.available?
|
|
97
|
+
say "✓ Tool found at: #{tool.executable}", :green
|
|
98
|
+
else
|
|
99
|
+
say '✗ Tool not found', :red
|
|
100
|
+
say " Searched in: #{tool.search_paths.join(', ')}", :dim
|
|
101
|
+
exit 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Test version detection if defined
|
|
105
|
+
if tool.profile.version_detection
|
|
106
|
+
say '', :clear
|
|
107
|
+
say 'Testing version detection...', :cyan
|
|
108
|
+
|
|
109
|
+
begin
|
|
110
|
+
detected_version = tool.detect_version
|
|
111
|
+
|
|
112
|
+
if detected_version
|
|
113
|
+
say "✓ Version detected: #{detected_version}", :green
|
|
114
|
+
else
|
|
115
|
+
say '⚠ Version detection failed - could not extract version', :yellow
|
|
116
|
+
end
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
say "⚠ Version check failed: #{e.message}", :yellow
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Run smoke tests from profile if defined
|
|
123
|
+
smoke_tests = profile_data[:smoke_tests] || profile_data['smoke_tests']
|
|
124
|
+
if smoke_tests && !smoke_tests.empty?
|
|
125
|
+
say '', :clear
|
|
126
|
+
say "Running #{smoke_tests.length} smoke test(s)...", :cyan
|
|
127
|
+
run_smoke_tests(tool, smoke_tests, profile_data)
|
|
128
|
+
elsif tool.profile.profiles&.any?
|
|
129
|
+
# Fallback: test basic command execution if commands are defined
|
|
130
|
+
profile = tool.profile.profiles.first
|
|
131
|
+
if profile.commands && !profile.commands.empty?
|
|
132
|
+
say '', :clear
|
|
133
|
+
say 'Testing command execution (smoke test)...', :cyan
|
|
134
|
+
|
|
135
|
+
profile.commands.each do |cmd_def|
|
|
136
|
+
cmd_name = cmd_def.name
|
|
137
|
+
begin
|
|
138
|
+
test_result = execute_smoke_test(tool, cmd_name)
|
|
139
|
+
|
|
140
|
+
if test_result[:success]
|
|
141
|
+
say "✓ Command '#{cmd_name}' is available", :green
|
|
142
|
+
else
|
|
143
|
+
say "⚠ Command '#{cmd_name}' test failed: #{test_result[:message]}", :yellow
|
|
144
|
+
end
|
|
145
|
+
rescue StandardError => e
|
|
146
|
+
say "⚠ Command '#{cmd_name}' test error: #{e.message}", :yellow
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
break
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
rescue Ukiryu::ToolNotFoundError => e
|
|
154
|
+
say "✗ Tool not found: #{e.message}", :red
|
|
155
|
+
exit 1
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
say "✗ Executable test failed: #{e.message}", :red
|
|
158
|
+
exit 1 if options[:strict]
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Run smoke tests from profile
|
|
163
|
+
#
|
|
164
|
+
# @param tool [Ukiryu::Tool] the tool instance
|
|
165
|
+
# @param smoke_tests [Array<Hash>] smoke test definitions
|
|
166
|
+
# @param profile_data [Hash] the loaded profile data
|
|
167
|
+
def run_smoke_tests(tool, smoke_tests, profile_data)
|
|
168
|
+
smoke_tests.each_with_index do |test, index|
|
|
169
|
+
test_name = test[:name] || test['name']
|
|
170
|
+
test_description = test[:description] || test['description'] || test_name
|
|
35
171
|
|
|
36
|
-
if result.valid?
|
|
37
|
-
say result.status_message, :green
|
|
38
|
-
else
|
|
39
|
-
say result.status_message, :red
|
|
40
172
|
say '', :clear
|
|
41
|
-
|
|
42
|
-
|
|
173
|
+
say "[#{index + 1}/#{smoke_tests.length}] #{test_name}: #{test_description}", :cyan
|
|
174
|
+
|
|
175
|
+
# Check platform filter
|
|
176
|
+
platforms = test[:platforms] || test['platforms']
|
|
177
|
+
current_platform = current_platform_symbol
|
|
178
|
+
if platforms && !platforms.include?(current_platform.to_s)
|
|
179
|
+
say " ⊘ Skipped (not for this platform: #{current_platform})", :dim
|
|
180
|
+
next
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Check skip_if condition
|
|
184
|
+
skip_if = test[:skip_if] || test['skip_if']
|
|
185
|
+
if skip_if && evaluate_condition(skip_if, tool, profile_data)
|
|
186
|
+
say " ⊘ Skipped (condition: #{skip_if})", :dim
|
|
187
|
+
next
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Get the command to run
|
|
191
|
+
test_command = test[:command] || test['command']
|
|
192
|
+
test_timeout = test[:timeout] || test['timeout'] || tool.profile.timeout || 30
|
|
193
|
+
|
|
194
|
+
begin
|
|
195
|
+
# Execute the test command
|
|
196
|
+
result = execute_test_command(tool, test_command, test_timeout)
|
|
197
|
+
|
|
198
|
+
# Validate the result
|
|
199
|
+
validation_result = validate_test_result(result, test)
|
|
200
|
+
|
|
201
|
+
if validation_result[:passed]
|
|
202
|
+
say " ✓ PASSED", :green
|
|
203
|
+
if options[:verbose] && validation_result[:details]
|
|
204
|
+
validation_result[:details].each do |detail|
|
|
205
|
+
say " #{detail}", :dim
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
else
|
|
209
|
+
say " ✗ FAILED", :red
|
|
210
|
+
validation_result[:errors].each do |error|
|
|
211
|
+
say " ✗ #{error}", :red
|
|
212
|
+
end
|
|
213
|
+
exit 1 if options[:strict]
|
|
214
|
+
end
|
|
215
|
+
rescue StandardError => e
|
|
216
|
+
say " ✗ ERROR: #{e.message}", :red
|
|
217
|
+
exit 1 if options[:strict]
|
|
43
218
|
end
|
|
44
219
|
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Execute a test command
|
|
223
|
+
#
|
|
224
|
+
# @param tool [Ukiryu::Tool] the tool instance
|
|
225
|
+
# @param test_command [String, Array] command to run
|
|
226
|
+
# @param timeout [Integer] timeout in seconds
|
|
227
|
+
# @return [Hash] execution result
|
|
228
|
+
def execute_test_command(tool, test_command, _timeout)
|
|
229
|
+
cmd_array = if test_command.is_a?(Array)
|
|
230
|
+
test_command
|
|
231
|
+
else
|
|
232
|
+
# Parse command string into array (basic parsing)
|
|
233
|
+
test_command.shellsplit
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Build full command with tool executable
|
|
237
|
+
full_command = [tool.executable] + cmd_array
|
|
45
238
|
|
|
46
|
-
#
|
|
47
|
-
|
|
239
|
+
# Execute using Open3
|
|
240
|
+
require 'open3'
|
|
241
|
+
stdout_str, stderr_str, status = Open3.capture3(*full_command)
|
|
242
|
+
|
|
243
|
+
{
|
|
244
|
+
stdout: stdout_str,
|
|
245
|
+
stderr: stderr_str,
|
|
246
|
+
exit_code: status.exitstatus,
|
|
247
|
+
success: status.success?,
|
|
248
|
+
runtime: nil # TODO: add runtime measurement
|
|
249
|
+
}
|
|
48
250
|
end
|
|
49
251
|
|
|
50
|
-
# Validate
|
|
51
|
-
|
|
52
|
-
|
|
252
|
+
# Validate test result against expectations
|
|
253
|
+
#
|
|
254
|
+
# @param result [Hash] execution result
|
|
255
|
+
# @param test [Hash] test definition with expect section
|
|
256
|
+
# @return [Hash] validation result with :passed, :errors, :details
|
|
257
|
+
def validate_test_result(result, test)
|
|
258
|
+
errors = []
|
|
259
|
+
details = []
|
|
260
|
+
passed = true
|
|
261
|
+
|
|
262
|
+
expect = test[:expect] || test['expect'] || {}
|
|
53
263
|
|
|
54
|
-
|
|
55
|
-
|
|
264
|
+
# Check exit code
|
|
265
|
+
expected_exit_code = expect[:exit_code] || expect['exit_code'] || 0
|
|
266
|
+
if result[:exit_code] != expected_exit_code
|
|
267
|
+
errors << "Exit code mismatch: expected #{expected_exit_code}, got #{result[:exit_code]}"
|
|
268
|
+
passed = false
|
|
269
|
+
else
|
|
270
|
+
details << "Exit code: #{result[:exit_code]} (as expected)"
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Check output_match regex
|
|
274
|
+
output_match = expect[:output_match] || expect['output_match']
|
|
275
|
+
if output_match
|
|
276
|
+
regex = Regexp.new(output_match)
|
|
277
|
+
if result[:stdout] =~ regex
|
|
278
|
+
details << "Output matches pattern: #{output_match}"
|
|
279
|
+
else
|
|
280
|
+
errors << "Output does not match pattern: #{output_match}"
|
|
281
|
+
passed = false
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Check output_contains
|
|
286
|
+
output_contains = expect[:output_contains] || expect['output_contains']
|
|
287
|
+
if output_contains && !output_contains.empty?
|
|
288
|
+
output_contains.each do |str|
|
|
289
|
+
if result[:stdout].include?(str)
|
|
290
|
+
details << "Output contains: #{str}"
|
|
291
|
+
else
|
|
292
|
+
errors << "Output missing string: #{str}"
|
|
293
|
+
passed = false
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Check stderr_match regex
|
|
299
|
+
stderr_match = expect[:stderr_match] || expect['stderr_match']
|
|
300
|
+
if stderr_match
|
|
301
|
+
regex = Regexp.new(stderr_match)
|
|
302
|
+
if result[:stderr] =~ regex
|
|
303
|
+
details << "Stderr matches pattern: #{stderr_match}"
|
|
304
|
+
else
|
|
305
|
+
errors << "Stderr does not match pattern: #{stderr_match}"
|
|
306
|
+
passed = false
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
{ passed: passed, errors: errors, details: details }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Get current platform as symbol
|
|
314
|
+
#
|
|
315
|
+
# @return [Symbol] platform symbol
|
|
316
|
+
def current_platform_symbol
|
|
317
|
+
case RbConfig::CONFIG['host_os']
|
|
318
|
+
when /linux/i
|
|
319
|
+
:linux
|
|
320
|
+
when /darwin/i
|
|
321
|
+
:macos
|
|
322
|
+
when /mswin|mingw|cygwin/i
|
|
323
|
+
:windows
|
|
324
|
+
else
|
|
325
|
+
:unknown
|
|
326
|
+
end
|
|
327
|
+
end
|
|
56
328
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
329
|
+
# Evaluate skip condition (basic implementation)
|
|
330
|
+
#
|
|
331
|
+
# @param condition [String] condition string
|
|
332
|
+
# @param tool [Ukiryu::Tool] tool instance
|
|
333
|
+
# @param profile_data [Hash] profile data
|
|
334
|
+
# @return [Boolean] true if condition is met
|
|
335
|
+
def evaluate_condition(_condition, _tool, _profile_data)
|
|
336
|
+
# Very basic condition evaluation - can be expanded later
|
|
337
|
+
# For now, just return false to not skip any tests
|
|
338
|
+
false
|
|
339
|
+
end
|
|
60
340
|
|
|
61
|
-
|
|
62
|
-
|
|
341
|
+
# Execute a simple smoke test for a command
|
|
342
|
+
#
|
|
343
|
+
# @param tool [Ukiryu::Tool] the tool instance
|
|
344
|
+
# @param cmd_name [Symbol] the command name
|
|
345
|
+
# @return [Hash] test result with :success and :message
|
|
346
|
+
def execute_smoke_test(tool, cmd_name)
|
|
347
|
+
# Try to get help for the command - most CLI tools support --help or -h
|
|
63
348
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
not_found_count += 1 if result.not_found?
|
|
349
|
+
# Try with empty arguments first, just to see if the command runs
|
|
350
|
+
result = tool.execute(cmd_name, {})
|
|
67
351
|
|
|
68
|
-
|
|
69
|
-
|
|
352
|
+
# If we get here, the command executed
|
|
353
|
+
# Check for common error patterns
|
|
354
|
+
if result.exit_status.zero?
|
|
355
|
+
{ success: true, message: 'Command executed successfully' }
|
|
356
|
+
elsif result.stderr.include?('unrecognized') || result.stderr.include?('unknown')
|
|
357
|
+
{ success: false, message: 'Command not recognized by tool' }
|
|
358
|
+
else
|
|
359
|
+
{ success: true, message: "Command executed (exit code: #{result.exit_status})" }
|
|
360
|
+
end
|
|
361
|
+
rescue Ukiryu::ExecutionError => e
|
|
362
|
+
if e.result.stderr.include?('unrecognized') || e.result.stderr.include?('unknown')
|
|
363
|
+
{ success: false, message: 'Command not recognized by tool' }
|
|
364
|
+
else
|
|
365
|
+
{ success: false, message: e.message }
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Validate all definitions in register
|
|
370
|
+
def validate_all
|
|
371
|
+
require_relative '../register_auto_manager'
|
|
372
|
+
|
|
373
|
+
register_path = Ukiryu::RegisterAutoManager.register_path
|
|
374
|
+
return say_error("Register not found: #{register_path}") unless Dir.exist?(register_path)
|
|
375
|
+
|
|
376
|
+
tools_dir = File.join(register_path, 'tools')
|
|
377
|
+
return say_error("Tools directory not found: #{tools_dir}") unless Dir.exist?(tools_dir)
|
|
378
|
+
|
|
379
|
+
results = {}
|
|
380
|
+
yaml_files = Dir.glob(File.join(tools_dir, '*', '*.yaml')).sort
|
|
381
|
+
|
|
382
|
+
say "Validating #{yaml_files.length} tool definitions...", :cyan
|
|
383
|
+
say '', :clear
|
|
384
|
+
|
|
385
|
+
yaml_files.each do |file|
|
|
386
|
+
# Get relative path from tools directory
|
|
387
|
+
relative_path = file.sub("#{tools_dir}/", '')
|
|
388
|
+
|
|
389
|
+
result = Ukiryu::Definition::DefinitionValidator.validate_file(
|
|
390
|
+
file,
|
|
391
|
+
schema_path: options[:schema]
|
|
392
|
+
)
|
|
393
|
+
results[file] = result
|
|
394
|
+
|
|
395
|
+
# Show file and result on same line
|
|
396
|
+
if result.valid?
|
|
397
|
+
status = result.has_warnings? ? '✓ VALID (with warnings)' : '✓ VALID'
|
|
398
|
+
say " #{relative_path.ljust(35)} #{status}", result.has_warnings? ? :yellow : :green
|
|
399
|
+
else
|
|
400
|
+
say " #{relative_path.ljust(35)} ✗ INVALID", :red
|
|
70
401
|
result.errors.each do |error|
|
|
71
|
-
say "
|
|
402
|
+
say " - #{error}", :red
|
|
72
403
|
end
|
|
73
404
|
end
|
|
405
|
+
|
|
406
|
+
next unless result.has_warnings?
|
|
407
|
+
|
|
408
|
+
result.warnings.each do |warning|
|
|
409
|
+
say " ⚠ #{warning}", :yellow
|
|
410
|
+
end
|
|
74
411
|
end
|
|
75
412
|
|
|
413
|
+
# Summary
|
|
76
414
|
say '', :clear
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
say " Missing: #{not_found_count}", not_found_count > 0 ? :yellow : :white
|
|
415
|
+
total = results.length
|
|
416
|
+
valid = results.values.count(&:valid?)
|
|
417
|
+
invalid = results.values.count(&:invalid?)
|
|
81
418
|
|
|
82
|
-
|
|
83
|
-
|
|
419
|
+
say 'Validation Summary:', :cyan
|
|
420
|
+
say " Total: #{total}", :white
|
|
421
|
+
say " Valid: #{valid}", :green
|
|
422
|
+
say " Invalid: #{invalid}", invalid.zero? ? :green : :red
|
|
423
|
+
|
|
424
|
+
exit 1 if invalid.positive?
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Validate a YAML string
|
|
428
|
+
#
|
|
429
|
+
# @param yaml_string [String] YAML content
|
|
430
|
+
def validate_string(yaml_string)
|
|
431
|
+
result = Ukiryu::Definition::DefinitionValidator.validate_string(
|
|
432
|
+
yaml_string,
|
|
433
|
+
schema_path: options[:schema]
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
output_result(result, '<string>')
|
|
437
|
+
exit 1 if result.invalid? || (result.has_warnings? && options[:strict])
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Output validation result
|
|
441
|
+
#
|
|
442
|
+
# @param result [ValidationResult] validation result
|
|
443
|
+
# @param source [String] source identifier
|
|
444
|
+
def output_result(result, source)
|
|
445
|
+
case options[:format]
|
|
446
|
+
when 'json'
|
|
447
|
+
say result.to_json, :white
|
|
448
|
+
else
|
|
449
|
+
output_text(result, source)
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Output as text
|
|
454
|
+
#
|
|
455
|
+
# @param result [ValidationResult] validation result
|
|
456
|
+
# @param source [String] source identifier
|
|
457
|
+
def output_text(result, source)
|
|
458
|
+
say "Validating: #{source}", :cyan
|
|
459
|
+
|
|
460
|
+
if result.valid?
|
|
461
|
+
if result.has_warnings?
|
|
462
|
+
say '✓ Valid with warnings', :yellow
|
|
463
|
+
say '', :clear
|
|
464
|
+
result.warnings.each { |w| say " Warning: #{w}", :yellow }
|
|
465
|
+
else
|
|
466
|
+
say '✓ Valid', :green
|
|
467
|
+
end
|
|
468
|
+
else
|
|
469
|
+
say '✗ Invalid', :red
|
|
470
|
+
say '', :clear
|
|
471
|
+
result.errors.each { |e| say " Error: #{e}", :red }
|
|
472
|
+
if result.has_warnings?
|
|
473
|
+
say '', :clear
|
|
474
|
+
result.warnings.each { |w| say " Warning: #{w}", :yellow }
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Show error message
|
|
480
|
+
#
|
|
481
|
+
# @param message [String] error message
|
|
482
|
+
def say_error(message)
|
|
483
|
+
say message, :red
|
|
484
|
+
exit 1
|
|
84
485
|
end
|
|
85
486
|
end
|
|
86
487
|
end
|
|
@@ -13,7 +13,7 @@ module Ukiryu
|
|
|
13
13
|
#
|
|
14
14
|
# @param identifier [String] the tool name, interface, or alias
|
|
15
15
|
def run(identifier)
|
|
16
|
-
|
|
16
|
+
setup_register
|
|
17
17
|
|
|
18
18
|
runtime = Runtime.instance
|
|
19
19
|
platform = options[:platform] || runtime.platform
|
|
@@ -34,7 +34,7 @@ module Ukiryu
|
|
|
34
34
|
if tool
|
|
35
35
|
show_selected_tool(tool, identifier, platform, shell)
|
|
36
36
|
else
|
|
37
|
-
error! "No tool found for: #{identifier}\nAvailable tools: #{
|
|
37
|
+
error! "No tool found for: #{identifier}\nAvailable tools: #{Register.tools.sort.join(', ')}"
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -61,12 +61,12 @@ module Ukiryu
|
|
|
61
61
|
# @param shell [Symbol] the shell
|
|
62
62
|
# @return [Tool, nil] the tool or nil if not found
|
|
63
63
|
def try_interface_discovery(identifier, platform, shell)
|
|
64
|
-
require_relative '../
|
|
64
|
+
require_relative '../register'
|
|
65
65
|
|
|
66
66
|
candidates = []
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
tool_metadata =
|
|
68
|
+
Register.tools.each do |tool_name|
|
|
69
|
+
tool_metadata = Register.load_tool_metadata(tool_name.to_sym)
|
|
70
70
|
next unless tool_metadata
|
|
71
71
|
|
|
72
72
|
# Check for interface match
|