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,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../registry'
|
|
5
|
+
require_relative '../models/validation_result'
|
|
6
|
+
|
|
7
|
+
module Ukiryu
|
|
8
|
+
module CliCommands
|
|
9
|
+
# Schema validation command for tool profiles
|
|
10
|
+
class ValidateCommand < BaseCommand
|
|
11
|
+
# Execute the validate command
|
|
12
|
+
#
|
|
13
|
+
# @param tool_name [String, nil] the tool name (nil to validate all)
|
|
14
|
+
# @param options [Hash] command options
|
|
15
|
+
def run(tool_name = nil)
|
|
16
|
+
setup_registry
|
|
17
|
+
|
|
18
|
+
if tool_name
|
|
19
|
+
validate_single_tool(tool_name)
|
|
20
|
+
else
|
|
21
|
+
validate_all_tools
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Validate a single tool
|
|
28
|
+
#
|
|
29
|
+
# @param tool_name [String] the tool name
|
|
30
|
+
def validate_single_tool(tool_name)
|
|
31
|
+
result = Registry.validate_tool(tool_name, registry_path: config.registry)
|
|
32
|
+
|
|
33
|
+
say "Validating tool: #{tool_name}", :cyan
|
|
34
|
+
say '', :clear
|
|
35
|
+
|
|
36
|
+
if result.valid?
|
|
37
|
+
say result.status_message, :green
|
|
38
|
+
else
|
|
39
|
+
say result.status_message, :red
|
|
40
|
+
say '', :clear
|
|
41
|
+
result.errors.each do |error|
|
|
42
|
+
say " - #{error}", :white
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Exit with error code if validation failed
|
|
47
|
+
exit(result.invalid? ? 1 : 0)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Validate all tools
|
|
51
|
+
def validate_all_tools
|
|
52
|
+
results = Registry.validate_all_tools(registry_path: config.registry)
|
|
53
|
+
|
|
54
|
+
say "Validating all tools in registry: #{config.registry}", :cyan
|
|
55
|
+
say '', :clear
|
|
56
|
+
|
|
57
|
+
valid_count = 0
|
|
58
|
+
invalid_count = 0
|
|
59
|
+
not_found_count = 0
|
|
60
|
+
|
|
61
|
+
results.each do |result|
|
|
62
|
+
say "#{result.tool_name.ljust(20)}: #{result.status_message}", result.valid? ? :green : :red
|
|
63
|
+
|
|
64
|
+
valid_count += 1 if result.valid?
|
|
65
|
+
invalid_count += 1 if result.invalid? && !result.not_found?
|
|
66
|
+
not_found_count += 1 if result.not_found?
|
|
67
|
+
|
|
68
|
+
# Show errors for invalid tools
|
|
69
|
+
if result.invalid? && !result.not_found?
|
|
70
|
+
result.errors.each do |error|
|
|
71
|
+
say " - #{error}", :dim
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
say '', :clear
|
|
77
|
+
say "Summary:", :cyan
|
|
78
|
+
say " Valid: #{valid_count}", :green
|
|
79
|
+
say " Invalid: #{invalid_count}", invalid_count > 0 ? :red : :white
|
|
80
|
+
say " Missing: #{not_found_count}", not_found_count > 0 ? :yellow : :white
|
|
81
|
+
|
|
82
|
+
# Exit with error code if any validations failed
|
|
83
|
+
exit(invalid_count > 0 ? 1 : 0)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../version'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Show Ukiryu version
|
|
9
|
+
class VersionCommand < BaseCommand
|
|
10
|
+
# Execute the version command
|
|
11
|
+
def run
|
|
12
|
+
say "Ukiryu version #{Ukiryu::VERSION}", :cyan
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_command'
|
|
4
|
+
require_relative '../tool'
|
|
5
|
+
require_relative '../platform'
|
|
6
|
+
require_relative '../runtime'
|
|
7
|
+
|
|
8
|
+
module Ukiryu
|
|
9
|
+
module CliCommands
|
|
10
|
+
# Show which tool implementation would be selected
|
|
11
|
+
class WhichCommand < BaseCommand
|
|
12
|
+
# Execute the which command
|
|
13
|
+
#
|
|
14
|
+
# @param identifier [String] the tool name, interface, or alias
|
|
15
|
+
def run(identifier)
|
|
16
|
+
setup_registry
|
|
17
|
+
|
|
18
|
+
runtime = Runtime.instance
|
|
19
|
+
platform = options[:platform] || runtime.platform
|
|
20
|
+
shell = options[:shell] || runtime.shell
|
|
21
|
+
|
|
22
|
+
say '', :clear
|
|
23
|
+
say "Resolving: #{identifier}", :cyan
|
|
24
|
+
say " Platform: #{platform}", :white
|
|
25
|
+
say " Shell: #{shell}", :white
|
|
26
|
+
say '', :clear
|
|
27
|
+
|
|
28
|
+
# First try exact name match
|
|
29
|
+
tool = try_exact_match(identifier, platform, shell)
|
|
30
|
+
|
|
31
|
+
# If not found, try interface/alias discovery
|
|
32
|
+
tool ||= try_interface_discovery(identifier, platform, shell)
|
|
33
|
+
|
|
34
|
+
if tool
|
|
35
|
+
show_selected_tool(tool, identifier, platform, shell)
|
|
36
|
+
else
|
|
37
|
+
error! "No tool found for: #{identifier}\nAvailable tools: #{Registry.tools.sort.join(', ')}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# Try exact name match
|
|
44
|
+
#
|
|
45
|
+
# @param identifier [String] the tool name
|
|
46
|
+
# @param platform [Symbol] the platform
|
|
47
|
+
# @param shell [Symbol] the shell
|
|
48
|
+
# @return [Tool, nil] the tool or nil if not found
|
|
49
|
+
def try_exact_match(identifier, platform, shell)
|
|
50
|
+
tool = Tool.get(identifier, platform: platform, shell: shell)
|
|
51
|
+
say 'Match type: Exact name match', :green
|
|
52
|
+
tool
|
|
53
|
+
rescue Ukiryu::ToolNotFoundError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Try interface/alias discovery
|
|
58
|
+
#
|
|
59
|
+
# @param identifier [String] the tool name
|
|
60
|
+
# @param platform [Symbol] the platform
|
|
61
|
+
# @param shell [Symbol] the shell
|
|
62
|
+
# @return [Tool, nil] the tool or nil if not found
|
|
63
|
+
def try_interface_discovery(identifier, platform, shell)
|
|
64
|
+
require_relative '../registry'
|
|
65
|
+
|
|
66
|
+
candidates = []
|
|
67
|
+
|
|
68
|
+
Registry.tools.each do |tool_name|
|
|
69
|
+
tool_metadata = Registry.load_tool_metadata(tool_name.to_sym)
|
|
70
|
+
next unless tool_metadata
|
|
71
|
+
|
|
72
|
+
# Check for interface match
|
|
73
|
+
interface_match = tool_metadata.implements == identifier.to_sym
|
|
74
|
+
|
|
75
|
+
# Check for alias match
|
|
76
|
+
alias_match = tool_metadata.aliases&.include?(identifier)
|
|
77
|
+
|
|
78
|
+
next unless interface_match || alias_match
|
|
79
|
+
|
|
80
|
+
# Check platform compatibility
|
|
81
|
+
profile = find_compatible_profile(tool_metadata, platform, shell)
|
|
82
|
+
next unless profile
|
|
83
|
+
|
|
84
|
+
match_type = interface_match ? 'interface' : 'alias'
|
|
85
|
+
candidates << {
|
|
86
|
+
name: tool_name,
|
|
87
|
+
metadata: tool_metadata,
|
|
88
|
+
profile: profile,
|
|
89
|
+
match_type: match_type
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
return nil if candidates.empty?
|
|
94
|
+
|
|
95
|
+
# Select best candidate (prefer available tools)
|
|
96
|
+
selected = candidates.find do |c|
|
|
97
|
+
Tool.get(c[:name], platform: platform, shell: shell).available?
|
|
98
|
+
end || candidates.first
|
|
99
|
+
|
|
100
|
+
say "Match type: #{selected[:match_type]} match", :green
|
|
101
|
+
|
|
102
|
+
Tool.get(selected[:name], platform: platform, shell: shell)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Find compatible profile for platform/shell
|
|
106
|
+
#
|
|
107
|
+
# @param metadata [ToolMetadata] the tool metadata
|
|
108
|
+
# @param platform [Symbol] the platform
|
|
109
|
+
# @param shell [Symbol] the shell
|
|
110
|
+
# @return [Hash, nil] compatible profile or nil
|
|
111
|
+
def find_compatible_profile(metadata, platform, shell)
|
|
112
|
+
require_relative '../tools/generator'
|
|
113
|
+
|
|
114
|
+
tool_def = Tools::Generator.load_tool_definition(metadata.name)
|
|
115
|
+
return nil unless tool_def
|
|
116
|
+
|
|
117
|
+
tool_def.compatible_profile(platform: platform, shell: shell)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Show selected tool information
|
|
121
|
+
#
|
|
122
|
+
# @param tool [Tool] the selected tool
|
|
123
|
+
# @param identifier [String] the original identifier
|
|
124
|
+
# @param platform [Symbol] the platform
|
|
125
|
+
# @param shell [Symbol] the shell
|
|
126
|
+
def show_selected_tool(tool, identifier, _platform, _shell)
|
|
127
|
+
say '', :clear
|
|
128
|
+
say 'Selected tool:', :yellow
|
|
129
|
+
|
|
130
|
+
if tool.name != identifier
|
|
131
|
+
say " Query: #{identifier}", :white
|
|
132
|
+
say " Resolved to: #{tool.name}", :white
|
|
133
|
+
else
|
|
134
|
+
say " Tool: #{tool.name}", :white
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Show implementation info
|
|
138
|
+
say " Implements: #{tool.profile.implements}", :white if tool.profile.implements
|
|
139
|
+
|
|
140
|
+
# Show profile used
|
|
141
|
+
profile = tool.instance_variable_get(:@command_profile)
|
|
142
|
+
if profile
|
|
143
|
+
say " Profile: #{profile.name || 'default'}", :white
|
|
144
|
+
|
|
145
|
+
# Show profile details
|
|
146
|
+
platforms = profile.platforms || ['all']
|
|
147
|
+
shells = profile.shells || ['all']
|
|
148
|
+
say " Platforms: #{Array(platforms).join(', ')}", :dim
|
|
149
|
+
say " Shells: #{Array(shells).join(', ')}", :dim
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Show availability
|
|
153
|
+
say '', :clear
|
|
154
|
+
if tool.available?
|
|
155
|
+
say 'Status: AVAILABLE', :green
|
|
156
|
+
say " Executable: #{tool.executable}", :white
|
|
157
|
+
detected_version = tool.version
|
|
158
|
+
say " Version: #{detected_version || 'unknown'}", :white if detected_version
|
|
159
|
+
else
|
|
160
|
+
say 'Status: NOT AVAILABLE', :red
|
|
161
|
+
say ' Tool is not installed or not in PATH', :dim
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'type'
|
|
4
|
+
require_relative 'shell'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
# CommandBuilder module provides shared command building functionality.
|
|
8
|
+
#
|
|
9
|
+
# This module contains methods for building command-line arguments from
|
|
10
|
+
# command definitions and parameters. It is used by both Tool and
|
|
11
|
+
# Tools::Base to eliminate code duplication.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
module CommandBuilder
|
|
15
|
+
# Build command arguments from parameters
|
|
16
|
+
#
|
|
17
|
+
# @param command [Models::CommandDefinition] the command definition
|
|
18
|
+
# @param params [Hash] the parameters hash
|
|
19
|
+
# @return [Array<String>] the formatted command arguments
|
|
20
|
+
def build_args(command, params)
|
|
21
|
+
args = []
|
|
22
|
+
|
|
23
|
+
# Add subcommand prefix if present (e.g., for ImageMagick "magick convert")
|
|
24
|
+
args << command.subcommand if command.subcommand
|
|
25
|
+
|
|
26
|
+
# Add options first (before arguments)
|
|
27
|
+
command.options&.each do |opt_def|
|
|
28
|
+
param_key = opt_def.name_sym
|
|
29
|
+
next unless params.key?(param_key)
|
|
30
|
+
next if params[param_key].nil?
|
|
31
|
+
|
|
32
|
+
formatted_opt = format_option(opt_def, params[param_key])
|
|
33
|
+
Array(formatted_opt).each { |opt| args << opt unless opt.nil? || opt.empty? }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Add flags
|
|
37
|
+
command.flags&.each do |flag_def|
|
|
38
|
+
param_key = flag_def.name_sym
|
|
39
|
+
value = params[param_key]
|
|
40
|
+
value = flag_def.default if value.nil?
|
|
41
|
+
|
|
42
|
+
formatted_flag = format_flag(flag_def, value)
|
|
43
|
+
Array(formatted_flag).each { |flag| args << flag unless flag.nil? || flag.empty? }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Separate "last" positioned argument from other arguments
|
|
47
|
+
arguments = command.arguments || []
|
|
48
|
+
last_arg = arguments.find(&:last?)
|
|
49
|
+
regular_args = arguments.reject(&:last?)
|
|
50
|
+
|
|
51
|
+
# Add regular positional arguments (in order, excluding "last")
|
|
52
|
+
regular_args.sort_by(&:numeric_position).each do |arg_def|
|
|
53
|
+
param_key = arg_def.name_sym
|
|
54
|
+
next unless params.key?(param_key)
|
|
55
|
+
|
|
56
|
+
value = params[param_key]
|
|
57
|
+
next if value.nil?
|
|
58
|
+
|
|
59
|
+
if arg_def.variadic
|
|
60
|
+
# Variadic argument - expand array
|
|
61
|
+
array = Type.validate(value, :array, arg_def)
|
|
62
|
+
array.each { |v| args << format_arg(v, arg_def) }
|
|
63
|
+
else
|
|
64
|
+
args << format_arg(value, arg_def)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Add post_options (options that come before the "last" argument)
|
|
69
|
+
command.post_options&.each do |opt_def|
|
|
70
|
+
param_key = opt_def.name_sym
|
|
71
|
+
next unless params.key?(param_key)
|
|
72
|
+
next if params[param_key].nil?
|
|
73
|
+
|
|
74
|
+
formatted_opt = format_option(opt_def, params[param_key])
|
|
75
|
+
Array(formatted_opt).each { |opt| args << opt unless opt.nil? || opt.empty? }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Add the "last" positioned argument (typically output file)
|
|
79
|
+
if last_arg
|
|
80
|
+
param_key = last_arg.name_sym
|
|
81
|
+
if params.key?(param_key) && !params[param_key].nil?
|
|
82
|
+
if last_arg.variadic
|
|
83
|
+
array = Type.validate(params[param_key], :array, last_arg)
|
|
84
|
+
array.each { |v| args << format_arg(v, last_arg) }
|
|
85
|
+
else
|
|
86
|
+
args << format_arg(params[param_key], last_arg)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
args
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Format a positional argument
|
|
95
|
+
#
|
|
96
|
+
# @param value [Object] the argument value
|
|
97
|
+
# @param arg_def [Models::ArgumentDefinition] the argument definition
|
|
98
|
+
# @return [String] the formatted argument
|
|
99
|
+
def format_arg(value, arg_def)
|
|
100
|
+
# Validate type
|
|
101
|
+
Type.validate(value, arg_def.type || :string, arg_def)
|
|
102
|
+
|
|
103
|
+
# Apply platform-specific path formatting
|
|
104
|
+
if arg_def.type == :file
|
|
105
|
+
shell_class = Shell.class_for(@shell)
|
|
106
|
+
shell_class.new.format_path(value.to_s)
|
|
107
|
+
else
|
|
108
|
+
value.to_s
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Format an option
|
|
113
|
+
#
|
|
114
|
+
# @param opt_def [Models::OptionDefinition] the option definition
|
|
115
|
+
# @param value [Object] the option value
|
|
116
|
+
# @return [String, Array<String>] the formatted option(s)
|
|
117
|
+
def format_option(opt_def, value)
|
|
118
|
+
# Validate type
|
|
119
|
+
Type.validate(value, opt_def.type || :string, opt_def)
|
|
120
|
+
|
|
121
|
+
# Handle boolean types - just return the CLI flag (no value)
|
|
122
|
+
type_val = opt_def.type
|
|
123
|
+
if [:boolean, TrueClass, 'boolean'].include?(type_val)
|
|
124
|
+
return nil if value.nil? || value == false
|
|
125
|
+
|
|
126
|
+
return opt_def.cli || ''
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
cli = opt_def.cli || ''
|
|
130
|
+
format_sym = opt_def.format_sym
|
|
131
|
+
separator = opt_def.separator || '='
|
|
132
|
+
|
|
133
|
+
# Convert value to string (handle symbols)
|
|
134
|
+
value_str = value.is_a?(Symbol) ? value.to_s : value.to_s
|
|
135
|
+
|
|
136
|
+
# Handle array values with separator
|
|
137
|
+
if value.is_a?(Array) && separator
|
|
138
|
+
joined = value.join(separator)
|
|
139
|
+
case format_sym
|
|
140
|
+
when :double_dash_equals
|
|
141
|
+
"#{cli}#{joined}"
|
|
142
|
+
when :double_dash_space, :single_dash_space
|
|
143
|
+
[cli, joined] # Return array for space-separated
|
|
144
|
+
when :single_dash_equals
|
|
145
|
+
"#{cli}#{joined}"
|
|
146
|
+
else
|
|
147
|
+
"#{cli}#{joined}"
|
|
148
|
+
end
|
|
149
|
+
else
|
|
150
|
+
case format_sym
|
|
151
|
+
when :double_dash_equals
|
|
152
|
+
"#{cli}#{separator}#{value_str}"
|
|
153
|
+
when :double_dash_space, :single_dash_space
|
|
154
|
+
[cli, value_str] # Return array for space-separated
|
|
155
|
+
when :single_dash_equals
|
|
156
|
+
"#{cli}#{separator}#{value_str}"
|
|
157
|
+
when :slash_colon
|
|
158
|
+
"#{cli}:#{value_str}"
|
|
159
|
+
when :slash_space
|
|
160
|
+
"#{cli} #{value_str}"
|
|
161
|
+
else
|
|
162
|
+
"#{cli}#{separator}#{value_str}"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Format a flag
|
|
168
|
+
#
|
|
169
|
+
# @param flag_def [Models::FlagDefinition] the flag definition
|
|
170
|
+
# @param value [Object] the flag value
|
|
171
|
+
# @return [String, nil] the formatted flag
|
|
172
|
+
def format_flag(flag_def, value)
|
|
173
|
+
return nil if value.nil? || value == false
|
|
174
|
+
|
|
175
|
+
flag_def.cli || ''
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Build environment variables for command
|
|
179
|
+
#
|
|
180
|
+
# @param command [Models::CommandDefinition] the command definition
|
|
181
|
+
# @param params [Hash] the parameters hash
|
|
182
|
+
# @return [Hash] the environment variables hash
|
|
183
|
+
def build_env_vars(command, params)
|
|
184
|
+
env_vars = {}
|
|
185
|
+
|
|
186
|
+
command.env_vars&.each do |ev|
|
|
187
|
+
# Check platform restriction
|
|
188
|
+
platforms = ev.platforms || []
|
|
189
|
+
next if platforms.any? && !platforms.map(&:to_sym).include?(@platform)
|
|
190
|
+
|
|
191
|
+
# Get value - use ev.value if provided, or extract from params
|
|
192
|
+
value = if ev.value
|
|
193
|
+
ev.value
|
|
194
|
+
elsif ev.env_var
|
|
195
|
+
params[ev.env_var.to_sym]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Set the environment variable if value is defined (including empty string)
|
|
199
|
+
env_vars[ev.name] = value.to_s unless value.nil?
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
env_vars
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'env_schema'
|
|
4
|
+
require_relative 'type_converter'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
class Config
|
|
8
|
+
# Provides environment variable values for configuration
|
|
9
|
+
# Reads and parses UKIRYU_* environment variables and standard env vars like NO_COLOR
|
|
10
|
+
class EnvProvider
|
|
11
|
+
class << self
|
|
12
|
+
# Load all environment overrides
|
|
13
|
+
def load_all
|
|
14
|
+
result = {}
|
|
15
|
+
|
|
16
|
+
# Load UKIRYU_* environment variables
|
|
17
|
+
EnvSchema.all_attributes.each do |attr|
|
|
18
|
+
env_key = EnvSchema.env_key(attr)
|
|
19
|
+
value = ENV[env_key]
|
|
20
|
+
|
|
21
|
+
# Convert and store if value exists
|
|
22
|
+
result[attr] = TypeConverter.convert(attr, value) if value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Handle NO_COLOR standard environment variable (https://no-color.org/)
|
|
26
|
+
# NO_COLOR takes precedence over UKIRYU_USE_COLOR
|
|
27
|
+
# When set (to any value), colors are disabled
|
|
28
|
+
result[:use_color] = false if ENV['NO_COLOR']
|
|
29
|
+
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Load execution-specific environment overrides
|
|
34
|
+
def load_execution
|
|
35
|
+
load_attributes(EnvSchema.all_execution_attributes)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Load output-specific environment overrides
|
|
39
|
+
def load_output
|
|
40
|
+
load_attributes(EnvSchema.all_output_attributes)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Load registry-specific environment overrides
|
|
44
|
+
def load_registry
|
|
45
|
+
load_attributes(EnvSchema.all_registry_attributes)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def load_attributes(attributes)
|
|
51
|
+
result = {}
|
|
52
|
+
attributes.each do |attr|
|
|
53
|
+
env_key = EnvSchema.env_key(attr)
|
|
54
|
+
value = ENV[env_key]
|
|
55
|
+
|
|
56
|
+
# Convert and store if value exists
|
|
57
|
+
result[attr] = TypeConverter.convert(attr, value) if value
|
|
58
|
+
end
|
|
59
|
+
result
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ukiryu
|
|
4
|
+
class Config
|
|
5
|
+
# Schema definition for configuration attributes
|
|
6
|
+
# Defines attribute types and ENV variable mappings
|
|
7
|
+
class EnvSchema
|
|
8
|
+
ATTRIBUTE_TYPES = {
|
|
9
|
+
# Execution options
|
|
10
|
+
timeout: :integer,
|
|
11
|
+
debug: :boolean,
|
|
12
|
+
dry_run: :boolean,
|
|
13
|
+
metrics: :boolean,
|
|
14
|
+
shell: :symbol,
|
|
15
|
+
|
|
16
|
+
# Output options
|
|
17
|
+
format: :symbol,
|
|
18
|
+
output: :string,
|
|
19
|
+
|
|
20
|
+
# Registry options
|
|
21
|
+
registry: :string,
|
|
22
|
+
|
|
23
|
+
# Tool discovery options
|
|
24
|
+
search_paths: :string, # Comma-separated paths
|
|
25
|
+
|
|
26
|
+
# Color options
|
|
27
|
+
use_color: :boolean
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def type_for(attribute)
|
|
32
|
+
ATTRIBUTE_TYPES[attribute.to_sym]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Generate ENV key for a config attribute
|
|
36
|
+
# e.g., env_key(:timeout) => "UKIRYU_TIMEOUT"
|
|
37
|
+
def env_key(attribute)
|
|
38
|
+
"UKIRYU_#{attribute.to_s.upcase}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# All execution attributes
|
|
42
|
+
def all_execution_attributes
|
|
43
|
+
%i[timeout debug dry_run metrics shell]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# All output attributes
|
|
47
|
+
def all_output_attributes
|
|
48
|
+
%i[format output use_color]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# All registry attributes
|
|
52
|
+
def all_registry_attributes
|
|
53
|
+
%i[registry search_paths]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# All attributes
|
|
57
|
+
def all_attributes
|
|
58
|
+
%i[timeout debug dry_run metrics shell format output registry search_paths use_color]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ukiryu
|
|
4
|
+
class Config
|
|
5
|
+
# Resolves configuration values using priority chain
|
|
6
|
+
# Priority: CLI options > ENV > programmatic > defaults
|
|
7
|
+
class OverrideResolver
|
|
8
|
+
attr_reader :defaults, :programmatic, :env, :cli
|
|
9
|
+
|
|
10
|
+
def initialize(defaults: {}, programmatic: {}, env: {}, cli: {})
|
|
11
|
+
@defaults = defaults
|
|
12
|
+
@programmatic = programmatic
|
|
13
|
+
@env = env
|
|
14
|
+
@cli = cli
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Resolve a single value using priority chain
|
|
18
|
+
# Uses .key? to properly handle false values
|
|
19
|
+
def resolve(key)
|
|
20
|
+
return @cli[key] if @cli.key?(key)
|
|
21
|
+
return @env[key] if @env.key?(key)
|
|
22
|
+
return @programmatic[key] if @programmatic.key?(key)
|
|
23
|
+
|
|
24
|
+
@defaults[key]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Update programmatic value
|
|
28
|
+
def set_programmatic(key, value)
|
|
29
|
+
@programmatic[key] = value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Update CLI option value
|
|
33
|
+
def set_cli(key, value)
|
|
34
|
+
@cli[key] = value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Update ENV override
|
|
38
|
+
def set_env(key, value)
|
|
39
|
+
@env[key] = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if value is set by CLI
|
|
43
|
+
def cli_set?(key)
|
|
44
|
+
@cli.key?(key)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check if value is set by ENV
|
|
48
|
+
def env_set?(key)
|
|
49
|
+
@env.key?(key)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Check if value is set programmatically
|
|
53
|
+
def programmatic_set?(key)
|
|
54
|
+
@programmatic.key?(key)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get the source of a value
|
|
58
|
+
def source_for(key)
|
|
59
|
+
return :cli if @cli.key?(key)
|
|
60
|
+
return :env if @env.key?(key)
|
|
61
|
+
return :programmatic if @programmatic.key?(key)
|
|
62
|
+
return :default if @defaults.key?(key)
|
|
63
|
+
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|