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,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+ require_relative '../tool'
5
+
6
+ module Ukiryu
7
+ module CliCommands
8
+ # Show comprehensive documentation for a tool or specific command
9
+ class DescribeCommand < BaseCommand
10
+ # Execute the describe 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
+ # Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
18
+ tool = Tool.find_by(tool_name.to_sym)
19
+ error!("Tool not found: #{tool_name}\nAvailable tools: #{Registry.tools.sort.join(', ')}") unless tool
20
+
21
+ tool_commands = tool.commands
22
+ error! "No commands defined for #{tool_name}" unless tool_commands
23
+
24
+ # Special handling for "help" command - show tool-level help
25
+ if command_name&.to_s == 'help'
26
+ show_tool_help(tool, tool_commands)
27
+ return
28
+ end
29
+
30
+ # If no command specified, show overview of all commands
31
+ unless command_name
32
+ describe_tool_overview(tool, tool_commands)
33
+ return
34
+ end
35
+
36
+ # Find the specific command
37
+ cmd = tool_commands.find { |c| c.name.to_s == command_name.to_s || c.name.to_sym == command_name.to_sym }
38
+ error! "Command '#{command_name}' not found for #{tool_name}\nAvailable commands: #{tool_commands.map(&:name).join(', ')}" unless cmd
39
+
40
+ describe_command(tool, tool_name, command_name, cmd)
41
+ end
42
+
43
+ private
44
+
45
+ # Show tool-level help (similar to exec help)
46
+ def show_tool_help(tool, tool_commands)
47
+ profile = tool.profile
48
+
49
+ say '', :clear
50
+ say '=' * 60, :cyan
51
+ say "Tool: #{profile.name || tool.name}", :cyan
52
+ say '=' * 60, :cyan
53
+ say '', :clear
54
+
55
+ # Basic info
56
+ say "Display Name: #{profile.display_name || 'N/A'}", :white
57
+ say "Version: #{profile.version || 'N/A'}", :white
58
+ say "Homepage: #{profile.homepage || 'N/A'}", :white
59
+ say "Aliases: #{profile.aliases.join(', ')}", :white if profile.aliases && !profile.aliases.empty?
60
+
61
+ # Availability
62
+ say '', :clear
63
+ if tool.available?
64
+ say 'Status: INSTALLED', :green
65
+ say "Executable: #{tool.executable}", :white
66
+ say "Detected Version: #{tool.version || 'unknown'}", :white
67
+ else
68
+ say 'Status: NOT FOUND', :red
69
+ end
70
+
71
+ # Commands
72
+ say '', :clear
73
+ say "Commands (#{tool_commands.count}):", :yellow
74
+ tool_commands.each do |cmd|
75
+ cmd_display = (cmd.name || 'unnamed').to_s.ljust(20)
76
+ desc_display = cmd.description || 'No description'
77
+ say " #{cmd_display} #{desc_display}", :white
78
+
79
+ # Show usage if available
80
+ say " Usage: #{cmd.usage}", :dim if cmd.usage
81
+ end
82
+
83
+ say '', :clear
84
+ say "Usage: ukiryu exec #{tool.name} <command> [KEY=VALUE ...]", :dim
85
+ say " or: ukiryu exec #{tool.name} help", :dim
86
+ say " or: ukiryu describe #{tool.name} <command>", :dim
87
+ say '', :clear
88
+ say 'For more information on a specific command:', :dim
89
+ say " ukiryu opts #{tool.name} <command>", :dim
90
+ say " ukiryu describe #{tool.name} <command>", :dim
91
+ end
92
+
93
+ # Describe tool overview with all commands
94
+ def describe_tool_overview(tool, tool_commands)
95
+ profile = tool.profile
96
+
97
+ say '', :clear
98
+ say '=' * 60, :cyan
99
+ say "Tool: #{profile.name || tool.name}", :cyan
100
+ say '=' * 60, :cyan
101
+ say '', :clear
102
+
103
+ # Basic info
104
+ say "Display Name: #{profile.display_name || 'N/A'}", :white
105
+ say "Version: #{profile.version || 'N/A'}", :white
106
+ say "Homepage: #{profile.homepage || 'N/A'}", :white
107
+ say "Aliases: #{profile.aliases.join(', ')}", :white if profile.aliases && !profile.aliases.empty?
108
+
109
+ # Availability
110
+ say '', :clear
111
+ if tool.available?
112
+ say 'Status: INSTALLED', :green
113
+ say "Executable: #{tool.executable}", :white
114
+ say "Detected Version: #{tool.version || 'unknown'}", :white
115
+ else
116
+ say 'Status: NOT FOUND', :red
117
+ end
118
+
119
+ # Commands
120
+ say '', :clear
121
+ say "Commands (#{tool_commands.count}):", :yellow
122
+ tool_commands.each do |cmd|
123
+ cmd_display = (cmd.name || 'unnamed').to_s.ljust(20)
124
+ desc_display = cmd.description || 'No description'
125
+ say " #{cmd_display} #{desc_display}", :white
126
+ end
127
+
128
+ say '', :clear
129
+ say "Use 'ukiryu describe #{tool.name} <command>' for detailed command documentation", :dim
130
+ end
131
+
132
+ # Describe a specific command with all options, types, and option sets
133
+ def describe_command(tool, tool_name, command_name, cmd)
134
+ say '', :clear
135
+ say '=' * 60, :cyan
136
+ say "#{tool.name} #{command_name}", :cyan
137
+ say '=' * 60, :cyan
138
+ say '', :clear
139
+
140
+ # Description and usage
141
+ say cmd.description if cmd.description
142
+ say '', :clear
143
+
144
+ # Usage
145
+ if cmd.usage
146
+ say 'Usage:', :yellow
147
+ say " #{cmd.usage}", :white
148
+ say '', :clear
149
+ end
150
+
151
+ # Subcommand
152
+ if cmd.subcommand
153
+ say "Subcommand: #{cmd.subcommand}", :white
154
+ say '', :clear
155
+ end
156
+
157
+ # Execution mode
158
+ if cmd.execution_mode
159
+ say "Execution Mode: #{cmd.execution_mode}", :white
160
+ say '', :clear
161
+ end
162
+
163
+ # Arguments
164
+ if cmd.arguments && !cmd.arguments.empty?
165
+ say 'Arguments:', :yellow
166
+ cmd.arguments.each do |arg|
167
+ name = arg.name || 'unnamed'
168
+ type = arg.type || 'string'
169
+ required = arg.required ? 'required' : 'optional'
170
+ variadic = arg.variadic ? '(variadic)' : ''
171
+ position = arg.position || 'default'
172
+
173
+ say " #{name} (#{type}, #{required}#{variadic})", :white
174
+ say " Position: #{position}", :dim if position != 'default'
175
+ say " Description: #{arg.description}", :dim if arg.description
176
+
177
+ # Type constraints
178
+ if arg.min || arg.max || arg.size
179
+ constraints = []
180
+ constraints << "min: #{arg.min}" if arg.min
181
+ constraints << "max: #{arg.max}" if arg.max
182
+ constraints << "size: #{arg.size.inspect}" if arg.size
183
+ say " Constraints: #{constraints.join(', ')}", :dim
184
+ end
185
+
186
+ # Range
187
+ say " Range: #{arg.range.join('..')}", :dim if arg.range
188
+
189
+ # Valid values
190
+ say " Valid values: #{arg.values.join(', ')}", :dim if arg.values
191
+
192
+ say '', :clear
193
+ end
194
+ end
195
+
196
+ # Options
197
+ if cmd.options && !cmd.options.empty?
198
+ say 'Options:', :yellow
199
+ cmd.options.each do |opt|
200
+ name = opt.name || 'unnamed'
201
+ cli = opt.cli || 'N/A'
202
+ type = opt.type || 'string'
203
+ format = opt.format || 'N/A'
204
+ default = opt.default
205
+ platforms = opt.platforms || []
206
+
207
+ say " #{name} (#{type})", :white
208
+ say " CLI: #{cli}", :dim
209
+ say " Format: #{format}", :dim if format != 'N/A'
210
+ say " Default: #{default}", :dim if default
211
+ say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
212
+ say " Description: #{opt.description}", :dim if opt.description
213
+
214
+ # Type constraints
215
+ say " Range: #{opt.range.join('..')}", :dim if opt.range
216
+
217
+ # Valid values (for symbol type)
218
+ say " Valid values: #{opt.values.join(', ')}", :dim if opt.values
219
+
220
+ # Element type (for arrays)
221
+ say " Element type: #{opt.of}", :dim if opt.of
222
+
223
+ say '', :clear
224
+ end
225
+ end
226
+
227
+ # Post-options (options between input and output)
228
+ if cmd.post_options && !cmd.post_options.empty?
229
+ say 'Post-Options (between input and output):', :yellow
230
+ cmd.post_options.each do |opt|
231
+ name = opt.name || 'unnamed'
232
+ cli = opt.cli || 'N/A'
233
+ type = opt.type || 'string'
234
+
235
+ say " #{name} (#{type})", :white
236
+ say " CLI: #{cli}", :dim
237
+ say " Description: #{opt.description}", :dim if opt.description
238
+ say '', :clear
239
+ end
240
+ end
241
+
242
+ # Flags
243
+ if cmd.flags && !cmd.flags.empty?
244
+ say 'Flags:', :yellow
245
+ cmd.flags.each do |flag|
246
+ name = flag.name || 'unnamed'
247
+ cli = flag.cli || 'N/A'
248
+ default = flag.default
249
+ platforms = flag.platforms || []
250
+
251
+ say " #{name} (boolean)", :white
252
+ say " CLI: #{cli}", :dim
253
+ say " Default: #{default}", :dim unless default.nil?
254
+ say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
255
+ say " Description: #{flag.description}", :dim if flag.description
256
+ say '', :clear
257
+ end
258
+ end
259
+
260
+ # Environment variables
261
+ if cmd.env_vars && !cmd.env_vars.empty?
262
+ say 'Environment Variables:', :yellow
263
+ cmd.env_vars.each do |ev|
264
+ name = ev.name || 'unnamed'
265
+ value = ev.value
266
+ env_var = ev.env_var
267
+ platforms = ev.platforms || []
268
+
269
+ say " #{name}", :white
270
+ say " Value: #{value.inspect}", :dim if value
271
+ say " From env var: #{env_var}", :dim if env_var
272
+ say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
273
+ say '', :clear
274
+ end
275
+ end
276
+
277
+ # Option sets (commonly used option combinations)
278
+ say 'Option Sets (common combinations):', :yellow
279
+ say ' --help (show help)', :dim
280
+ say ' --version (show version)', :dim
281
+
282
+ # Group related options by function
283
+ if cmd.options && !cmd.options.empty?
284
+ output_opts = cmd.options.select { |o| o.name =~ /output|out|file|format/i }
285
+ if output_opts.any?
286
+ say '', :clear
287
+ say "Output options: #{output_opts.map(&:name).join(', ')}", :dim
288
+ end
289
+
290
+ quality_opts = cmd.options.select { |o| o.name =~ /quality|q|compression/i }
291
+ say "Quality options: #{quality_opts.map(&:name).join(', ')}", :dim if quality_opts.any?
292
+ end
293
+
294
+ # Exit codes
295
+ # Exit codes are bound to individual commands/actions
296
+ exit_codes = cmd.exit_codes
297
+ if exit_codes && (exit_codes.standard_codes&.any? || exit_codes.custom_codes&.any?)
298
+ say '', :clear
299
+ say 'Exit Codes:', :yellow
300
+
301
+ standard_codes = exit_codes.standard_codes
302
+ if standard_codes&.any?
303
+ say ' Standard:', :dim
304
+ standard_codes.sort_by { |k, _v| k.to_i }.each do |code, meaning|
305
+ say " #{code.rjust(3)}: #{meaning}", :white
306
+ end
307
+ end
308
+
309
+ custom_codes = exit_codes.custom_codes
310
+ if custom_codes&.any?
311
+ say ' Custom:', :dim
312
+ custom_codes.sort_by { |k, _v| k.to_i }.each do |code, meaning|
313
+ say " #{code.rjust(3)}: #{meaning}", :white
314
+ end
315
+ end
316
+ end
317
+
318
+ say '', :clear
319
+ say '=' * 60, :cyan
320
+ say 'Example usage:', :yellow
321
+ say " ukiryu exec #{tool_name} #{command_name} key=value", :white
322
+ say '=' * 60, :cyan
323
+ end
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+ require_relative '../tool'
5
+
6
+ module Ukiryu
7
+ module CliCommands
8
+ # Show comprehensive documentation for a tool or specific command
9
+ class DescribeCommand < BaseCommand
10
+ # Execute the describe 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
+ # If no command specified, show overview of all commands
23
+ unless command_name
24
+ describe_tool_overview(tool, tool_commands)
25
+ return
26
+ end
27
+
28
+ # Find the specific command
29
+ cmd = tool_commands[command_name.to_sym] || tool_commands[command_name]
30
+ error! "Command '#{command_name}' not found for #{tool_name}\nAvailable commands: #{tool_commands.keys.join(', ')}" unless cmd
31
+
32
+ describe_command(tool, tool_name, command_name, cmd)
33
+ end
34
+
35
+ private
36
+
37
+ # Describe tool overview with all commands
38
+ def describe_tool_overview(tool, tool_commands)
39
+ profile = tool.profile
40
+
41
+ say '', :clear
42
+ say '=' * 60, :cyan
43
+ say "Tool: #{profile.name || tool.name}", :cyan
44
+ say '=' * 60, :cyan
45
+ say '', :clear
46
+
47
+ # Basic info
48
+ say "Display Name: #{profile.display_name || 'N/A'}", :white
49
+ say "Version: #{profile.version || 'N/A'}", :white
50
+ say "Homepage: #{profile.homepage || 'N/A'}", :white
51
+ say "Aliases: #{profile.aliases.join(', ')}", :white if profile.aliases && !profile.aliases.empty?
52
+
53
+ # Availability
54
+ say '', :clear
55
+ if tool.available?
56
+ say 'Status: INSTALLED', :green
57
+ say "Executable: #{tool.executable}", :white
58
+ say "Detected Version: #{tool.version || 'unknown'}", :white
59
+ else
60
+ say 'Status: NOT FOUND', :red
61
+ end
62
+
63
+ # Commands
64
+ say '', :clear
65
+ say "Commands (#{tool_commands.count}):", :yellow
66
+ tool_commands.each do |cmd_name, cmd|
67
+ cmd_display = cmd_name.to_s.ljust(20)
68
+ desc_display = cmd.description || 'No description'
69
+ say " #{cmd_display} #{desc_display}", :white
70
+ end
71
+
72
+ say '', :clear
73
+ say "Use 'ukiryu describe #{tool.name} <command>' for detailed command documentation", :dim
74
+ end
75
+
76
+ # Describe a specific command with all options, types, and option sets
77
+ def describe_command(tool, tool_name, command_name, cmd)
78
+ say '', :clear
79
+ say '=' * 60, :cyan
80
+ say "#{tool.name} #{command_name}", :cyan
81
+ say '=' * 60, :cyan
82
+ say '', :clear
83
+
84
+ # Description and usage
85
+ say cmd.description if cmd.description
86
+ say '', :clear
87
+
88
+ # Usage
89
+ if cmd.usage
90
+ say 'Usage:', :yellow
91
+ say " #{cmd.usage}", :white
92
+ say '', :clear
93
+ end
94
+
95
+ # Subcommand
96
+ if cmd.subcommand
97
+ say "Subcommand: #{cmd.subcommand}", :white
98
+ say '', :clear
99
+ end
100
+
101
+ # Execution mode
102
+ if cmd.execution_mode
103
+ say "Execution Mode: #{cmd.execution_mode}", :white
104
+ say '', :clear
105
+ end
106
+
107
+ # Arguments
108
+ if cmd.arguments && !cmd.arguments.empty?
109
+ say 'Arguments:', :yellow
110
+ cmd.arguments.each do |arg|
111
+ name = arg.name || 'unnamed'
112
+ type = arg.type || 'string'
113
+ required = arg.required ? 'required' : 'optional'
114
+ variadic = arg.variadic ? '(variadic)' : ''
115
+ position = arg.position || 'default'
116
+
117
+ say " #{name} (#{type}, #{required}#{variadic})", :white
118
+ say " Position: #{position}", :dim if position != 'default'
119
+ say " Description: #{arg.description}", :dim if arg.description
120
+
121
+ # Type constraints
122
+ if arg.min || arg.max || arg.size
123
+ constraints = []
124
+ constraints << "min: #{arg.min}" if arg.min
125
+ constraints << "max: #{arg.max}" if arg.max
126
+ constraints << "size: #{arg.size.inspect}" if arg.size
127
+ say " Constraints: #{constraints.join(', ')}", :dim
128
+ end
129
+
130
+ # Range
131
+ say " Range: #{arg.range.join('..')}", :dim if arg.range
132
+
133
+ # Valid values
134
+ say " Valid values: #{arg.values.join(', ')}", :dim if arg.values
135
+
136
+ say '', :clear
137
+ end
138
+ end
139
+
140
+ # Options
141
+ if cmd.options && !cmd.options.empty?
142
+ say 'Options:', :yellow
143
+ cmd.options.each do |opt|
144
+ name = opt.name || 'unnamed'
145
+ cli = opt.cli || 'N/A'
146
+ type = opt.type || 'string'
147
+ format = opt.format || 'N/A'
148
+ default = opt.default
149
+ platforms = opt.platforms || []
150
+
151
+ say " #{name} (#{type})", :white
152
+ say " CLI: #{cli}", :dim
153
+ say " Format: #{format}", :dim if format != 'N/A'
154
+ say " Default: #{default}", :dim if default
155
+ say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
156
+ say " Description: #{opt.description}", :dim if opt.description
157
+
158
+ # Type constraints
159
+ if opt.min || opt.max || opt.range
160
+ constraints = []
161
+ constraints << "min: #{opt.min}" if opt.min
162
+ constraints << "max: #{opt.max}" if opt.max
163
+ constraints << "range: #{opt.range.join('..')}" if opt.range
164
+ say " Constraints: #{constraints.join(', ')}", :dim
165
+ end
166
+
167
+ # Valid values (for symbol type)
168
+ say " Valid values: #{opt.values.join(', ')}", :dim if opt.values
169
+
170
+ # Element type (for arrays)
171
+ say " Element type: #{opt.of}", :dim if opt.of
172
+
173
+ say '', :clear
174
+ end
175
+ end
176
+
177
+ # Post-options (options between input and output)
178
+ if cmd.post_options && !cmd.post_options.empty?
179
+ say 'Post-Options (between input and output):', :yellow
180
+ cmd.post_options.each do |opt|
181
+ name = opt.name || 'unnamed'
182
+ cli = opt.cli || 'N/A'
183
+ type = opt.type || 'string'
184
+
185
+ say " #{name} (#{type})", :white
186
+ say " CLI: #{cli}", :dim
187
+ say " Description: #{opt.description}", :dim if opt.description
188
+ say '', :clear
189
+ end
190
+ end
191
+
192
+ # Flags
193
+ if cmd.flags && !cmd.flags.empty?
194
+ say 'Flags:', :yellow
195
+ cmd.flags.each do |flag|
196
+ name = flag.name || 'unnamed'
197
+ cli = flag.cli || 'N/A'
198
+ default = flag.default
199
+ platforms = flag.platforms || []
200
+
201
+ say " #{name} (boolean)", :white
202
+ say " CLI: #{cli}", :dim
203
+ say " Default: #{default}", :dim unless default.nil?
204
+ say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
205
+ say " Description: #{flag.description}", :dim if flag.description
206
+ say '', :clear
207
+ end
208
+ end
209
+
210
+ # Environment variables
211
+ if cmd.env_vars && !cmd.env_vars.empty?
212
+ say 'Environment Variables:', :yellow
213
+ cmd.env_vars.each do |ev|
214
+ name = ev.name || 'unnamed'
215
+ value = ev.value
216
+ env_var = ev.env_var
217
+ platforms = ev.platforms || []
218
+
219
+ say " #{name}", :white
220
+ say " Value: #{value.inspect}", :dim if value
221
+ say " From env var: #{env_var}", :dim if env_var
222
+ say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
223
+ say '', :clear
224
+ end
225
+ end
226
+
227
+ # Option sets (commonly used option combinations)
228
+ say 'Option Sets (common combinations):', :yellow
229
+ say ' --help (show help)', :dim
230
+ say ' --version (show version)', :dim
231
+
232
+ # Group related options by function
233
+ if cmd.options && !cmd.options.empty?
234
+ output_opts = cmd.options.select { |o| o.name =~ /output|out|file|format/i }
235
+ if output_opts.any?
236
+ say '', :clear
237
+ say "Output options: #{output_opts.map(&:name).join(', ')}", :dim
238
+ end
239
+
240
+ quality_opts = cmd.options.select { |o| o.name =~ /quality|q|compression/i }
241
+ say "Quality options: #{quality_opts.map(&:name).join(', ')}", :dim if quality_opts.any?
242
+ end
243
+
244
+ say '', :clear
245
+ say '=' * 60, :cyan
246
+ say 'Example usage:', :yellow
247
+ say " ukiryu execute #{tool_name} #{command_name} [options...]", :white
248
+ say " ukiryu exec-inline #{tool_name} #{command_name} key=value", :white
249
+ say '=' * 60, :cyan
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end