ukiryu 0.1.0

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.
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "time"
5
+
6
+ module Ukiryu
7
+ # Type validation and conversion
8
+ #
9
+ # Validates and converts parameters according to their type definitions.
10
+ module Type
11
+ class << self
12
+ # Validate a value against a type definition
13
+ #
14
+ # @param value [Object] the value to validate
15
+ # @param type [Symbol, Hash] the type definition
16
+ # @param options [Hash] additional validation options
17
+ # @return [Object] the validated and converted value
18
+ # @raise [ValidationError] if validation fails
19
+ def validate(value, type, options = {})
20
+ type_definition = normalize_type(type)
21
+
22
+ case type_definition[:name]
23
+ when :file
24
+ validate_file(value, options)
25
+ when :string
26
+ validate_string(value, options)
27
+ when :integer
28
+ validate_integer(value, options)
29
+ when :float
30
+ validate_float(value, options)
31
+ when :symbol
32
+ validate_symbol(value, options)
33
+ when :boolean
34
+ validate_boolean(value)
35
+ when :uri
36
+ validate_uri(value, options)
37
+ when :datetime
38
+ validate_datetime(value, options)
39
+ when :hash
40
+ validate_hash(value, options)
41
+ when :array
42
+ validate_array(value, options)
43
+ else
44
+ raise ValidationError, "Unknown type: #{type_definition[:name]}"
45
+ end
46
+ end
47
+
48
+ # Normalize type definition to hash format
49
+ #
50
+ # @param type [Symbol, String, Hash] the type definition
51
+ # @return [Hash] normalized type definition
52
+ def normalize_type(type)
53
+ if type.is_a?(Hash)
54
+ type
55
+ else
56
+ # Convert string types to symbols for consistency
57
+ type_sym = type.is_a?(String) ? type.to_sym : type
58
+ { name: type_sym }
59
+ end
60
+ end
61
+
62
+ # Check if a type is valid
63
+ #
64
+ # @param type [Symbol, Hash] the type to check
65
+ # @return [Boolean]
66
+ def valid_type?(type)
67
+ type_definition = normalize_type(type)
68
+ VALID_TYPES.include?(type_definition[:name])
69
+ end
70
+
71
+ private
72
+
73
+ VALID_TYPES = %i[file string integer float symbol boolean uri datetime hash array].freeze
74
+
75
+ # Validate file type
76
+ def validate_file(value, options)
77
+ value = value.to_s
78
+ raise ValidationError, "File path cannot be empty" if value.empty?
79
+
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
84
+
85
+ value
86
+ end
87
+
88
+ # Validate string type
89
+ def validate_string(value, options)
90
+ value = value.to_s
91
+ raise ValidationError, "String cannot be empty" if value.empty? && !options[:allow_empty]
92
+
93
+ if options[:pattern] && !(value =~ options[:pattern])
94
+ raise ValidationError, "String does not match required pattern: #{options[:pattern]}"
95
+ end
96
+
97
+ value
98
+ end
99
+
100
+ # Validate integer type
101
+ def validate_integer(value, options)
102
+ value = value.is_a?(String) ? value.strip : value
103
+
104
+ begin
105
+ integer = Integer(value)
106
+ rescue ArgumentError, TypeError
107
+ raise ValidationError, "Invalid integer: #{value.inspect}"
108
+ end
109
+
110
+ if options[:range]
111
+ min, max = options[:range]
112
+ if integer < min || integer > max
113
+ raise ValidationError, "Integer #{integer} out of range [#{min}, #{max}]"
114
+ end
115
+ end
116
+
117
+ if options[:min] && integer < options[:min]
118
+ raise ValidationError, "Integer #{integer} below minimum #{options[:min]}"
119
+ end
120
+
121
+ if options[:max] && integer > options[:max]
122
+ raise ValidationError, "Integer #{integer} above maximum #{options[:max]}"
123
+ end
124
+
125
+ integer
126
+ end
127
+
128
+ # Validate float type
129
+ def validate_float(value, options)
130
+ value = value.is_a?(String) ? value.strip : value
131
+
132
+ begin
133
+ float = Float(value)
134
+ rescue ArgumentError, TypeError
135
+ raise ValidationError, "Invalid float: #{value.inspect}"
136
+ end
137
+
138
+ if options[:range]
139
+ min, max = options[:range]
140
+ if float < min || float > max
141
+ raise ValidationError, "Float #{float} out of range [#{min}, #{max}]"
142
+ end
143
+ end
144
+
145
+ float
146
+ end
147
+
148
+ # Validate symbol type
149
+ def validate_symbol(value, options)
150
+ value = value.is_a?(Symbol) ? value : value.to_s.downcase.to_sym
151
+
152
+ if options[:values]
153
+ # Convert values to symbols for comparison (handle both string and symbol values)
154
+ 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
158
+ end
159
+
160
+ value
161
+ end
162
+
163
+ # Validate boolean type
164
+ def validate_boolean(value)
165
+ # Accept various boolean representations
166
+ case value
167
+ when TrueClass, FalseClass
168
+ value
169
+ when "true", "1", "yes", "on"
170
+ true
171
+ when "false", "0", "no", "off", ""
172
+ false
173
+ else
174
+ raise ValidationError, "Invalid boolean: #{value.inspect}"
175
+ end
176
+ end
177
+
178
+ # Validate URI type
179
+ def validate_uri(value, options)
180
+ value = value.to_s
181
+ raise ValidationError, "URI cannot be empty" if value.empty?
182
+
183
+ begin
184
+ uri = URI.parse(value)
185
+ raise ValidationError, "Invalid URI: #{value}" unless uri.is_a?(URI::Generic)
186
+ uri.to_s
187
+ rescue URI::InvalidURIError => e
188
+ raise ValidationError, "Invalid URI: #{value} - #{e.message}"
189
+ end
190
+ end
191
+
192
+ # Validate datetime type
193
+ def validate_datetime(value, options)
194
+ if value.is_a?(Time) || value.is_a?(DateTime)
195
+ value
196
+ else
197
+ begin
198
+ Time.parse(value.to_s)
199
+ rescue ArgumentError => e
200
+ raise ValidationError, "Invalid datetime: #{value.inspect} - #{e.message}"
201
+ end
202
+ end
203
+ end
204
+
205
+ # Validate hash type
206
+ def validate_hash(value, options)
207
+ unless value.is_a?(Hash)
208
+ raise ValidationError, "Hash expected, got #{value.class}: #{value.inspect}"
209
+ end
210
+
211
+ if options[:keys]
212
+ unknown_keys = value.keys - options[:keys]
213
+ if unknown_keys.any?
214
+ raise ValidationError, "Unknown hash keys: #{unknown_keys.inspect}. Valid keys: #{options[:keys].inspect}"
215
+ end
216
+ end
217
+
218
+ value
219
+ end
220
+
221
+ # Validate array type
222
+ def validate_array(value, options)
223
+ array = value.is_a?(Array) ? value : [value]
224
+
225
+ if options[:min] && array.size < options[:min]
226
+ raise ValidationError, "Array has #{array.size} elements, minimum is #{options[:min]}"
227
+ end
228
+
229
+ if options[:max] && array.size > options[:max]
230
+ raise ValidationError, "Array has #{array.size} elements, maximum is #{options[:max]}"
231
+ end
232
+
233
+ if options[:size]
234
+ if options[:size].is_a?(Integer)
235
+ if array.size != options[:size]
236
+ raise ValidationError, "Array has #{array.size} elements, expected #{options[:size]}"
237
+ end
238
+ 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
242
+ end
243
+ end
244
+
245
+ # Validate element type if specified
246
+ if options[:of]
247
+ array = array.map { |v| validate(v, options[:of], options) }
248
+ end
249
+
250
+ array
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ukiryu
4
+ VERSION = "0.1.0"
5
+ end
data/lib/ukiryu.rb ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ukiryu/version"
4
+ require_relative "ukiryu/errors"
5
+
6
+ # Core modules
7
+ require_relative "ukiryu/platform"
8
+ require_relative "ukiryu/shell"
9
+ require_relative "ukiryu/type"
10
+ require_relative "ukiryu/executor"
11
+ require_relative "ukiryu/registry"
12
+ require_relative "ukiryu/tool"
13
+ require_relative "ukiryu/schema_validator"
14
+
15
+ # CLI (optional, only load if thor is available)
16
+ begin
17
+ require "thor"
18
+ require_relative "ukiryu/cli"
19
+ rescue LoadError
20
+ # Thor not available, CLI will not be available
21
+ end
22
+
23
+ module Ukiryu
24
+ class Error < StandardError; end
25
+
26
+ class << self
27
+ # Configure global Ukiryu settings
28
+ def configure
29
+ yield configuration
30
+ end
31
+
32
+ # Get global configuration
33
+ def configuration
34
+ @configuration ||= Configuration.new
35
+ end
36
+
37
+ # Reset configuration (mainly for testing)
38
+ def reset_configuration
39
+ @configuration = nil
40
+ Shell.reset
41
+ end
42
+ end
43
+
44
+ # Configuration class for global settings
45
+ class Configuration
46
+ attr_accessor :default_shell
47
+ attr_accessor :registry_path
48
+
49
+ def initialize
50
+ @default_shell = nil # Auto-detect by default
51
+ @registry_path = nil
52
+ end
53
+ end
54
+ end
data/ukiryu.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/ukiryu/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "ukiryu"
7
+ spec.version = Ukiryu::VERSION
8
+ spec.authors = ["Ribose Inc."]
9
+ spec.email = ["open.source@ribose.com"]
10
+
11
+ spec.summary = "Platform-adaptive command execution framework"
12
+ spec.description = <<~DESCRIPTION
13
+ Ukiryu is a Ruby framework for creating robust, cross-platform wrappers
14
+ around external command-line tools through declarative YAML profiles.
15
+ Ukiryu turns external CLIs into Ruby APIs with explicit type safety,
16
+ shell detection, and platform profiles.
17
+ DESCRIPTION
18
+
19
+ spec.homepage = "https://github.com/riboseinc/ukiryu"
20
+ spec.license = "BSD-2-Clause"
21
+
22
+ spec.bindir = "exe"
23
+ spec.executables = []
24
+ spec.require_paths = ["lib"]
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(bin|spec)/})
27
+ end
28
+ spec.test_files = `git ls-files -- {spec}/*`.split("\n")
29
+ spec.required_ruby_version = ">= 2.7.0"
30
+
31
+ # Ukiryu has zero dependencies for core functionality (Ruby stdlib only)
32
+ # json-schema is only needed for registry validation (optional)
33
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ukiryu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ribose Inc.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-21 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Ukiryu is a Ruby framework for creating robust, cross-platform wrappers
15
+ around external command-line tools through declarative YAML profiles.
16
+ Ukiryu turns external CLIs into Ruby APIs with explicit type safety,
17
+ shell detection, and platform profiles.
18
+ email:
19
+ - open.source@ribose.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - ".github/workflows/test.yml"
25
+ - ".gitignore"
26
+ - ".gitmodules"
27
+ - Gemfile
28
+ - README.adoc
29
+ - Rakefile
30
+ - lib/ukiryu.rb
31
+ - lib/ukiryu/cli.rb
32
+ - lib/ukiryu/errors.rb
33
+ - lib/ukiryu/executor.rb
34
+ - lib/ukiryu/platform.rb
35
+ - lib/ukiryu/registry.rb
36
+ - lib/ukiryu/schema_validator.rb
37
+ - lib/ukiryu/shell.rb
38
+ - lib/ukiryu/shell/base.rb
39
+ - lib/ukiryu/shell/bash.rb
40
+ - lib/ukiryu/shell/cmd.rb
41
+ - lib/ukiryu/shell/fish.rb
42
+ - lib/ukiryu/shell/powershell.rb
43
+ - lib/ukiryu/shell/sh.rb
44
+ - lib/ukiryu/shell/zsh.rb
45
+ - lib/ukiryu/tool.rb
46
+ - lib/ukiryu/type.rb
47
+ - lib/ukiryu/version.rb
48
+ - ukiryu.gemspec
49
+ homepage: https://github.com/riboseinc/ukiryu
50
+ licenses:
51
+ - BSD-2-Clause
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.7.0
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.5.22
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Platform-adaptive command execution framework
72
+ test_files: []