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,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ukiryu
|
|
4
|
+
module Definition
|
|
5
|
+
# Result of a definition validation
|
|
6
|
+
#
|
|
7
|
+
# This class represents the result of validating a tool definition
|
|
8
|
+
# against a JSON Schema or other validation rules.
|
|
9
|
+
class ValidationResult
|
|
10
|
+
attr_reader :errors, :warnings, :schema_path
|
|
11
|
+
|
|
12
|
+
def initialize(valid:, errors: [], warnings: [], schema_path: nil)
|
|
13
|
+
@valid = valid
|
|
14
|
+
@errors = errors
|
|
15
|
+
@warnings = warnings
|
|
16
|
+
@schema_path = schema_path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Check if validation passed
|
|
20
|
+
#
|
|
21
|
+
# @return [Boolean] true if validation passed
|
|
22
|
+
def valid?
|
|
23
|
+
@valid
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Check if validation failed
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean] true if validation failed
|
|
29
|
+
def invalid?
|
|
30
|
+
!@valid
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check if there are any errors
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean] true if there are errors
|
|
36
|
+
def has_errors?
|
|
37
|
+
!@errors.empty?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if there are any warnings
|
|
41
|
+
#
|
|
42
|
+
# @return [Boolean] true if there are warnings
|
|
43
|
+
def has_warnings?
|
|
44
|
+
!@warnings.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Get total issue count
|
|
48
|
+
#
|
|
49
|
+
# @return [Integer] total number of issues (errors + warnings)
|
|
50
|
+
def issue_count
|
|
51
|
+
@errors.length + @warnings.length
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get error count
|
|
55
|
+
#
|
|
56
|
+
# @return [Integer] number of errors
|
|
57
|
+
def error_count
|
|
58
|
+
@errors.length
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get warning count
|
|
62
|
+
#
|
|
63
|
+
# @return [Integer] number of warnings
|
|
64
|
+
def warning_count
|
|
65
|
+
@warnings.length
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Create a successful validation result
|
|
69
|
+
#
|
|
70
|
+
# @return [ValidationResult] a successful result
|
|
71
|
+
def self.success
|
|
72
|
+
new(valid: true)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Create a failed validation result
|
|
76
|
+
#
|
|
77
|
+
# @param errors [Array<String>] validation errors
|
|
78
|
+
# @param warnings [Array<String>] validation warnings
|
|
79
|
+
# @return [ValidationResult] a failed result
|
|
80
|
+
def self.failure(errors, warnings = [])
|
|
81
|
+
new(valid: false, errors: errors, warnings: warnings)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Create a result with warnings
|
|
85
|
+
#
|
|
86
|
+
# @param warnings [Array<String>] validation warnings
|
|
87
|
+
# @return [ValidationResult] a result with warnings
|
|
88
|
+
def self.with_warnings(warnings)
|
|
89
|
+
new(valid: true, warnings: warnings)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Convert to hash
|
|
93
|
+
#
|
|
94
|
+
# @return [Hash] hash representation
|
|
95
|
+
def to_h
|
|
96
|
+
{
|
|
97
|
+
valid: @valid,
|
|
98
|
+
errors: @errors,
|
|
99
|
+
warnings: @warnings,
|
|
100
|
+
schema_path: @schema_path,
|
|
101
|
+
error_count: error_count,
|
|
102
|
+
warning_count: warning_count
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Convert to JSON
|
|
107
|
+
#
|
|
108
|
+
# @return [String] JSON representation
|
|
109
|
+
def to_json(*args)
|
|
110
|
+
require 'json'
|
|
111
|
+
to_h.to_json(*args)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Human-readable summary
|
|
115
|
+
#
|
|
116
|
+
# @return [String] summary text
|
|
117
|
+
def summary
|
|
118
|
+
if valid?
|
|
119
|
+
if has_warnings?
|
|
120
|
+
"Valid with #{warning_count} warning(s)"
|
|
121
|
+
else
|
|
122
|
+
'Valid'
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
msg = "Invalid (#{error_count} error(s)"
|
|
126
|
+
msg += ", #{warning_count} warning(s)" if has_warnings?
|
|
127
|
+
"#{msg})"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Detailed message string
|
|
132
|
+
#
|
|
133
|
+
# @return [String] detailed message
|
|
134
|
+
def to_s
|
|
135
|
+
output = []
|
|
136
|
+
output << "Validation: #{summary}"
|
|
137
|
+
|
|
138
|
+
if has_errors?
|
|
139
|
+
output << ''
|
|
140
|
+
output << 'Errors:'
|
|
141
|
+
@errors.each_with_index do |error, i|
|
|
142
|
+
output << " #{i + 1}. #{error}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if has_warnings?
|
|
147
|
+
output << ''
|
|
148
|
+
output << 'Warnings:'
|
|
149
|
+
@warnings.each_with_index do |warning, i|
|
|
150
|
+
output << " #{i + 1}. #{warning}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
output.join("\n")
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ukiryu
|
|
4
|
+
module Definition
|
|
5
|
+
# Resolve semantic version constraints
|
|
6
|
+
#
|
|
7
|
+
# This class handles semantic versioning constraints like:
|
|
8
|
+
# - "1.0" - exact version
|
|
9
|
+
# - ">= 1.0" - minimum version (inclusive)
|
|
10
|
+
# - "~> 1.2" - pessimistic version constraint
|
|
11
|
+
class VersionResolver
|
|
12
|
+
# Version constraint structure
|
|
13
|
+
class Constraint
|
|
14
|
+
attr_reader :operator, :version, :raw
|
|
15
|
+
|
|
16
|
+
def initialize(operator, version, raw = nil)
|
|
17
|
+
@operator = operator
|
|
18
|
+
@version = version
|
|
19
|
+
@raw = raw || "#{operator} #{version}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Exact version constraint
|
|
23
|
+
def self.exact(version)
|
|
24
|
+
new(:==, version, version)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Minimum version constraint (inclusive)
|
|
28
|
+
def self.min(version)
|
|
29
|
+
new(:>=, version)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Maximum version constraint (inclusive)
|
|
33
|
+
def self.max(version)
|
|
34
|
+
new(:<=, version)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Pessimistic version constraint
|
|
38
|
+
def self.pessimistic(version)
|
|
39
|
+
new('~>'.to_sym, version)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Range constraint
|
|
43
|
+
def self.range(min_version, max_version)
|
|
44
|
+
# This is represented as two constraints internally
|
|
45
|
+
[new(:>=, min_version), new(:<, max_version)]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_s
|
|
49
|
+
@raw
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Parse a version constraint string
|
|
54
|
+
#
|
|
55
|
+
# @param constraint_string [String] the constraint string
|
|
56
|
+
# @return [Array<Constraint>] array of constraints
|
|
57
|
+
def self.parse_constraint(constraint_string)
|
|
58
|
+
return [Constraint.exact(constraint_string)] unless constraint_string.match?(/[<>=~]/)
|
|
59
|
+
|
|
60
|
+
constraints = []
|
|
61
|
+
|
|
62
|
+
# Split by comma for compound constraints
|
|
63
|
+
constraint_string.split(',').map(&:strip).each do |part|
|
|
64
|
+
case part
|
|
65
|
+
when /\A~>(?:\s*(.+))?/ # Updated regex for ~> operator
|
|
66
|
+
# Pessimistic version constraint
|
|
67
|
+
version = ::Regexp.last_match(1) || ''
|
|
68
|
+
constraints << Constraint.pessimistic(version.strip)
|
|
69
|
+
when /\A>=\s*(.+)/
|
|
70
|
+
# Minimum version (inclusive)
|
|
71
|
+
constraints << Constraint.min(::Regexp.last_match(1))
|
|
72
|
+
when /\A>\s*(.+)/
|
|
73
|
+
# Minimum version (exclusive)
|
|
74
|
+
constraints << Constraint.new(:>, ::Regexp.last_match(1))
|
|
75
|
+
when /\A<=\s*(.+)/
|
|
76
|
+
# Maximum version (inclusive)
|
|
77
|
+
constraints << Constraint.max(::Regexp.last_match(1))
|
|
78
|
+
when /\A<\s*(.+)/
|
|
79
|
+
# Maximum version (exclusive)
|
|
80
|
+
constraints << Constraint.new(:<, ::Regexp.last_match(1))
|
|
81
|
+
when /\A==\s*(.+)/
|
|
82
|
+
# Exact version
|
|
83
|
+
constraints << Constraint.exact(::Regexp.last_match(1))
|
|
84
|
+
else
|
|
85
|
+
# Assume exact version
|
|
86
|
+
constraints << Constraint.exact(part.strip)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
constraints
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check if a version satisfies a constraint
|
|
94
|
+
#
|
|
95
|
+
# @param version [String] the version to check
|
|
96
|
+
# @param constraint [String, Array<Constraint>] the constraint(s)
|
|
97
|
+
# @return [Boolean] true if version satisfies constraint
|
|
98
|
+
def self.satisfies?(version, constraint)
|
|
99
|
+
constraints = constraint.is_a?(Array) ? constraint : parse_constraint(constraint)
|
|
100
|
+
|
|
101
|
+
v_parts = parse_version(version)
|
|
102
|
+
|
|
103
|
+
constraints.all? do |c|
|
|
104
|
+
case c.operator
|
|
105
|
+
when :==
|
|
106
|
+
v_parts == parse_version(c.version)
|
|
107
|
+
when :>=
|
|
108
|
+
compare_versions(v_parts, parse_version(c.version)) >= 0
|
|
109
|
+
when :>
|
|
110
|
+
compare_versions(v_parts, parse_version(c.version)).positive?
|
|
111
|
+
when :<=
|
|
112
|
+
compare_versions(v_parts, parse_version(c.version)) <= 0
|
|
113
|
+
when :<
|
|
114
|
+
compare_versions(v_parts, parse_version(c.version)).negative?
|
|
115
|
+
when '~>'.to_sym
|
|
116
|
+
# Pessimistic version constraint: >= x.y.z, < x.(y+1).0
|
|
117
|
+
base = parse_version(c.version)
|
|
118
|
+
upper = base[0...-1] + [base[-1] + 1, 0]
|
|
119
|
+
compare_versions(v_parts, base) >= 0 && compare_versions(v_parts, upper).negative?
|
|
120
|
+
else
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Resolve the best matching version from available versions
|
|
127
|
+
#
|
|
128
|
+
# @param constraint [String] the version constraint
|
|
129
|
+
# @param available_versions [Array<String>] available versions
|
|
130
|
+
# @return [String, nil] best matching version, or nil if none match
|
|
131
|
+
def self.resolve(constraint, available_versions)
|
|
132
|
+
return nil if available_versions.nil? || available_versions.empty?
|
|
133
|
+
|
|
134
|
+
# Parse constraint
|
|
135
|
+
constraints = parse_constraint(constraint)
|
|
136
|
+
|
|
137
|
+
# Filter versions that satisfy the constraint
|
|
138
|
+
matching = available_versions.select { |v| satisfies?(v, constraints) }
|
|
139
|
+
|
|
140
|
+
# Return highest matching version
|
|
141
|
+
matching.max_by { |v| parse_version(v) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Compare two version strings
|
|
145
|
+
#
|
|
146
|
+
# @param v1 [String, Array] first version or parts
|
|
147
|
+
# @param v2 [String, Array] second version or parts
|
|
148
|
+
# @return [Integer] comparison result (-1, 0, 1)
|
|
149
|
+
def self.compare_versions(v1, v2)
|
|
150
|
+
parts1 = v1.is_a?(Array) ? v1 : parse_version(v1)
|
|
151
|
+
parts2 = v2.is_a?(Array) ? v2 : parse_version(v2)
|
|
152
|
+
|
|
153
|
+
max_length = [parts1.length, parts2.length].max
|
|
154
|
+
max_length.times do |i|
|
|
155
|
+
p1 = parts1[i] || 0
|
|
156
|
+
p2 = parts2[i] || 0
|
|
157
|
+
comparison = p1 <=> p2
|
|
158
|
+
return comparison unless comparison.zero?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
0
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Parse a version string into components
|
|
165
|
+
#
|
|
166
|
+
# @param version_string [String] the version string
|
|
167
|
+
# @return [Array<Integer>] version components
|
|
168
|
+
def self.parse_version(version_string)
|
|
169
|
+
version_string.to_s.split('.').map(&:to_i)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Get the latest version from a list
|
|
173
|
+
#
|
|
174
|
+
# @param versions [Array<String>] list of versions
|
|
175
|
+
# @return [String, nil] the latest version
|
|
176
|
+
def self.latest(versions)
|
|
177
|
+
return nil if versions.nil? || versions.empty?
|
|
178
|
+
|
|
179
|
+
versions.max_by { |v| parse_version(v) }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Get the compatible version range for a pessimistic constraint
|
|
183
|
+
#
|
|
184
|
+
# @param version [String] the base version
|
|
185
|
+
# @return [Array<String>] [min_version, max_version]
|
|
186
|
+
def self.pessimistic_range(version)
|
|
187
|
+
parts = parse_version(version)
|
|
188
|
+
min = version
|
|
189
|
+
max = "#{parts[0...-1].join('.')}.#{parts[-1] + 1}"
|
|
190
|
+
[min, max]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'definition/source'
|
|
4
|
+
require_relative 'definition/sources/file'
|
|
5
|
+
require_relative 'definition/sources/string'
|
|
6
|
+
|
|
7
|
+
# Export classes for convenience
|
|
8
|
+
Ukiryu::Definition::FileSource = Ukiryu::Definition::Sources::FileSource
|
|
9
|
+
Ukiryu::Definition::StringSource = Ukiryu::Definition::Sources::StringSource
|
|
10
|
+
|
|
11
|
+
require_relative 'definition/loader'
|
|
12
|
+
require_relative 'definition/metadata'
|
|
13
|
+
require_relative 'definition/discovery'
|
|
14
|
+
require_relative 'definition/version_resolver'
|
|
15
|
+
require_relative 'definition/definition_cache'
|
|
16
|
+
require_relative 'definition/definition_composer'
|
|
17
|
+
|
|
18
|
+
# Phase 5: Ecosystem - Validation, linting, documentation
|
|
19
|
+
require_relative 'definition/validation_result'
|
|
20
|
+
require_relative 'definition/definition_validator'
|
|
21
|
+
require_relative 'definition/lint_issue'
|
|
22
|
+
require_relative 'definition/definition_linter'
|
|
23
|
+
require_relative 'definition/documentation_generator'
|
|
24
|
+
|
|
25
|
+
module Ukiryu
|
|
26
|
+
# Definition loading module
|
|
27
|
+
#
|
|
28
|
+
# Provides functionality for loading tool definitions from various sources:
|
|
29
|
+
# - Files on the filesystem
|
|
30
|
+
# - YAML strings
|
|
31
|
+
# - XDG-compliant system paths
|
|
32
|
+
# - Tool-bundled locations
|
|
33
|
+
# - Central register (existing)
|
|
34
|
+
#
|
|
35
|
+
# @see Ukiryu::Tool::load
|
|
36
|
+
# @see Ukiryu::Tool::load_from_string
|
|
37
|
+
# @see Ukiryu::Definition::Discovery
|
|
38
|
+
module Definition
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/ukiryu/errors.rb
CHANGED
|
@@ -18,6 +18,12 @@ module Ukiryu
|
|
|
18
18
|
class ProfileLoadError < Error; end
|
|
19
19
|
|
|
20
20
|
# Definition loading errors
|
|
21
|
+
class DefinitionError < Error; end
|
|
22
|
+
class DefinitionNotFoundError < DefinitionError; end
|
|
23
|
+
class DefinitionLoadError < DefinitionError; end
|
|
24
|
+
class DefinitionValidationError < DefinitionError; end
|
|
25
|
+
|
|
26
|
+
# Definition loading errors (legacy, use DefinitionError instead)
|
|
21
27
|
class LoadError < Error; end
|
|
22
28
|
|
|
23
29
|
# Tool errors
|
|
@@ -32,13 +32,13 @@ module Ukiryu
|
|
|
32
32
|
# context = Ukiryu::ExecutionContext.current
|
|
33
33
|
# context.platform # => :macos
|
|
34
34
|
# context.shell # => :bash
|
|
35
|
-
# context.
|
|
35
|
+
# context.register # => '/path/to/register'
|
|
36
36
|
#
|
|
37
37
|
# @example Using ExecutionContext in tests
|
|
38
38
|
# context = Ukiryu::ExecutionContext.new(
|
|
39
39
|
# platform: :linux,
|
|
40
40
|
# shell: :zsh,
|
|
41
|
-
#
|
|
41
|
+
# register_path: '/test/register'
|
|
42
42
|
# )
|
|
43
43
|
# context.platform # => :linux
|
|
44
44
|
# context.shell # => :zsh
|
|
@@ -85,7 +85,7 @@ module Ukiryu
|
|
|
85
85
|
new(
|
|
86
86
|
platform: runtime.platform,
|
|
87
87
|
shell: runtime.shell,
|
|
88
|
-
|
|
88
|
+
register_path: Register.default_register_path,
|
|
89
89
|
timeout: Config.timeout,
|
|
90
90
|
debug: Config.debug,
|
|
91
91
|
metrics: Config.metrics
|
|
@@ -110,10 +110,10 @@ module Ukiryu
|
|
|
110
110
|
# @return [Symbol] the shell
|
|
111
111
|
attr_reader :shell
|
|
112
112
|
|
|
113
|
-
#
|
|
113
|
+
# Register path for tool profiles
|
|
114
114
|
#
|
|
115
|
-
# @return [String, nil] the
|
|
116
|
-
attr_reader :
|
|
115
|
+
# @return [String, nil] the register path
|
|
116
|
+
attr_reader :register_path
|
|
117
117
|
|
|
118
118
|
# Execution timeout in seconds
|
|
119
119
|
#
|
|
@@ -139,21 +139,21 @@ module Ukiryu
|
|
|
139
139
|
#
|
|
140
140
|
# @param platform [Symbol] the platform (:macos, :linux, :windows)
|
|
141
141
|
# @param shell [Symbol] the shell (:bash, :zsh, :fish, :powershell, :cmd)
|
|
142
|
-
# @param
|
|
142
|
+
# @param register_path [String, nil] the register path
|
|
143
143
|
# @param timeout [Integer, nil] execution timeout in seconds
|
|
144
144
|
# @param debug [Boolean] debug mode flag
|
|
145
145
|
# @param metrics [Boolean] metrics collection flag
|
|
146
146
|
# @param options [Hash] additional options
|
|
147
147
|
def initialize(platform: nil,
|
|
148
148
|
shell: nil,
|
|
149
|
-
|
|
149
|
+
register_path: nil,
|
|
150
150
|
timeout: nil,
|
|
151
151
|
debug: false,
|
|
152
152
|
metrics: false,
|
|
153
153
|
options: {})
|
|
154
154
|
@platform = platform
|
|
155
155
|
@shell = shell
|
|
156
|
-
@
|
|
156
|
+
@register_path = register_path
|
|
157
157
|
@timeout = timeout
|
|
158
158
|
@debug = debug
|
|
159
159
|
@metrics = metrics
|
|
@@ -226,7 +226,7 @@ module Ukiryu
|
|
|
226
226
|
self.class.new(
|
|
227
227
|
platform: changes.fetch(:platform, @platform),
|
|
228
228
|
shell: changes.fetch(:shell, @shell),
|
|
229
|
-
|
|
229
|
+
register_path: changes.fetch(:register_path, @register_path),
|
|
230
230
|
timeout: changes.fetch(:timeout, @timeout),
|
|
231
231
|
debug: changes.fetch(:debug, @debug),
|
|
232
232
|
metrics: changes.fetch(:metrics, @metrics),
|
|
@@ -238,7 +238,7 @@ module Ukiryu
|
|
|
238
238
|
#
|
|
239
239
|
# @return [String] the context as a string
|
|
240
240
|
def to_s
|
|
241
|
-
"ExecutionContext(platform=#{@platform}, shell=#{@shell},
|
|
241
|
+
"ExecutionContext(platform=#{@platform}, shell=#{@shell}, register=#{@register_path})"
|
|
242
242
|
end
|
|
243
243
|
|
|
244
244
|
# Inspect
|
data/lib/ukiryu/executor.rb
CHANGED
|
@@ -269,6 +269,12 @@ module Ukiryu
|
|
|
269
269
|
|
|
270
270
|
# Add shell-specific headless environment
|
|
271
271
|
headless = shell_instance.headless_environment
|
|
272
|
+
|
|
273
|
+
# For headless mode, explicitly remove DISPLAY
|
|
274
|
+
# If user_env explicitly didn't set DISPLAY, respect that (caller wants it removed)
|
|
275
|
+
# Otherwise, check if headless environment specifies DISPLAY
|
|
276
|
+
env.delete('DISPLAY') unless headless.key?('DISPLAY')
|
|
277
|
+
|
|
272
278
|
env.merge!(headless)
|
|
273
279
|
|
|
274
280
|
env
|
|
@@ -48,11 +48,12 @@ module Ukiryu
|
|
|
48
48
|
def extract
|
|
49
49
|
method = @options[:method] || :auto
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
case method
|
|
52
|
+
when :auto
|
|
52
53
|
extract_auto
|
|
53
|
-
|
|
54
|
+
when :native
|
|
54
55
|
extract_with_native
|
|
55
|
-
|
|
56
|
+
when :help
|
|
56
57
|
extract_with_help
|
|
57
58
|
else
|
|
58
59
|
{
|
|
@@ -105,7 +106,7 @@ module Ukiryu
|
|
|
105
106
|
else
|
|
106
107
|
{
|
|
107
108
|
success: false,
|
|
108
|
-
error:
|
|
109
|
+
error: 'Native extraction failed',
|
|
109
110
|
method: :native,
|
|
110
111
|
yaml: nil
|
|
111
112
|
}
|
|
@@ -139,7 +140,7 @@ module Ukiryu
|
|
|
139
140
|
else
|
|
140
141
|
{
|
|
141
142
|
success: false,
|
|
142
|
-
error:
|
|
143
|
+
error: 'Help parsing failed',
|
|
143
144
|
method: :help,
|
|
144
145
|
yaml: nil
|
|
145
146
|
}
|
|
@@ -75,7 +75,7 @@ module Ukiryu
|
|
|
75
75
|
#
|
|
76
76
|
# @param help_text [String] the help output
|
|
77
77
|
# @return [String] the tool name
|
|
78
|
-
def extract_name(
|
|
78
|
+
def extract_name(_help_text)
|
|
79
79
|
# Use the tool name passed to the extractor
|
|
80
80
|
@tool_name.to_s
|
|
81
81
|
end
|
|
@@ -88,9 +88,7 @@ module Ukiryu
|
|
|
88
88
|
if version_result[:exit_status].zero?
|
|
89
89
|
version_text = version_result[:stdout]
|
|
90
90
|
# Try to extract version number
|
|
91
|
-
if version_text =~ /(\d+\.\d+(?:\.\d+)?)/
|
|
92
|
-
return Regexp.last_match(1)
|
|
93
|
-
end
|
|
91
|
+
return Regexp.last_match(1) if version_text =~ /(\d+\.\d+(?:\.\d+)?)/
|
|
94
92
|
end
|
|
95
93
|
nil
|
|
96
94
|
end
|
|
@@ -124,17 +122,13 @@ module Ukiryu
|
|
|
124
122
|
'description' => line.strip
|
|
125
123
|
}
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
option['cli'] = short_opt
|
|
129
|
-
else
|
|
130
|
-
option['cli'] = "--#{long_opt}"
|
|
131
|
-
end
|
|
125
|
+
option['cli'] = (short_opt || "--#{long_opt}")
|
|
132
126
|
|
|
133
|
-
if param
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
option['type'] = if param
|
|
128
|
+
infer_type(param)
|
|
129
|
+
else
|
|
130
|
+
'boolean'
|
|
131
|
+
end
|
|
138
132
|
|
|
139
133
|
options << option
|
|
140
134
|
elsif line =~ /^\s*\[--([a-z-]+)(?:[=\s]+([A-Z_]+))?\]/i
|
|
@@ -147,11 +141,11 @@ module Ukiryu
|
|
|
147
141
|
'description' => line.strip
|
|
148
142
|
}
|
|
149
143
|
|
|
150
|
-
if param
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
option['type'] = if param
|
|
145
|
+
infer_type(param)
|
|
146
|
+
else
|
|
147
|
+
'boolean'
|
|
148
|
+
end
|
|
155
149
|
|
|
156
150
|
options << option
|
|
157
151
|
end
|
data/lib/ukiryu/logger.rb
CHANGED
|
@@ -172,7 +172,9 @@ module Ukiryu
|
|
|
172
172
|
if @paint_available
|
|
173
173
|
paint = Paint.method(:[])
|
|
174
174
|
@output.puts ''
|
|
175
|
-
@output.puts "#{paint[' ✓',
|
|
175
|
+
@output.puts "#{paint[' ✓',
|
|
176
|
+
:green]} #{paint[selected_tool, :cyan,
|
|
177
|
+
:bright]}#{paint[' implements: ', :white]}#{paint[identifier, :yellow]}"
|
|
176
178
|
else
|
|
177
179
|
@output.puts ''
|
|
178
180
|
@output.puts " ✓ #{selected_tool} implements: #{identifier}"
|
|
@@ -23,10 +23,10 @@ module Ukiryu
|
|
|
23
23
|
attribute :description, :string
|
|
24
24
|
attribute :usage, :string
|
|
25
25
|
attribute :subcommand, :string
|
|
26
|
-
attribute :execution_mode, :string
|
|
27
26
|
attribute :belongs_to, :string # Parent command this action belongs to
|
|
28
27
|
attribute :cli_flag, :string # CLI flag for this action (e.g., '-d' for delete)
|
|
29
28
|
attribute :aliases, :string, collection: true, default: []
|
|
29
|
+
attribute :use_env_vars, :string, collection: true, default: []
|
|
30
30
|
|
|
31
31
|
# Collections of model objects (lutaml-model handles serialization automatically)
|
|
32
32
|
attribute :options, OptionDefinition, collection: true
|
|
@@ -34,7 +34,7 @@ module Ukiryu
|
|
|
34
34
|
attribute :arguments, ArgumentDefinition, collection: true
|
|
35
35
|
attribute :post_options, OptionDefinition, collection: true
|
|
36
36
|
attribute :env_vars, EnvVarDefinition, collection: true
|
|
37
|
-
attribute :exit_codes, ExitCodes
|
|
37
|
+
attribute :exit_codes, ExitCodes # Exit code definitions for this command
|
|
38
38
|
|
|
39
39
|
yaml do
|
|
40
40
|
map_element 'name', to: :name
|
|
@@ -47,7 +47,7 @@ module Ukiryu
|
|
|
47
47
|
map_element 'post_options', to: :post_options
|
|
48
48
|
map_element 'env_vars', to: :env_vars
|
|
49
49
|
map_element 'exit_codes', to: :exit_codes
|
|
50
|
-
map_element '
|
|
50
|
+
map_element 'use_env_vars', to: :use_env_vars
|
|
51
51
|
map_element 'belongs_to', to: :belongs_to
|
|
52
52
|
map_element 'cli_flag', to: :cli_flag
|
|
53
53
|
map_element 'aliases', to: :aliases
|
|
@@ -12,7 +12,7 @@ module Ukiryu
|
|
|
12
12
|
class CommandInfo < Lutaml::Model::Serializable
|
|
13
13
|
attribute :executable, :string
|
|
14
14
|
attribute :executable_name, :string
|
|
15
|
-
attribute :tool_name, :string
|
|
15
|
+
attribute :tool_name, :string # Name of the tool being executed
|
|
16
16
|
attribute :arguments, Arguments
|
|
17
17
|
attribute :full_command, :string
|
|
18
18
|
attribute :shell, :string
|
|
@@ -8,7 +8,7 @@ require_relative 'exit_codes'
|
|
|
8
8
|
|
|
9
9
|
module Ukiryu
|
|
10
10
|
module Models
|
|
11
|
-
# Components
|
|
11
|
+
# Components register for reusable definitions
|
|
12
12
|
#
|
|
13
13
|
# Enables sharing common option/argument/flag/exit_codes definitions
|
|
14
14
|
# across commands through `$ref` references.
|
|
@@ -98,8 +98,6 @@ module Ukiryu
|
|
|
98
98
|
argument(name)
|
|
99
99
|
when 'exit_codes'
|
|
100
100
|
@exit_codes
|
|
101
|
-
else
|
|
102
|
-
nil
|
|
103
101
|
end
|
|
104
102
|
end
|
|
105
103
|
end
|