value_semantics 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|