ukiryu 0.1.1 → 0.1.3
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/release.yml +58 -14
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +170 -79
- data/Gemfile +1 -1
- data/README.adoc +1603 -576
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +261 -0
- data/docs/_config.yml +180 -0
- data/docs/advanced/custom-tool-classes.adoc +581 -0
- data/docs/advanced/index.adoc +20 -0
- data/docs/features/configuration.adoc +657 -0
- data/docs/features/index.adoc +31 -0
- data/docs/features/platform-support.adoc +488 -0
- data/docs/getting-started/core-concepts.adoc +666 -0
- data/docs/getting-started/index.adoc +36 -0
- data/docs/getting-started/installation.adoc +216 -0
- data/docs/getting-started/quick-start.adoc +258 -0
- data/docs/guides/env-var-sets.adoc +388 -0
- data/docs/guides/index.adoc +20 -0
- data/docs/interfaces/cli.adoc +609 -0
- data/docs/interfaces/index.adoc +153 -0
- data/docs/interfaces/ruby-api.adoc +538 -0
- data/docs/lychee.toml +49 -0
- data/docs/reference/configuration-options.adoc +720 -0
- data/docs/reference/error-codes.adoc +634 -0
- data/docs/reference/index.adoc +20 -0
- data/docs/reference/ruby-api.adoc +1217 -0
- data/docs/understanding/index.adoc +20 -0
- data/lib/ukiryu/cli.rb +43 -58
- data/lib/ukiryu/cli_commands/base_command.rb +16 -27
- data/lib/ukiryu/cli_commands/cache_command.rb +100 -0
- data/lib/ukiryu/cli_commands/commands_command.rb +8 -8
- data/lib/ukiryu/cli_commands/commands_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/config_command.rb +49 -7
- data/lib/ukiryu/cli_commands/definitions_command.rb +254 -0
- data/lib/ukiryu/cli_commands/describe_command.rb +13 -7
- data/lib/ukiryu/cli_commands/describe_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/docs_command.rb +148 -0
- data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/extract_command.rb +2 -2
- data/lib/ukiryu/cli_commands/info_command.rb +7 -7
- data/lib/ukiryu/cli_commands/lint_command.rb +167 -0
- data/lib/ukiryu/cli_commands/list_command.rb +6 -6
- data/lib/ukiryu/cli_commands/opts_command.rb +2 -2
- data/lib/ukiryu/cli_commands/opts_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/register_command.rb +144 -0
- data/lib/ukiryu/cli_commands/resolve_command.rb +124 -0
- data/lib/ukiryu/cli_commands/run_command.rb +38 -14
- data/lib/ukiryu/cli_commands/run_file_command.rb +2 -2
- data/lib/ukiryu/cli_commands/system_command.rb +50 -32
- data/lib/ukiryu/cli_commands/validate_command.rb +452 -51
- data/lib/ukiryu/cli_commands/which_command.rb +5 -5
- data/lib/ukiryu/command_builder.rb +81 -23
- data/lib/ukiryu/config/env_provider.rb +3 -3
- data/lib/ukiryu/config/env_schema.rb +6 -6
- data/lib/ukiryu/config.rb +11 -11
- data/lib/ukiryu/definition/definition_cache.rb +238 -0
- data/lib/ukiryu/definition/definition_composer.rb +257 -0
- data/lib/ukiryu/definition/definition_linter.rb +460 -0
- data/lib/ukiryu/definition/definition_validator.rb +320 -0
- data/lib/ukiryu/definition/discovery.rb +239 -0
- data/lib/ukiryu/definition/documentation_generator.rb +429 -0
- data/lib/ukiryu/definition/lint_issue.rb +168 -0
- data/lib/ukiryu/definition/loader.rb +139 -0
- data/lib/ukiryu/definition/metadata.rb +159 -0
- data/lib/ukiryu/definition/source.rb +87 -0
- data/lib/ukiryu/definition/sources/file.rb +138 -0
- data/lib/ukiryu/definition/sources/string.rb +88 -0
- data/lib/ukiryu/definition/validation_result.rb +158 -0
- data/lib/ukiryu/definition/version_resolver.rb +194 -0
- data/lib/ukiryu/definition.rb +40 -0
- data/lib/ukiryu/errors.rb +6 -0
- data/lib/ukiryu/execution_context.rb +11 -11
- data/lib/ukiryu/executor.rb +6 -0
- data/lib/ukiryu/extractors/extractor.rb +6 -5
- data/lib/ukiryu/extractors/help_parser.rb +13 -19
- data/lib/ukiryu/logger.rb +3 -1
- data/lib/ukiryu/models/command_definition.rb +3 -3
- data/lib/ukiryu/models/command_info.rb +1 -1
- data/lib/ukiryu/models/components.rb +1 -3
- data/lib/ukiryu/models/env_var_definition.rb +11 -3
- data/lib/ukiryu/models/flag_definition.rb +15 -0
- data/lib/ukiryu/models/option_definition.rb +7 -7
- data/lib/ukiryu/models/platform_profile.rb +6 -3
- data/lib/ukiryu/models/routing.rb +1 -1
- data/lib/ukiryu/models/tool_definition.rb +2 -4
- data/lib/ukiryu/models/tool_metadata.rb +6 -6
- data/lib/ukiryu/models/validation_result.rb +1 -1
- data/lib/ukiryu/models/version_compatibility.rb +6 -3
- data/lib/ukiryu/models/version_detection.rb +10 -1
- data/lib/ukiryu/{registry.rb → register.rb} +54 -38
- data/lib/ukiryu/register_auto_manager.rb +268 -0
- data/lib/ukiryu/schema_validator.rb +31 -10
- data/lib/ukiryu/shell/base.rb +18 -0
- data/lib/ukiryu/shell/bash.rb +19 -1
- data/lib/ukiryu/shell/cmd.rb +11 -1
- data/lib/ukiryu/shell/powershell.rb +11 -1
- data/lib/ukiryu/shell.rb +1 -1
- data/lib/ukiryu/tool.rb +107 -95
- data/lib/ukiryu/tool_index.rb +22 -22
- data/lib/ukiryu/tools/base.rb +12 -25
- data/lib/ukiryu/tools/generator.rb +7 -7
- data/lib/ukiryu/tools.rb +3 -3
- data/lib/ukiryu/type.rb +20 -5
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu/version_detector.rb +21 -2
- data/lib/ukiryu.rb +6 -3
- data/ukiryu-proposal.md +41 -41
- data/ukiryu.gemspec +1 -0
- metadata +64 -8
- data/.gitmodules +0 -3
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../definition'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module CliCommands
|
|
7
|
+
# CLI commands for managing tool definitions
|
|
8
|
+
#
|
|
9
|
+
# This command group provides functionality for:
|
|
10
|
+
# - Listing discovered definitions
|
|
11
|
+
# - Showing definition search paths
|
|
12
|
+
# - Adding definitions to the user library
|
|
13
|
+
# - Removing definitions from the user library
|
|
14
|
+
# - Showing details about specific definitions
|
|
15
|
+
class DefinitionsCommand < Thor
|
|
16
|
+
default_command :list
|
|
17
|
+
|
|
18
|
+
# List all discovered definitions
|
|
19
|
+
#
|
|
20
|
+
# @param verbose [Boolean] show detailed information
|
|
21
|
+
desc 'list', 'List all discovered definitions'
|
|
22
|
+
method_option :verbose, aliases: :v, desc: 'Show detailed information', type: :boolean, default: false
|
|
23
|
+
def list
|
|
24
|
+
definitions = Definition::Discovery.discover
|
|
25
|
+
|
|
26
|
+
if definitions.empty?
|
|
27
|
+
puts 'No definitions found.'
|
|
28
|
+
puts
|
|
29
|
+
puts 'Search paths:'
|
|
30
|
+
Definition::Discovery.search_paths.each do |path|
|
|
31
|
+
puts " - #{path}"
|
|
32
|
+
end
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if options[:verbose]
|
|
37
|
+
# Verbose output with details
|
|
38
|
+
definitions.each do |tool_name, defs|
|
|
39
|
+
puts "#{tool_name}:"
|
|
40
|
+
defs.each do |metadata|
|
|
41
|
+
priority_indicator = defs.index(metadata).zero? ? '*' : ' '
|
|
42
|
+
puts " #{priority_indicator} #{metadata.version} (#{metadata.source_type})"
|
|
43
|
+
puts " Path: #{metadata.path}"
|
|
44
|
+
end
|
|
45
|
+
puts
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
# Simple output
|
|
49
|
+
definitions.each do |tool_name, defs|
|
|
50
|
+
best = defs.first
|
|
51
|
+
versions_str = defs.map(&:version).join(', ')
|
|
52
|
+
puts "#{tool_name}: #{versions_str} [#{best.source_type}]"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
puts "\nLegend: * = highest priority (will be used by default)"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Show definition search paths
|
|
60
|
+
#
|
|
61
|
+
desc 'path', 'Show definition search paths'
|
|
62
|
+
def path
|
|
63
|
+
puts 'Definition search paths (in priority order):'
|
|
64
|
+
puts
|
|
65
|
+
|
|
66
|
+
paths = Definition::Discovery.search_paths
|
|
67
|
+
paths.each_with_index do |path, index|
|
|
68
|
+
prefix = index.zero? ? '(1) [Highest Priority]' : "(#{index + 1})"
|
|
69
|
+
exists = File.directory?(path) ? '✓' : '✗'
|
|
70
|
+
puts "#{prefix} #{exists} #{path}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
puts
|
|
74
|
+
puts "User directory: #{Definition::Discovery.user_definitions_directory}"
|
|
75
|
+
puts "XDG_DATA_HOME: #{Definition::Discovery.xdg_data_home}"
|
|
76
|
+
puts "XDG_DATA_DIRS: #{Definition::Discovery.xdg_data_dirs.join(':')}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Add a definition to the user library
|
|
80
|
+
#
|
|
81
|
+
# @param source_path [String] path to the definition file to add
|
|
82
|
+
desc 'add SOURCE', 'Add definition to user library'
|
|
83
|
+
method_option :name, aliases: :n, desc: 'Tool name (auto-detected from file if not specified)', type: :string
|
|
84
|
+
method_option :version, aliases: :V, desc: 'Version (auto-detected from file if not specified)', type: :string
|
|
85
|
+
def add(source_path)
|
|
86
|
+
source_path = File.expand_path(source_path)
|
|
87
|
+
|
|
88
|
+
unless File.exist?(source_path)
|
|
89
|
+
warn "Error: Source file not found: #{source_path}"
|
|
90
|
+
exit 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Load the definition to get metadata
|
|
94
|
+
begin
|
|
95
|
+
metadata = Definition::Loader.load_from_file(source_path, validation: :strict)
|
|
96
|
+
rescue DefinitionLoadError, DefinitionValidationError => e
|
|
97
|
+
warn "Error: Failed to load definition: #{e.message}"
|
|
98
|
+
exit 1
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
tool_name = options[:name] || metadata.name
|
|
102
|
+
version = options[:version] || metadata.version || '1.0'
|
|
103
|
+
|
|
104
|
+
# Determine target directory
|
|
105
|
+
user_dir = Definition::Discovery.user_definitions_directory
|
|
106
|
+
tool_dir = File.join(user_dir, tool_name)
|
|
107
|
+
|
|
108
|
+
# Create directories if needed
|
|
109
|
+
FileUtils.mkdir_p(tool_dir)
|
|
110
|
+
|
|
111
|
+
# Target file path
|
|
112
|
+
target_path = File.join(tool_dir, "#{version}.yaml")
|
|
113
|
+
|
|
114
|
+
# Check if target already exists
|
|
115
|
+
if File.exist?(target_path)
|
|
116
|
+
warn "Error: Definition already exists: #{target_path}"
|
|
117
|
+
warn 'Use --name and --version to specify a different tool/version.'
|
|
118
|
+
exit 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Copy the definition
|
|
122
|
+
FileUtils.cp(source_path, target_path)
|
|
123
|
+
|
|
124
|
+
puts 'Definition added successfully:'
|
|
125
|
+
puts " Tool: #{tool_name}"
|
|
126
|
+
puts " Version: #{version}"
|
|
127
|
+
puts " Location: #{target_path}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Remove a definition from the user library
|
|
131
|
+
#
|
|
132
|
+
# @param tool_name [String] the tool name
|
|
133
|
+
desc 'remove TOOL', 'Remove definition from user library'
|
|
134
|
+
method_option :version, aliases: :v, desc: 'Specific version to remove (removes all if not specified)',
|
|
135
|
+
type: :string
|
|
136
|
+
method_option :force, aliases: :f, desc: 'Skip confirmation prompt', type: :boolean, default: false
|
|
137
|
+
def remove(tool_name)
|
|
138
|
+
user_dir = Definition::Discovery.user_definitions_directory
|
|
139
|
+
tool_dir = File.join(user_dir, tool_name)
|
|
140
|
+
|
|
141
|
+
unless File.directory?(tool_dir)
|
|
142
|
+
warn "Error: No definitions found for tool '#{tool_name}'"
|
|
143
|
+
warn "User definitions directory: #{user_dir}"
|
|
144
|
+
exit 1
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Find versions to remove
|
|
148
|
+
versions = if options[:version]
|
|
149
|
+
specific_file = File.join(tool_dir, "#{options[:version]}.yaml")
|
|
150
|
+
if File.exist?(specific_file)
|
|
151
|
+
[options[:version]]
|
|
152
|
+
else
|
|
153
|
+
warn "Error: Version #{options[:version]} not found for tool '#{tool_name}'"
|
|
154
|
+
exit 1
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
# Get all versions
|
|
158
|
+
Dir.glob(File.join(tool_dir, '*.yaml')).map do |file|
|
|
159
|
+
File.basename(file, '.yaml')
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Confirm removal
|
|
164
|
+
unless options[:force]
|
|
165
|
+
puts 'This will remove the following definitions:'
|
|
166
|
+
versions.each do |v|
|
|
167
|
+
puts " - #{tool_name}/#{v}"
|
|
168
|
+
end
|
|
169
|
+
print 'Are you sure? [y/N] '
|
|
170
|
+
response = $stdin.gets.chomp
|
|
171
|
+
unless response.downcase == 'y'
|
|
172
|
+
puts 'Cancelled.'
|
|
173
|
+
return
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Remove files
|
|
178
|
+
versions.each do |v|
|
|
179
|
+
file_path = File.join(tool_dir, "#{v}.yaml")
|
|
180
|
+
FileUtils.rm(file_path)
|
|
181
|
+
puts "Removed: #{tool_name}/#{v}"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Remove tool directory if empty
|
|
185
|
+
if Dir.empty?(tool_dir)
|
|
186
|
+
FileUtils.rmdir(tool_dir)
|
|
187
|
+
puts "Removed empty directory: #{tool_dir}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
puts 'Done.'
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Show details about a specific definition
|
|
194
|
+
#
|
|
195
|
+
# @param tool_name [String] the tool name
|
|
196
|
+
desc 'info TOOL', 'Show definition details'
|
|
197
|
+
method_option :version, aliases: :v, desc: 'Specific version to show (shows best available if not specified)',
|
|
198
|
+
type: :string
|
|
199
|
+
def info(tool_name)
|
|
200
|
+
definitions = Definition::Discovery.definitions_for(tool_name)
|
|
201
|
+
|
|
202
|
+
if definitions.nil? || definitions.empty?
|
|
203
|
+
warn "Error: No definitions found for tool '#{tool_name}'"
|
|
204
|
+
exit 1
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Find the requested version or best available
|
|
208
|
+
metadata = if options[:version]
|
|
209
|
+
definitions.find { |d| d.version == options[:version] }
|
|
210
|
+
else
|
|
211
|
+
definitions.first
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
unless metadata
|
|
215
|
+
warn "Error: Version '#{options[:version]}' not found for tool '#{tool_name}'"
|
|
216
|
+
warn "Available versions: #{definitions.map(&:version).join(', ')}"
|
|
217
|
+
exit 1
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Show details
|
|
221
|
+
puts "Tool: #{metadata.name}"
|
|
222
|
+
puts "Version: #{metadata.version}"
|
|
223
|
+
puts "Source: #{metadata.source_type}"
|
|
224
|
+
puts "Path: #{metadata.path}"
|
|
225
|
+
puts "Exists: #{metadata.exists? ? 'Yes' : 'No'}"
|
|
226
|
+
puts "Modified: #{metadata.mtime}" if metadata.exists?
|
|
227
|
+
|
|
228
|
+
# Show all available versions
|
|
229
|
+
if definitions.length > 1
|
|
230
|
+
puts
|
|
231
|
+
puts 'Available versions:'
|
|
232
|
+
definitions.each do |defn|
|
|
233
|
+
current = defn.version == metadata.version
|
|
234
|
+
indicator = current ? '*' : ' '
|
|
235
|
+
priority_note = defn == definitions.first ? ' [default]' : ''
|
|
236
|
+
puts " #{indicator} #{defn.version} (#{defn.source_type})#{priority_note}"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Try to load and validate the definition
|
|
241
|
+
puts
|
|
242
|
+
begin
|
|
243
|
+
tool_def = metadata.load_definition
|
|
244
|
+
puts 'Validation: ✓ Valid'
|
|
245
|
+
puts "Display Name: #{tool_def.display_name}" if tool_def.display_name
|
|
246
|
+
puts "Homepage: #{tool_def.homepage}" if tool_def.homepage
|
|
247
|
+
rescue DefinitionLoadError, DefinitionValidationError => e
|
|
248
|
+
puts 'Validation: ✗ Invalid'
|
|
249
|
+
puts "Error: #{e.message}"
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
@@ -12,11 +12,11 @@ module Ukiryu
|
|
|
12
12
|
# @param tool_name [String] the tool name
|
|
13
13
|
# @param command_name [String, nil] optional command name
|
|
14
14
|
def run(tool_name, command_name = nil)
|
|
15
|
-
|
|
15
|
+
setup_register
|
|
16
16
|
|
|
17
17
|
# Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
|
|
18
18
|
tool = Tool.find_by(tool_name.to_sym)
|
|
19
|
-
error!("Tool not found: #{tool_name}\nAvailable tools: #{
|
|
19
|
+
error!("Tool not found: #{tool_name}\nAvailable tools: #{Register.tools.sort.join(', ')}") unless tool
|
|
20
20
|
|
|
21
21
|
tool_commands = tool.commands
|
|
22
22
|
error! "No commands defined for #{tool_name}" unless tool_commands
|
|
@@ -78,6 +78,9 @@ module Ukiryu
|
|
|
78
78
|
|
|
79
79
|
# Show usage if available
|
|
80
80
|
say " Usage: #{cmd.usage}", :dim if cmd.usage
|
|
81
|
+
|
|
82
|
+
# Show env var sets if available
|
|
83
|
+
say " Env Var Sets: #{cmd.use_env_vars.join(', ')}", :dim if cmd.use_env_vars && !cmd.use_env_vars.empty?
|
|
81
84
|
end
|
|
82
85
|
|
|
83
86
|
say '', :clear
|
|
@@ -123,6 +126,9 @@ module Ukiryu
|
|
|
123
126
|
cmd_display = (cmd.name || 'unnamed').to_s.ljust(20)
|
|
124
127
|
desc_display = cmd.description || 'No description'
|
|
125
128
|
say " #{cmd_display} #{desc_display}", :white
|
|
129
|
+
|
|
130
|
+
# Show env var sets if available
|
|
131
|
+
say " Env Var Sets: #{cmd.use_env_vars.join(', ')}", :dim if cmd.use_env_vars && !cmd.use_env_vars.empty?
|
|
126
132
|
end
|
|
127
133
|
|
|
128
134
|
say '', :clear
|
|
@@ -154,9 +160,9 @@ module Ukiryu
|
|
|
154
160
|
say '', :clear
|
|
155
161
|
end
|
|
156
162
|
|
|
157
|
-
#
|
|
158
|
-
if cmd.
|
|
159
|
-
say "
|
|
163
|
+
# Env var sets
|
|
164
|
+
if cmd.use_env_vars && !cmd.use_env_vars.empty?
|
|
165
|
+
say "Env Var Sets: #{cmd.use_env_vars.join(', ')}", :white
|
|
160
166
|
say '', :clear
|
|
161
167
|
end
|
|
162
168
|
|
|
@@ -200,13 +206,13 @@ module Ukiryu
|
|
|
200
206
|
name = opt.name || 'unnamed'
|
|
201
207
|
cli = opt.cli || 'N/A'
|
|
202
208
|
type = opt.type || 'string'
|
|
203
|
-
|
|
209
|
+
assignment_delimiter = opt.assignment_delimiter || 'auto'
|
|
204
210
|
default = opt.default
|
|
205
211
|
platforms = opt.platforms || []
|
|
206
212
|
|
|
207
213
|
say " #{name} (#{type})", :white
|
|
208
214
|
say " CLI: #{cli}", :dim
|
|
209
|
-
say "
|
|
215
|
+
say " Assignment Delimiter: #{assignment_delimiter}", :dim if assignment_delimiter != 'auto'
|
|
210
216
|
say " Default: #{default}", :dim if default
|
|
211
217
|
say " Platforms: #{platforms.join(', ')}", :dim if platforms.any?
|
|
212
218
|
say " Description: #{opt.description}", :dim if opt.description
|
|
@@ -12,7 +12,7 @@ module Ukiryu
|
|
|
12
12
|
# @param tool_name [String] the tool name
|
|
13
13
|
# @param command_name [String, nil] optional command name
|
|
14
14
|
def run(tool_name, command_name = nil)
|
|
15
|
-
|
|
15
|
+
setup_register
|
|
16
16
|
|
|
17
17
|
tool = Tool.get(tool_name)
|
|
18
18
|
tool_commands = tool.commands
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative '../definition/documentation_generator'
|
|
5
|
+
|
|
6
|
+
module Ukiryu
|
|
7
|
+
module CliCommands
|
|
8
|
+
# Generate documentation from tool definitions
|
|
9
|
+
#
|
|
10
|
+
# The docs command generates human-readable documentation
|
|
11
|
+
# from tool definitions in various formats.
|
|
12
|
+
class DocsCommand < Thor
|
|
13
|
+
class_option :verbose, type: :boolean, default: false
|
|
14
|
+
class_option :format, type: :string, default: 'markdown', enum: %w[markdown md asciidoc adoc]
|
|
15
|
+
class_option :output, type: :string, desc: 'Output file path'
|
|
16
|
+
|
|
17
|
+
desc 'generate TOOL', 'Generate documentation for a tool'
|
|
18
|
+
option :register, type: :string, desc: 'Register path'
|
|
19
|
+
def generate(tool_name)
|
|
20
|
+
generate_docs(tool_name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc 'generate-all', 'Generate documentation for all tools'
|
|
24
|
+
option :register, type: :string, desc: 'Register path'
|
|
25
|
+
option :output_dir, type: :string, desc: 'Output directory', default: 'docs'
|
|
26
|
+
def generate_all
|
|
27
|
+
generate_all_docs
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc 'serve', 'Serve documentation locally (experimental)'
|
|
31
|
+
option :port, type: :numeric, default: 8000
|
|
32
|
+
def serve
|
|
33
|
+
serve_docs
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Generate documentation for a tool
|
|
39
|
+
#
|
|
40
|
+
# @param tool_name [String] the tool name
|
|
41
|
+
def generate_docs(tool_name)
|
|
42
|
+
register_path = options[:register] || Ukiryu::Register.default_register_path
|
|
43
|
+
tool_path = File.join(register_path, 'tools', tool_name)
|
|
44
|
+
|
|
45
|
+
say_error("Tool not found: #{tool_name}") unless Dir.exist?(tool_path)
|
|
46
|
+
|
|
47
|
+
# Find latest version
|
|
48
|
+
version_dirs = Dir.entries(tool_path)
|
|
49
|
+
.reject { |e| e.start_with?('.') }
|
|
50
|
+
.select { |e| File.directory?(File.join(tool_path, e)) }
|
|
51
|
+
.sort { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
|
52
|
+
|
|
53
|
+
say_error("No versions found for tool: #{tool_name}") if version_dirs.empty?
|
|
54
|
+
|
|
55
|
+
latest_version = version_dirs.first
|
|
56
|
+
definition_file = Dir.glob(File.join(tool_path, latest_version, '*.yaml')).first
|
|
57
|
+
|
|
58
|
+
say_error("No definition file found for #{tool_name} #{latest_version}") unless definition_file
|
|
59
|
+
|
|
60
|
+
definition = Ukiryu::Definition::Loader.load_from_file(definition_file)
|
|
61
|
+
format = normalize_format
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
docs = Ukiryu::Definition::DocumentationGenerator.generate(definition, format: format)
|
|
65
|
+
rescue ArgumentError => e
|
|
66
|
+
say_error("Error: #{e.message}")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Output
|
|
70
|
+
if options[:output]
|
|
71
|
+
File.write(options[:output], docs)
|
|
72
|
+
say "✓ Documentation written to: #{options[:output]}", :green
|
|
73
|
+
else
|
|
74
|
+
say docs, :white
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Generate documentation for all tools
|
|
79
|
+
def generate_all_docs
|
|
80
|
+
register_path = options[:register] || Ukiryu::Register.default_register_path
|
|
81
|
+
tools_dir = File.join(register_path, 'tools')
|
|
82
|
+
|
|
83
|
+
say_error("Tools directory not found: #{tools_dir}") unless Dir.exist?(tools_dir)
|
|
84
|
+
|
|
85
|
+
output_dir = options[:output_dir]
|
|
86
|
+
FileUtils.mkdir_p(output_dir)
|
|
87
|
+
|
|
88
|
+
count = 0
|
|
89
|
+
Dir.glob(File.join(tools_dir, '*', '*/*.yaml')).each do |file|
|
|
90
|
+
parts = file.split('/')
|
|
91
|
+
tool_name = parts[-3]
|
|
92
|
+
version = parts[-2]
|
|
93
|
+
File.basename(file, '.yaml')
|
|
94
|
+
|
|
95
|
+
definition = Ukiryu::Definition::Loader.load_from_file(file)
|
|
96
|
+
format = normalize_format
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
docs = Ukiryu::Definition::DocumentationGenerator.generate(definition, format: format)
|
|
100
|
+
rescue ArgumentError => e
|
|
101
|
+
say "Warning: Could not generate docs for #{file}: #{e.message}", :yellow
|
|
102
|
+
next
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Write to file
|
|
106
|
+
output_file = File.join(output_dir, "#{tool_name}-#{version}.#{format == :asciidoc ? 'adoc' : 'md'}")
|
|
107
|
+
File.write(output_file, docs)
|
|
108
|
+
count += 1
|
|
109
|
+
|
|
110
|
+
say "✓ Generated: #{output_file}", :green
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
say '', :clear
|
|
114
|
+
say "Generated #{count} documentation file(s) in: #{output_dir}", :cyan
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Serve documentation locally
|
|
118
|
+
def serve_docs
|
|
119
|
+
say 'Documentation server is not yet implemented.', :yellow
|
|
120
|
+
say "\nFor now, you can use a simple HTTP server:", :white
|
|
121
|
+
say " python3 -m http.server #{options[:port]}", :dim
|
|
122
|
+
say " ruby -run -e httpd . -p #{options[:port]}", :dim
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Normalize format option
|
|
126
|
+
#
|
|
127
|
+
# @return [Symbol] normalized format
|
|
128
|
+
def normalize_format
|
|
129
|
+
case options[:format]
|
|
130
|
+
when 'adoc'
|
|
131
|
+
:asciidoc
|
|
132
|
+
when 'md'
|
|
133
|
+
:markdown
|
|
134
|
+
else
|
|
135
|
+
options[:format].to_sym
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Show error message
|
|
140
|
+
#
|
|
141
|
+
# @param message [String] error message
|
|
142
|
+
def say_error(message)
|
|
143
|
+
say message, :red
|
|
144
|
+
exit 1
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -18,7 +18,7 @@ module Ukiryu
|
|
|
18
18
|
# @param command_name [String] the command name
|
|
19
19
|
# @param params [Array<String>] key=value parameter pairs
|
|
20
20
|
def run(tool_name, command_name, *params)
|
|
21
|
-
|
|
21
|
+
setup_register
|
|
22
22
|
|
|
23
23
|
# Parse key=value pairs into arguments hash
|
|
24
24
|
arguments = parse_inline_params(params)
|
|
@@ -68,8 +68,8 @@ module Ukiryu
|
|
|
68
68
|
if options[:method] && options[:method] != 'auto'
|
|
69
69
|
say " - #{options[:method]} (explicitly selected)", :white
|
|
70
70
|
else
|
|
71
|
-
say
|
|
72
|
-
say
|
|
71
|
+
say ' - native (try --ukiryu-definition flag)', :white
|
|
72
|
+
say ' - help (parse --help output)', :white
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
say '', :clear
|
|
@@ -11,11 +11,11 @@ module Ukiryu
|
|
|
11
11
|
#
|
|
12
12
|
# @param tool_name [String] the tool name
|
|
13
13
|
def run(tool_name)
|
|
14
|
-
|
|
14
|
+
setup_register
|
|
15
15
|
|
|
16
16
|
# Use find_by for interface-based discovery (ping -> ping_bsd/ping_gnu)
|
|
17
17
|
tool = Tool.find_by(tool_name.to_sym)
|
|
18
|
-
error!("Tool not found: #{tool_name}\nAvailable tools: #{
|
|
18
|
+
error!("Tool not found: #{tool_name}\nAvailable tools: #{Register.tools.sort.join(', ')}") unless tool
|
|
19
19
|
|
|
20
20
|
profile = tool.profile
|
|
21
21
|
show_all = options[:all]
|
|
@@ -122,22 +122,22 @@ module Ukiryu
|
|
|
122
122
|
# @param current_tool_name [String] the current tool name to exclude
|
|
123
123
|
# @return [Array<String>] list of other tool names
|
|
124
124
|
def find_other_implementations(interface_name, current_tool_name)
|
|
125
|
-
require_relative '../
|
|
125
|
+
require_relative '../register'
|
|
126
126
|
|
|
127
127
|
implementations = []
|
|
128
128
|
interface_sym = interface_name.to_sym
|
|
129
129
|
|
|
130
130
|
if config.debug
|
|
131
131
|
say " [DEBUG] Looking for tools implementing '#{interface_name}' (excluding '#{current_tool_name}')", :dim
|
|
132
|
-
say " [DEBUG]
|
|
132
|
+
say " [DEBUG] Register tools: #{Register.tools.inspect}", :dim
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
Register.tools.each do |tool_name|
|
|
136
136
|
next if tool_name == current_tool_name
|
|
137
137
|
|
|
138
138
|
begin
|
|
139
|
-
# Don't pass
|
|
140
|
-
tool_metadata =
|
|
139
|
+
# Don't pass register_path - let it use the default
|
|
140
|
+
tool_metadata = Register.load_tool_metadata(tool_name.to_sym)
|
|
141
141
|
if config.debug
|
|
142
142
|
say " [DEBUG] #{tool_name} -> metadata: #{tool_metadata ? tool_metadata.implements : 'nil'}", :dim
|
|
143
143
|
end
|