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,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ # Version compatibility model
5
+ #
6
+ # Checks if an installed tool version is compatible with
7
+ # the version requirements specified in a tool profile.
8
+ class VersionCompatibility
9
+ attr_reader :installed_version, :required_version, :compatible, :reason
10
+
11
+ # Initialize version compatibility check
12
+ #
13
+ # @param installed_version [String] the installed tool version
14
+ # @param required_version [String] the version requirement (e.g., ">= 2.30")
15
+ # @param compatible [Boolean] whether the versions are compatible
16
+ # @param reason [String, nil] the reason for incompatibility
17
+ def initialize(installed_version:, required_version:, compatible:, reason: nil)
18
+ @installed_version = installed_version
19
+ @required_version = required_version
20
+ @compatible = compatible
21
+ @reason = reason
22
+ end
23
+
24
+ # Check if versions are compatible
25
+ #
26
+ # @return [Boolean] true if compatible
27
+ def compatible?
28
+ @compatible
29
+ end
30
+
31
+ # Check if versions are incompatible
32
+ #
33
+ # @return [Boolean] true if incompatible
34
+ def incompatible?
35
+ !@compatible
36
+ end
37
+
38
+ # Get a human-readable status message
39
+ #
40
+ # @return [String] the status message
41
+ def status_message
42
+ if @compatible
43
+ "Version #{@installed_version} is compatible with requirement #{@required_version}"
44
+ else
45
+ @reason || "Version #{@installed_version} is not compatible with requirement #{@required_version}"
46
+ end
47
+ end
48
+
49
+ # Check compatibility against a version requirement
50
+ #
51
+ # @param installed_version [String] the installed version
52
+ # @param requirement [String] the version requirement (e.g., ">= 2.30")
53
+ # @return [VersionCompatibility] the compatibility result
54
+ def self.check(installed_version, requirement)
55
+ return new(installed_version: installed_version, required_version: requirement, compatible: true, reason: nil) if !requirement || requirement.empty?
56
+
57
+ parser = RequirementParser.new(requirement)
58
+ compatible = parser.satisfied_by?(installed_version)
59
+
60
+ if compatible
61
+ new(installed_version: installed_version, required_version: requirement, compatible: true, reason: nil)
62
+ else
63
+ new(installed_version: installed_version, required_version: requirement, compatible: false,
64
+ reason: "Version #{installed_version} does not satisfy requirement: #{requirement}")
65
+ end
66
+ end
67
+
68
+ # Requirement parser for semantic versioning
69
+ class RequirementParser
70
+ # Parse a version requirement
71
+ #
72
+ # @param requirement [String] the requirement string (e.g., ">= 2.30, < 3.0")
73
+ def initialize(requirement)
74
+ @requirement = requirement
75
+ @constraints = parse_requirements
76
+ end
77
+
78
+ # Check if a version satisfies the requirements
79
+ #
80
+ # @param version [String] the version to check
81
+ # @return [Boolean] true if satisfied
82
+ def satisfied_by?(version)
83
+ return true if @constraints.empty?
84
+
85
+ @constraints.all? { |constraint| satisfied?(version, constraint) }
86
+ end
87
+
88
+ private
89
+
90
+ # Parse requirement string into constraint array
91
+ #
92
+ # @return [Array<Hash>] array of constraint hashes
93
+ def parse_requirements
94
+ @requirement.split(',').map(&:strip).map do |req|
95
+ if req =~ /^([><=!~]+)\s*(.+)/
96
+ { operator: Regexp.last_match(1), version: Regexp.last_match(2) }
97
+ else
98
+ # Default to equality
99
+ { operator: '==', version: req }
100
+ end
101
+ end
102
+ end
103
+
104
+ # Check if a version satisfies a single constraint
105
+ #
106
+ # @param version [String] the version to check
107
+ # @param constraint [Hash] the constraint hash
108
+ # @return [Boolean] true if satisfied
109
+ def satisfied?(version, constraint)
110
+ v = parse_version(version)
111
+ req_v = parse_version(constraint[:version])
112
+
113
+ case constraint[:operator]
114
+ when '>', '>='
115
+ compare_versions(v, req_v) > 0 || (constraint[:operator] == '>=' && v == req_v)
116
+ when '<', '<='
117
+ compare_versions(v, req_v) < 0 || (constraint[:operator] == '<=' && v == req_v)
118
+ when '==', '='
119
+ v == req_v
120
+ when '!='
121
+ v != req_v
122
+ when '~>'
123
+ # Optimistic operator (~> 2.5 means >= 2.5 and < 3.0)
124
+ compare_versions(v, req_v) >= 0 && (v[0] == req_v[0])
125
+ else
126
+ false
127
+ end
128
+ end
129
+
130
+ # Parse version string into array of integers
131
+ #
132
+ # @param version [String] the version string
133
+ # @return [Array<Integer>] the version components
134
+ def parse_version(version)
135
+ version.split('.').map(&:to_i)
136
+ end
137
+
138
+ # Compare two version arrays
139
+ #
140
+ # @param v1 [Array<Integer>] first version
141
+ # @param v2 [Array<Integer>] second version
142
+ # @return [Integer] -1, 0, or 1
143
+ def compare_versions(v1, v2)
144
+ max_length = [v1.length, v2.length].max
145
+ v1_padded = v1 + [0] * (max_length - v1.length)
146
+ v2_padded = v2 + [0] * (max_length - v2.length)
147
+
148
+ v1_padded <=> v2_padded
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lutaml/model'
4
+
5
+ module Ukiryu
6
+ module Models
7
+ # Version detection configuration
8
+ #
9
+ # @example
10
+ # vd = VersionDetection.new(
11
+ # command: '--version',
12
+ # pattern: '(\d+\.\d+)',
13
+ # modern_threshold: '7.0'
14
+ # )
15
+ class VersionDetection < Lutaml::Model::Serializable
16
+ attribute :command, :string, collection: true, default: []
17
+ attribute :pattern, :string
18
+ attribute :modern_threshold, :string
19
+
20
+ yaml do
21
+ map_element 'command', to: :command
22
+ map_element 'pattern', to: :pattern
23
+ map_element 'modern_threshold', to: :modern_threshold
24
+ end
25
+
26
+ # Hash-like access for Base.detect_version compatibility
27
+ #
28
+ # @param key [Symbol, String] the attribute key
29
+ # @return [Object] the attribute value
30
+ def [](key)
31
+ key_sym = key.to_sym
32
+ # Return nil for unknown keys
33
+ return nil unless respond_to?(key_sym, true)
34
+
35
+ send(key_sym)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'models/tool_metadata'
4
+ require_relative 'models/tool_definition'
5
+ require_relative 'models/platform_profile'
6
+ require_relative 'models/command_definition'
7
+ require_relative 'models/option_definition'
8
+ require_relative 'models/flag_definition'
9
+ require_relative 'models/argument_definition'
10
+ require_relative 'models/version_detection'
11
+ require_relative 'models/search_paths'
12
+ require_relative 'models/execution_report'
13
+ require_relative 'models/version_compatibility'
14
+ require_relative 'models/exit_codes'
15
+ require_relative 'models/components'
16
+
17
+ module Ukiryu
18
+ module Models
19
+ # Models namespace for tool profile model classes
20
+ #
21
+ # These models use lutaml-model for proper YAML serialization/deserialization
22
+ end
23
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ module Options
5
+ # Abstract base class for all option classes
6
+ #
7
+ # This class provides common functionality for all dynamically generated
8
+ # option classes, including batch setting via .set() and execution via .run().
9
+ #
10
+ # @abstract
11
+ class Base
12
+ # Set multiple options at once
13
+ #
14
+ # This method allows batch assignment of options. Each key in the params
15
+ # hash should correspond to an attribute name.
16
+ #
17
+ # @param params [Hash] hash of option names to values
18
+ # @return [self] returns self for method chaining
19
+ #
20
+ # @example Batch setting options
21
+ # options.set(inputs: ["image.png"], resize: "50%")
22
+ # options.output = "output.jpg"
23
+ def set(params)
24
+ params.each do |key, value|
25
+ setter = "#{key}="
26
+ send(setter, value) if respond_to?(setter)
27
+ end
28
+ self
29
+ end
30
+
31
+ # Validate the options
32
+ #
33
+ # Performs comprehensive validation using constraint-based validation.
34
+ # This includes:
35
+ # - Type checking using TypeConstraint
36
+ # - Required argument checking using RequiredConstraint
37
+ # - Value constraints (min, max, range) using RangeConstraint
38
+ # - Enum value validation using EnumConstraint
39
+ # - Option dependencies using DependencyConstraint
40
+ #
41
+ # @return [Boolean] true if valid
42
+ # @raise [Ukiryu::ValidationError] if validation fails
43
+ def validate!
44
+ command_def = self.class.command_def
45
+ return true unless command_def
46
+
47
+ validator = Validation::Validator.new(self, command_def)
48
+ validator.validate!
49
+ end
50
+
51
+ # Check if options are valid without raising errors
52
+ #
53
+ # @return [Boolean] true if valid, false otherwise
54
+ def valid?
55
+ validate!
56
+ true
57
+ rescue Ukiryu::ValidationError
58
+ false
59
+ end
60
+
61
+ # Get validation errors without raising exceptions
62
+ #
63
+ # @return [Array<String>] list of error messages
64
+ def validation_errors
65
+ command_def = self.class.command_def
66
+ return [] unless command_def
67
+
68
+ validator = Validation::Validator.new(self, command_def)
69
+ validator.errors
70
+ end
71
+
72
+ # Convert options to shell command string
73
+ #
74
+ # @param shell_type [Symbol] the shell type (:bash, :zsh, :fish, :powershell, etc.)
75
+ # @return [String] the formatted shell command (without executable)
76
+ def to_shell(shell_type: :bash)
77
+ raise NotImplementedError, 'Subclasses must implement to_shell'
78
+ end
79
+
80
+ # Get the command definition
81
+ #
82
+ # @return [Hash, nil] the command definition from the profile
83
+ def command_def
84
+ self.class.command_def
85
+ end
86
+
87
+ # Get the command name
88
+ #
89
+ # @return [Symbol] the command name
90
+ def command_name
91
+ self.class.command_name
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shell'
4
+
5
+ module Ukiryu
6
+ module OptionsBuilder
7
+ # Formatting utilities for options, flags, and arguments
8
+ #
9
+ # This module handles the conversion of option values into shell command
10
+ # arguments according to various format styles.
11
+ module Formatter
12
+ # Format an option according to its definition
13
+ #
14
+ # @param opt_def [OptionDefinition] the option definition
15
+ # @param value [Object] the value to format
16
+ # @param shell_instance [Shell::Base] the shell instance
17
+ # @return [String, Array<String>] the formatted option(s)
18
+ def self.format_option(opt_def, value, _shell_instance)
19
+ type = opt_def.type
20
+
21
+ # Handle boolean types - just return the CLI flag
22
+ if [:boolean, TrueClass, 'boolean'].include?(type)
23
+ return nil if value.nil? || value == false
24
+
25
+ return opt_def.cli || ''
26
+ end
27
+
28
+ cli = opt_def.cli || ''
29
+ format = opt_def.format || 'double_dash_equals'
30
+ format_sym = format.is_a?(String) ? format.to_sym : format
31
+
32
+ value_str = value.is_a?(Symbol) ? value.to_s : value.to_s
33
+
34
+ # Handle array values
35
+ if value.is_a?(Array) && opt_def.separator
36
+ joined = value.join(opt_def.separator)
37
+ return case format_sym
38
+ when :double_dash_equals
39
+ "#{cli}#{joined}"
40
+ when :double_dash_space, :single_dash_space
41
+ [cli, joined]
42
+ else
43
+ "#{cli}#{joined}"
44
+ end
45
+ end
46
+
47
+ case format_sym
48
+ when :double_dash_equals
49
+ "#{cli}=#{value_str}"
50
+ when :double_dash_space, :single_dash_space
51
+ [cli, value_str]
52
+ when :single_dash_equals
53
+ "#{cli}=#{value_str}"
54
+ when :slash_colon
55
+ "#{cli}:#{value_str}"
56
+ when :slash_space
57
+ "#{cli} #{value_str}"
58
+ else
59
+ "#{cli}=#{value_str}"
60
+ end
61
+ end
62
+
63
+ # Format a flag according to its definition
64
+ #
65
+ # @param flag_def [FlagDefinition] the flag definition
66
+ # @param shell_instance [Shell::Base] the shell instance
67
+ # @return [String, nil] the formatted flag
68
+ def self.format_flag(flag_def, _shell_instance)
69
+ flag_def.cli || ''
70
+ end
71
+
72
+ # Format an argument value
73
+ #
74
+ # @param value [Object] the value to format
75
+ # @param arg_def [ArgumentDefinition] the argument definition
76
+ # @param shell_instance [Shell::Base] the shell instance
77
+ # @return [String] the formatted argument
78
+ def self.format_arg(value, arg_def, shell_instance)
79
+ if arg_def.type == :file
80
+ shell_instance.format_path(value.to_s)
81
+ else
82
+ value.to_s
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../errors'
4
+
5
+ module Ukiryu
6
+ module OptionsBuilder
7
+ # Validation utilities for options classes
8
+ #
9
+ # This module provides validation methods for dynamically generated
10
+ # options classes, ensuring required arguments are present.
11
+ module Validator
12
+ # Define validation method on an options class
13
+ #
14
+ # @param klass [Class] the class to define the method on
15
+ # @param command_def [CommandDefinition] the command definition
16
+ def self.define_validation_method(klass, command_def)
17
+ klass.define_method(:validate!) do
18
+ errors = []
19
+
20
+ # Check required arguments
21
+ (command_def.arguments || []).each do |arg_def|
22
+ attr_name = arg_def.name
23
+ value = instance_variable_get("@#{attr_name}")
24
+
25
+ # Check if required (no min specified or min > 0)
26
+ min = arg_def.min || (arg_def.variadic ? 1 : 1)
27
+ errors << "Missing required argument: #{attr_name}" if min.positive? && (value.nil? || (value.is_a?(Array) && value.empty?))
28
+ end
29
+
30
+ raise ValidationError, errors.join(', ') if errors.any?
31
+ end
32
+ end
33
+
34
+ # Validation error for options
35
+ #
36
+ class ValidationError < Error
37
+ def initialize(messages)
38
+ super("Validation failed: #{messages.join(', ')}")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end