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,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+ require_relative '../tool'
5
+
6
+ module Ukiryu
7
+ module CliCommands
8
+ # List all commands available for a tool
9
+ class CommandsCommand < BaseCommand
10
+ # Execute the commands command
11
+ #
12
+ # @param tool_name [String] the tool name
13
+ def run(tool_name)
14
+ setup_registry
15
+
16
+ # Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
17
+ tool = Tool.find_by(tool_name.to_sym)
18
+ error!("Tool not found: #{tool_name}\nAvailable tools: #{Registry.tools.sort.join(', ')}") unless tool
19
+
20
+ tool_commands = tool.commands
21
+ error! "No commands defined for #{tool_name}" unless tool_commands
22
+
23
+ say "Commands for #{tool_name}:", :cyan
24
+
25
+ # Group commands by their parent (belongs_to) for hierarchical tools
26
+ grouped = group_commands_by_parent(tool_commands)
27
+
28
+ if grouped.key?(nil) && grouped.size == 1
29
+ # Flat structure - no routing/hierarchy
30
+ grouped[nil].each do |cmd|
31
+ display_command(cmd)
32
+ end
33
+ else
34
+ # Hierarchical structure - show routing groups
35
+ display_hierarchical_commands(tool, grouped)
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # Group commands by their parent (belongs_to)
42
+ #
43
+ # @param commands [Array] list of commands
44
+ # @return [Hash] grouped commands with nil key for top-level
45
+ #
46
+ def group_commands_by_parent(commands)
47
+ grouped = Hash.new { |h, k| h[k] = [] }
48
+
49
+ commands.each do |cmd|
50
+ parent = cmd.belongs_to
51
+ grouped[parent] << cmd
52
+ end
53
+
54
+ grouped
55
+ end
56
+
57
+ # Display a single command
58
+ #
59
+ # @param cmd [CommandDefinition] the command to display
60
+ #
61
+ def display_command(cmd)
62
+ cmd_name = cmd.name || 'unnamed'
63
+ description = cmd.description || 'No description'
64
+ say " #{cmd_name.to_s.ljust(20)} #{description}", :white
65
+
66
+ # Show usage if available
67
+ say " Usage: #{cmd.usage}", :dim if cmd.usage
68
+
69
+ # Show subcommand if exists
70
+ if cmd.subcommand
71
+ subcommand_info = cmd.subcommand.nil? ? '(none)' : cmd.subcommand
72
+ say " Subcommand: #{subcommand_info}", :dim
73
+ end
74
+
75
+ # Show cli_flag if this is a flag-based action
76
+ if cmd.flag_action?
77
+ say " Action flag: #{cmd.cli_flag}", :dim
78
+ end
79
+ end
80
+
81
+ # Display hierarchical commands with routing information
82
+ #
83
+ # @param tool [Tool] the tool instance
84
+ # @param grouped [Hash] grouped commands
85
+ #
86
+ def display_hierarchical_commands(tool, grouped)
87
+ # Show routing table first
88
+ if tool.routing?
89
+ say " Routing table:", :dim
90
+ tool.routing.keys.each do |key|
91
+ target = tool.routing.resolve(key)
92
+ say " #{key} => #{target}", :dim
93
+ end
94
+ say '', :clear
95
+ end
96
+
97
+ # Show top-level commands (no belongs_to)
98
+ if grouped.key?(nil) && !grouped[nil].empty?
99
+ say " Top-level commands:", :cyan
100
+ grouped[nil].each do |cmd|
101
+ display_command(cmd)
102
+ end
103
+ say '', :clear
104
+ end
105
+
106
+ # Show commands grouped by parent
107
+ grouped.each do |parent, cmds|
108
+ next if parent.nil?
109
+
110
+ target = tool.routing&.resolve(parent) || parent
111
+ say " #{parent} commands (route to: #{target}):", :cyan
112
+ cmds.each do |cmd|
113
+ display_command(cmd)
114
+ end
115
+ say '', :clear
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+ require_relative '../tool'
5
+
6
+ module Ukiryu
7
+ module CliCommands
8
+ # List all commands available for a tool
9
+ class CommandsCommand < BaseCommand
10
+ # Execute the commands command
11
+ #
12
+ # @param tool_name [String] the tool name
13
+ def run(tool_name)
14
+ setup_registry
15
+
16
+ tool = Tool.get(tool_name)
17
+ tool_commands = tool.commands
18
+
19
+ error! "No commands defined for #{tool_name}" unless tool_commands
20
+
21
+ say "Commands for #{tool_name}:", :cyan
22
+ tool_commands.each do |cmd_name, cmd|
23
+ cmd_name ||= 'unnamed'
24
+ description = cmd.description || 'No description'
25
+ say " #{cmd_name.to_s.ljust(20)} #{description}", :white
26
+
27
+ # Show usage if available
28
+ say " Usage: #{cmd.usage}", :dim if cmd.usage
29
+
30
+ # Show subcommand if exists
31
+ if cmd.subcommand
32
+ subcommand_info = cmd.subcommand.nil? ? '(none)' : cmd.subcommand
33
+ say " Subcommand: #{subcommand_info}", :dim
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_command'
4
+ require_relative '../config'
5
+ require 'yaml'
6
+ require 'fileutils'
7
+
8
+ module Ukiryu
9
+ module CliCommands
10
+ # Configuration management command
11
+ class ConfigCommand < BaseCommand
12
+ CONFIG_DIR = File.expand_path('~/.ukiryu')
13
+ CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml')
14
+
15
+ # Execute the config command
16
+ #
17
+ # @param action [String] the action (list, get, set, unset)
18
+ # @param key [String, nil] the config key
19
+ # @param value [String, nil] the config value
20
+ def run(action = 'list', key = nil, value = nil)
21
+ setup_registry
22
+
23
+ case action
24
+ when 'list'
25
+ action_list
26
+ when 'get'
27
+ action_get(key)
28
+ when 'set'
29
+ action_set(key, value)
30
+ when 'unset'
31
+ action_unset(key)
32
+ else
33
+ error! "Unknown action: #{action}\nValid actions: list, get, set, unset"
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # List all configuration
40
+ def action_list
41
+ say 'Current configuration:', :cyan
42
+ say '', :clear
43
+
44
+ config_data = {
45
+ 'Registry' => config.registry || '(not set)',
46
+ 'Timeout' => format_config_value(config.timeout, '(no timeout)'),
47
+ 'Debug' => format_config_value(config.debug),
48
+ 'Dry run' => format_config_value(config.dry_run),
49
+ 'Metrics' => format_config_value(config.metrics),
50
+ 'Shell' => format_config_value(config.shell, '(auto-detect)'),
51
+ 'Format' => format_config_value(config.format),
52
+ 'Output' => format_config_value(config.output, '(stdout)'),
53
+ 'Search paths' => format_config_value(config.search_paths, '(not set)'),
54
+ 'Use color' => format_config_value(config.use_color, '(auto-detect)')
55
+ }
56
+
57
+ config_data.each do |k, v|
58
+ value_str = v.is_a?(String) ? "'#{v}'" : v.to_s
59
+ say " #{k.ljust(20)}: #{value_str}", :white
60
+ end
61
+
62
+ # Show persistent config if exists
63
+ say '', :clear
64
+ if File.exist?(CONFIG_FILE)
65
+ say 'Persistent configuration:', :cyan
66
+ say " File: #{CONFIG_FILE}", :dim
67
+
68
+ persistent = load_persistent_config
69
+ if persistent && !persistent.empty?
70
+ persistent.each do |k, v|
71
+ say " #{k}: #{v}", :dim
72
+ end
73
+ else
74
+ say ' (empty)', :dim
75
+ end
76
+ else
77
+ say 'No persistent configuration file found.', :dim
78
+ say " Config would be saved to: #{CONFIG_FILE}", :dim
79
+ end
80
+
81
+ # Show environment variables
82
+ say '', :clear
83
+ say 'Environment variables:', :cyan
84
+ env_vars = {
85
+ 'UKIRYU_REGISTRY' => ENV['UKIRYU_REGISTRY'],
86
+ 'UKIRYU_TIMEOUT' => ENV['UKIRYU_TIMEOUT'],
87
+ 'UKIRYU_DEBUG' => ENV['UKIRYU_DEBUG'],
88
+ 'UKIRYU_DRY_RUN' => ENV['UKIRYU_DRY_RUN'],
89
+ 'UKIRYU_METRICS' => ENV['UKIRYU_METRICS'],
90
+ 'UKIRYU_SHELL' => ENV['UKIRYU_SHELL'],
91
+ 'UKIRYU_FORMAT' => ENV['UKIRYU_FORMAT'],
92
+ 'UKIRYU_OUTPUT' => ENV['UKIRYU_OUTPUT'],
93
+ 'UKIRYU_SEARCH_PATHS' => ENV['UKIRYU_SEARCH_PATHS'],
94
+ 'UKIRYU_USE_COLOR' => ENV['UKIRYU_USE_COLOR']
95
+ }
96
+
97
+ has_env = false
98
+ env_vars.each do |k, v|
99
+ if v
100
+ has_env = true
101
+ say " #{k.ljust(25)}: #{v}", :white
102
+ end
103
+ end
104
+ say ' (none set)', :dim unless has_env
105
+ end
106
+
107
+ # Get a configuration value
108
+ #
109
+ # @param key [String] the config key
110
+ def action_get(key)
111
+ normalized_key = normalize_key(key)
112
+
113
+ value = case normalized_key
114
+ when :registry then config.registry
115
+ when :timeout then config.timeout
116
+ when :debug then config.debug
117
+ when :dry_run then config.dry_run
118
+ when :metrics then config.metrics
119
+ when :shell then config.shell
120
+ when :format then config.format
121
+ when :output then config.output
122
+ when :search_paths then config.search_paths
123
+ when :use_color then config.use_color
124
+ else
125
+ error! "Unknown config key: #{key}\nValid keys: registry, timeout, debug, dry_run, metrics, shell, format, output, search_paths, use_color"
126
+ end
127
+
128
+ say "#{key}: #{format_config_value(value, '(not set)')}", :white
129
+ end
130
+
131
+ # Set a configuration value (persisted to file)
132
+ #
133
+ # @param key [String] the config key
134
+ # @param value [String] the config value
135
+ def action_set(key, value)
136
+ error! 'Usage: ukiryu config set <key> <value>' unless key && value
137
+
138
+ normalized_key = normalize_key(key)
139
+ parsed_value = parse_value(normalized_key, value)
140
+
141
+ # Save to persistent config
142
+ ensure_config_dir
143
+ persistent = load_persistent_config || {}
144
+ persistent[key] = value
145
+ save_persistent_config(persistent)
146
+
147
+ # Also set in current config
148
+ set_config_value(normalized_key, parsed_value)
149
+
150
+ say "Set #{key} = #{value}", :green
151
+ say "Saved to: #{CONFIG_FILE}", :dim
152
+ end
153
+
154
+ # Unset a configuration value
155
+ #
156
+ # @param key [String] the config key
157
+ def action_unset(key)
158
+ error! 'Usage: ukiryu config unset <key>' unless key
159
+
160
+ persistent = load_persistent_config
161
+ error! "Key not set in persistent config: #{key}" unless persistent&.key?(key)
162
+
163
+ persistent.delete(key)
164
+ save_persistent_config(persistent)
165
+
166
+ say "Unset #{key}", :green
167
+ say "Updated: #{CONFIG_FILE}", :dim
168
+ end
169
+
170
+ # Normalize key to symbol
171
+ #
172
+ # @param key [String] the key
173
+ # @return [Symbol] normalized key
174
+ def normalize_key(key)
175
+ key.to_s.downcase.to_sym
176
+ end
177
+
178
+ # Parse value based on key type
179
+ #
180
+ # @param key [Symbol] the config key
181
+ # @param value [String] the string value
182
+ # @return [Object] parsed value
183
+ def parse_value(key, value)
184
+ case key
185
+ when :timeout
186
+ value.to_i
187
+ when :debug, :dry_run, :metrics, :use_color
188
+ %w[true yes 1].include?(value.downcase)
189
+ when :format
190
+ value.to_sym
191
+ else
192
+ value
193
+ end
194
+ end
195
+
196
+ # Set config value
197
+ #
198
+ # @param key [Symbol] the config key
199
+ # @param value [Object] the value
200
+ def set_config_value(key, value)
201
+ case key
202
+ when :timeout then config.timeout = value
203
+ when :debug then config.debug = value
204
+ when :dry_run then config.dry_run = value
205
+ when :metrics then config.metrics = value
206
+ when :shell then config.shell = value
207
+ when :format then config.format = value
208
+ when :output then config.output = value
209
+ when :registry then config.registry = value
210
+ when :search_paths then config.search_paths = value
211
+ when :use_color then config.use_color = value
212
+ end
213
+ end
214
+
215
+ # Format config value for display
216
+ #
217
+ # @param value [Object] the value
218
+ # @param default [String] default string for nil
219
+ # @return [String] formatted value
220
+ def format_config_value(value, default = '(nil)')
221
+ value.nil? ? default : value
222
+ end
223
+
224
+ # Ensure config directory exists
225
+ def ensure_config_dir
226
+ FileUtils.mkdir_p(CONFIG_DIR) unless Dir.exist?(CONFIG_DIR)
227
+ end
228
+
229
+ # Load persistent config from file
230
+ #
231
+ # @return [Hash, nil] the persistent config or nil
232
+ def load_persistent_config
233
+ return nil unless File.exist?(CONFIG_FILE)
234
+
235
+ YAML.load_file(CONFIG_FILE, permitted_classes: [Symbol])
236
+ rescue StandardError => e
237
+ say "Warning: Failed to load config file: #{e.message}", :red
238
+ nil
239
+ end
240
+
241
+ # Save persistent config to file
242
+ #
243
+ # @param data [Hash] the config data
244
+ def save_persistent_config(data)
245
+ File.write(CONFIG_FILE, data.to_yaml)
246
+ end
247
+ end
248
+ end
249
+ end