lab42_checked_class 0.1.1 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6a2f963809a4e9d945d7f856d5ed6e8bd2af2462e6af8f1c27ec666900adf89
4
- data.tar.gz: b40b601f78decd3af0d7ee5e24e097f64bd546381e92b7d17cd6be532de80431
3
+ metadata.gz: 010c1b5675022a9214b591ff4a4516928d5a55019c2203fadf75b6206a3fa744
4
+ data.tar.gz: b9b6e7396a49f3fe5b2e29734623108112556095a8b13d3086b42e94d987166c
5
5
  SHA512:
6
- metadata.gz: 909347acc4fe94f3573718e6a74f325a438dbd4c05af727356c2defce73cac2e4a8e0e80748f6c0333e5cb0b3aff25da52433ab836d76701caf0a33bcca5949d
7
- data.tar.gz: 66921f5b30a49125c5a437a494260dd7c5602e27088d0b34c280cb6eccee9b151cac5582fb15a1ba5e0af224895ca367ce33765614fbca0a00bed3c077b4d6e7
6
+ metadata.gz: c128e03d74d01e824ba153e5d21a2dee18035c0953b1f4ccea9f346fdcbe0f57bf83ec7815c7e83559e72e2c0174ce4b0bcdb0a5d1d0b0da1e813beacb61eece
7
+ data.tar.gz: 8e4ddb87d4b4dfa542c76fb5c8a91203fdd6bb99b598d67575692dd2003d0e96007d8ae0063f275a97518b587d703a954142530cc8c76c9deb234ae0e558cf16
data/README.md CHANGED
@@ -58,8 +58,27 @@ These specs assume the following setup code
58
58
  require 'lab42/checked_class'
59
59
  CheckedClass = Lab42::CheckedClass
60
60
  ConstraintError = CheckedClass::ConstraintError
61
+ Constraint = CheckedClass::Constraint
61
62
  ```
63
+ This is realized in the spec_helper, but if you want to have these three constants in your namespace you can
64
+ do the following:
62
65
 
66
+ ## Context: Importing constants into your namespace
67
+
68
+ Then we will get them loaded
69
+ ```ruby
70
+ expected_output = <<~EOT
71
+ #{ENV["PWD"]}/lib/lab42/checked_class/all.rb:5: warning: already initialized constant CheckedClass
72
+ #{ENV["PWD"]}/spec/spec_helper.rb:38: warning: previous definition of CheckedClass was here
73
+ #{ENV["PWD"]}/lib/lab42/checked_class/all.rb:6: warning: already initialized constant Constraint
74
+ #{ENV["PWD"]}/spec/spec_helper.rb:39: warning: previous definition of Constraint was here
75
+ #{ENV["PWD"]}/lib/lab42/checked_class/all.rb:7: warning: already initialized constant ConstraintError
76
+ #{ENV["PWD"]}/spec/spec_helper.rb:40: warning: previous definition of ConstraintError was here
77
+ EOT
78
+ expect {
79
+ require 'lab42/checked_class/all'
80
+ }.to output(expected_output).to_stderr
81
+ ```
63
82
  ## Context: Attribute definitions and constraints
64
83
 
65
84
  Given a checked class
@@ -70,10 +89,10 @@ Given a checked class
70
89
  attr :a # No constraints, no defaults
71
90
  attr b: 42 # No constraints, but a default
72
91
  attr :c, Integer # Constraint, no default
73
- attr(d: 1) { it > 0 } # Constraint and default
92
+ attr(d: 1) { it > 0 } # Default and block constraint
74
93
  attr(:e, :even?) # Symbolic constraint, no default
75
94
  attr(:f) { (it % 10).zero? } # Block constraint, no default
76
- attr(g: 2) { it > 1 } # Block Constraint and default
95
+ attr(:g, [:>, 1], default: 2) # Default and functional constraint
77
96
 
78
97
  # Named constraint
79
98
  constrain("a + b > e") { it.a + it.b > it.e }
@@ -119,7 +138,40 @@ And also we cannot create an instance that violates the constraints
119
138
  expect { MyChecked.new(a: 10, c: -4, e: 90, f: 0) }.to raise_error(ConstraintError, "a + b > e")
120
139
  ```
121
140
 
