u-struct 0.9.0 → 1.0.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.
@@ -2,22 +2,20 @@
2
2
 
3
3
  module RGB
4
4
  Color = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
5
- def to_a
6
- super.map(&:value)
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
+ )
7
11
  end
8
12
 
9
- def to_hex
10
- "##{red}#{green}#{blue}"
13
+ def to_a
14
+ @to_a ||= super.map(&:value)
11
15
  end
12
- end
13
16
 
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
- )
17
+ def to_hex
18
+ @to_hex ||= "##{red}#{green}#{blue}"
21
19
  end
22
20
  end
23
21
  end
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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
4
+ Number = Micro::Struct.with(:readonly).new(:value, :label) do
11
5
  Input = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
12
6
  value.is_a?(::Integer) && value >= 0 && value <= 255
13
7
  end
14
8
 
15
- def self.new(value, label:)
16
- __new__(value: Input[value, label: label])
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}>"
17
19
  end
18
20
  end
19
21
  end
data/examples/rgb_1.rb CHANGED
@@ -6,36 +6,33 @@ gemfile do
6
6
  source 'https://rubygems.org'
7
7
 
8
8
  gem 'u-struct', path: '..'
9
- gem 'kind'
10
9
  end
11
10
 
12
11
  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)}"
12
+ Number = ->(value) do
13
+ return value if value.is_a?(::Integer) && value >= 0 && value <= 255
14
+
15
+ raise TypeError, "#{value} must be an Integer(>= 0 and <= 255)"
15
16
  end
16
- end
17
17
 
18
- module RGBColor
19
- ColorNumber = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
20
- value.is_a?(::Integer) && value >= 0 && value <= 255
18
+ def initialize(r, g, b)
19
+ super(Number[r], Number[g], Number[b])
21
20
  end
22
21
 
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
- )
22
+ def to_hex
23
+ '#%02x%02x%02x' % self
29
24
  end
30
25
  end
31
26
 
32
- rgb_color = RGBColor.new(r: 1, g: 1, b: 255)
27
+ puts
28
+
29
+ rgb_color = RGBColor.new(red: 1, green: 1, blue: 255)
33
30
 
34
31
  p rgb_color
35
32
 
36
33
  puts
37
- puts format('to_hex: %p', rgb_color.to_hex)
38
34
  puts format('to_a: %p', rgb_color.to_a)
35
+ puts format('to_hex: %p', rgb_color.to_hex)
39
36
  puts
40
37
 
41
38
  r, g, b = rgb_color
@@ -43,5 +40,15 @@ r, g, b = rgb_color
43
40
  puts format('red: %p', r)
44
41
  puts format('green: %p', g)
45
42
  puts format('blue: %p', b)
43
+ puts
44
+
45
+ *rgb = rgb_color
46
46
 
47
- RGBColor.new(r: 1, g: -1, b: 255) # Kind::Error (g: -1 expected to be a kind of Integer(>= 0 and <= 255))
47
+ puts rgb.inspect
48
+ puts
49
+
50
+ begin
51
+ RGBColor.new(red: 1, green: -1, blue: 255)
52
+ rescue => exception
53
+ puts exception # TypeError (-1 must be an Integer(>= 0 and <= 255))
54
+ end
data/examples/rgb_2.rb CHANGED
@@ -10,22 +10,32 @@ gemfile do
10
10
  end
11
11
 
12
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
13
  Input = Kind.object(name: 'Integer(>= 0 and <= 255)') do |value|
20
14
  value.is_a?(::Integer) && value >= 0 && value <= 255
21
15
  end
22
16
 
23
- def self.new(value, label:)
24
- __new__(value: Input[value, label: label])
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}>"
25
27
  end
26
28
  end
27
29
 
28
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
+
29
39
  def to_a
30
40
  [red.value, green.value, blue.value]
31
41
  end
@@ -35,23 +45,15 @@ RGBColor = Micro::Struct.with(:readonly, :to_ary).new(:red, :green, :blue) do
35
45
  end
36
46
  end
37
47
 
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
48
+ puts
47
49
 
48
50
  rgb_color = RGBColor.new(r: 1, g: 1, b: 255)
49
51
 
50
52
  p rgb_color
51
53
 
52
54
  puts
53
- puts format('to_hex: %p', rgb_color.to_hex)
54
55
  puts format('to_a: %p', rgb_color.to_a)
56
+ puts format('to_hex: %p', rgb_color.to_hex)
55
57
  puts
56
58
 
57
59
  r, g, b = rgb_color
@@ -59,5 +61,15 @@ r, g, b = rgb_color
59
61
  puts format('red: %p', r)
60
62
  puts format('green: %p', g)
61
63
  puts format('blue: %p', b)
64
+ puts
65
+
66
+ *rgb = rgb_color
62
67
 
63
- RGB::Color.new(r: 1, g: -1, b: 255) # Kind::Error (g: -1 expected to be a kind of Integer(>= 0 and <= 255))
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 CHANGED
@@ -12,13 +12,15 @@ end
12
12
  require_relative 'rgb/number'
13
13
  require_relative 'rgb/color'
14
14
 
15
- rgb_color = RGB::Color.new(r: 1, g: 1, b: 255)
15
+ puts
16
+
17
+ rgb_color = RGB::Color.new(red: 1, green: 1, blue: 255)
16
18
 
17
19
  p rgb_color
18
20
 
19
21
  puts
20
- puts format('to_hex: %p', rgb_color.to_hex)
21
22
  puts format('to_a: %p', rgb_color.to_a)
23
+ puts format('to_hex: %p', rgb_color.to_hex)
22
24
  puts
23
25
 
24
26
  r, g, b = rgb_color
@@ -26,5 +28,15 @@ r, g, b = rgb_color
26
28
  puts format('red: %p', r)
27
29
  puts format('green: %p', g)
28
30
  puts format('blue: %p', b)
31
+ puts
29
32
 
30
- RGB::Color.new(r: 1, g: -1, b: 255) # Kind::Error (g: -1 expected to be a kind of Integer(>= 0 and <= 255))
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.9.0'
5
+ VERSION = '1.0.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.9.0
4
+ version: 1.0.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-15 00:00:00.000000000 Z
11
+ date: 2022-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -45,16 +45,19 @@ 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
58
61
  - examples/person_1.rb
59
62
  - examples/person_2.rb
60
63
  - examples/rgb/color.rb
@@ -63,9 +66,9 @@ files:
63
66
  - examples/rgb_2.rb
64
67
  - examples/rgb_3.rb
65
68
  - 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/factory.rb
70
+ - lib/micro/struct/factory/create_struct.rb
71
+ - lib/micro/struct/factory/members.rb
69
72
  - lib/micro/struct/features.rb
70
73
  - lib/micro/struct/normalize_names.rb
71
74
  - lib/micro/struct/version.rb
@@ -86,14 +89,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
89
  requirements:
87
90
  - - ">="
88
91
  - !ruby/object:Gem::Version
89
- version: 2.4.0
92
+ version: 2.2.0
90
93
  required_rubygems_version: !ruby/object:Gem::Requirement
91
94
  requirements:
92
95
  - - ">="
93
96
  - !ruby/object:Gem::Version
94
97
  version: '0'
95
98
  requirements: []
96
- rubygems_version: 3.2.17
99
+ rubygems_version: 3.3.4
97
100
  signing_key:
98
101
  specification_version: 4
99
102
  summary: Create powered Ruby structs.
data/Gemfile.lock DELETED
@@ -1,31 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- u-struct (0.9.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