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,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ module Tools
5
+ # Class generation utilities for tool-specific classes
6
+ #
7
+ # This module handles the dynamic generation of Options, Action, and Response
8
+ # classes for each tool command, keeping the Base class focused on execution.
9
+ module ClassGenerator
10
+ # Generate an options class for a command
11
+ #
12
+ # @param tool_class [Class] the tool class
13
+ # @param command_name [Symbol] the command name
14
+ # @param command_def [Models::CommandDefinition] the command definition
15
+ # @return [Class] the generated options class
16
+ def self.generate_options_class(tool_class, command_name, command_def)
17
+ require_relative '../options/base'
18
+ require_relative '../options_builder'
19
+
20
+ # Capture tool class in closure
21
+ tool_class_ref = tool_class
22
+
23
+ # Create class name
24
+ class_name = "#{command_name.to_s.capitalize}Options"
25
+
26
+ # Define the class in the tool's namespace
27
+ options_class = Class.new(::Ukiryu::Options::Base) do
28
+ # Store command definition
29
+ @command_def = command_def
30
+ @command_name = command_name
31
+ @tool_class = tool_class_ref
32
+
33
+ # Class methods
34
+ singleton_class.send(:define_method, :command_def) do
35
+ @command_def
36
+ end
37
+
38
+ singleton_class.send(:define_method, :command_name) do
39
+ @command_name
40
+ end
41
+
42
+ singleton_class.send(:define_method, :tool_class) do
43
+ @tool_class
44
+ end
45
+ end
46
+
47
+ # Define accessors using the OptionsBuilder
48
+ Ukiryu::OptionsBuilder.define_accessors(options_class, command_def)
49
+ Ukiryu::OptionsBuilder.define_to_shell_method(options_class, command_def)
50
+ Ukiryu::OptionsBuilder.define_validation_method(options_class, command_def)
51
+
52
+ # Define extra_args accessor for manual option injection
53
+ options_class.send(:attr_accessor, :extra_args)
54
+
55
+ # Define set() method for batch assignment
56
+ options_class.send(:define_method, :set) do |params|
57
+ params.each do |key, value|
58
+ setter = "#{key}="
59
+ send(setter, value) if respond_to?(setter)
60
+ end
61
+ self
62
+ end
63
+
64
+ # Define run() method that executes on the associated tool
65
+ options_class.send(:define_method, :run) do
66
+ # Validate options before execution
67
+ validate!
68
+ tool_instance = tool_class_ref.new
69
+ tool_instance.execute(command_name, self)
70
+ end
71
+
72
+ # Const the class in the tool's namespace
73
+ tool_class_ref.const_set(class_name, options_class) unless tool_class_ref.const_defined?(class_name)
74
+
75
+ options_class
76
+ end
77
+
78
+ # Generate an action class for a command
79
+ #
80
+ # @param tool_class [Class] the tool class
81
+ # @param command_name [Symbol] the command name
82
+ # @param command_def [Models::CommandDefinition] the command definition
83
+ # @return [Class] the generated action class
84
+ def self.generate_action_class(tool_class, command_name, command_def)
85
+ require_relative '../action/base'
86
+
87
+ class_name = "#{command_name.to_s.capitalize}Action"
88
+
89
+ action_class = Class.new(::Ukiryu::Action::Base) do
90
+ @command_name = command_name
91
+ @command_def = command_def
92
+ @tool_class = tool_class
93
+
94
+ singleton_class.send(:define_method, :command_name) do
95
+ @command_name
96
+ end
97
+
98
+ singleton_class.send(:define_method, :command_def) do
99
+ @command_def
100
+ end
101
+
102
+ singleton_class.send(:define_method, :tool_class) do
103
+ @tool_class
104
+ end
105
+ end
106
+
107
+ tool_class.const_set(class_name, action_class) unless tool_class.const_defined?(class_name)
108
+ action_class
109
+ end
110
+
111
+ # Generate a response class for a command
112
+ #
113
+ # @param tool_class [Class] the tool class
114
+ # @param command_name [Symbol] the command name
115
+ # @param command_def [Models::CommandDefinition] the command definition
116
+ # @return [Class] the generated response class
117
+ def self.generate_response_class(tool_class, command_name, command_def)
118
+ require_relative '../response/base'
119
+
120
+ class_name = "#{command_name.to_s.capitalize}Response"
121
+
122
+ response_class = Class.new(::Ukiryu::Response::Base) do
123
+ @command_name = command_name
124
+ @command_def = command_def
125
+ end
126
+
127
+ tool_class.const_set(class_name, response_class) unless tool_class.const_defined?(class_name)
128
+ response_class
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ module Tools
5
+ # Executable finder utilities for tool classes
6
+ #
7
+ # This module provides methods to find executables on the system
8
+ # using search paths and aliases from tool definitions.
9
+ module ExecutableFinder
10
+ # Find the executable for a tool
11
+ #
12
+ # @param tool_name [String] the tool name
13
+ # @param tool_definition [Models::ToolDefinition] the tool definition
14
+ # @return [String, nil] path to executable or nil
15
+ def self.find_executable(tool_name, tool_definition)
16
+ require_relative '../executable_locator'
17
+
18
+ platform = Ukiryu::Runtime.instance.platform
19
+
20
+ ExecutableLocator.find(
21
+ tool_name: tool_name,
22
+ aliases: tool_definition.aliases || [],
23
+ search_paths: tool_definition.search_paths,
24
+ platform: platform
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../models/tool_definition'
4
+ require_relative '../cache'
5
+
6
+ module Ukiryu
7
+ module Tools
8
+ # Generator module for dynamically creating tool-specific classes
9
+ #
10
+ # This module is responsible for:
11
+ # - Loading tool definitions from YAML as lutaml-model models
12
+ # - Generating tool classes (e.g., Ukiryu::Tools::Imagemagick)
13
+ # - Caching generated classes with bounded LRU cache
14
+ # - Providing constant autoloading via const_missing
15
+ #
16
+ module Generator
17
+ class << self
18
+ # Get the generated classes cache (bounded LRU cache)
19
+ #
20
+ # @return [Cache] the generated classes cache
21
+ def generated_classes_cache
22
+ @generated_classes_cache ||= Cache.new(max_size: 50, ttl: 3600)
23
+ end
24
+
25
+ # Get or generate a tool class
26
+ #
27
+ # @param tool_name [Symbol, String] the tool name
28
+ # @return [Class] the tool class
29
+ def generate(tool_name)
30
+ tool_name = tool_name.to_sym
31
+ cached = generated_classes_cache[tool_name]
32
+ return cached if cached
33
+
34
+ # Load the tool definition as a lutaml-model
35
+ tool_definition = load_tool_definition(tool_name)
36
+ raise Ukiryu::ToolNotFoundError, "Tool not found: #{tool_name}" unless tool_definition
37
+
38
+ # Get the compatible platform profile
39
+ platform_profile = tool_definition.compatible_profile
40
+
41
+ # Generate the tool class
42
+ tool_class = generate_tool_class(tool_name, tool_definition, platform_profile)
43
+
44
+ generated_classes_cache[tool_name] = tool_class
45
+ tool_class
46
+ end
47
+
48
+ # Load a ToolDefinition model from the registry
49
+ #
50
+ # @param tool_name [Symbol] the tool name
51
+ # @return [Models::ToolDefinition, nil] the tool definition model
52
+ def load_tool_definition(tool_name)
53
+ require_relative '../registry'
54
+
55
+ # Load the YAML file content
56
+ yaml_content = Registry.load_tool_yaml(tool_name)
57
+ return nil unless yaml_content
58
+
59
+ # Use lutaml-model's from_yaml to parse
60
+ tool_definition = Models::ToolDefinition.from_yaml(yaml_content)
61
+
62
+ # Resolve profile inheritance (e.g., windows profiles inherit from unix)
63
+ tool_definition&.resolve_inheritance!
64
+
65
+ tool_definition
66
+ end
67
+
68
+ # Generate a tool class from a tool definition
69
+ #
70
+ # @param tool_name [Symbol] the tool name
71
+ # @param tool_definition [Models::ToolDefinition] the tool definition
72
+ # @param platform_profile [Models::PlatformProfile] the compatible platform profile
73
+ # @return [Class] the generated tool class
74
+ def generate_tool_class(tool_name, tool_definition, platform_profile)
75
+ Class.new(::Ukiryu::Tools::Base) do
76
+ @tool_name = tool_name
77
+ @tool_definition = tool_definition
78
+ @platform_profile = platform_profile
79
+
80
+ # Define class methods
81
+ singleton_class.send(:define_method, :tool_name) do
82
+ @tool_name
83
+ end
84
+
85
+ singleton_class.send(:define_method, :tool_definition) do
86
+ @tool_definition
87
+ end
88
+
89
+ singleton_class.send(:define_method, :platform_profile) do
90
+ @platform_profile
91
+ end
92
+ end
93
+ end
94
+
95
+ # Generate a tool class and const it in the Tools namespace
96
+ #
97
+ # @param tool_name [Symbol] the tool name
98
+ # @return [Class] the tool class
99
+ def generate_and_const_set(tool_name)
100
+ tool_class = generate(tool_name)
101
+ class_name = tool_name.to_s.capitalize
102
+
103
+ # Const the class in the Tools module
104
+ Ukiryu::Tools.const_set(class_name, tool_class) unless Ukiryu::Tools.const_defined?(class_name)
105
+
106
+ tool_class
107
+ end
108
+
109
+ # Clear the cache of generated classes
110
+ #
111
+ # Useful for testing or reloading profiles
112
+ def clear_cache
113
+ generated_classes_cache.clear
114
+ end
115
+
116
+ # Check if a tool class has been generated
117
+ #
118
+ # @param tool_name [Symbol] the tool name
119
+ # @return [Boolean] true if the class has been generated
120
+ def generated?(tool_name)
121
+ generated_classes_cache.key?(tool_name.to_sym)
122
+ end
123
+
124
+ # Get all generated tool classes
125
+ #
126
+ # @return [Hash] map of tool name to class
127
+ def all_generated
128
+ result = {}
129
+ generated_classes_cache.each_key do |key|
130
+ result[key] = generated_classes_cache[key]
131
+ end
132
+ result
133
+ end
134
+
135
+ # Get a list of all available tool names
136
+ #
137
+ # @return [Array<Symbol>] list of tool names
138
+ def available_tools
139
+ require_relative '../registry'
140
+
141
+ registry_path = Registry.default_registry_path
142
+ return [] unless registry_path
143
+
144
+ tools_dir = File.join(registry_path, 'tools')
145
+ return [] unless Dir.exist?(tools_dir)
146
+
147
+ Dir.entries(tools_dir)
148
+ .reject { |e| e.start_with?('.') }
149
+ .map(&:to_sym)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'tools/base'
4
+ require_relative 'tools/generator'
5
+
6
+ module Ukiryu
7
+ # Tools namespace for tool-specific classes
8
+ #
9
+ # This namespace provides lazy-autoloaded tool classes.
10
+ # When you reference Ukiryu::Tools::Imagemagick, it automatically
11
+ # generates the class if it doesn't exist.
12
+ #
13
+ # Platform aliases are also supported - e.g., Ping resolves to PingBsd
14
+ # on macOS or PingGnu on Linux.
15
+ #
16
+ # @example
17
+ # Ukiryu::Tools::Imagemagick.new.tap do |tool|
18
+ # options = tool.options_for(:convert)
19
+ # options.set(inputs: ["image.png"], resize: "50%")
20
+ # options.run
21
+ # end
22
+ #
23
+ # @example Platform alias
24
+ # # Automatically uses PingBsd on macOS, PingGnu on Linux
25
+ # Ukiryu::Tools::Ping.new.execute(:ping, host: 'localhost', count: 1)
26
+ module Tools
27
+ class << self
28
+ # Autoload tool classes via const_missing
29
+ #
30
+ # When you reference Ukiryu::Tools::Imagemagick, this method automatically
31
+ # generates the class if it doesn't exist.
32
+ #
33
+ # Platform aliases are resolved first - e.g., Ping resolves to PingBsd
34
+ # on macOS or PingGnu on Linux based on the current platform.
35
+ #
36
+ # @param name [String, Symbol] the constant name
37
+ # @return [Class] the generated tool class
38
+ def const_missing(name)
39
+ # Convert CamelCase constant name to snake_case tool name
40
+ # e.g., PingBsd -> ping_bsd, PingGnu -> ping_gnu, Ping -> ping
41
+ tool_name_str = name.to_s
42
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') # Add underscore before caps that follow lowercase
43
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2') # Add underscore between lowercase and uppercase
44
+ .downcase
45
+
46
+ tool_name = tool_name_str.to_sym
47
+
48
+ # First, check if it's a platform alias
49
+ # Look for tools that implement this alias for the current platform
50
+ platform_impl = find_platform_implementation(tool_name)
51
+ return Generator.generate_and_const_set(platform_impl) if platform_impl
52
+
53
+ # If not an alias, try to generate the tool directly
54
+ generated = Generator.generate_and_const_set(tool_name)
55
+ return generated if generated
56
+
57
+ # If nothing found, let the error propagate
58
+ nil
59
+ end
60
+
61
+ private
62
+
63
+ # Find a platform-specific implementation for a tool alias
64
+ #
65
+ # @param alias_name [Symbol] the alias to resolve
66
+ # @return [Symbol, nil] the platform-specific tool name
67
+ def find_platform_implementation(alias_name)
68
+ registry_path = Registry.default_registry_path
69
+ return nil unless registry_path && Dir.exist?(registry_path)
70
+
71
+ tools_dir = File.join(registry_path, 'tools')
72
+ return nil unless Dir.exist?(tools_dir)
73
+
74
+ current_platform = Platform.detect
75
+
76
+ # Search through all tool directories
77
+ Dir.entries(tools_dir).each do |tool_dir|
78
+ next if tool_dir.start_with?('.')
79
+
80
+ tool_path = File.join(tools_dir, tool_dir)
81
+ next unless File.directory?(tool_path)
82
+
83
+ # Look for YAML files in this directory
84
+ Dir.entries(tool_path).each do |yaml_file|
85
+ next unless yaml_file.end_with?('.yaml')
86
+
87
+ yaml_path = File.join(tool_path, yaml_file)
88
+ profile = YAML.load_file(yaml_path, symbolize_names: true)
89
+
90
+ # Check if this tool implements the alias and matches current platform
91
+ # Note: implements field is a string in YAML, convert to symbol for comparison
92
+ next unless profile[:implements]&.to_sym == alias_name
93
+
94
+ # Check platform compatibility
95
+ profiles = profile[:profiles] || []
96
+ compatible = profiles.any? do |p|
97
+ platforms = p[:platforms] || p[:platform]
98
+ platforms.nil? || platforms.map(&:to_sym).include?(current_platform)
99
+ end
100
+
101
+ return tool_dir.to_sym if compatible
102
+ end
103
+ end
104
+
105
+ nil
106
+ end
107
+ end
108
+ end
109
+ end
data/lib/ukiryu/type.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "uri"
4
- require "time"
3
+ require 'uri'
4
+ require 'time'
5
5
 
6
6
  module Ukiryu
7
7
  # Type validation and conversion
@@ -75,12 +75,10 @@ module Ukiryu
75
75
  # Validate file type
76
76
  def validate_file(value, options)
77
77
  value = value.to_s
78
- raise ValidationError, "File path cannot be empty" if value.empty?
78
+ raise ValidationError, 'File path cannot be empty' if value.empty?
79
79
 
80
80
  # Check if file exists (only if require_existing is true)
81
- if options[:require_existing] && !File.exist?(value)
82
- raise ValidationError, "File not found: #{value}"
83
- end
81
+ raise ValidationError, "File not found: #{value}" if options[:require_existing] && !File.exist?(value)
84
82
 
85
83
  value
86
84
  end
@@ -88,11 +86,9 @@ module Ukiryu
88
86
  # Validate string type
89
87
  def validate_string(value, options)
90
88
  value = value.to_s
91
- raise ValidationError, "String cannot be empty" if value.empty? && !options[:allow_empty]
89
+ raise ValidationError, 'String cannot be empty' if value.empty? && !options[:allow_empty]
92
90
 
93
- if options[:pattern] && !(value =~ options[:pattern])
94
- raise ValidationError, "String does not match required pattern: #{options[:pattern]}"
95
- end
91
+ raise ValidationError, "String does not match required pattern: #{options[:pattern]}" if options[:pattern] && value !~ options[:pattern]
96
92
 
97
93
  value
98
94
  end
@@ -109,17 +105,17 @@ module Ukiryu
109
105
 
110
106
  if options[:range]
111
107
  min, max = options[:range]
112
- if integer < min || integer > max
113
- raise ValidationError, "Integer #{integer} out of range [#{min}, #{max}]"
114
- end
108
+ raise ValidationError, "Integer #{integer} out of range [#{min}, #{max}]" if integer < min || integer > max
115
109
  end
116
110
 
117
111
  if options[:min] && integer < options[:min]
118
- raise ValidationError, "Integer #{integer} below minimum #{options[:min]}"
112
+ raise ValidationError,
113
+ "Integer #{integer} below minimum #{options[:min]}"
119
114
  end
120
115
 
121
116
  if options[:max] && integer > options[:max]
122
- raise ValidationError, "Integer #{integer} above maximum #{options[:max]}"
117
+ raise ValidationError,
118
+ "Integer #{integer} above maximum #{options[:max]}"
123
119
  end
124
120
 
125
121
  integer
@@ -137,9 +133,7 @@ module Ukiryu
137
133
 
138
134
  if options[:range]
139
135
  min, max = options[:range]
140
- if float < min || float > max
141
- raise ValidationError, "Float #{float} out of range [#{min}, #{max}]"
142
- end
136
+ raise ValidationError, "Float #{float} out of range [#{min}, #{max}]" if float < min || float > max
143
137
  end
144
138
 
145
139
  float
@@ -152,9 +146,7 @@ module Ukiryu
152
146
  if options[:values]
153
147
  # Convert values to symbols for comparison (handle both string and symbol values)
154
148
  valid_values = options[:values].map { |v| v.is_a?(String) ? v.to_sym : v }
155
- unless valid_values.include?(value)
156
- raise ValidationError, "Invalid symbol: #{value.inspect}. Valid values: #{options[:values].inspect}"
157
- end
149
+ raise ValidationError, "Invalid symbol: #{value.inspect}. Valid values: #{options[:values].inspect}" unless valid_values.include?(value)
158
150
  end
159
151
 
160
152
  value
@@ -166,9 +158,9 @@ module Ukiryu
166
158
  case value
167
159
  when TrueClass, FalseClass
168
160
  value
169
- when "true", "1", "yes", "on"
161
+ when 'true', '1', 'yes', 'on'
170
162
  true
171
- when "false", "0", "no", "off", ""
163
+ when 'false', '0', 'no', 'off', ''
172
164
  false
173
165
  else
174
166
  raise ValidationError, "Invalid boolean: #{value.inspect}"
@@ -176,13 +168,14 @@ module Ukiryu
176
168
  end
177
169
 
178
170
  # Validate URI type
179
- def validate_uri(value, options)
171
+ def validate_uri(value, _options)
180
172
  value = value.to_s
181
- raise ValidationError, "URI cannot be empty" if value.empty?
173
+ raise ValidationError, 'URI cannot be empty' if value.empty?
182
174
 
183
175
  begin
184
176
  uri = URI.parse(value)
185
177
  raise ValidationError, "Invalid URI: #{value}" unless uri.is_a?(URI::Generic)
178
+
186
179
  uri.to_s
187
180
  rescue URI::InvalidURIError => e
188
181
  raise ValidationError, "Invalid URI: #{value} - #{e.message}"
@@ -190,7 +183,7 @@ module Ukiryu
190
183
  end
191
184
 
192
185
  # Validate datetime type
193
- def validate_datetime(value, options)
186
+ def validate_datetime(value, _options)
194
187
  if value.is_a?(Time) || value.is_a?(DateTime)
195
188
  value
196
189
  else
@@ -204,14 +197,13 @@ module Ukiryu
204
197
 
205
198
  # Validate hash type
206
199
  def validate_hash(value, options)
207
- unless value.is_a?(Hash)
208
- raise ValidationError, "Hash expected, got #{value.class}: #{value.inspect}"
209
- end
200
+ raise ValidationError, "Hash expected, got #{value.class}: #{value.inspect}" unless value.is_a?(Hash)
210
201
 
211
202
  if options[:keys]
212
203
  unknown_keys = value.keys - options[:keys]
213
204
  if unknown_keys.any?
214
- raise ValidationError, "Unknown hash keys: #{unknown_keys.inspect}. Valid keys: #{options[:keys].inspect}"
205
+ raise ValidationError,
206
+ "Unknown hash keys: #{unknown_keys.inspect}. Valid keys: #{options[:keys].inspect}"
215
207
  end
216
208
  end
217
209
 
@@ -222,30 +214,23 @@ module Ukiryu
222
214
  def validate_array(value, options)
223
215
  array = value.is_a?(Array) ? value : [value]
224
216
 
225
- if options[:min] && array.size < options[:min]
226
- raise ValidationError, "Array has #{array.size} elements, minimum is #{options[:min]}"
227
- end
217
+ raise ValidationError, "Array has #{array.size} elements, minimum is #{options[:min]}" if options[:min] && array.size < options[:min]
228
218
 
229
- if options[:max] && array.size > options[:max]
230
- raise ValidationError, "Array has #{array.size} elements, maximum is #{options[:max]}"
231
- end
219
+ raise ValidationError, "Array has #{array.size} elements, maximum is #{options[:max]}" if options[:max] && array.size > options[:max]
232
220
 
233
221
  if options[:size]
234
222
  if options[:size].is_a?(Integer)
235
223
  if array.size != options[:size]
236
- raise ValidationError, "Array has #{array.size} elements, expected #{options[:size]}"
224
+ raise ValidationError,
225
+ "Array has #{array.size} elements, expected #{options[:size]}"
237
226
  end
238
227
  elsif options[:size].is_a?(Array)
239
- unless options[:size].include?(array.size)
240
- raise ValidationError, "Array has #{array.size} elements, expected one of: #{options[:size].inspect}"
241
- end
228
+ raise ValidationError, "Array has #{array.size} elements, expected one of: #{options[:size].inspect}" unless options[:size].include?(array.size)
242
229
  end
243
230
  end
244
231
 
245
232
  # Validate element type if specified
246
- if options[:of]
247
- array = array.map { |v| validate(v, options[:of], options) }
248
- end
233
+ array = array.map { |v| validate(v, options[:of], options) } if options[:of]
249
234
 
250
235
  array
251
236
  end