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,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ukiryu
|
|
4
|
+
# Configures Thor to behave more like a typical modern CLI.
|
|
5
|
+
#
|
|
6
|
+
# Features:
|
|
7
|
+
# - Passing -h or --help to a command will show help for that command
|
|
8
|
+
# - Unrecognized options will be treated as errors (not silently ignored)
|
|
9
|
+
# - Error messages are printed to stderr in red, without stack trace
|
|
10
|
+
# - Full stack traces can be enabled with VERBOSE environment variable
|
|
11
|
+
# - Errors cause Thor to exit with non-zero status
|
|
12
|
+
# - Missing required arguments show help instead of errors
|
|
13
|
+
#
|
|
14
|
+
# @example Extend your CLI with this module
|
|
15
|
+
# class Cli < Thor
|
|
16
|
+
# extend FriendlyCLI
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# Start your CLI with:
|
|
20
|
+
# Cli.start
|
|
21
|
+
#
|
|
22
|
+
# In tests, prevent Kernel.exit from being called:
|
|
23
|
+
# Cli.start(args, exit_on_failure: false)
|
|
24
|
+
module FriendlyCLI
|
|
25
|
+
# Regex patterns for error message parsing
|
|
26
|
+
MISSING_ARGS_PATTERN = /"(\w+) (\w+)"/.freeze
|
|
27
|
+
HELP_OPTIONS = %w[-h --help].freeze
|
|
28
|
+
|
|
29
|
+
# Environment variables for trace mode
|
|
30
|
+
TRACE_ENV_VARS = %w[UKIRYU_TRACE VERBOSE].freeze
|
|
31
|
+
|
|
32
|
+
def self.extended(base)
|
|
33
|
+
super
|
|
34
|
+
base.check_unknown_options!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Override Thor's start method to provide better CLI behavior
|
|
38
|
+
#
|
|
39
|
+
# @param given_args [Array<String>] the command-line arguments
|
|
40
|
+
# @param config [Hash] configuration options
|
|
41
|
+
# @option config [Thor::Shell] :shell the Thor shell instance
|
|
42
|
+
# @option config [Boolean] :exit_on_failure whether to exit on errors (default: true)
|
|
43
|
+
def start(given_args = ARGV, config = {})
|
|
44
|
+
config[:shell] ||= Thor::Base.shell.new
|
|
45
|
+
|
|
46
|
+
handle_help_switches(given_args) do |args|
|
|
47
|
+
dispatch(nil, args, nil, config)
|
|
48
|
+
end
|
|
49
|
+
rescue StandardError, Exception => e
|
|
50
|
+
handle_exception_on_start(e, config)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Override Thor's handle_argument_error to show help for missing arguments
|
|
54
|
+
#
|
|
55
|
+
# @param command [Thor::Command] the Thor command object
|
|
56
|
+
# @param error [Exception] the error that was raised
|
|
57
|
+
# @param _args [Array] the arguments that were passed (unused)
|
|
58
|
+
# @param _arity [Integer] the arity of the command (unused)
|
|
59
|
+
def handle_argument_error(command, error, _args, _arity)
|
|
60
|
+
return show_help_for_command(error) if missing_arguments_error?(error)
|
|
61
|
+
return handle_argument_count_error(command, error) if wrong_argument_count_error?(error)
|
|
62
|
+
|
|
63
|
+
# Otherwise, handle as normal error
|
|
64
|
+
handle_exception_on_start(error, {})
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Check if error indicates missing arguments
|
|
70
|
+
#
|
|
71
|
+
# @param error [Exception] the error to check
|
|
72
|
+
# @return [Boolean] true if this is a missing arguments error
|
|
73
|
+
def missing_arguments_error?(error)
|
|
74
|
+
error.message.include?('was called with no arguments') &&
|
|
75
|
+
error.message.match?(MISSING_ARGS_PATTERN)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if error indicates wrong argument count
|
|
79
|
+
#
|
|
80
|
+
# @param error [Exception] the error to check
|
|
81
|
+
# @return [Boolean] true if this is a wrong argument count error
|
|
82
|
+
def wrong_argument_count_error?(error)
|
|
83
|
+
error.is_a?(ArgumentError) && error.message.include?('wrong number of arguments')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Show help for a command when arguments are missing
|
|
87
|
+
#
|
|
88
|
+
# @param error [Exception] the error containing the command name
|
|
89
|
+
def show_help_for_command(error)
|
|
90
|
+
return unless (match = error.message.match(MISSING_ARGS_PATTERN))
|
|
91
|
+
|
|
92
|
+
command_name = match[2]
|
|
93
|
+
start(['help', command_name])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Handle wrong number of arguments error
|
|
97
|
+
#
|
|
98
|
+
# @param command [Thor::Command] the Thor command object
|
|
99
|
+
# @param error [Exception] the ArgumentError
|
|
100
|
+
def handle_argument_count_error(command, error)
|
|
101
|
+
cmd_name = command.name
|
|
102
|
+
message = build_argument_error_message(cmd_name, error.message)
|
|
103
|
+
|
|
104
|
+
print_error_message(message)
|
|
105
|
+
exit(false)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Build appropriate error message for argument errors
|
|
109
|
+
#
|
|
110
|
+
# @param cmd_name [String] the command name
|
|
111
|
+
# @param error_message [String] the original error message
|
|
112
|
+
# @return [String] a user-friendly error message
|
|
113
|
+
def build_argument_error_message(cmd_name, error_message)
|
|
114
|
+
if error_message.include?('given 0')
|
|
115
|
+
"Missing required argument for '#{cmd_name}'. Check the command syntax with: ukiryu help #{cmd_name}"
|
|
116
|
+
else
|
|
117
|
+
"Invalid number of arguments for '#{cmd_name}'. Check the command syntax with: ukiryu help #{cmd_name}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Handle -h and --help switches by converting them to Thor's help format
|
|
122
|
+
#
|
|
123
|
+
# @param given_args [Array<String>] the command-line arguments
|
|
124
|
+
# @yield [Array<String>] the processed arguments
|
|
125
|
+
def handle_help_switches(given_args)
|
|
126
|
+
yield(given_args.dup)
|
|
127
|
+
rescue Thor::UnknownArgumentError => e
|
|
128
|
+
retry_with_args = build_help_args(given_args, e)
|
|
129
|
+
|
|
130
|
+
return yield(retry_with_args) if retry_with_args.any?
|
|
131
|
+
|
|
132
|
+
# Not a help-related error, re-raise to be handled by outer rescue
|
|
133
|
+
raise
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Build help arguments from the given args and error
|
|
137
|
+
#
|
|
138
|
+
# @param given_args [Array<String>] the original command-line arguments
|
|
139
|
+
# @param error [Thor::UnknownArgumentError] the error from Thor
|
|
140
|
+
# @return [Array<String>] help arguments or empty array
|
|
141
|
+
def build_help_args(given_args, error)
|
|
142
|
+
return ['help'] if given_args.first == 'help' && given_args.length > 1
|
|
143
|
+
return ['help', (given_args - error.unknown).first] if error.unknown.intersect?(HELP_OPTIONS)
|
|
144
|
+
|
|
145
|
+
[]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Handle exceptions during CLI execution
|
|
149
|
+
#
|
|
150
|
+
# @param error [Exception] the exception that was raised
|
|
151
|
+
# @param config [Hash] configuration options
|
|
152
|
+
def handle_exception_on_start(error, config)
|
|
153
|
+
# EPIPE errors are safe to ignore (happens when piping to head and similar)
|
|
154
|
+
return if error.is_a?(Errno::EPIPE)
|
|
155
|
+
|
|
156
|
+
# SystemExit is used for intentional exits (from handle_argument_error or error!)
|
|
157
|
+
# Just exit with the same status without printing anything
|
|
158
|
+
return Kernel.exit(error.status || 1) if error.is_a?(SystemExit)
|
|
159
|
+
|
|
160
|
+
# Re-raise (show full stack trace) if user has opted into trace mode
|
|
161
|
+
return raise if trace_mode_enabled?(config)
|
|
162
|
+
|
|
163
|
+
# Build error message with class prefix for non-Thor errors
|
|
164
|
+
message = format_error_message(error)
|
|
165
|
+
|
|
166
|
+
# Print error to stderr in red
|
|
167
|
+
print_error_message(message, config[:shell])
|
|
168
|
+
|
|
169
|
+
# Exit with non-zero status
|
|
170
|
+
exit(false)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Check if trace mode is enabled via environment or config
|
|
174
|
+
#
|
|
175
|
+
# @param config [Hash] configuration options
|
|
176
|
+
# @return [Boolean] true if trace mode is enabled
|
|
177
|
+
def trace_mode_enabled?(config)
|
|
178
|
+
TRACE_ENV_VARS.any? { |var| ENV[var] } || config[:trace]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Format error message with appropriate class prefix
|
|
182
|
+
#
|
|
183
|
+
# @param error [Exception] the error to format
|
|
184
|
+
# @return [String] the formatted error message
|
|
185
|
+
def format_error_message(error)
|
|
186
|
+
message = error.message.to_s
|
|
187
|
+
return message if error.is_a?(Thor::Error) && !message.empty?
|
|
188
|
+
|
|
189
|
+
# Add class prefix for non-Thor errors or empty messages
|
|
190
|
+
message.prepend("[#{error.class}] ") if message.empty? || !error.is_a?(Thor::Error)
|
|
191
|
+
message
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Print error message to stderr in red (if supported)
|
|
195
|
+
#
|
|
196
|
+
# @param message [String] the error message
|
|
197
|
+
# @param shell [Thor::Shell, nil] the Thor shell instance (optional)
|
|
198
|
+
def print_error_message(message, shell = nil)
|
|
199
|
+
shell ||= Thor::Base.shell.new
|
|
200
|
+
|
|
201
|
+
if shell.respond_to?(:say_error)
|
|
202
|
+
shell.say_error(message, :red)
|
|
203
|
+
else
|
|
204
|
+
warn "\e[31m#{message}\e[0m"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|