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 +4 -4
- data/README.md +55 -3
- data/lib/lab42/checked_class/all.rb +9 -0
- data/lib/lab42/checked_class/constraint/predefined.rb +24 -0
- data/lib/lab42/checked_class/constraint.rb +10 -8
- data/lib/lab42/checked_class/injector.rb +7 -0
- data/lib/lab42/checked_class/proxy.rb +52 -19
- data/lib/lab42/checked_class/version.rb +1 -1
- metadata +20 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 010c1b5675022a9214b591ff4a4516928d5a55019c2203fadf75b6206a3fa744
|
|
4
|
+
data.tar.gz: b9b6e7396a49f3fe5b2e29734623108112556095a8b13d3086b42e94d987166c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 } #
|
|
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)
|
|
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
|
-
|
|
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,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
|
-
|
|
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
|
|
29
|
-
constraint
|
|
30
|
-
in [
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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, "
|
|
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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
_constraints[name] = Constraint.new(attr: name, constraint
|
|
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
|
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.
|
|
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.
|
|
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.
|
|
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: []
|