u-struct 0.5.0 → 0.9.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 +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile +4 -3
- data/Gemfile.lock +10 -1
- data/README.md +42 -3
- data/examples/person_1.rb +27 -0
- data/examples/person_2.rb +37 -0
- data/examples/rgb/color.rb +23 -0
- data/examples/rgb/number.rb +19 -0
- data/examples/rgb_1.rb +47 -0
- data/examples/rgb_2.rb +63 -0
- data/examples/rgb_3.rb +30 -0
- data/lib/micro/struct/creator/create_module.rb +67 -0
- data/lib/micro/struct/creator/create_struct.rb +51 -0
- data/lib/micro/struct/creator.rb +29 -0
- data/lib/micro/struct/features.rb +33 -0
- data/lib/micro/struct/normalize_names.rb +17 -0
- data/lib/micro/struct/version.rb +1 -1
- data/lib/micro/struct.rb +43 -116
- metadata +15 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0765309623bfa9eb720102c66cd30f5ba785bdada60af711f19a03a51a7027bd'
|
4
|
+
data.tar.gz: 216dd4920427d682c9e797e5ec0a8631486b5d8c0613391440bd0f94f2778824
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed911103da03769ac8014a618d713b313e20a19a65074ac1a737f2d4cd1717dbdf521d0f7693665d840bdbff145e518afe3e7ef875b1eef55d2555e198bf8d62
|
7
|
+
data.tar.gz: 683f94804b10f3ff03ed3f6ecbf4ff40c889dacf8dfd964496e8807974f96cbb611399fa21d95d73881a9c20d4dd0296975528cafc59dfbb20128471149c398a
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in micro-struct.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
-
gem
|
8
|
+
gem 'rake', '~> 13.0'
|
9
9
|
|
10
|
-
gem
|
10
|
+
gem 'minitest', '~> 5.0'
|
11
|
+
gem 'simplecov', '~> 0.21.2'
|
data/Gemfile.lock
CHANGED
@@ -1,21 +1,30 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
u-struct (0.
|
4
|
+
u-struct (0.9.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
docile (1.4.0)
|
9
10
|
minitest (5.14.4)
|
10
11
|
rake (13.0.6)
|
12
|
+
simplecov (0.21.2)
|
13
|
+
docile (~> 1.1)
|
14
|
+
simplecov-html (~> 0.11)
|
15
|
+
simplecov_json_formatter (~> 0.1)
|
16
|
+
simplecov-html (0.12.3)
|
17
|
+
simplecov_json_formatter (0.1.3)
|
11
18
|
|
12
19
|
PLATFORMS
|
20
|
+
ruby
|
13
21
|
x86_64-darwin-19
|
14
22
|
|
15
23
|
DEPENDENCIES
|
16
24
|
bundler
|
17
25
|
minitest (~> 5.0)
|
18
26
|
rake (~> 13.0)
|
27
|
+
simplecov (~> 0.21.2)
|
19
28
|
u-struct!
|
20
29
|
|
21
30
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -22,19 +22,58 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
```ruby
|
24
24
|
# Like in a regular Struct, you can define one or many attributes.
|
25
|
+
# But all of them will be required by default.
|
26
|
+
|
25
27
|
Micro::Struct.new(:first_name, :last_name, ...)
|
26
28
|
|
29
|
+
# Use the `optional:` arg if you want some optional attributes.
|
30
|
+
|
31
|
+
Micro::Struct.new(:first_name, :last_name, optional: :gender)
|
32
|
+
|
33
|
+
# Using `optional:` to define all attributes are optional.
|
34
|
+
|
35
|
+
Micro::Struct.new(optional: [:first_name, :last_name])
|
36
|
+
|
37
|
+
# Use the `required:` arg to define required attributes.
|
38
|
+
|
39
|
+
Micro::Struct.new(
|
40
|
+
required: [:first_name, :last_name],
|
41
|
+
optional: [:gender, :age]
|
42
|
+
)
|
43
|
+
|
27
44
|
# You can also pass a block to define custom methods.
|
45
|
+
|
28
46
|
Micro::Struct.new(:name) {}
|
29
47
|
|
30
|
-
# Available features (use one, many or all):
|
31
|
-
#
|
48
|
+
# Available features (use one, many, or all) to create Structs with a special behavior:
|
49
|
+
# .with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
|
32
50
|
|
33
51
|
Micro::Struct.with(:to_ary).new(:name)
|
34
52
|
Micro::Struct.with(:to_ary, :to_hash).new(:name)
|
35
53
|
Micro::Struct.with(:to_ary, :to_hash, :to_proc).new(:name)
|
54
|
+
Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly).new(:name)
|
55
|
+
Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy).new(:name)
|
56
|
+
|
57
|
+
# All of the possible combinations to create a Ruby Struct. ;)
|
58
|
+
|
59
|
+
Micro::Struct.new(*required)
|
60
|
+
Micro::Struct.new(*required) {}
|
61
|
+
|
62
|
+
Micro::Struct.new(optional: *)
|
63
|
+
Micro::Struct.new(optional: *) {}
|
64
|
+
|
65
|
+
Micro::Struct.new(required: *)
|
66
|
+
Micro::Struct.new(required: *) {}
|
67
|
+
|
68
|
+
Micro::Struct.new(*required, optional: *)
|
69
|
+
Micro::Struct.new(*required, optional: *) {}
|
70
|
+
|
71
|
+
Micro::Struct.new(required: *, optional: *)
|
72
|
+
Micro::Struct.new(required: *, optional: *) {}
|
73
|
+
|
74
|
+
# Any options above can be used by the `.new()` method of the struct creator returned by the `.with()` method.
|
36
75
|
|
37
|
-
Micro::Struct.with(
|
76
|
+
Micro::Struct.with(*features).new(...) {}
|
38
77
|
```
|
39
78
|
|
40
79
|
## Development
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
|
8
|
+
gem 'u-struct', path: '..'
|
9
|
+
end
|
10
|
+
|
11
|
+
Person = Micro::Struct.new(:first_name, :last_name, optional: :age) do
|
12
|
+
def name
|
13
|
+
"#{first_name} #{last_name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
|
18
|
+
|
19
|
+
puts format('first_name: %p', person.first_name)
|
20
|
+
puts format('last_name: %p', person.last_name)
|
21
|
+
puts format('name: %p', person.name)
|
22
|
+
puts format('age: %p', person.age)
|
23
|
+
puts
|
24
|
+
|
25
|
+
person.age = rand(18..100)
|
26
|
+
|
27
|
+
puts format('age: %p', person.age)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
|
8
|
+
gem 'u-struct', path: '..'
|
9
|
+
end
|
10
|
+
|
11
|
+
Person = Micro::Struct.with(:readonly, :instance_copy).new(
|
12
|
+
required: [:first_name, :last_name],
|
13
|
+
optional: [:age]
|
14
|
+
) do
|
15
|
+
def name
|
16
|
+
"#{first_name} #{last_name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
person = Person.new(first_name: 'Rodrigo', last_name: 'Serradura')
|
21
|
+
|
22
|
+
puts format('first_name: %p', person.first_name)
|
23
|
+
puts format('last_name: %p', person.last_name)
|
24
|
+
puts format('name: %p', person.name)
|
25
|
+
puts format('age: %p', person.age)
|
26
|
+
puts
|
27
|
+
|
28
|
+
rand_age = rand(18..100)
|
29
|
+
|
30
|
+
new_person = person.with(age: rand_age)
|
31
|
+
|
32
|
+
puts new_person.equal?(person)
|
33
|
+
|
34
|
+
puts format('age: %p', person.age)
|
35
|
+
puts format('age: %p', new_person.age)
|
36
|
+
|
37
|
+
person.age = rand_age
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RGB
|
4
|
+
Color = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
|
5
|
+
def to_a
|
6
|
+
super.map(&:value)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_hex
|
10
|
+
"##{red}#{green}#{blue}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Color
|
15
|
+
def self.new(r:, g:, b:)
|
16
|
+
__new__(
|
17
|
+
red: Number.new(r, label: 'r'),
|
18
|
+
green: Number.new(g, label: 'g'),
|
19
|
+
blue: Number.new(b, label: 'b')
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RGB
|
4
|
+
Number = Micro::Struct.with(:readonly).new(:value) do
|
5
|
+
def to_s
|
6
|
+
value.to_s(16)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Number
|
11
|
+
Input = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
|
12
|
+
value.is_a?(::Integer) && value >= 0 && value <= 255
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.new(value, label:)
|
16
|
+
__new__(value: Input[value, label: label])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/examples/rgb_1.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
|
8
|
+
gem 'u-struct', path: '..'
|
9
|
+
gem 'kind'
|
10
|
+
end
|
11
|
+
|
12
|
+
RGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
|
13
|
+
def to_hex
|
14
|
+
"##{red.to_s(16)}#{green.to_s(16)}#{blue.to_s(16)}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module RGBColor
|
19
|
+
ColorNumber = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
|
20
|
+
value.is_a?(::Integer) && value >= 0 && value <= 255
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.new(r:, g:, b:)
|
24
|
+
__new__(
|
25
|
+
red: ColorNumber[r, label: 'r'],
|
26
|
+
green: ColorNumber[g, label: 'g'],
|
27
|
+
blue: ColorNumber[b, label: 'b']
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
rgb_color = RGBColor.new(r: 1, g: 1, b: 255)
|
33
|
+
|
34
|
+
p rgb_color
|
35
|
+
|
36
|
+
puts
|
37
|
+
puts format('to_hex: %p', rgb_color.to_hex)
|
38
|
+
puts format('to_a: %p', rgb_color.to_a)
|
39
|
+
puts
|
40
|
+
|
41
|
+
r, g, b = rgb_color
|
42
|
+
|
43
|
+
puts format('red: %p', r)
|
44
|
+
puts format('green: %p', g)
|
45
|
+
puts format('blue: %p', b)
|
46
|
+
|
47
|
+
RGBColor.new(r: 1, g: -1, b: 255) # Kind::Error (g: -1 expected to be a kind of Integer(>= 0 and <= 255))
|
data/examples/rgb_2.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
|
8
|
+
gem 'u-struct', path: '..'
|
9
|
+
gem 'kind'
|
10
|
+
end
|
11
|
+
|
12
|
+
RGBNumber = Micro::Struct.with(:readonly).new(:value) do
|
13
|
+
def to_s
|
14
|
+
value.to_s(16)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module RGBNumber
|
19
|
+
Input = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
|
20
|
+
value.is_a?(::Integer) && value >= 0 && value <= 255
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.new(value, label:)
|
24
|
+
__new__(value: Input[value, label: label])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
RGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
|
29
|
+
def to_a
|
30
|
+
[red.value, green.value, blue.value]
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_hex
|
34
|
+
"##{red}#{green}#{blue}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module RGBColor
|
39
|
+
def self.new(r:, g:, b:)
|
40
|
+
__new__(
|
41
|
+
red: RGBNumber.new(r, label: 'r'),
|
42
|
+
green: RGBNumber.new(g, label: 'g'),
|
43
|
+
blue: RGBNumber.new(b, label: 'b')
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
rgb_color = RGBColor.new(r: 1, g: 1, b: 255)
|
49
|
+
|
50
|
+
p rgb_color
|
51
|
+
|
52
|
+
puts
|
53
|
+
puts format('to_hex: %p', rgb_color.to_hex)
|
54
|
+
puts format('to_a: %p', rgb_color.to_a)
|
55
|
+
puts
|
56
|
+
|
57
|
+
r, g, b = rgb_color
|
58
|
+
|
59
|
+
puts format('red: %p', r)
|
60
|
+
puts format('green: %p', g)
|
61
|
+
puts format('blue: %p', b)
|
62
|
+
|
63
|
+
RGB::Color.new(r: 1, g: -1, b: 255) # Kind::Error (g: -1 expected to be a kind of Integer(>= 0 and <= 255))
|
data/examples/rgb_3.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
|
8
|
+
gem 'u-struct', path: '..'
|
9
|
+
gem 'kind'
|
10
|
+
end
|
11
|
+
|
12
|
+
require_relative 'rgb/number'
|
13
|
+
require_relative 'rgb/color'
|
14
|
+
|
15
|
+
rgb_color = RGB::Color.new(r: 1, g: 1, b: 255)
|
16
|
+
|
17
|
+
p rgb_color
|
18
|
+
|
19
|
+
puts
|
20
|
+
puts format('to_hex: %p', rgb_color.to_hex)
|
21
|
+
puts format('to_a: %p', rgb_color.to_a)
|
22
|
+
puts
|
23
|
+
|
24
|
+
r, g, b = rgb_color
|
25
|
+
|
26
|
+
puts format('red: %p', r)
|
27
|
+
puts format('green: %p', g)
|
28
|
+
puts format('blue: %p', b)
|
29
|
+
|
30
|
+
RGB::Color.new(r: 1, g: -1, b: 255) # Kind::Error (g: -1 expected to be a kind of Integer(>= 0 and <= 255))
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Micro::Struct::Creator
|
4
|
+
module CreateModule
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def with(required_members, optional_members, features)
|
8
|
+
container = Module.new
|
9
|
+
|
10
|
+
def_initialize(container, required_members, optional_members)
|
11
|
+
def_triple_eq(container)
|
12
|
+
def_members(container)
|
13
|
+
def_to_proc(container) if features[:to_proc]
|
14
|
+
|
15
|
+
container
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def def_initialize(container, required_members, optional_members)
|
21
|
+
required = "#{required_members.join(':, ')}#{':' unless required_members.empty?}"
|
22
|
+
optional = "#{optional_members.join(': nil, ')}#{': nil' unless optional_members.empty?}"
|
23
|
+
|
24
|
+
method_arguments = [required, optional].reject(&:empty?).join(', ')
|
25
|
+
struct_arguments = (required_members + optional_members).join(', ')
|
26
|
+
|
27
|
+
# The .new() method will require all required keyword arguments.
|
28
|
+
# We are doing this because the Struct constructor keyword init option treats everything as optional.
|
29
|
+
#
|
30
|
+
container.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
31
|
+
def self.__new__(#{method_arguments}) # def self.__new__(a:, b:) do
|
32
|
+
Struct.send(:new, #{struct_arguments}) # Struct.send(:new, a, b)
|
33
|
+
end # end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
alias new __new__
|
37
|
+
end
|
38
|
+
RUBY
|
39
|
+
end
|
40
|
+
|
41
|
+
def def_triple_eq(container)
|
42
|
+
container.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
43
|
+
def self.===(other)
|
44
|
+
Struct === other
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
end
|
48
|
+
|
49
|
+
def def_members(container)
|
50
|
+
container.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
51
|
+
def self.members
|
52
|
+
Struct.members
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
|
57
|
+
def def_to_proc(container)
|
58
|
+
container.module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
59
|
+
def self.to_proc
|
60
|
+
->(hash) { new(**hash) }
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private_constant :CreateModule
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Micro::Struct::Creator
|
4
|
+
module CreateStruct
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def with(required_members, optional_members, features, &block)
|
8
|
+
struct = ::Struct.new(*(required_members + optional_members), &block)
|
9
|
+
struct.send(:private_class_method, :new)
|
10
|
+
|
11
|
+
def_to_ary(struct) if features[:to_ary]
|
12
|
+
def_to_hash(struct) if features[:to_hash]
|
13
|
+
def_readonly(struct) if features[:readonly]
|
14
|
+
def_instance_copy(struct) if features[:instance_copy]
|
15
|
+
|
16
|
+
struct
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def def_to_ary(struct)
|
22
|
+
struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
23
|
+
def to_ary
|
24
|
+
to_a
|
25
|
+
end
|
26
|
+
RUBY
|
27
|
+
end
|
28
|
+
|
29
|
+
def def_to_hash(struct)
|
30
|
+
struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
31
|
+
def to_hash
|
32
|
+
to_h
|
33
|
+
end
|
34
|
+
RUBY
|
35
|
+
end
|
36
|
+
|
37
|
+
def def_readonly(struct)
|
38
|
+
struct.send(:private, *struct.members.map { |member| "#{member}=" })
|
39
|
+
end
|
40
|
+
|
41
|
+
def def_instance_copy(struct)
|
42
|
+
struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
43
|
+
def with(**members)
|
44
|
+
self.class.const_get(:Container, false).new(**to_h.merge(members))
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private_constant :CreateStruct
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro::Struct
|
4
|
+
class Creator
|
5
|
+
require_relative 'creator/create_module'
|
6
|
+
require_relative 'creator/create_struct'
|
7
|
+
|
8
|
+
def initialize(features)
|
9
|
+
@features = Features.require(features)
|
10
|
+
end
|
11
|
+
|
12
|
+
NormalizeMemberNames = ->(values) do
|
13
|
+
NormalizeNames::AsSymbols.(values, context: 'member')
|
14
|
+
end
|
15
|
+
|
16
|
+
def new(*members, required: nil, optional: nil, &block)
|
17
|
+
optional_members = NormalizeMemberNames[optional]
|
18
|
+
required_members = NormalizeMemberNames[members] + NormalizeMemberNames[required]
|
19
|
+
|
20
|
+
container = CreateModule.with(required_members, optional_members, @features)
|
21
|
+
struct = CreateStruct.with(required_members, optional_members, @features, &block)
|
22
|
+
|
23
|
+
container.const_set(:Struct, struct)
|
24
|
+
struct.const_set(:Container, container)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private_constant :Creator
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro::Struct
|
4
|
+
module Features
|
5
|
+
DISABLED =
|
6
|
+
{ to_ary: false,
|
7
|
+
to_hash: false,
|
8
|
+
to_proc: false,
|
9
|
+
readonly: false,
|
10
|
+
instance_copy: false }.freeze
|
11
|
+
|
12
|
+
Check = ->(to_ary:, to_hash:, to_proc:, readonly:, instance_copy:) do
|
13
|
+
{ to_ary: to_ary,
|
14
|
+
to_hash: to_hash,
|
15
|
+
to_proc: to_proc,
|
16
|
+
readonly: readonly,
|
17
|
+
instance_copy: instance_copy }
|
18
|
+
end
|
19
|
+
|
20
|
+
NormalizeFeatureNames = ->(values) do
|
21
|
+
NormalizeNames::AsSymbols.(values, context: 'feature')
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.require(names)
|
25
|
+
to_enable =
|
26
|
+
NormalizeFeatureNames[names].each_with_object({}) { |name, memo| memo[name] = true }
|
27
|
+
|
28
|
+
Check.(**DISABLED.merge(to_enable))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private_constant :Features
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Micro::Struct
|
4
|
+
module NormalizeNames
|
5
|
+
module AsSymbols
|
6
|
+
REGEXP = /\A[_A-Za-z]\w*\z/.freeze
|
7
|
+
Invalid = ->(context, val) { raise NameError.new("invalid #{context} name: #{val}") }
|
8
|
+
AsSymbol = ->(context, val) { REGEXP =~ val ? val.to_sym : Invalid[context, val] }.curry
|
9
|
+
|
10
|
+
def self.call(values, context:)
|
11
|
+
Array(values).map(&AsSymbol[context])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private_constant :NormalizeNames
|
17
|
+
end
|
data/lib/micro/struct/version.rb
CHANGED
data/lib/micro/struct.rb
CHANGED
@@ -1,141 +1,68 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'struct/version'
|
4
|
+
require_relative 'struct/features'
|
5
|
+
require_relative 'struct/creator'
|
6
|
+
require_relative 'struct/normalize_names'
|
4
7
|
|
5
8
|
module Micro
|
6
9
|
# Like in a regular Struct, you can define one or many attributes.
|
10
|
+
# But all of them will be required by default.
|
11
|
+
#
|
7
12
|
# Micro::Struct.new(:first_name, :last_name, ...)
|
8
13
|
#
|
14
|
+
# Use the `optional:` arg if you want some optional attributes.
|
15
|
+
#
|
16
|
+
# Micro::Struct.new(:first_name, :last_name, optional: :gender)
|
17
|
+
#
|
18
|
+
# Using `optional:` to define all attributes are optional.
|
19
|
+
#
|
20
|
+
# Micro::Struct.new(optional: [:first_name, :last_name])
|
21
|
+
#
|
22
|
+
# Use the `required:` arg to define required attributes.
|
23
|
+
#
|
24
|
+
# Micro::Struct.new(
|
25
|
+
# required: [:first_name, :last_name],
|
26
|
+
# optional: [:gender, :age]
|
27
|
+
# )
|
28
|
+
#
|
9
29
|
# You can also pass a block to define custom methods.
|
30
|
+
#
|
10
31
|
# Micro::Struct.new(:name) {}
|
11
32
|
#
|
12
|
-
# Available features (use one, many or all):
|
33
|
+
# Available features (use one, many, or all) to create Structs with a special behavior:
|
13
34
|
# .with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
|
14
35
|
#
|
15
36
|
# Micro::Struct.with(:to_ary).new(:name)
|
16
37
|
# Micro::Struct.with(:to_ary, :to_hash).new(:name)
|
17
38
|
# Micro::Struct.with(:to_ary, :to_hash, :to_proc).new(:name)
|
39
|
+
# Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly).new(:name)
|
40
|
+
# Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy).new(:name)
|
41
|
+
#
|
42
|
+
# All of the possible combinations to create a Ruby Struct. ;)
|
43
|
+
#
|
44
|
+
# Micro::Struct.new(optional: *)
|
45
|
+
# Micro::Struct.new(optional: *) {}
|
18
46
|
#
|
19
|
-
# Micro::Struct.
|
47
|
+
# Micro::Struct.new(required: *)
|
48
|
+
# Micro::Struct.new(required: *) {}
|
49
|
+
#
|
50
|
+
# Micro::Struct.new(*required, optional: *)
|
51
|
+
# Micro::Struct.new(*required, optional: *) {}
|
52
|
+
#
|
53
|
+
# Micro::Struct.new(required: *, optional: *)
|
54
|
+
# Micro::Struct.new(required: *, optional: *) {}
|
55
|
+
#
|
56
|
+
# Any options above can be used by the `.new()` method of the struct creator returned by the `.with()` method.
|
57
|
+
#
|
58
|
+
# Micro::Struct.with(*features).new(...) {}
|
20
59
|
module Struct
|
21
|
-
|
22
|
-
|
23
|
-
DISABLED =
|
24
|
-
{ to_ary: false,
|
25
|
-
to_hash: false,
|
26
|
-
to_proc: false,
|
27
|
-
readonly: false,
|
28
|
-
instance_copy: false
|
29
|
-
}.freeze
|
30
|
-
|
31
|
-
Expose = ->(to_ary:, to_hash:, to_proc:, readonly:, instance_copy:) do
|
32
|
-
{ to_ary: to_ary,
|
33
|
-
to_hash: to_hash,
|
34
|
-
to_proc: to_proc,
|
35
|
-
readonly: readonly,
|
36
|
-
instance_copy: instance_copy }
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.check(names)
|
40
|
-
features_to_enable =
|
41
|
-
Array(names).each_with_object({}) { |name, memo| memo[name] = true }
|
42
|
-
|
43
|
-
Expose.(**DISABLED.merge(features_to_enable))
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def initialize(features)
|
48
|
-
@features = Features.check(features)
|
49
|
-
end
|
50
|
-
|
51
|
-
def new(*members, &block)
|
52
|
-
def_container do |container|
|
53
|
-
def_struct(container, members, block) do |struct|
|
54
|
-
def_initialize(container, struct)
|
55
|
-
|
56
|
-
def_to_ary(struct)
|
57
|
-
def_to_hash(struct)
|
58
|
-
def_readonly(struct)
|
59
|
-
def_instance_copy(struct)
|
60
|
-
end
|
61
|
-
|
62
|
-
def_to_proc(container)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def def_container(&block)
|
69
|
-
Module.new.tap(&block)
|
70
|
-
end
|
71
|
-
|
72
|
-
def def_struct(container, members, block)
|
73
|
-
struct = ::Struct.new(*members, &block)
|
74
|
-
struct.const_set(:Container, container)
|
75
|
-
struct.send(:private_class_method, :new)
|
76
|
-
|
77
|
-
container.const_set(:Struct, struct)
|
78
|
-
|
79
|
-
yield struct
|
80
|
-
end
|
81
|
-
|
82
|
-
def def_initialize(container, struct)
|
83
|
-
# The .new() method will require all of the Struct's keyword arguments.
|
84
|
-
# We are doing this because Struct's keyword_init option doesn't do that.
|
85
|
-
container.module_eval(<<~RUBY, __FILE__, __LINE__ + 1) #
|
86
|
-
def self.new(#{struct.members.join(':, ')}:) # def self.new(a:, b:) do
|
87
|
-
Struct.send(:new, #{struct.members.join(', ')}) # Struct.send(:new, a, b)
|
88
|
-
end # end
|
89
|
-
|
90
|
-
def self.members
|
91
|
-
Struct.members
|
92
|
-
end
|
93
|
-
RUBY
|
94
|
-
end
|
95
|
-
|
96
|
-
def def_to_ary(struct)
|
97
|
-
struct.send(:alias_method, :to_ary, :to_a) if @features[:to_ary]
|
98
|
-
end
|
99
|
-
|
100
|
-
def def_to_hash(struct)
|
101
|
-
struct.send(:alias_method, :to_hash, :to_h) if @features[:to_hash]
|
102
|
-
end
|
103
|
-
|
104
|
-
def def_readonly(struct)
|
105
|
-
return unless @features[:readonly]
|
106
|
-
|
107
|
-
struct.send(:private, *struct.members.map { |member| "#{member}=" })
|
108
|
-
end
|
109
|
-
|
110
|
-
def def_instance_copy(struct)
|
111
|
-
return unless (@features[:readonly] || @features[:instance_copy])
|
112
|
-
|
113
|
-
struct.class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
114
|
-
def with(**members)
|
115
|
-
self.class.const_get(:Container, false).new(**to_h.merge(members))
|
116
|
-
end
|
117
|
-
RUBY
|
118
|
-
end
|
119
|
-
|
120
|
-
def def_to_proc(container)
|
121
|
-
return unless @features[:to_proc]
|
122
|
-
|
123
|
-
container.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
124
|
-
def self.to_proc
|
125
|
-
->(hash) { new(**hash) }
|
126
|
-
end
|
127
|
-
RUBY
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def self.new(*members, &block)
|
132
|
-
with.new(*members, &block)
|
60
|
+
def self.new(*members, required: nil, optional: nil, &block)
|
61
|
+
with.new(*members, required: required, optional: optional, &block)
|
133
62
|
end
|
134
63
|
|
135
64
|
def self.with(*features)
|
136
65
|
Creator.new(features)
|
137
66
|
end
|
138
|
-
|
139
|
-
private_constant :Creator
|
140
67
|
end
|
141
68
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: u-struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rodrigo Serradura
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -55,7 +55,19 @@ files:
|
|
55
55
|
- Rakefile
|
56
56
|
- bin/console
|
57
57
|
- bin/setup
|
58
|
+
- examples/person_1.rb
|
59
|
+
- examples/person_2.rb
|
60
|
+
- examples/rgb/color.rb
|
61
|
+
- examples/rgb/number.rb
|
62
|
+
- examples/rgb_1.rb
|
63
|
+
- examples/rgb_2.rb
|
64
|
+
- examples/rgb_3.rb
|
58
65
|
- lib/micro/struct.rb
|
66
|
+
- lib/micro/struct/creator.rb
|
67
|
+
- lib/micro/struct/creator/create_module.rb
|
68
|
+
- lib/micro/struct/creator/create_struct.rb
|
69
|
+
- lib/micro/struct/features.rb
|
70
|
+
- lib/micro/struct/normalize_names.rb
|
59
71
|
- lib/micro/struct/version.rb
|
60
72
|
- lib/u-struct.rb
|
61
73
|
- u-struct.gemspec
|
@@ -81,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
93
|
- !ruby/object:Gem::Version
|
82
94
|
version: '0'
|
83
95
|
requirements: []
|
84
|
-
rubygems_version: 3.2.
|
96
|
+
rubygems_version: 3.2.17
|
85
97
|
signing_key:
|
86
98
|
specification_version: 4
|
87
99
|
summary: Create powered Ruby structs.
|