u-struct 0.8.0 → 0.12.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,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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGB
4
+ Color = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
5
+ def initialize(r, g, b)
6
+ super(
7
+ Number.new(value: r, label: 'red'),
8
+ Number.new(value: g, label: 'green'),
9
+ Number.new(value: b, label: 'blue')
10
+ )
11
+ end
12
+
13
+ def to_a
14
+ @to_a ||= super.map(&:value)
15
+ end
16
+
17
+ def to_hex
18
+ @to_hex ||= "##{red}#{green}#{blue}"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RGB
4
+ Number = Micro::Struct.with(:readonly).new(:value, :label) do
5
+ Input = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
6
+ value.is_a?(::Integer) && value >= 0 && value <= 255
7
+ end
8
+
9
+ def initialize(value, label)
10
+ super(Input[value, label: label])
11
+ end
12
+
13
+ def to_s
14
+ @to_s ||= '%02x' % value
15
+ end
16
+
17
+ def inspect
18
+ "#<RGB::Number #{value}>"
19
+ end
20
+ end
21
+ end
data/examples/rgb_1.rb ADDED
@@ -0,0 +1,53 @@
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
+ Number = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
14
+ value.is_a?(::Integer) && value >= 0 && value <= 255
15
+ end
16
+
17
+ def initialize(r, g, b)
18
+ super(Number[r], Number[g], Number[b])
19
+ end
20
+
21
+ def to_hex
22
+ '#%02x%02x%02x' % self
23
+ end
24
+ end
25
+
26
+ puts
27
+
28
+ rgb_color = RGBColor.new(red: 1, green: 1, blue: 255)
29
+
30
+ p rgb_color
31
+
32
+ puts
33
+ puts format('to_a: %p', rgb_color.to_a)
34
+ puts format('to_hex: %p', rgb_color.to_hex)
35
+ puts
36
+
37
+ r, g, b = rgb_color
38
+
39
+ puts format('red: %p', r)
40
+ puts format('green: %p', g)
41
+ puts format('blue: %p', b)
42
+ puts
43
+
44
+ *rgb = rgb_color
45
+
46
+ puts rgb.inspect
47
+ puts
48
+
49
+ begin
50
+ RGBColor.new(red: 1, green: -1, blue: 255)
51
+ rescue => exception
52
+ puts exception # Kind::Error (-1 expected to be a kind of Integer(>= 0 and <= 255))
53
+ end
data/examples/rgb_2.rb ADDED
@@ -0,0 +1,75 @@
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
+ Input = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
14
+ value.is_a?(::Integer) && value >= 0 && value <= 255
15
+ end
16
+
17
+ def initialize(value)
18
+ super(Input[value])
19
+ end
20
+
21
+ def to_s
22
+ '%02x' % value
23
+ end
24
+
25
+ def inspect
26
+ "#<RGBNumber #{value}>"
27
+ end
28
+ end
29
+
30
+ RGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
31
+ def self.new(r:, g:, b:)
32
+ __new__(
33
+ red: RGBNumber.new(value: r),
34
+ green: RGBNumber.new(value: g),
35
+ blue: RGBNumber.new(value: b)
36
+ )
37
+ end
38
+
39
+ def to_a
40
+ [red.value, green.value, blue.value]
41
+ end
42
+
43
+ def to_hex
44
+ "##{red}#{green}#{blue}"
45
+ end
46
+ end
47
+
48
+ puts
49
+
50
+ rgb_color = RGBColor.new(r: 1, g: 1, b: 255)
51
+
52
+ p rgb_color
53
+
54
+ puts
55
+ puts format('to_a: %p', rgb_color.to_a)
56
+ puts format('to_hex: %p', rgb_color.to_hex)
57
+ puts
58
+
59
+ r, g, b = rgb_color
60
+
61
+ puts format('red: %p', r)
62
+ puts format('green: %p', g)
63
+ puts format('blue: %p', b)
64
+ puts
65
+
66
+ *rgb = rgb_color
67
+
68
+ puts rgb.inspect
69
+ puts
70
+
71
+ begin
72
+ RGBColor.new(r: 1, g: -1, b: 255)
73
+ rescue => exception
74
+ puts exception # Kind::Error (-1 expected to be a kind of Integer(>= 0 and <= 255))
75
+ end
data/examples/rgb_3.rb ADDED
@@ -0,0 +1,42 @@
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
+ puts
16
+
17
+ rgb_color = RGB::Color.new(red: 1, green: 1, blue: 255)
18
+
19
+ p rgb_color
20
+
21
+ puts
22
+ puts format('to_a: %p', rgb_color.to_a)
23
+ puts format('to_hex: %p', rgb_color.to_hex)
24
+ puts
25
+
26
+ r, g, b = rgb_color
27
+
28
+ puts format('red: %p', r)
29
+ puts format('green: %p', g)
30
+ puts format('blue: %p', b)
31
+ puts
32
+
33
+ *rgb = rgb_color
34
+
35
+ puts rgb.inspect
36
+ puts
37
+
38
+ begin
39
+ RGB::Color.new(red: 1, green: -1, blue: 255)
40
+ rescue => exception
41
+ puts exception # Kind::Error (green: -1 expected to be a kind of Integer(>= 0 and <= 255))
42
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Struct
4
+ class Factory
5
+ module CreateStruct
6
+ extend self
7
+
8
+ def with(members, block, features)
9
+ struct = ::Struct.new(*members.required_and_optional)
10
+
11
+ ClassScope.def_new(struct, members)
12
+
13
+ ClassScope.def_features(struct, features) if features.is_a?(Features::Exposed)
14
+ ClassScope.def_to_proc(struct) if features.option?(:to_proc)
15
+ ClassScope.def_private_writers(struct) if features.option?(:readonly)
16
+
17
+ InstanceScope.def_with(struct) if features.option?(:instance_copy)
18
+ InstanceScope.def_to_ary(struct) if features.option?(:to_ary)
19
+ InstanceScope.def_to_hash(struct) if features.option?(:to_hash)
20
+
21
+ ClassScope.evaluate(struct, block)
22
+
23
+ struct
24
+ end
25
+
26
+ module ClassScope
27
+ def self.def_new(struct, members)
28
+ # The .new() method will require all required keyword arguments.
29
+ # We are doing this because the Struct constructor keyword init option treats everything as optional.
30
+ #
31
+ struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
32
+ class << self
33
+ undef_method :new
34
+
35
+ def new(#{members.to_eval.keyword_args}) # def new(a:, b:, c: nil) do
36
+ instance = allocate # instance = allocate
37
+ instance.send(:initialize, #{members.to_eval.positional_args}) # instance.send(:initialize, a, b, c)
38
+ instance # instance
39
+ end # end
40
+
41
+ alias __new__ new
42
+ end
43
+ RUBY
44
+ end
45
+
46
+ def self.def_features(struct, features)
47
+ struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
48
+ class << self
49
+ attr_accessor :__features__
50
+
51
+ alias features __features__
52
+ end
53
+ RUBY
54
+
55
+ struct.__features__ = features
56
+
57
+ struct.send(:private_class_method, :__features__=)
58
+ end
59
+
60
+ def self.def_to_proc(struct)
61
+ struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
62
+ def self.to_proc
63
+ ->(hash) { new(**hash) }
64
+ end
65
+ RUBY
66
+ end
67
+
68
+ def self.def_private_writers(struct)
69
+ struct.send(:private, :[]=)
70
+ struct.send(:private, *struct.members.map { |member| "#{member}=" })
71
+ end
72
+
73
+ def self.evaluate(struct, block)
74
+ struct.class_eval(&block) if block
75
+ end
76
+ end
77
+
78
+ module InstanceScope
79
+ def self.def_to_ary(struct)
80
+ struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
81
+ def to_ary
82
+ to_a
83
+ end
84
+ RUBY
85
+ end
86
+
87
+ def self.def_to_hash(struct)
88
+ struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
89
+ def to_hash
90
+ to_h
91
+ end
92
+ RUBY
93
+ end
94
+
95
+ def self.def_with(struct)
96
+ struct.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
97
+ def with(**members)
98
+ self.class.new(**to_h.merge(members))
99
+ end
100
+ RUBY
101
+ end
102
+ end
103
+ end
104
+
105
+ private_constant :CreateStruct
106
+ end
107
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Struct
4
+ class Factory
5
+ class Members
6
+ attr_reader :required_and_optional
7
+
8
+ Names = ->(values) { NormalizeNames::AsSymbols.(values, context: 'member') }
9
+
10
+ def initialize(required_members, required_option, optional_option)
11
+ @required = Names[required_members] + Names[required_option]
12
+ @optional = Names[optional_option]
13
+
14
+ @required_and_optional = @required + @optional
15
+ end
16
+
17
+ ToEval = ::Struct.new(:required, :optional, :required_and_optional) do
18
+ def keyword_args
19
+ required_kargs = "#{required.join(':, ')}#{':' unless required.empty?}"
20
+ optional_kargs = "#{optional.join(': nil, ')}#{': nil' unless optional.empty?}"
21
+
22
+ [required_kargs, optional_kargs].reject(&:empty?).join(', ')
23
+ end
24
+
25
+ def positional_args
26
+ required_and_optional.join(', ')
27
+ end
28
+ end
29
+
30
+ def to_eval
31
+ @to_eval ||= ToEval.new(@required, @optional, required_and_optional)
32
+ end
33
+ end
34
+
35
+ private_constant :Members
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro::Struct
4
+ class Factory
5
+ require_relative 'factory/members'
6
+ require_relative 'factory/create_struct'
7
+
8
+ def initialize(features)
9
+ @features = Features.config(features)
10
+ end
11
+
12
+ def new(*required_members, required: nil, optional: nil, &struct_block)
13
+ members = Members.new(required_members, required, optional)
14
+
15
+ CreateStruct.with(members, struct_block, @features)
16
+ end
17
+
18
+ def instance(**members, &block)
19
+ new(*members.keys, &block).new(**members)
20
+ end
21
+ end
22
+
23
+ private_constant :Factory
24
+ end
@@ -2,30 +2,53 @@
2
2
 
3
3
  module Micro::Struct
4
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 }
5
+ Names = ->(values) do
6
+ NormalizeNames::AsSymbols.(values, context: 'feature')
18
7
  end
19
8
 
20
- NormalizeFeatureNames = ->(values) do
21
- NormalizeNames::AsSymbols.(values, context: 'feature')
9
+ module Options
10
+ def self.check(to_ary:, to_hash:, to_proc:, readonly:, instance_copy:, exposed_features:)
11
+ { to_ary: to_ary,
12
+ to_hash: to_hash,
13
+ to_proc: to_proc,
14
+ readonly: readonly,
15
+ instance_copy: instance_copy,
16
+ exposed_features: exposed_features }
17
+ end
18
+
19
+ With = ->(bool, names) { names.each_with_object({}) { |name, memo| memo[name] = bool } }
20
+
21
+ DISABLED = With.(false, method(:check).parameters.map(&:last)).freeze
22
+
23
+ def self.from_names(values)
24
+ enabled = With.(true, values)
25
+
26
+ check(**DISABLED.merge(enabled))
27
+ end
22
28
  end
23
29
 
24
- def self.require(names)
25
- to_enable =
26
- NormalizeFeatureNames[names].each_with_object({}) { |name, memo| memo[name] = true }
30
+ Config = ::Struct.new(:names, :options) do
31
+ def option?(name)
32
+ options.fetch(name)
33
+ end
34
+
35
+ def options?(*names)
36
+ names.all? { |name| option?(name) }
37
+ end
38
+ end
39
+
40
+ Exposed = Class.new(Config)
41
+
42
+ def self.config(values)
43
+ names = Names[values]
44
+ options = Options.from_names(names)
45
+
46
+ return Config.new(names, options) unless options[:exposed_features]
47
+
48
+ names.delete(:exposed_features)
49
+ options.delete(:exposed_features)
27
50
 
28
- Check.(**DISABLED.merge(to_enable))
51
+ Exposed.new(names.freeze, options.freeze).freeze
29
52
  end
30
53
  end
31
54
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Micro
4
4
  module Struct
5
- VERSION = '0.8.0'
5
+ VERSION = '0.12.0'
6
6
  end
7
7
  end
data/lib/micro/struct.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'struct/version'
4
4
  require_relative 'struct/features'
5
- require_relative 'struct/creator'
5
+ require_relative 'struct/factory'
6
6
  require_relative 'struct/normalize_names'
7
7
 
8
8
  module Micro
@@ -31,13 +31,14 @@ module Micro
31
31
  # Micro::Struct.new(:name) {}
32
32
  #
33
33
  # Available features (use one, many, or all) to create Structs with a special behavior:
34
- # .with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy)
34
+ # .with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy, :exposed_features)
35
35
  #
36
36
  # Micro::Struct.with(:to_ary).new(:name)
37
37
  # Micro::Struct.with(:to_ary, :to_hash).new(:name)
38
38
  # Micro::Struct.with(:to_ary, :to_hash, :to_proc).new(:name)
39
39
  # Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly).new(:name)
40
40
  # Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy).new(:name)
41
+ # Micro::Struct.with(:to_ary, :to_hash, :to_proc, :readonly, :instance_copy, :exposed_features).new(:name)
41
42
  #
42
43
  # All of the possible combinations to create a Ruby Struct. ;)
43
44
  #
@@ -62,7 +63,11 @@ module Micro
62
63
  end
63
64
 
64
65
  def self.with(*features)
65
- Creator.new(features)
66
+ Factory.new(features)
67
+ end
68
+
69
+ def self.instance(**members, &block)
70
+ with.instance(**members, &block)
66
71
  end
67
72
  end
68
73
  end
data/u-struct.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = %q{Create powered Ruby structs.}
13
13
  spec.homepage = 'https://github.com/serradura/u-struct'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 2.4.0'
15
+ spec.required_ruby_version = '>= 2.2.0'
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = 'https://github.com/serradura/u-struct'
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.8.0
4
+ version: 0.12.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-05 00:00:00.000000000 Z
11
+ date: 2021-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -45,20 +45,30 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".github/workflows/ci.yml"
48
49
  - ".gitignore"
50
+ - ".vscode/settings.json"
49
51
  - CHANGELOG.md
50
52
  - CODE_OF_CONDUCT.md
51
53
  - Gemfile
52
- - Gemfile.lock
53
54
  - LICENSE.txt
54
55
  - README.md
55
56
  - Rakefile
56
57
  - bin/console
58
+ - bin/prepare_coverage
57
59
  - bin/setup
60
+ - bin/test
61
+ - examples/person_1.rb
62
+ - examples/person_2.rb
63
+ - examples/rgb/color.rb
64
+ - examples/rgb/number.rb
65
+ - examples/rgb_1.rb
66
+ - examples/rgb_2.rb
67
+ - examples/rgb_3.rb
58
68
  - lib/micro/struct.rb
59
- - lib/micro/struct/creator.rb
60
- - lib/micro/struct/creator/create_module.rb
61
- - lib/micro/struct/creator/create_struct.rb
69
+ - lib/micro/struct/factory.rb
70
+ - lib/micro/struct/factory/create_struct.rb
71
+ - lib/micro/struct/factory/members.rb
62
72
  - lib/micro/struct/features.rb
63
73
  - lib/micro/struct/normalize_names.rb
64
74
  - lib/micro/struct/version.rb
@@ -79,14 +89,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
89
  requirements:
80
90
  - - ">="
81
91
  - !ruby/object:Gem::Version
82
- version: 2.4.0
92
+ version: 2.2.0
83
93
  required_rubygems_version: !ruby/object:Gem::Requirement
84
94
  requirements:
85
95
  - - ">="
86
96
  - !ruby/object:Gem::Version
87
97
  version: '0'
88
98
  requirements: []
89
- rubygems_version: 3.2.17
99
+ rubygems_version: 3.2.22
90
100
  signing_key:
91
101
  specification_version: 4
92
102
  summary: Create powered Ruby structs.
data/Gemfile.lock DELETED
@@ -1,31 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- u-struct (0.8.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- docile (1.4.0)
10
- minitest (5.14.4)
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)
18
-
19
- PLATFORMS
20
- ruby
21
- x86_64-darwin-19
22
-
23
- DEPENDENCIES
24
- bundler
25
- minitest (~> 5.0)
26
- rake (~> 13.0)
27
- simplecov (~> 0.21.2)
28
- u-struct!
29
-
30
- BUNDLED WITH
31
- 2.2.32