122
- ## Context: More on constraints
141
+ ### Context: Defining attributes with `method` missing.
142
+
143
+ As long as a method is called that is not defined inside the `attributes` block, it will be
144
+ interpreted as an attribute definition, as it cannot have a default value assigned, we can
145
+ also use the `defaults` macro
146
+
147
+ Given such implicit attribute definitions
148
+ ```ruby
149
+ Implicit = Class.new do
150
+ extend CheckedClass
151
+ attributes do
152
+ alpha Numeric
153
+ beta Constraint.bool?
154
+
155
+ defaults alpha: 0, beta: true
156
+ end
157
+ end
158
+ let(:instance) { Implicit.new }
159
+ ```
160
+
161
+ Then we can access the implicitly defined attributes
162
+ ```ruby
163
+ expect(instance.to_h).to eq(alpha: 0, beta: true)
164
+ ```
165
+ And we will get a constraint error as expeced
166
+ ```ruby
167
+ expected_message = "bool? constraint for beta"
168
+ expect { instance.update(beta: 42) }
169
+ .to raise_error(ConstraintError, expected_message)
170
+ ```
171
+
172
+ You can see all predefined builtin constraints [here](speculations/built_in_constraints.md)
173
+
174
+ ## Context: Runtime Semantics of Constraints
123
175
 
124
176
  ### Context: Checked und unchecked attribute updates
125
177
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lab42/checked_class'
4
+
5
+ CheckedClass = Lab42::CheckedClass
6
+ Constraint = CheckedClass::Constraint
7
+ ConstraintError = CheckedClass::ConstraintError
8
+
9
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,24 @@
1
+
2
+ # frozen_string_literal: true
3
+ module Lab42
4
+ module CheckedClass
5
+ class Constraint
6
+ module Predefined
7
+
8
+ def bool? =
9
+ tagged('bool?') { [false, true].member? it }
10
+
11
+ def match?(rgx) =
12
+ tagged("match?(#{rgx}") { rgx === it rescue false }
13
+
14
+ def member?(container) =
15
+ tagged("member?(#{container.inspect}") { container.member? it }
16
+
17
+ private
18
+ def tagged(name, &blk) = [self, blk, name]
19
+
20
+ end
21
+ end
22
+ end
23
+ end
24
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
+ require_relative 'constraint/predefined'
2
3
  module Lab42
3
4
  module CheckedClass
4
5
  class Constraint
6
+ extend Predefined
7
+
5
8
 
6
9
  def check(subject)
7
- subject = @attr ? subject[@attr] : subject
10
+ subject = @attr ? subject[@attr] : subject
8
11
  return nil if @constraint.(subject)
9
12
 
10
13
  @name
@@ -25,15 +28,14 @@ module Lab42
25
28
 
26
29
  def _mk_constraint(constraint, name, &blk)
27
30
  case constraint
28
- in Symbol
29
- constraint.to_proc
30
- in [Symbol, *] => args
31
- -> { it.send(*args) }
32
- in Module
33
- -> { constraint === it }
31
+ in Proc
32
+ constraint
33
+ in [Predefined, blk, name]
34
+ @name = [name, @name].join(' ')
35
+ blk
34
36
  in nil
35
37
  blk
36
- in _
38
+ else
37
39
  raise ArgumentError, "Type Error for constraint of attr #{name.inspect}; must be a Module, Symbol, or an array starting with a Symbol, but was #{constraint.inspect}"
38
40
  end
39
41
  end
@@ -59,6 +59,13 @@ module Lab42
59
59
  constraint_checker.check!(self)
60
60
  end
61
61
 
62
+ define_method :legal? do
63
+ check!
64
+ true
65
+ rescue ConstraintError
66
+ false
67
+ end
68
+
62
69
  define_method :to_h do
