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,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'etc'
|
|
6
|
+
|
|
7
|
+
module Ukiryu
|
|
8
|
+
module Models
|
|
9
|
+
# Metrics for a single stage of execution
|
|
10
|
+
class StageMetrics < Lutaml::Model::Serializable
|
|
11
|
+
attribute :name, :string, default: ''
|
|
12
|
+
attribute :duration, :float, default: 0.0
|
|
13
|
+
attribute :formatted_duration, :string, default: ''
|
|
14
|
+
attribute :memory_before, :integer, default: 0
|
|
15
|
+
attribute :memory_after, :integer, default: 0
|
|
16
|
+
attribute :memory_delta, :integer, default: 0
|
|
17
|
+
attribute :success, :boolean, default: true
|
|
18
|
+
attribute :error, :string, default: ''
|
|
19
|
+
|
|
20
|
+
yaml do
|
|
21
|
+
map_element 'name', to: :name
|
|
22
|
+
map_element 'duration', to: :duration
|
|
23
|
+
map_element 'formatted_duration', to: :formatted_duration
|
|
24
|
+
map_element 'memory_before', to: :memory_before
|
|
25
|
+
map_element 'memory_after', to: :memory_after
|
|
26
|
+
map_element 'memory_delta', to: :memory_delta
|
|
27
|
+
map_element 'success', to: :success
|
|
28
|
+
map_element 'error', to: :error
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Record the end of a stage
|
|
32
|
+
def finish!(success: true, error: nil)
|
|
33
|
+
@duration = Time.now - @start_time if @start_time
|
|
34
|
+
@formatted_duration = format_duration(@duration)
|
|
35
|
+
@success = success
|
|
36
|
+
@error = error if error
|
|
37
|
+
|
|
38
|
+
# Record memory after
|
|
39
|
+
@memory_after = get_memory_usage
|
|
40
|
+
@memory_delta = @memory_after - @memory_before
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Start recording this stage
|
|
44
|
+
def start!
|
|
45
|
+
@start_time = Time.now
|
|
46
|
+
@memory_before = get_memory_usage
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def get_memory_usage
|
|
53
|
+
# Get RSS memory usage in KB
|
|
54
|
+
`ps -o rss= -p #{Process.pid}`.to_i
|
|
55
|
+
rescue StandardError
|
|
56
|
+
0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def format_duration(seconds)
|
|
60
|
+
return '0ms' if seconds.nil? || seconds.zero?
|
|
61
|
+
return "#{(seconds * 1000).round(2)}ms" if seconds < 1
|
|
62
|
+
|
|
63
|
+
"#{seconds.round(2)}s"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Run environment information
|
|
68
|
+
class RunEnvironment < Lutaml::Model::Serializable
|
|
69
|
+
attribute :hostname, :string, default: ''
|
|
70
|
+
attribute :platform, :string, default: ''
|
|
71
|
+
attribute :os_version, :string, default: ''
|
|
72
|
+
attribute :shell, :string, default: ''
|
|
73
|
+
attribute :shell_override, :boolean, default: false
|
|
74
|
+
attribute :shell_version, :string, default: ''
|
|
75
|
+
attribute :ruby_version, :string, default: ''
|
|
76
|
+
attribute :ukiryu_version, :string, default: ''
|
|
77
|
+
attribute :cpu_count, :integer, default: 0
|
|
78
|
+
attribute :total_memory, :integer, default: 0
|
|
79
|
+
attribute :working_directory, :string, default: ''
|
|
80
|
+
|
|
81
|
+
yaml do
|
|
82
|
+
map_element 'hostname', to: :hostname
|
|
83
|
+
map_element 'platform', to: :platform
|
|
84
|
+
map_element 'os_version', to: :os_version
|
|
85
|
+
map_element 'shell', to: :shell
|
|
86
|
+
map_element 'shell_override', to: :shell_override
|
|
87
|
+
map_element 'shell_version', to: :shell_version
|
|
88
|
+
map_element 'ruby_version', to: :ruby_version
|
|
89
|
+
map_element 'ukiryu_version', to: :ukiryu_version
|
|
90
|
+
map_element 'cpu_count', to: :cpu_count
|
|
91
|
+
map_element 'total_memory', to: :total_memory
|
|
92
|
+
map_element 'working_directory', to: :working_directory
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Collect all environment information
|
|
96
|
+
#
|
|
97
|
+
# @return [RunEnvironment] the environment info
|
|
98
|
+
def self.collect
|
|
99
|
+
require_relative '../runtime'
|
|
100
|
+
require_relative '../config'
|
|
101
|
+
require_relative '../shell'
|
|
102
|
+
|
|
103
|
+
runtime = Runtime.instance
|
|
104
|
+
begin
|
|
105
|
+
Shell.detect
|
|
106
|
+
rescue Ukiryu::UnknownShellError
|
|
107
|
+
'unknown'
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Determine if shell was overridden
|
|
111
|
+
shell_override = !Config.shell.nil?
|
|
112
|
+
actual_shell = runtime.shell.to_s
|
|
113
|
+
|
|
114
|
+
new(
|
|
115
|
+
hostname: Socket.gethostname,
|
|
116
|
+
platform: RUBY_PLATFORM,
|
|
117
|
+
os_version: os_version_string,
|
|
118
|
+
shell: actual_shell,
|
|
119
|
+
shell_override: shell_override,
|
|
120
|
+
shell_version: detect_shell_version_for(actual_shell),
|
|
121
|
+
ruby_version: RUBY_VERSION,
|
|
122
|
+
ukiryu_version: Ukiryu::VERSION,
|
|
123
|
+
cpu_count: Etc.nprocessors,
|
|
124
|
+
total_memory: detect_total_memory,
|
|
125
|
+
working_directory: Dir.pwd
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Detect shell version for a specific shell
|
|
130
|
+
#
|
|
131
|
+
# @param shell_name [String] the shell name
|
|
132
|
+
# @return [String] the shell version string
|
|
133
|
+
def self.detect_shell_version_for(shell_name)
|
|
134
|
+
return '' if shell_name == 'unknown' || shell_name.empty?
|
|
135
|
+
|
|
136
|
+
shell_path = ENV['SHELL']
|
|
137
|
+
return '' unless shell_path
|
|
138
|
+
|
|
139
|
+
`#{shell_path} --version 2>&1`.strip
|
|
140
|
+
rescue StandardError
|
|
141
|
+
''
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Get OS version string
|
|
145
|
+
#
|
|
146
|
+
# @return [String] the OS version
|
|
147
|
+
def self.os_version_string
|
|
148
|
+
# Try to get OS version from RbConfig
|
|
149
|
+
RbConfig::CONFIG['host_os'] || RbConfig::CONFIG['target_os'] || RUBY_PLATFORM
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Detect total system memory
|
|
153
|
+
#
|
|
154
|
+
# @return [Integer] total memory in GB
|
|
155
|
+
def self.detect_total_memory
|
|
156
|
+
# Get total system memory in GB
|
|
157
|
+
if RUBY_PLATFORM =~ /darwin/i
|
|
158
|
+
# macOS
|
|
159
|
+
`sysctl hw.memsize`.to_i / (1024**3)
|
|
160
|
+
elsif RUBY_PLATFORM =~ /linux/i
|
|
161
|
+
# Linux
|
|
162
|
+
`grep MemTotal /proc/meminfo`.split[1].to_i / 1024
|
|
163
|
+
else
|
|
164
|
+
0
|
|
165
|
+
end
|
|
166
|
+
rescue StandardError
|
|
167
|
+
0
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private_class_method :detect_shell_version_for, :detect_total_memory, :os_version_string
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Execution report containing metrics and timing information
|
|
174
|
+
#
|
|
175
|
+
# Provides detailed metrics about the execution process including:
|
|
176
|
+
# - Stage timings (tool resolution, command building, execution)
|
|
177
|
+
# - Memory usage
|
|
178
|
+
# - Run environment information
|
|
179
|
+
class ExecutionReport < Lutaml::Model::Serializable
|
|
180
|
+
attribute :tool_resolution, StageMetrics
|
|
181
|
+
attribute :command_building, StageMetrics
|
|
182
|
+
attribute :execution, StageMetrics
|
|
183
|
+
attribute :response_building, StageMetrics
|
|
184
|
+
attribute :total_duration, :float, default: 0.0
|
|
185
|
+
attribute :formatted_total_duration, :string, default: ''
|
|
186
|
+
attribute :run_environment, RunEnvironment
|
|
187
|
+
attribute :timestamp, :string, default: ''
|
|
188
|
+
|
|
189
|
+
yaml do
|
|
190
|
+
map_element 'tool_resolution', to: :tool_resolution
|
|
191
|
+
map_element 'command_building', to: :command_building
|
|
192
|
+
map_element 'execution', to: :execution
|
|
193
|
+
map_element 'response_building', to: :response_building
|
|
194
|
+
map_element 'total_duration', to: :total_duration
|
|
195
|
+
map_element 'formatted_total_duration', to: :formatted_total_duration
|
|
196
|
+
map_element 'run_environment', to: :run_environment
|
|
197
|
+
map_element 'timestamp', to: :timestamp
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
json do
|
|
201
|
+
map 'tool_resolution', to: :tool_resolution
|
|
202
|
+
map 'command_building', to: :command_building
|
|
203
|
+
map 'execution', to: :execution
|
|
204
|
+
map 'response_building', to: :response_building
|
|
205
|
+
map 'total_duration', to: :total_duration
|
|
206
|
+
map 'formatted_total_duration', to: :formatted_total_duration
|
|
207
|
+
map 'run_environment', to: :run_environment
|
|
208
|
+
map 'timestamp', to: :timestamp
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Calculate total duration from all stages
|
|
212
|
+
def calculate_total
|
|
213
|
+
stages = [tool_resolution, command_building, execution, response_building]
|
|
214
|
+
total = stages.compact.map(&:duration).sum
|
|
215
|
+
@total_duration = total
|
|
216
|
+
@formatted_total_duration = format_duration(total)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Get all stages in order
|
|
220
|
+
#
|
|
221
|
+
# @return [Array<StageMetrics>] all stages
|
|
222
|
+
def all_stages
|
|
223
|
+
[tool_resolution, command_building, execution, response_building].compact
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
private
|
|
227
|
+
|
|
228
|
+
def format_duration(seconds)
|
|
229
|
+
return '0ms' if seconds.zero?
|
|
230
|
+
return "#{(seconds * 1000).round(2)}ms" if seconds < 1
|
|
231
|
+
|
|
232
|
+
"#{seconds.round(2)}s"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module Models
|
|
7
|
+
# Exit code definitions for tool commands
|
|
8
|
+
#
|
|
9
|
+
# Provides machine-readable error semantics for exit codes.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# exit_codes = ExitCodes.new(
|
|
13
|
+
# standard: { '0' => 'success', '1' => 'general_error' },
|
|
14
|
+
# custom: { '3' => 'merge_conflict', '4' => 'permission_denied' }
|
|
15
|
+
# )
|
|
16
|
+
class ExitCodes < Lutaml::Model::Serializable
|
|
17
|
+
attribute :standard, :hash, default: {}
|
|
18
|
+
attribute :custom, :hash, default: {}
|
|
19
|
+
|
|
20
|
+
yaml do
|
|
21
|
+
map_element 'standard', to: :standard
|
|
22
|
+
map_element 'custom', to: :custom
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get the meaning of an exit code
|
|
26
|
+
#
|
|
27
|
+
# @param code [Integer] the exit code
|
|
28
|
+
# @return [String, nil] the meaning or nil if not defined
|
|
29
|
+
def meaning(code)
|
|
30
|
+
code_str = code.to_s
|
|
31
|
+
|
|
32
|
+
# Check custom codes first (more specific)
|
|
33
|
+
@custom&.dig(code_str) || @standard&.dig(code_str)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if an exit code is defined
|
|
37
|
+
#
|
|
38
|
+
# @param code [Integer] the exit code
|
|
39
|
+
# @return [Boolean] true if defined
|
|
40
|
+
def defined?(code)
|
|
41
|
+
!meaning(code).nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if an exit code indicates success
|
|
45
|
+
#
|
|
46
|
+
# @param code [Integer] the exit code
|
|
47
|
+
# @return [Boolean] true if success (0 or defined as success)
|
|
48
|
+
def success?(code)
|
|
49
|
+
code.zero? || meaning(code) == 'success'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get all defined exit codes
|
|
53
|
+
#
|
|
54
|
+
# @return [Hash] all codes merged (standard + custom)
|
|
55
|
+
def all_codes
|
|
56
|
+
@standard.to_h.merge(@custom.to_h)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get standard exit codes
|
|
60
|
+
#
|
|
61
|
+
# @return [Hash] standard codes
|
|
62
|
+
def standard_codes
|
|
63
|
+
@standard.to_h
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get custom exit codes
|
|
67
|
+
#
|
|
68
|
+
# @return [Hash] custom codes
|
|
69
|
+
def custom_codes
|
|
70
|
+
@custom.to_h
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module Models
|
|
7
|
+
# Flag definition for a command
|
|
8
|
+
#
|
|
9
|
+
# Represents a boolean flag (present or absent)
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# flag = FlagDefinition.new(
|
|
13
|
+
# name: 'verbose',
|
|
14
|
+
# cli: '-v',
|
|
15
|
+
# default: false,
|
|
16
|
+
# description: 'Enable verbose output'
|
|
17
|
+
# )
|
|
18
|
+
class FlagDefinition < Lutaml::Model::Serializable
|
|
19
|
+
attribute :name, :string
|
|
20
|
+
attribute :cli, :string
|
|
21
|
+
attribute :default, :boolean, default: false
|
|
22
|
+
attribute :description, :string
|
|
23
|
+
attribute :platforms, :string, collection: true, default: []
|
|
24
|
+
|
|
25
|
+
yaml do
|
|
26
|
+
map_element 'name', to: :name
|
|
27
|
+
map_element 'cli', to: :cli
|
|
28
|
+
map_element 'default', to: :default
|
|
29
|
+
map_element 'description', to: :description
|
|
30
|
+
map_element 'platforms', to: :platforms
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get the effective default value
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean] the default value
|
|
36
|
+
def default_value
|
|
37
|
+
default || false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check if flag applies to a platform
|
|
41
|
+
#
|
|
42
|
+
# @param platform [Symbol] the platform
|
|
43
|
+
# @return [Boolean] true if applies
|
|
44
|
+
def applies_to?(platform)
|
|
45
|
+
return true if platforms.nil? || platforms.empty?
|
|
46
|
+
|
|
47
|
+
cached_platforms_sym.include?(platform.to_sym)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get name as symbol (cached for performance)
|
|
51
|
+
#
|
|
52
|
+
# @return [Symbol] the name as symbol
|
|
53
|
+
def name_sym
|
|
54
|
+
@name_sym ||= name.to_sym
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Get platforms as cached symbol array
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
def cached_platforms_sym
|
|
63
|
+
@cached_platforms_sym ||= platforms&.map(&:to_sym) || []
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module Models
|
|
7
|
+
# Option definition for a command
|
|
8
|
+
#
|
|
9
|
+
# Represents a named option (flag with value)
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# opt = OptionDefinition.new(
|
|
13
|
+
# name: 'quality',
|
|
14
|
+
# cli: '-q',
|
|
15
|
+
# type: 'integer',
|
|
16
|
+
# format: 'single_dash_space',
|
|
17
|
+
# description: 'JPEG quality'
|
|
18
|
+
# )
|
|
19
|
+
class OptionDefinition < Lutaml::Model::Serializable
|
|
20
|
+
attribute :name, :string
|
|
21
|
+
attribute :cli, :string
|
|
22
|
+
attribute :type, :string, default: 'string'
|
|
23
|
+
attribute :format, :string, default: 'single_dash_space'
|
|
24
|
+
attribute :separator, :string
|
|
25
|
+
attribute :default, :string
|
|
26
|
+
# Array for numeric range [min, max]
|
|
27
|
+
attribute :range, :integer, collection: true
|
|
28
|
+
# Valid values for symbols
|
|
29
|
+
attribute :values, :string, collection: true
|
|
30
|
+
# Type of array elements
|
|
31
|
+
attribute :of, :string
|
|
32
|
+
attribute :description, :string
|
|
33
|
+
attribute :platforms, :string, collection: true, default: []
|
|
34
|
+
|
|
35
|
+
yaml do
|
|
36
|
+
map_element 'name', to: :name
|
|
37
|
+
map_element 'cli', to: :cli
|
|
38
|
+
map_element 'type', to: :type
|
|
39
|
+
map_element 'format', to: :format
|
|
40
|
+
map_element 'separator', to: :separator
|
|
41
|
+
map_element 'default', to: :default
|
|
42
|
+
map_element 'range', to: :range
|
|
43
|
+
map_element 'values', to: :values
|
|
44
|
+
map_element 'of', to: :of
|
|
45
|
+
map_element 'description', to: :description
|
|
46
|
+
map_element 'platforms', to: :platforms
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Check if option applies to a platform
|
|
50
|
+
#
|
|
51
|
+
# @param platform [Symbol] the platform
|
|
52
|
+
# @return [Boolean] true if applies
|
|
53
|
+
def applies_to?(platform)
|
|
54
|
+
return true if platforms.nil? || platforms.empty?
|
|
55
|
+
|
|
56
|
+
cached_platforms_sym.include?(platform.to_sym)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check if type is boolean
|
|
60
|
+
#
|
|
61
|
+
# @return [Boolean] true if boolean type
|
|
62
|
+
def boolean?
|
|
63
|
+
type == 'boolean'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get format as symbol (cached for performance)
|
|
67
|
+
#
|
|
68
|
+
# @return [Symbol] the format
|
|
69
|
+
def format_sym
|
|
70
|
+
@format_sym ||= format&.to_sym || :single_dash_space
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Hash-like access for Type validation compatibility
|
|
74
|
+
#
|
|
75
|
+
# @param key [Symbol, String] the attribute key
|
|
76
|
+
# @return [Object] the attribute value
|
|
77
|
+
def [](key)
|
|
78
|
+
key_sym = key.to_sym
|
|
79
|
+
# Return nil for unknown keys (like Type validation options)
|
|
80
|
+
return nil unless respond_to?(key_sym, true)
|
|
81
|
+
|
|
82
|
+
send(key_sym)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get name as symbol (cached for performance)
|
|
86
|
+
#
|
|
87
|
+
# @return [Symbol] the name as symbol
|
|
88
|
+
def name_sym
|
|
89
|
+
@name_sym ||= name.to_sym
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Get platforms as cached symbol array
|
|
95
|
+
#
|
|
96
|
+
# @api private
|
|
97
|
+
def cached_platforms_sym
|
|
98
|
+
@cached_platforms_sym ||= platforms&.map(&:to_sym) || []
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module Models
|
|
7
|
+
# Command output information
|
|
8
|
+
#
|
|
9
|
+
# Contains the standard output and error output from command execution.
|
|
10
|
+
class OutputInfo < Lutaml::Model::Serializable
|
|
11
|
+
attribute :stdout, :string, default: ''
|
|
12
|
+
attribute :stderr, :string, default: ''
|
|
13
|
+
|
|
14
|
+
yaml do
|
|
15
|
+
map_element 'stdout', to: :stdout
|
|
16
|
+
map_element 'stderr', to: :stderr
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
json do
|
|
20
|
+
map 'stdout', to: :stdout
|
|
21
|
+
map 'stderr', to: :stderr
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lutaml/model'
|
|
4
|
+
require_relative 'command_definition'
|
|
5
|
+
require_relative 'routing'
|
|
6
|
+
require_relative 'exit_codes'
|
|
7
|
+
|
|
8
|
+
module Ukiryu
|
|
9
|
+
module Models
|
|
10
|
+
# Platform-specific profile for a tool
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# profile = PlatformProfile.new(
|
|
14
|
+
# name: 'default',
|
|
15
|
+
# platforms: [:macos, :linux],
|
|
16
|
+
# commands: [CommandDefinition.new(...)]
|
|
17
|
+
# )
|
|
18
|
+
class PlatformProfile < Lutaml::Model::Serializable
|
|
19
|
+
attribute :name, :string
|
|
20
|
+
attribute :display_name, :string
|
|
21
|
+
attribute :platforms, :string, collection: true, default: []
|
|
22
|
+
attribute :shells, :string, collection: true, default: []
|
|
23
|
+
attribute :option_style, :string, default: 'single_dash_space'
|
|
24
|
+
attribute :commands, CommandDefinition, collection: true, initialize_empty: true
|
|
25
|
+
attribute :inherits, :string
|
|
26
|
+
attribute :routing_data, :hash, default: {} # Raw routing from YAML
|
|
27
|
+
attribute :version_requirement, :string # Semantic version requirement (e.g., ">= 2.30")
|
|
28
|
+
attribute :exit_codes, ExitCodes # Exit code definitions for this profile
|
|
29
|
+
|
|
30
|
+
yaml do
|
|
31
|
+
map_element 'name', to: :name
|
|
32
|
+
map_element 'display_name', to: :display_name
|
|
33
|
+
map_element 'platforms', to: :platforms
|
|
34
|
+
map_element 'shells', to: :shells
|
|
35
|
+
map_element 'option_style', to: :option_style
|
|
36
|
+
map_element 'commands', to: :commands
|
|
37
|
+
map_element 'inherits', to: :inherits
|
|
38
|
+
map_element 'routing', to: :routing_data
|
|
39
|
+
map_element 'version_requirement', to: :version_requirement
|
|
40
|
+
map_element 'exit_codes', to: :exit_codes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the routing table as a Routing model
|
|
44
|
+
#
|
|
45
|
+
# @return [Routing] the routing table
|
|
46
|
+
def routing
|
|
47
|
+
@routing ||= Routing.new(@routing_data || {})
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if this profile has routing defined
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean] true if routing table is non-empty
|
|
53
|
+
def routing?
|
|
54
|
+
!@routing_data.nil? && !@routing_data.empty?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Check if profile supports a platform
|
|
58
|
+
#
|
|
59
|
+
# @param platform [Symbol] the platform
|
|
60
|
+
# @return [Boolean] true if supported
|
|
61
|
+
def supports_platform?(platform)
|
|
62
|
+
platform_list = cached_platforms_sym
|
|
63
|
+
platform_list.nil? || platform_list.empty? ||
|
|
64
|
+
platform_list.include?(platform.to_sym)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check if profile supports a shell
|
|
68
|
+
#
|
|
69
|
+
# @param shell [Symbol] the shell
|
|
70
|
+
# @return [Boolean] true if supported
|
|
71
|
+
def supports_shell?(shell)
|
|
72
|
+
shell_list = cached_shells_sym
|
|
73
|
+
shell_list.nil? || shell_list.empty? ||
|
|
74
|
+
shell_list.include?(shell.to_sym)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check if profile is compatible with platform and shell
|
|
78
|
+
#
|
|
79
|
+
# @param platform [Symbol] the platform
|
|
80
|
+
# @param shell [Symbol] the shell
|
|
81
|
+
# @return [Boolean] true if compatible
|
|
82
|
+
def compatible?(platform, shell)
|
|
83
|
+
supports_platform?(platform) && supports_shell?(shell)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get a command by name using indexed O(1) lookup
|
|
87
|
+
#
|
|
88
|
+
# @param name [String, Symbol] the command name
|
|
89
|
+
# @return [CommandDefinition, nil] the command
|
|
90
|
+
def command(name)
|
|
91
|
+
return nil unless commands
|
|
92
|
+
|
|
93
|
+
build_commands_index unless @commands_index_built
|
|
94
|
+
@commands_index[name.to_s]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Get all command names
|
|
98
|
+
#
|
|
99
|
+
# @return [Array<String>] command names
|
|
100
|
+
def command_names
|
|
101
|
+
return [] unless commands
|
|
102
|
+
|
|
103
|
+
build_commands_index unless @commands_index_built
|
|
104
|
+
@commands_index.keys
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Check if universal (supports all)
|
|
108
|
+
#
|
|
109
|
+
# @return [Boolean] true if universal
|
|
110
|
+
def universal?
|
|
111
|
+
(platforms.nil? || platforms.empty?) &&
|
|
112
|
+
(shells.nil? || shells.empty?)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Clear the commands index
|
|
116
|
+
#
|
|
117
|
+
# Call this if commands are modified after initial loading
|
|
118
|
+
# (e.g., during inheritance resolution)
|
|
119
|
+
#
|
|
120
|
+
# @api private
|
|
121
|
+
def clear_commands_index!
|
|
122
|
+
@commands_index = nil
|
|
123
|
+
@commands_index_built = false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
# Get platforms as cached symbol array
|
|
129
|
+
#
|
|
130
|
+
# @api private
|
|
131
|
+
def cached_platforms_sym
|
|
132
|
+
@cached_platforms_sym ||= platforms&.map(&:to_sym)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Get shells as cached symbol array
|
|
136
|
+
#
|
|
137
|
+
# @api private
|
|
138
|
+
def cached_shells_sym
|
|
139
|
+
@cached_shells_sym ||= shells&.map(&:to_sym)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Build the commands index hash for O(1) lookup
|
|
143
|
+
#
|
|
144
|
+
# @api private
|
|
145
|
+
def build_commands_index
|
|
146
|
+
return unless commands
|
|
147
|
+
|
|
148
|
+
@commands_index = commands.to_h { |c| [c.name, c] }
|
|
149
|
+
@commands_index_built = true
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|