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,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+ require_relative '../tool'
5
+
6
+ module Ukiryu
7
+ module CliCommands
8
+ # Show options for a tool or specific command
9
+ class OptsCommand < BaseCommand
10
+ # Execute the opts command
11
+ #
12
+ # @param tool_name [String] the tool name
13
+ # @param command_name [String, nil] optional command name
14
+ def run(tool_name, command_name = nil)
15
+ setup_registry
16
+
17
+ tool = Tool.get(tool_name)
18
+ tool_commands = tool.commands
19
+
20
+ error! "No commands defined for #{tool_name}" unless tool_commands
21
+
22
+ # Find the command
23
+ cmds = if command_name
24
+ cmd = tool_commands[command_name.to_sym] || tool_commands[command_name]
25
+ cmd ? [cmd] : []
26
+ else
27
+ tool_commands.values
28
+ end
29
+
30
+ cmds.each do |cmd|
31
+ cmd_title = command_name ? "#{tool_name} #{command_name}" : tool_name
32
+ say '', :clear
33
+ say "Options for #{cmd_title}:", :cyan
34
+ say cmd.description.to_s if cmd.description
35
+
36
+ # Arguments
37
+ if cmd.arguments && !cmd.arguments.empty?
38
+ say '', :clear
39
+ say 'Arguments:', :yellow
40
+ cmd.arguments.each do |arg|
41
+ name = arg.name || 'unnamed'
42
+ type = arg.type || 'unknown'
43
+ position = arg.position || 'default'
44
+ variadic = arg.variadic ? '(variadic)' : ''
45
+
46
+ say " #{name} (#{type}#{variadic})", :white
47
+ say " Position: #{position}", :dim if position != 'default'
48
+ say " Description: #{arg.description}", :dim if arg.description
49
+ end
50
+ end
51
+
52
+ # Options
53
+ if cmd.options && !cmd.options.empty?
54
+ say '', :clear
55
+ say 'Options:', :yellow
56
+ cmd.options.each do |opt|
57
+ name = opt.name || 'unnamed'
58
+ cli = opt.cli || 'N/A'
59
+ type = opt.type || 'unknown'
60
+ description = opt.description || ''
61
+
62
+ say " --#{name.ljust(20)} #{cli}", :white
63
+ say " Type: #{type}", :dim
64
+ say " #{description}", :dim if description
65
+ say " Values: #{opt.values.join(', ')}", :dim if opt.values
66
+ say " Range: #{opt.range.join('..')}", :dim if opt.range
67
+ end
68
+ end
69
+
70
+ # Post-options (options between input and output)
71
+ if cmd.post_options && !cmd.post_options.empty?
72
+ say '', :clear
73
+ say 'Post-Options (between input and output):', :yellow
74
+ cmd.post_options.each do |opt|
75
+ name = opt.name || 'unnamed'
76
+ cli = opt.cli || 'N/A'
77
+ type = opt.type || 'unknown'
78
+ description = opt.description || ''
79
+
80
+ say " --#{name.ljust(20)} #{cli}", :white
81
+ say " Type: #{type}", :dim
82
+ say " #{description}", :dim if description
83
+ end
84
+ end
85
+
86
+ # Flags
87
+ next unless cmd.flags && !cmd.flags.empty?
88
+
89
+ say '', :clear
90
+ say 'Flags:', :yellow
91
+ cmd.flags.each do |flag|
92
+ name = flag.name || 'unnamed'
93
+ cli = flag.cli || 'N/A'
94
+ default = flag.default
95
+ default_str = default.nil? ? '' : " (default: #{default})"
96
+
97
+ say " #{cli.ljust(25)} #{name}#{default_str}", :white
98
+ say " #{flag.description}", :dim if flag.description
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ module CliCommands
5
+ # Response formatting module for CLI commands
6
+ #
7
+ # Provides reusable formatting methods for execution responses
8
+ # in multiple formats (YAML, JSON, table, raw).
9
+ module ResponseFormatter
10
+ # Supported output formats
11
+ OUTPUT_FORMATS = %i[yaml json table raw].freeze
12
+
13
+ # Output response in specified format
14
+ #
15
+ # @param response [Models::SuccessResponse, Models::ErrorResponse] the response to format
16
+ # @param format [Symbol] the output format (:yaml, :json, :table, or :raw)
17
+ # @param output_file [String, nil] optional file path to write output
18
+ # @param config [Object] the CLI config object
19
+ # @return [void]
20
+ def output_response(response, format, output_file, config)
21
+ if format == :raw
22
+ output_raw_response(response, output_file)
23
+ else
24
+ output_string = case format
25
+ when :yaml
26
+ format_yaml_response(response, config)
27
+ when :json
28
+ format_json_response(response)
29
+ when :table, :human
30
+ format_table_response(response, config)
31
+ else
32
+ response.to_yaml
33
+ end
34
+
35
+ if output_file
36
+ File.write(output_file, output_string)
37
+ say "Response written to: #{output_file}", :green
38
+ else
39
+ say output_string
40
+ end
41
+ end
42
+ end
43
+
44
+ # Show dry run output
45
+ #
46
+ # @param request [Hash] the execution request
47
+ def say_dry_run(request)
48
+ say 'DRY RUN - Ukiryu Structured Execution Request:', :yellow
49
+ say '', :clear
50
+ say "Tool: #{request['tool']}", :cyan
51
+ say "Command: #{request['command']}", :cyan
52
+ say 'Arguments:', :cyan
53
+ request['arguments'].each do |key, value|
54
+ say " #{key}: #{value.inspect}", :white
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # Format response as colored YAML
61
+ #
62
+ # @param response [Models::SuccessResponse, Models::ErrorResponse] the response
63
+ # @param config [Object] the CLI config object
64
+ # @return [String] formatted YAML
65
+ def format_yaml_response(response, config)
66
+ yaml_content = response.to_yaml
67
+
68
+ # Determine if we should use colors:
69
+ # - Config.use_color can be true, false, or nil (auto-detect)
70
+ # - Auto-detect: only use colors if TTY and colors are not disabled via config/NO_COLOR
71
+ use_colors = if config.use_color.nil?
72
+ $stdout.tty? && !config.colors_disabled?
73
+ else
74
+ config.use_color
75
+ end
76
+
77
+ return yaml_content unless use_colors
78
+
79
+ begin
80
+ require 'paint'
81
+ paint = Paint.method(:[])
82
+ # Add color coding to YAML output (no explicit newlines, let say handle it)
83
+ yaml_content.each_line.map do |line|
84
+ case line
85
+ when /^status:/
86
+ paint[line, :cyan, :bright]
87
+ when /^exit_code:/
88
+ paint[line, :yellow]
89
+ when /^ executable:/
90
+ paint[line, :green]
91
+ when /^ full_command:/
92
+ paint[line, :blue, :bright]
93
+ when /^stdout:/
94
+ line # No color for stdout - let terminal decide
95
+ when /^stderr:/
96
+ paint[line, :red]
97
+ when /^ started_at:|^ finished_at:/
98
+ line # No color for timestamps - let terminal decide
99
+ when /^ duration_seconds:|^ formatted_duration:/
100
+ paint[line, :magenta]
101
+ else
102
+ line
103
+ end
104
+ end.join
105
+ rescue LoadError
106
+ yaml_content
107
+ end
108
+ end
109
+
110
+ # Format response as JSON
111
+ #
112
+ # @param response [Models::SuccessResponse, Models::ErrorResponse] the response
113
+ # @return [String] formatted JSON
114
+ def format_json_response(response)
115
+ response.to_json(pretty: true)
116
+ end
117
+
118
+ # Output response in raw format (for pipe composition)
119
+ #
120
+ # In raw mode:
121
+ # - stdout from the command is written directly to stdout
122
+ # - stderr from the command is written directly to stderr
123
+ # - No wrapping in YAML/JSON structures
124
+ # - Enables clean pipe composition: echo "test" | ukiryu exec jq --raw filter="." | other_tool
125
+ #
126
+ # @param response [Models::SuccessResponse, Models::ErrorResponse] the response
127
+ # @param output_file [String, nil] optional file path to write output
128
+ # @return [void]
129
+ def output_raw_response(response, output_file)
130
+ if response.is_a?(Models::SuccessResponse)
131
+ # Write stdout to stdout (without say() to avoid extra newlines)
132
+ $stdout.write(response.output.stdout)
133
+ $stdout.flush if response.output.stdout.empty? || response.output.stdout.end_with?("\n")
134
+
135
+ # Write stderr to stderr
136
+ unless response.output.stderr.empty?
137
+ $stderr.write(response.output.stderr)
138
+ $stderr.flush
139
+ end
140
+
141
+ # Write to file if specified
142
+ File.write(output_file, response.output.stdout) if output_file
143
+ else
144
+ # Error response: write error message to stderr
145
+ $stderr.write("#{response.error}\n")
146
+ $stderr.flush
147
+ end
148
+ end
149
+
150
+ # Format response as human-readable table
151
+ #
152
+ # @param response [Models::SuccessResponse, Models::ErrorResponse] the response
153
+ # @param config [Object] the CLI config object
154
+ # @return [String] formatted table
155
+ def format_table_response(response, config)
156
+ if response.is_a?(Models::SuccessResponse)
157
+ format_success_table(response, config)
158
+ else
159
+ format_error_table(response, config)
160
+ end
161
+ end
162
+
163
+ # Format success response as table
164
+ #
165
+ # @param response [Models::SuccessResponse] the success response
166
+ # @param config [Object] the CLI config object
167
+ # @return [String] formatted success table
168
+ def format_success_table(response, config)
169
+ return format_plain_success_table(response, config) unless defined?(Paint)
170
+
171
+ begin
172
+ require 'paint'
173
+ paint = Paint.method(:[])
174
+ success_icon = paint['✓', :green]
175
+
176
+ output = []
177
+ output << "#{success_icon} #{paint['Command completed successfully', :green, :bright]}\n"
178
+ output << "#{paint['Exit code:', :white]} #{response.exit_code}\n"
179
+ output << "#{paint['Duration:', :white]} #{response.metadata.formatted_duration}\n"
180
+ output << "\n#{paint['Request:', :cyan, :bright]} #{format_request_summary(response.request)}\n"
181
+ output << "\n#{paint['Command:', :cyan, :bright]} #{response.command.full_command}\n"
182
+
183
+ unless response.output.stdout.empty?
184
+ first_line = response.output.stdout.split("\n").first
185
+ output << "\n#{paint['Output:', :white]} #{first_line}\n"
186
+ end
187
+
188
+ output << "\n#{paint['Errors:', :red]} #{response.output.stderr}\n" unless response.output.stderr.empty?
189
+
190
+ output.join("\n")
191
+ rescue LoadError
192
+ format_plain_success_table(response, config)
193
+ end
194
+ end
195
+
196
+ # Format request summary for table display
197
+ #
198
+ # @param request [Models::Request] the request object
199
+ # @return [String] formatted request summary
200
+ def format_request_summary(request)
201
+ parts = []
202
+ parts << request.options.map { |opt| "#{opt.name}=#{opt.value}" }.join(' ') unless request.options.empty?
203
+ parts << request.flags.map { |flag| "--#{flag}" }.join(' ') unless request.flags.empty?
204
+ parts << request.positional.map(&:value).join(' ') unless request.positional.empty?
205
+ parts.join(' ')
206
+ end
207
+
208
+ # Format success response without Paint
209
+ #
210
+ # @param response [Models::SuccessResponse] the success response
211
+ # @param _config [Object] the CLI config object (unused)
212
+ # @return [String] plain text success table
213
+ def format_plain_success_table(response, _config)
214
+ "✓ Command completed successfully\n" \
215
+ "Exit code: #{response.exit_code}\n" \
216
+ "Duration: #{response.metadata.formatted_duration}\n" \
217
+ "\nRequest: #{format_request_summary(response.request)}\n" \
218
+ "\nCommand: #{response.command.full_command}\n"
219
+ end
220
+
221
+ # Format error response as table
222
+ #
223
+ # @param response [Models::ErrorResponse] the error response
224
+ # @param _config [Object] the CLI config object (unused)
225
+ # @return [String] formatted error table
226
+ def format_error_table(response, _config)
227
+ return "✗ Command failed: #{response.error}\n" unless defined?(Paint)
228
+
229
+ begin
230
+ require 'paint'
231
+ paint = Paint.method(:[])
232
+ error_icon = paint['✗', :red]
233
+ "#{error_icon} #{paint['Command failed:', :red, :bright]} #{response.error}\n"
234
+ rescue LoadError
235
+ "✗ Command failed: #{response.error}\n"
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end