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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +143 -0
- data/.gitignore +8 -0
- data/.gitmodules +3 -0
- data/Gemfile +17 -0
- data/README.adoc +295 -0
- data/Rakefile +8 -0
- data/lib/ukiryu/cli.rb +348 -0
- data/lib/ukiryu/errors.rb +30 -0
- data/lib/ukiryu/executor.rb +716 -0
- data/lib/ukiryu/platform.rb +93 -0
- data/lib/ukiryu/registry.rb +246 -0
- data/lib/ukiryu/schema_validator.rb +103 -0
- data/lib/ukiryu/shell/base.rb +79 -0
- data/lib/ukiryu/shell/bash.rb +60 -0
- data/lib/ukiryu/shell/cmd.rb +75 -0
- data/lib/ukiryu/shell/fish.rb +16 -0
- data/lib/ukiryu/shell/powershell.rb +60 -0
- data/lib/ukiryu/shell/sh.rb +16 -0
- data/lib/ukiryu/shell/zsh.rb +16 -0
- data/lib/ukiryu/shell.rb +164 -0
- data/lib/ukiryu/tool.rb +439 -0
- data/lib/ukiryu/type.rb +254 -0
- data/lib/ukiryu/version.rb +5 -0
- data/lib/ukiryu.rb +54 -0
- data/ukiryu.gemspec +33 -0
- metadata +72 -0
data/lib/ukiryu/type.rb
ADDED
|
@@ -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
|
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: []
|