value_semantics 3.4.0 → 3.5.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 +7 -0
- data/README.md +113 -36
- data/lib/value_semantics.rb +123 -6
- data/lib/value_semantics/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '069ef7cc15f7d9b6eae3af432d486595bfa3f89c0f68ce68ab3eda2fb5d5f9f4'
|
4
|
+
data.tar.gz: f8d1839f7176743cb9d7a144d5452f999048a03a5d1d86d8b86b81b648a7bfd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cea465484d60d6343815072b4e81639e399b20ce528a12ce643be39e3a06051fdaddb4c58037ffac5237f7c62b823b49d888bc8d6fed925b181515751b4cfb46
|
7
|
+
data.tar.gz: 553a990a508956e7a2b6f96ea2ac65ee97737bc6ea5da1ea337f9d0d06b294aa61fb3e10ef1d8ce6ac5fff826b3f1bdbf17b9e805ac38b933308152929335efd
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,13 @@ Notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [3.5.0] - 2020-08-17
|
9
|
+
### Added
|
10
|
+
- Square bracket attr reader like `person[:name]`
|
11
|
+
- `HashOf` built-in validator, similar to `ArrayOf`
|
12
|
+
- `.coercer` class method, to help when composing value objects
|
13
|
+
- `ArrayCoercer` DSL method, to help when composing value objects
|
14
|
+
|
8
15
|
## [3.4.0] - 2020-08-01
|
9
16
|
### Added
|
10
17
|
- Value objects can be instantiated from any object that responds to `#to_h`.
|
data/README.md
CHANGED
@@ -19,6 +19,7 @@ See:
|
|
19
19
|
|
20
20
|
- The [announcement blog post][blog post] for some of the rationale behind the gem
|
21
21
|
- [RubyTapas episode #584][rubytapas] for an example usage scenario
|
22
|
+
- The [API documentation](https://rubydoc.info/gems/value_semantics)
|
22
23
|
- Some [discussion on Reddit][reddit]
|
23
24
|
|
24
25
|
[blog post]: https://www.rubypigeon.com/posts/value-semantics-gem-for-making-value-classes/
|
@@ -49,7 +50,7 @@ Person.new(name: "Tom", birthday: "2020-12-25")
|
|
49
50
|
#=> #<Person name="Tom" birthday=#<Date: 2020-12-25 ((2459209j,0s,0n),+0s,2299161j)>>
|
50
51
|
|
51
52
|
Person.new(birthday: Date.today)
|
52
|
-
#=> #<Person name="Anon Emous" birthday=#<Date:
|
53
|
+
#=> #<Person name="Anon Emous" birthday=#<Date: 2020-08-04 ((2459066j,0s,0n),+0s,2299161j)>>
|
53
54
|
|
54
55
|
Person.new(birthday: nil)
|
55
56
|
#=> #<Person name="Anon Emous" birthday=nil>
|
@@ -81,19 +82,15 @@ tom = Person.new(name: 'Tom')
|
|
81
82
|
|
82
83
|
|
83
84
|
# Read-only attributes
|
84
|
-
tom.name
|
85
|
-
tom
|
86
|
-
|
85
|
+
tom.name #=> "Tom"
|
86
|
+
tom[:name] #=> "Tom"
|
87
87
|
|
88
88
|
# Convert to Hash
|
89
|
-
tom.to_h #=> {
|
90
|
-
|
89
|
+
tom.to_h #=> {:name=>"Tom", :age=>31}
|
91
90
|
|
92
91
|
# Non-destructive updates
|
93
|
-
|
94
|
-
|
95
|
-
tom #=> #<Person name="Tom" age=31> (unchanged)
|
96
|
-
|
92
|
+
tom.with(age: 99) #=> #<Person name="Tom" age=99>
|
93
|
+
tom # (unchanged) #=> #<Person name="Tom" age=31>
|
97
94
|
|
98
95
|
# Equality
|
99
96
|
other_tom = Person.new(name: 'Tom', age: 31)
|
@@ -101,12 +98,12 @@ tom == other_tom #=> true
|
|
101
98
|
tom.eql?(other_tom) #=> true
|
102
99
|
tom.hash == other_tom.hash #=> true
|
103
100
|
|
104
|
-
|
105
101
|
# Ruby 2.7+ pattern matching
|
106
102
|
case tom
|
107
103
|
in name: "Tom", age:
|
108
|
-
puts age
|
104
|
+
puts age
|
109
105
|
end
|
106
|
+
# outputs: 31
|
110
107
|
```
|
111
108
|
|
112
109
|
|
@@ -116,7 +113,9 @@ Convenience (Monkey Patch)
|
|
116
113
|
There is a shorter way to define value attributes:
|
117
114
|
|
118
115
|
```ruby
|
119
|
-
|
116
|
+
require 'value_semantics/monkey_patched'
|
117
|
+
|
118
|
+
class Monkey
|
120
119
|
value_semantics do
|
121
120
|
name String
|
122
121
|
age Integer
|
@@ -160,7 +159,7 @@ class Cat
|
|
160
159
|
end
|
161
160
|
|
162
161
|
Cat.new
|
163
|
-
#=> #<Cat paws=4 born_at=
|
162
|
+
#=> #<Cat paws=4 born_at=2020-08-04 00:16:35.15632 +1000>
|
164
163
|
```
|
165
164
|
|
166
165
|
The `default` option is a single value.
|
@@ -189,15 +188,13 @@ class Person
|
|
189
188
|
}
|
190
189
|
end
|
191
190
|
|
192
|
-
Person.new(name: 'Tom',
|
193
|
-
Person.new(name: 5,
|
194
|
-
#=> ValueSemantics::InvalidValue:
|
195
|
-
#=> Attribute `Person#name` is invalid: 5
|
191
|
+
Person.new(name: 'Tom', birthday: '2000-01-01') # works
|
192
|
+
Person.new(name: 5, birthday: '2000-01-01')
|
193
|
+
#=> !!! ValueSemantics::InvalidValue: Attribute `Person#name` is invalid: 5
|
196
194
|
|
197
|
-
Person.new(birthday: "1970-01-01"
|
198
|
-
Person.new(birthday: "hello"
|
199
|
-
#=> ValueSemantics::InvalidValue:
|
200
|
-
#=> Attribute 'Person#birthday' is invalid: "hello"
|
195
|
+
Person.new(name: 'Tom', birthday: "1970-01-01") # works
|
196
|
+
Person.new(name: 'Tom', birthday: "hello")
|
197
|
+
#=> !!! ValueSemantics::InvalidValue: Attribute `Person#birthday` is invalid: "hello"
|
201
198
|
```
|
202
199
|
|
203
200
|
|
@@ -209,13 +206,15 @@ for common situations:
|
|
209
206
|
```ruby
|
210
207
|
class LightSwitch
|
211
208
|
include ValueSemantics.for_attributes {
|
212
|
-
|
213
209
|
# Bool: only allows `true` or `false`
|
214
210
|
on? Bool()
|
215
211
|
|
216
212
|
# ArrayOf: validates elements in an array
|
217
213
|
light_ids ArrayOf(Integer)
|
218
214
|
|
215
|
+
# HashOf: validates keys/values of a homogeneous hash
|
216
|
+
toggle_stats HashOf(Symbol => Integer)
|
217
|
+
|
219
218
|
# Either: value must match at least one of a list of validators
|
220
219
|
color Either(Integer, String, nil)
|
221
220
|
|
@@ -227,9 +226,11 @@ end
|
|
227
226
|
LightSwitch.new(
|
228
227
|
on?: true,
|
229
228
|
light_ids: [11, 12, 13],
|
229
|
+
toggle_stats: { day: 42, night: 69 },
|
230
230
|
color: "#FFAABB",
|
231
231
|
wierd_attr: [true, false, true, true],
|
232
232
|
)
|
233
|
+
#=> #<LightSwitch on?=true light_ids=[11, 12, 13] toggle_stats={:day=>42, :night=>69} color="#FFAABB" wierd_attr=[true, false, true, true]>
|
233
234
|
```
|
234
235
|
|
235
236
|
|
@@ -238,22 +239,25 @@ LightSwitch.new(
|
|
238
239
|
A custom validator might look something like this:
|
239
240
|
|
240
241
|
```ruby
|
241
|
-
module
|
242
|
+
module DottedQuad
|
242
243
|
def self.===(value)
|
243
|
-
value.
|
244
|
+
value.split('.').all? do |part|
|
245
|
+
('0'..'255').cover?(part)
|
246
|
+
end
|
244
247
|
end
|
245
248
|
end
|
246
249
|
|
247
|
-
class
|
250
|
+
class Server
|
248
251
|
include ValueSemantics.for_attributes {
|
249
|
-
|
252
|
+
address DottedQuad
|
250
253
|
}
|
251
254
|
end
|
252
255
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
256
|
+
Server.new(address: '127.0.0.1')
|
257
|
+
#=> #<Server address="127.0.0.1">
|
258
|
+
|
259
|
+
Server.new(address: '127.0.0.999')
|
260
|
+
#=> !!! ValueSemantics::InvalidValue: Attribute `Server#address` is invalid: "127.0.0.999"
|
257
261
|
```
|
258
262
|
|
259
263
|
Default attribute values also pass through validation.
|
@@ -296,8 +300,7 @@ Document.new(path: Pathname.new('~/Documents/whatever.doc'))
|
|
296
300
|
#=> #<Document path=#<Pathname:~/Documents/whatever.doc>>
|
297
301
|
|
298
302
|
Document.new(path: 42)
|
299
|
-
#=> ValueSemantics::InvalidValue:
|
300
|
-
#=> Attribute 'Document#path' is invalid: 42
|
303
|
+
#=> !!! ValueSemantics::InvalidValue: Attribute `Document#path` is invalid: 42
|
301
304
|
```
|
302
305
|
|
303
306
|
You can also use any callable object as a coercer.
|
@@ -347,6 +350,50 @@ For example, the default value could be a string,
|
|
347
350
|
which would then be coerced into an `Pathname` object.
|
348
351
|
|
349
352
|
|
353
|
+
## Nesting
|
354
|
+
|
355
|
+
It is fairly common to nest value objects inside each other. This
|
356
|
+
works as expected, but coercion is not automatic. For nested coercion,
|
357
|
+
use the `.coercer` class method and `ArrayCoercer` DSL method that
|
358
|
+
ValueSemantics provides.
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
class CrabClaw
|
362
|
+
include ValueSemantics.for_attributes {
|
363
|
+
size Either(:big, :small)
|
364
|
+
}
|
365
|
+
end
|
366
|
+
|
367
|
+
class Crab
|
368
|
+
include ValueSemantics.for_attributes {
|
369
|
+
left_claw CrabClaw, coerce: CrabClaw.coercer
|
370
|
+
right_claw CrabClaw, coerce: CrabClaw.coercer
|
371
|
+
}
|
372
|
+
end
|
373
|
+
|
374
|
+
class Ocean
|
375
|
+
include ValueSemantics.for_attributes {
|
376
|
+
crabs ArrayOf(Crab), coerce: ArrayCoercer(Crab.coercer)
|
377
|
+
}
|
378
|
+
end
|
379
|
+
|
380
|
+
ocean = Ocean.new(
|
381
|
+
crabs: [
|
382
|
+
{
|
383
|
+
left_claw: { size: :small },
|
384
|
+
right_claw: { size: :small },
|
385
|
+
}, {
|
386
|
+
left_claw: { size: :big },
|
387
|
+
right_claw: { size: :big },
|
388
|
+
}
|
389
|
+
]
|
390
|
+
)
|
391
|
+
|
392
|
+
ocean.crabs.first #=> #<Crab left_claw=#<CrabClaw size=:small> right_claw=#<CrabClaw size=:small>>
|
393
|
+
ocean.crabs.first.right_claw.size #=> :small
|
394
|
+
```
|
395
|
+
|
396
|
+
|
350
397
|
## ValueSemantics::Struct
|
351
398
|
|
352
399
|
This is a convenience for making a new class and including ValueSemantics in
|
@@ -354,11 +401,41 @@ one step, similar to how `Struct` works from the Ruby standard library. For
|
|
354
401
|
example:
|
355
402
|
|
356
403
|
```ruby
|
357
|
-
|
358
|
-
name String, default: "
|
404
|
+
Pigeon = ValueSemantics::Struct.new do
|
405
|
+
name String, default: "Jannie"
|
406
|
+
end
|
407
|
+
|
408
|
+
Pigeon.new.name #=> "Jannie"
|
409
|
+
```
|
410
|
+
|
411
|
+
|
412
|
+
## Known Issues
|
413
|
+
|
414
|
+
Some valid attribute names result in invalid Ruby syntax when using the DSL.
|
415
|
+
In these situations, you can use the DSL method `def_attr` instead.
|
416
|
+
|
417
|
+
For example, if you want an attribute named `then`:
|
418
|
+
|
419
|
+
```ruby
|
420
|
+
# Can't do this:
|
421
|
+
class Conditional
|
422
|
+
include ValueSemantics.for_attributes {
|
423
|
+
then String
|
424
|
+
else String
|
425
|
+
}
|
359
426
|
end
|
427
|
+
#=> !!! SyntaxError: README.md:375: syntax error, unexpected `then'
|
428
|
+
#=* then String
|
429
|
+
#=* ^~~~
|
430
|
+
|
360
431
|
|
361
|
-
|
432
|
+
# This will work
|
433
|
+
class Conditional
|
434
|
+
include ValueSemantics.for_attributes {
|
435
|
+
def_attr :then, String
|
436
|
+
def_attr :else, String
|
437
|
+
}
|
438
|
+
end
|
362
439
|
```
|
363
440
|
|
364
441
|
|
data/lib/value_semantics.rb
CHANGED
@@ -45,10 +45,9 @@ module ValueSemantics
|
|
45
45
|
end
|
46
46
|
|
47
47
|
#
|
48
|
-
# Makes the
|
48
|
+
# Makes the +.value_semantics+ convenience method available to all classes
|
49
49
|
#
|
50
|
-
#
|
51
|
-
# Instead of:
|
50
|
+
# +.value_semantics+ is a shortcut for {.for_attributes}. Instead of:
|
52
51
|
#
|
53
52
|
# class Person
|
54
53
|
# include ValueSemantics.for_attributes {
|
@@ -64,7 +63,7 @@ module ValueSemantics
|
|
64
63
|
# end
|
65
64
|
# end
|
66
65
|
#
|
67
|
-
# Alternatively, you can
|
66
|
+
# Alternatively, you can +require 'value_semantics/monkey_patched'+, which
|
68
67
|
# will call this method automatically.
|
69
68
|
#
|
70
69
|
def self.monkey_patch!
|
@@ -96,6 +95,26 @@ module ValueSemantics
|
|
96
95
|
|
97
96
|
self::VALUE_SEMANTICS_RECIPE__
|
98
97
|
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# A coercer object for the value class
|
101
|
+
#
|
102
|
+
# This is mostly useful when nesting value objects inside each other.
|
103
|
+
#
|
104
|
+
# The coercer will coerce hashes into an instance of the value class, using
|
105
|
+
# the hash for attribute values. It will return non-hash values unchanged.
|
106
|
+
#
|
107
|
+
# @return [#call] A callable object that can be used as a coercer
|
108
|
+
#
|
109
|
+
def coercer
|
110
|
+
->(obj) do
|
111
|
+
if Hash === obj
|
112
|
+
new(obj)
|
113
|
+
else
|
114
|
+
obj
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
99
118
|
end
|
100
119
|
|
101
120
|
#
|
@@ -106,14 +125,14 @@ module ValueSemantics
|
|
106
125
|
# Creates a value object based on a hash of attributes
|
107
126
|
#
|
108
127
|
# @param attributes [#to_h] A hash of attribute values by name. Typically a
|
109
|
-
#
|
128
|
+
# +Hash+, but can be any object that responds to +#to_h+.
|
110
129
|
#
|
111
130
|
# @raise [UnrecognizedAttributes] if given_attrs contains keys that are not
|
112
131
|
# attributes
|
113
132
|
# @raise [MissingAttributes] if given_attrs is missing any attributes that
|
114
133
|
# do not have defaults
|
115
134
|
# @raise [InvalidValue] if any attribute values do no pass their validators
|
116
|
-
# @raise [TypeError] if the argument does not respond to
|
135
|
+
# @raise [TypeError] if the argument does not respond to +#to_h+
|
117
136
|
#
|
118
137
|
def initialize(attributes = nil)
|
119
138
|
attributes_hash =
|
@@ -147,6 +166,26 @@ module ValueSemantics
|
|
147
166
|
end
|
148
167
|
end
|
149
168
|
|
169
|
+
#
|
170
|
+
# Returns the value for the given attribute name
|
171
|
+
#
|
172
|
+
# @param attr_name [Symbol] The name of the attribute. Can not be a +String+.
|
173
|
+
# @return The value of the attribute
|
174
|
+
#
|
175
|
+
# @raise [UnrecognizedAttributes] if the attribute does not exist
|
176
|
+
#
|
177
|
+
def [](attr_name)
|
178
|
+
attr = self.class.value_semantics.attributes.find do |attr|
|
179
|
+
attr.name.equal?(attr_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
if attr
|
183
|
+
public_send(attr_name)
|
184
|
+
else
|
185
|
+
raise UnrecognizedAttributes, "`#{self.class}` has no attribute named `#{attr_name.inspect}`"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
150
189
|
#
|
151
190
|
# Creates a copy of this object, with the given attributes changed (non-destructive update)
|
152
191
|
#
|
@@ -307,6 +346,8 @@ module ValueSemantics
|
|
307
346
|
# Contains all the configuration necessary to bake a ValueSemantics module
|
308
347
|
#
|
309
348
|
# @see ValueSemantics.bake_module
|
349
|
+
# @see ClassMethods#value_semantics
|
350
|
+
# @see DSL.run
|
310
351
|
#
|
311
352
|
class Recipe
|
312
353
|
attr_reader :attributes
|
@@ -359,6 +400,41 @@ module ValueSemantics
|
|
359
400
|
ArrayOf.new(element_validator)
|
360
401
|
end
|
361
402
|
|
403
|
+
def HashOf(key_validator_to_value_validator)
|
404
|
+
unless key_validator_to_value_validator.size.equal?(1)
|
405
|
+
raise ArgumentError, "HashOf() takes a hash with one key and one value"
|
406
|
+
end
|
407
|
+
|
408
|
+
HashOf.new(
|
409
|
+
key_validator_to_value_validator.keys.first,
|
410
|
+
key_validator_to_value_validator.values.first,
|
411
|
+
)
|
412
|
+
end
|
413
|
+
|
414
|
+
def ArrayCoercer(element_coercer)
|
415
|
+
ArrayCoercer.new(element_coercer)
|
416
|
+
end
|
417
|
+
|
418
|
+
#
|
419
|
+
# Defines one attribute.
|
420
|
+
#
|
421
|
+
# This is the method that gets called under the hood, when defining
|
422
|
+
# attributes the typical +#method_missing+ way.
|
423
|
+
#
|
424
|
+
# You can use this method directly if your attribute name results in invalid
|
425
|
+
# Ruby syntax. For example, if you want an attribute named +then+, you
|
426
|
+
# can do:
|
427
|
+
#
|
428
|
+
# include ValueSemantics.for_attributes {
|
429
|
+
# # Does not work:
|
430
|
+
# then String, default: "whatever"
|
431
|
+
# #=> SyntaxError: syntax error, unexpected `then'
|
432
|
+
#
|
433
|
+
# # Works:
|
434
|
+
# def_attr :then, String, default: "whatever"
|
435
|
+
# }
|
436
|
+
#
|
437
|
+
#
|
362
438
|
def def_attr(*args, **kwargs)
|
363
439
|
__attributes << Attribute.define(*args, **kwargs)
|
364
440
|
end
|
@@ -431,6 +507,47 @@ module ValueSemantics
|
|
431
507
|
end
|
432
508
|
end
|
433
509
|
|
510
|
+
#
|
511
|
+
# Validator that matches +Hash+es with homogeneous keys and values
|
512
|
+
#
|
513
|
+
class HashOf
|
514
|
+
attr_reader :key_validator, :value_validator
|
515
|
+
|
516
|
+
def initialize(key_validator, value_validator)
|
517
|
+
@key_validator, @value_validator = key_validator, value_validator
|
518
|
+
freeze
|
519
|
+
end
|
520
|
+
|
521
|
+
# @return [Boolean]
|
522
|
+
def ===(value)
|
523
|
+
Hash === value && value.all? do |key, value|
|
524
|
+
key_validator === key && value_validator === value
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
class ArrayCoercer
|
530
|
+
attr_reader :element_coercer
|
531
|
+
|
532
|
+
def initialize(element_coercer = nil)
|
533
|
+
@element_coercer = element_coercer
|
534
|
+
freeze
|
535
|
+
end
|
536
|
+
|
537
|
+
def call(obj)
|
538
|
+
if obj.respond_to?(:to_a)
|
539
|
+
array = obj.to_a
|
540
|
+
if element_coercer
|
541
|
+
array.map { |element| element_coercer.call(element) }
|
542
|
+
else
|
543
|
+
array
|
544
|
+
end
|
545
|
+
else
|
546
|
+
obj
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
434
551
|
#
|
435
552
|
# ValueSemantics equivalent of the Struct class from the Ruby standard
|
436
553
|
# library
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: value_semantics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Dalling
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: eceval
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
description: "\n Generates modules that provide conventional value semantics for
|
112
126
|
a given set of attributes.\n The behaviour is similar to an immutable `Struct`
|
113
127
|
class,\n plus extensible, lightweight validation and coercion.\n "
|
@@ -129,7 +143,7 @@ licenses:
|
|
129
143
|
metadata:
|
130
144
|
bug_tracker_uri: https://github.com/tomdalling/value_semantics/issues
|
131
145
|
changelog_uri: https://github.com/tomdalling/value_semantics/blob/master/CHANGELOG.md
|
132
|
-
documentation_uri: https://github.com/tomdalling/value_semantics/blob/v3.
|
146
|
+
documentation_uri: https://github.com/tomdalling/value_semantics/blob/v3.5.0/README.md
|
133
147
|
source_code_uri: https://github.com/tomdalling/value_semantics
|
134
148
|
post_install_message:
|
135
149
|
rdoc_options: []
|