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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +99 -0
  4. data/.github/workflows/rake.yml +19 -0
  5. data/.github/workflows/release.yml +27 -0
  6. data/.gitignore +18 -4
  7. data/.rubocop.yml +1 -0
  8. data/.rubocop_todo.yml +213 -0
  9. data/Gemfile +12 -8
  10. data/README.adoc +613 -0
  11. data/Rakefile +2 -2
  12. data/docs/assets/logo.svg +1 -0
  13. data/exe/ukiryu +11 -0
  14. data/lib/ukiryu/action/base.rb +77 -0
  15. data/lib/ukiryu/cache.rb +199 -0
  16. data/lib/ukiryu/cli.rb +133 -307
  17. data/lib/ukiryu/cli_commands/base_command.rb +155 -0
  18. data/lib/ukiryu/cli_commands/commands_command.rb +120 -0
  19. data/lib/ukiryu/cli_commands/commands_command.rb.fixed +40 -0
  20. data/lib/ukiryu/cli_commands/config_command.rb +249 -0
  21. data/lib/ukiryu/cli_commands/describe_command.rb +326 -0
  22. data/lib/ukiryu/cli_commands/describe_command.rb.fixed +254 -0
  23. data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +180 -0
  24. data/lib/ukiryu/cli_commands/extract_command.rb +84 -0
  25. data/lib/ukiryu/cli_commands/info_command.rb +156 -0
  26. data/lib/ukiryu/cli_commands/list_command.rb +70 -0
  27. data/lib/ukiryu/cli_commands/opts_command.rb +106 -0
  28. data/lib/ukiryu/cli_commands/opts_command.rb.fixed +105 -0
  29. data/lib/ukiryu/cli_commands/response_formatter.rb +240 -0
  30. data/lib/ukiryu/cli_commands/run_command.rb +375 -0
  31. data/lib/ukiryu/cli_commands/run_file_command.rb +215 -0
  32. data/lib/ukiryu/cli_commands/system_command.rb +90 -0
  33. data/lib/ukiryu/cli_commands/validate_command.rb +87 -0
  34. data/lib/ukiryu/cli_commands/version_command.rb +16 -0
  35. data/lib/ukiryu/cli_commands/which_command.rb +166 -0
  36. data/lib/ukiryu/command_builder.rb +205 -0
  37. data/lib/ukiryu/config/env_provider.rb +64 -0
  38. data/lib/ukiryu/config/env_schema.rb +63 -0
  39. data/lib/ukiryu/config/override_resolver.rb +68 -0
  40. data/lib/ukiryu/config/type_converter.rb +59 -0
  41. data/lib/ukiryu/config.rb +249 -0
  42. data/lib/ukiryu/errors.rb +3 -0
  43. data/lib/ukiryu/executable_locator.rb +114 -0
  44. data/lib/ukiryu/execution/command_info.rb +64 -0
  45. data/lib/ukiryu/execution/metadata.rb +97 -0
  46. data/lib/ukiryu/execution/output.rb +144 -0
  47. data/lib/ukiryu/execution/result.rb +194 -0
  48. data/lib/ukiryu/execution.rb +15 -0
  49. data/lib/ukiryu/execution_context.rb +251 -0
  50. data/lib/ukiryu/executor.rb +76 -493
  51. data/lib/ukiryu/extractors/base_extractor.rb +63 -0
  52. data/lib/ukiryu/extractors/extractor.rb +150 -0
  53. data/lib/ukiryu/extractors/help_parser.rb +188 -0
  54. data/lib/ukiryu/extractors/native_extractor.rb +47 -0
  55. data/lib/ukiryu/io.rb +196 -0
  56. data/lib/ukiryu/logger.rb +544 -0
  57. data/lib/ukiryu/models/argument.rb +28 -0
  58. data/lib/ukiryu/models/argument_definition.rb +119 -0
  59. data/lib/ukiryu/models/arguments.rb +113 -0
  60. data/lib/ukiryu/models/command_definition.rb +176 -0
  61. data/lib/ukiryu/models/command_info.rb +37 -0
  62. data/lib/ukiryu/models/components.rb +107 -0
  63. data/lib/ukiryu/models/env_var_definition.rb +30 -0
  64. data/lib/ukiryu/models/error_response.rb +41 -0
  65. data/lib/ukiryu/models/execution_metadata.rb +31 -0
  66. data/lib/ukiryu/models/execution_report.rb +236 -0
  67. data/lib/ukiryu/models/exit_codes.rb +74 -0
  68. data/lib/ukiryu/models/flag_definition.rb +67 -0
  69. data/lib/ukiryu/models/option_definition.rb +102 -0
  70. data/lib/ukiryu/models/output_info.rb +25 -0
  71. data/lib/ukiryu/models/platform_profile.rb +153 -0
  72. data/lib/ukiryu/models/routing.rb +211 -0
  73. data/lib/ukiryu/models/search_paths.rb +39 -0
  74. data/lib/ukiryu/models/success_response.rb +85 -0
  75. data/lib/ukiryu/models/tool_definition.rb +145 -0
  76. data/lib/ukiryu/models/tool_metadata.rb +82 -0
  77. data/lib/ukiryu/models/validation_result.rb +80 -0
  78. data/lib/ukiryu/models/version_compatibility.rb +152 -0
  79. data/lib/ukiryu/models/version_detection.rb +39 -0
  80. data/lib/ukiryu/models.rb +23 -0
  81. data/lib/ukiryu/options/base.rb +95 -0
  82. data/lib/ukiryu/options_builder/formatter.rb +87 -0
  83. data/lib/ukiryu/options_builder/validator.rb +43 -0
  84. data/lib/ukiryu/options_builder.rb +311 -0
  85. data/lib/ukiryu/platform.rb +6 -6
  86. data/lib/ukiryu/registry.rb +143 -183
  87. data/lib/ukiryu/response/base.rb +217 -0
  88. data/lib/ukiryu/runtime.rb +179 -0
  89. data/lib/ukiryu/schema_validator.rb +8 -10
  90. data/lib/ukiryu/shell/bash.rb +3 -3
  91. data/lib/ukiryu/shell/cmd.rb +4 -4
  92. data/lib/ukiryu/shell/fish.rb +1 -1
  93. data/lib/ukiryu/shell/powershell.rb +3 -3
  94. data/lib/ukiryu/shell/sh.rb +1 -1
  95. data/lib/ukiryu/shell/zsh.rb +1 -1
  96. data/lib/ukiryu/shell.rb +146 -39
  97. data/lib/ukiryu/thor_ext.rb +208 -0
  98. data/lib/ukiryu/tool.rb +649 -258
  99. data/lib/ukiryu/tool_index.rb +224 -0
  100. data/lib/ukiryu/tools/base.rb +381 -0
  101. data/lib/ukiryu/tools/class_generator.rb +132 -0
  102. data/lib/ukiryu/tools/executable_finder.rb +29 -0
  103. data/lib/ukiryu/tools/generator.rb +154 -0
  104. data/lib/ukiryu/tools.rb +109 -0
  105. data/lib/ukiryu/type.rb +28 -43
  106. data/lib/ukiryu/validation/constraints.rb +281 -0
  107. data/lib/ukiryu/validation/validator.rb +188 -0
  108. data/lib/ukiryu/validation.rb +21 -0
  109. data/lib/ukiryu/version.rb +1 -1
  110. data/lib/ukiryu/version_detector.rb +51 -0
  111. data/lib/ukiryu.rb +31 -15
  112. data/ukiryu-proposal.md +2952 -0
  113. data/ukiryu.gemspec +18 -14
  114. metadata +137 -5
  115. 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