63
70
  attributes.inject(Hash.new) { |result, ele|
64
71
  result.update(ele => self[ele])
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ex_aequo/base/fn'
4
+ require 'ex_aequo/base/to_proc'
5
+
3
6
  require_relative 'constraint'
4
7
  require_relative 'constraint_checker'
5
8
  require_relative 'injector'
@@ -7,6 +10,7 @@ require_relative 'injector'
7
10
  module Lab42
8
11
  module CheckedClass
9
12
  class Proxy
13
+ Fn = ExAequo::Base::Fn
10
14
  None = Object.new.freeze
11
15
 
12
16
  attr_reader :klass
@@ -26,22 +30,27 @@ module Lab42
26
30
  @klass = klass
27
31
  end
28
32
 
29
- def attr(att, *args, &blk)
30
- if Hash === att
31
- raise ArgumentError, "attr definition with default does not support symbolic constraints" unless args.empty?
32
- raise ArgumentError, "only one attribute can be defined with defaults, not #{att.keys.inspect}" if att.size != 1
33
-
34
- return _define_att(att.keys.first, default: att.values.first, &blk)
35
- end
36
-
37
- raise ArgumentError, "attributes must be symbols, not: #{att.inspect}" unless Symbol === att
38
-
39
- if args.empty?
40
- return _define_att(att, &blk)
33
+ def attr(*args, &block)
34
+ case [block, *args]
35
+ in [constraint, Symbol => name]
36
+ _def_undefaulted_attr(name:, constraint:)
37
+ in [nil, Symbol => name, fun]
38
+ constraint = _make_fun_or_constraint(fun)
39
+ _def_undefaulted_attr(name:, constraint:)
40
+ in [constraint, Symbol => name, {default:, **nil}]
41
+ _def_defaulted_attr(name:, constraint:, default:)
42
+ in [default, Symbol => name, fun]
43
+ constraint = _make_fun_or_constraint(fun)
44
+ _def_defaulted_attr(name:, default: default.(), constraint:)
45
+ in [constraint, Hash => arg]
46
+ _def_defaulted_attr(name: arg.keys.first, default: arg.values.first, constraint:)
47
+ in [nil, Symbol => name, fun, {default:, **nil}]
48
+ constraint = _make_fun_or_constraint(fun)
49
+ _def_defaulted_attr(name:, default:, constraint:)
50
+ in [_, Symbol => name, _, {default:, **nil}]
51
+ raise ArgumentError, "functional constraint default kwd and block"
41
52
  else
42
- raise ArgumentError, "must not define symbolic and functional constraint" if @blk
43
-
44
- _define_att(att, args.first)
53
+ raise ArgumentError, "too many arguments"
45
54
  end
46
55
  end
47
56
 
@@ -52,18 +61,42 @@ module Lab42
52
61
  _constraints["object"] << Constraint.new(name:, &blk)
53
62
  end
54
63
 
64
+ def defaults(**params)
65
+ params.each do |k, v|
66
+ raise ArgumentError, "must not redefine default for key #{k.inspect}" if _defaults.has_key?(k)
67
+
68
+ _defaults[k] = v
69
+ end
70
+ end
71
+
55
72
  def init(&blk)
56
73
  raise ArgumentError, "init needs a block" unless blk
57
74
 
58
75
  @init = blk
59
76
  end
60
77
 
61
- def _define_att(name, constraint = nil, default: None, &blk)
78
+ def method_missing(name, *a, **k, &b)
79
+ attr(name, *a, **k, &b)
80
+ end
81
+
82
+ def _def_defaulted_attr(name:, constraint:, default:)
62
83
  _attributes << name
84
+ _defaults[name] = default
85
+ _constraints[name] = Constraint.new(attr: name, constraint:) if constraint
86
+ end
63
87
 
64
- # allow nil default to avoid implicit nil defaults
65
- _defaults[name] = default unless default == None
66
- _constraints[name] = Constraint.new(attr: name, constraint:, &blk) if constraint || blk
88
+ def _def_undefaulted_attr(name:, constraint:)
89
+ _attributes << name
90
+ _constraints[name] = Constraint.new(attr: name, constraint:) if constraint
91
+ end
92
+
93
+ def _make_fun_or_constraint(fun)
94
+ case fun
95
+ in [c, *] if Constraint == c
96
+ fun
97
+ else
98
+ Fn.mk_fun(fun)
99
+ end
67
100
  end
68
101
 
69
102
  def _attributes
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module Lab42
3
3
  module CheckedClass
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.1'
5
5
  end
6
6
  end
7
7
  # SPDX-License-Identifier: AGPL-3.0-or-later
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lab42_checked_class
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ex_aequo_base
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.17
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.17
12
26
  description: 'Mutable classes with constraints and lots of boilerplate code removal.
13
27
 
14
28
  '
@@ -20,8 +34,10 @@ files:
20
34
  - LICENSE
21
35
  - README.md
22
36
  - lib/lab42/checked_class.rb
37
+ - lib/lab42/checked_class/all.rb
23
38
  - lib/lab42/checked_class/argument_checker.rb
24
39
  - lib/lab42/checked_class/constraint.rb
40
+ - lib/lab42/checked_class/constraint/predefined.rb
25
41
  - lib/lab42/checked_class/constraint_checker.rb
26
42
  - lib/lab42/checked_class/constraint_error.rb
27
43
  - lib/lab42/checked_class/injector.rb
@@ -40,14 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
40
56
  requirements:
41
57
  - - ">="
42
58
  - !ruby/object:Gem::Version
43
- version: 3.4.1
59
+ version: 3.4.5
44
60
  required_rubygems_version: !ruby/object:Gem::Requirement
45
61
  requirements:
46
62
  - - ">="
47
63
  - !ruby/object:Gem::Version
48
64
  version: '0'
49
65
  requirements: []
50
- rubygems_version: 3.6.8
66
+ rubygems_version: 3.7.1
51
67
  specification_version: 4
52
68
  summary: Mutable classes with constraints and lots of boilerplate code removal.
53
69
  test_files: []