xeme 0.3.1 → 1.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/README.md +352 -260
- data/lib/xeme.rb +422 -450
- metadata +24 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2dd9c9b62b1d0d2f4db4657ff6880cea68476a47ca4e366da77eab004d5ad71f
|
4
|
+
data.tar.gz: b15afd00d574e1382597d43df90f50668e0c8a651f2623ab5447af0d717bd404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 800b710d55a2745c7591a8c5867c5b97125750d525b9ed941f8826093c60efac36f03a135ca9104728ffe849351d176f257a0dc13382a25a87b5d2e9f553653f
|
7
|
+
data.tar.gz: 90c33ffa2e86e06895176ce1a16aa06fef19991fb0c7fbcadc8cb79f9480d6fc4ee1764e162d8b5e3e00993c07523926d38befb74c1a3b9ef22193b968eb64ff
|
data/README.md
CHANGED
@@ -1,263 +1,362 @@
|
|
1
1
|
# Xeme
|
2
2
|
|
3
|
-
Xeme provides a common format for returning the results of a process.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
xeme = Xeme.new
|
8
|
-
xeme.error 'error-1'
|
9
|
-
|
10
|
-
If there are any errors, such as in this first example, <tt>success</tt> returns false:
|
11
|
-
|
12
|
-
puts xeme.success # false
|
13
|
-
|
14
|
-
A Xeme object without any errors returns true for <tt>success</tt>:
|
15
|
-
|
16
|
-
xeme = Xeme.new
|
17
|
-
puts xeme.success # true
|
18
|
-
|
19
|
-
Xeme is a good way to report results that are more complex than just success or
|
20
|
-
failure. For example, consider the situation in which a record must have a name
|
21
|
-
field and an email field. If the record does not meet these requirements, it is
|
22
|
-
not sufficient to merely report success or failure, nor to just report that one
|
23
|
-
field or the other is missing. With Xeme you can report all of that information
|
24
|
-
in a single object:
|
25
|
-
|
26
|
-
xeme = Xeme.new
|
27
|
-
xeme.error 'name'
|
28
|
-
xeme.error 'email'
|
29
|
-
|
30
|
-
You can return the Xeme object to another Ruby process, or you can output it to
|
31
|
-
JSON for use in some other program:
|
32
|
-
|
33
|
-
puts xeme.to_json
|
34
|
-
|
35
|
-
# outputs:
|
36
|
-
{"success":false,"messages":{"errors":[{"id":"name"},{"id":"email"}]}}
|
37
|
-
|
38
|
-
You might prefer to add the <tt>pretty</tt> option for more readable output:
|
39
|
-
|
40
|
-
puts xeme.to_json('pretty'=>true)
|
41
|
-
|
42
|
-
# outputs:
|
43
|
-
{
|
44
|
-
"success": false,
|
45
|
-
"messages": {
|
46
|
-
"errors": [
|
47
|
-
{
|
48
|
-
"id": "name"
|
49
|
-
},
|
50
|
-
{
|
51
|
-
"id": "email"
|
52
|
-
}
|
53
|
-
]
|
54
|
-
}
|
55
|
-
}
|
56
|
-
|
57
|
-
In some situations, it's useful to give details about the error. For example,
|
58
|
-
if the <tt>name</tt> field is too long, you can report not just that there is
|
59
|
-
an error in that field, but specifics about that error:
|
60
|
-
|
61
|
-
xeme.error('name') do |msg|
|
62
|
-
msg['too-long'] = true
|
63
|
-
msg['max-length'] = 45
|
64
|
-
end
|
65
|
-
|
66
|
-
That would produce a structure like this:
|
67
|
-
|
68
|
-
{
|
69
|
-
"success": false,
|
70
|
-
"messages": {
|
71
|
-
"errors": [
|
72
|
-
{
|
73
|
-
"id": "name",
|
74
|
-
"details": {
|
75
|
-
"too-long": true,
|
76
|
-
"max-length": 45
|
77
|
-
}
|
78
|
-
}
|
79
|
-
]
|
80
|
-
}
|
81
|
-
}
|
82
|
-
|
83
|
-
|
84
|
-
You can also add warnings and notes. A warning indicates a problem but not an
|
85
|
-
actual failure. A note does not indicate any problem at all but just some
|
86
|
-
information that should be passed along.
|
87
|
-
|
88
|
-
xeme.warning 'database-reset'
|
89
|
-
xeme.note 'database ok'
|
90
|
-
|
91
|
-
It's often useful to add misc details to the results. For example, a database
|
92
|
-
query might result in some rows from a table. You might add these rows to the
|
93
|
-
<tt>misc</tt> property like this:
|
94
|
-
|
95
|
-
xeme = Xeme.new
|
96
|
-
xeme.misc['rows'] = []
|
97
|
-
xeme.misc['rows'].push 'a'
|
98
|
-
xeme.misc['rows'].push 'b'
|
99
|
-
xeme.misc['rows'].push 'c'
|
100
|
-
puts xeme.to_json('pretty'=>true)
|
101
|
-
|
102
|
-
# outputs:
|
103
|
-
{
|
104
|
-
"success": true,
|
105
|
-
"misc": {
|
106
|
-
"rows": [
|
107
|
-
"a",
|
108
|
-
"b",
|
109
|
-
"c"
|
110
|
-
]
|
111
|
-
}
|
112
|
-
}
|
113
|
-
|
114
|
-
Xeme can input a Xeme JSON structure to make a new Xeme object:
|
115
|
-
|
116
|
-
# json
|
117
|
-
json = <<~'JSON'
|
118
|
-
{
|
119
|
-
"success": false,
|
120
|
-
"messages": {
|
121
|
-
"errors": [
|
122
|
-
{ "id": "location" }
|
123
|
-
]
|
124
|
-
}
|
125
|
-
}
|
126
|
-
JSON
|
127
|
-
|
128
|
-
# xeme
|
129
|
-
xeme = Xeme.from_json(json)
|
3
|
+
Xeme provides a common format for returning the results of a process. First
|
4
|
+
we'll look at the Xeme format, which is language independent, then we'll look at
|
5
|
+
how the Xeme gem implements the format.
|
6
|
+
|
130
7
|
|
131
8
|
## Xeme structure
|
132
9
|
|
133
10
|
The Xeme structure can be used by any software, Ruby or otherwise, as a standard
|
134
11
|
way to report results. A Xeme structure can be stored in any format that
|
135
12
|
recognizes hashes, arrays, strings, numbers, booleans, and null. Such formats
|
136
|
-
include JSON and YAML.
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
That structure indicates no
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
13
|
+
include JSON and YAML. In these examples we'll use JSON.
|
14
|
+
|
15
|
+
A xeme can be as simple as an empty hash:
|
16
|
+
|
17
|
+
```json
|
18
|
+
{}
|
19
|
+
```
|
20
|
+
|
21
|
+
That structure indicates no errors, and, in fact, no details at all. However, it
|
22
|
+
also does not explicitly indicate success, so that xeme would be considered to
|
23
|
+
have failed.
|
24
|
+
|
25
|
+
To indicate a successful operation, a xeme must have an explicit `success`
|
26
|
+
element:
|
27
|
+
|
28
|
+
```json
|
29
|
+
{"success":true}
|
30
|
+
```
|
31
|
+
|
32
|
+
A xeme can be marked as explicitly failed:
|
33
|
+
|
34
|
+
```json
|
35
|
+
{"success":false}
|
36
|
+
```
|
37
|
+
|
38
|
+
If a xeme does not have an explicit `success` element, or it is set to `nil`, it
|
39
|
+
should be considered to have failed. However, depending on how you process the
|
40
|
+
xeme, `nil` could be considered as having not finished the test. In that case,
|
41
|
+
consider using [promises](#promises).
|
42
|
+
|
43
|
+
### Messages
|
44
|
+
|
45
|
+
A message is an error, a warning, a note, or a promise. Each type of message has
|
46
|
+
an array. For example, this xeme has two errors:
|
47
|
+
|
48
|
+
```json
|
49
|
+
{ "errors":[{"id":"http-fault"}, {"id":"transaction-error"}] }
|
50
|
+
```
|
51
|
+
|
52
|
+
A message does not have to have any particular structure, but best practice is
|
53
|
+
to give each message an identifier (`id`).
|
54
|
+
|
55
|
+
If there are any errors then that indicates a failure, regardless of the value
|
56
|
+
of `success`. Any implementation of Xeme should have a method for resolving
|
57
|
+
conflicts between `success` and `errors`, always giving priority to the presence
|
58
|
+
of errors over the value of `success`.
|
59
|
+
|
60
|
+
If a xeme has any promises then that indicates not success, though not
|
61
|
+
necessarily explicit failure. If a xeme is marked as successful, but there are
|
62
|
+
promises, then `success` should be considered nil.
|
63
|
+
|
64
|
+
Warnings indicate problems that don't outright cause a failure. Notes are
|
65
|
+
messages that don't indicate a problem of any kind.
|
66
|
+
|
67
|
+
### Metainformation
|
68
|
+
|
69
|
+
Sometimes it's useful to store metainformation the xeme. For example, log files
|
70
|
+
are more discoverable if each xeme has a unique id and timestamp. Generally a
|
71
|
+
xeme will have at least to elements, `uuid` and `timestamp`:
|
72
|
+
|
73
|
+
```json
|
74
|
+
{
|
75
|
+
"meta":{
|
76
|
+
"uuid":"dae1cf26-e8fa-43fa-bedc-88fea10255f4",
|
77
|
+
"timestamp":"2023-05-25 03:46:19 -0400"
|
78
|
+
}
|
79
|
+
}
|
80
|
+
```
|
81
|
+
|
82
|
+
### Nested xemes
|
83
|
+
|
84
|
+
A xeme can have nested xemes within it. Those nested xemes go in the `nested`
|
85
|
+
array:
|
86
|
+
|
87
|
+
```json
|
88
|
+
{
|
89
|
+
"nested": [
|
90
|
+
{ "success": true },
|
91
|
+
{ "errors": [{"id":"server-fault"}] },
|
92
|
+
]
|
93
|
+
}
|
94
|
+
```
|
95
|
+
|
96
|
+
For a xeme to be considered successful, all of its nested xemes must be marked
|
97
|
+
as success. If any nested xemes have errors, then the outermost xeme is
|
98
|
+
considered to be considered as failed. If any nested xemes has `success` as nil,
|
99
|
+
then the outermost xeme cannot `success` as true, though it can be explicitly
|
100
|
+
false.
|
101
|
+
|
102
|
+
## Xeme gem
|
103
|
+
|
104
|
+
### Install
|
105
|
+
|
106
|
+
The usual:
|
107
|
+
|
108
|
+
```sh
|
109
|
+
gem install xeme
|
110
|
+
```
|
111
|
+
|
112
|
+
### Using the Xeme gem
|
113
|
+
|
114
|
+
Xeme (the gem) is a thin layer over a hash that implements the Xeme format. (For
|
115
|
+
the rest of this document "Xeme" refers to the Ruby class, not the format.)
|
116
|
+
Create a new xeme by instantiating the Xeme class. Instantiation has no required
|
117
|
+
parameters.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
require 'xeme'
|
121
|
+
xeme = Xeme.new
|
122
|
+
puts xeme # => #<Xeme:0x000055586f1340a8>
|
123
|
+
```
|
124
|
+
|
125
|
+
Sometimes it's handy to give a xeme an identifier. You can do that by passing in
|
126
|
+
a string in `Xeme.new`.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
xeme = Xeme.new('my-xeme')
|
130
|
+
puts xeme.id # => my-xeme
|
131
|
+
```
|
132
|
+
|
133
|
+
If you want to access the hash stored in the xeme object, you can use the object
|
134
|
+
as if it were a hash.
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
xeme['errors'] = []
|
138
|
+
xeme['errors'].push({'id'=>'my-error'})
|
139
|
+
```
|
140
|
+
|
141
|
+
#### Success and failure
|
142
|
+
|
143
|
+
Because a xeme isn't considered successful until it has been explicitly declared
|
144
|
+
so, a new xeme is considered to indicate failure. However, because there are no
|
145
|
+
errors and `success` has not been explicitly set, `success?` returns `nil`
|
146
|
+
instead of `false`.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
xeme = Xeme.new
|
150
|
+
puts xeme.success?.class # => NilClas
|
151
|
+
```
|
152
|
+
|
153
|
+
There are two ways to mark a xeme as successful, one of which usually the better
|
154
|
+
choice. The not-so-good way to mark success is with the `succeed` method.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
xeme = Xeme.new
|
158
|
+
xeme.succeed
|
159
|
+
puts xeme.success? # => true
|
160
|
+
```
|
161
|
+
|
162
|
+
The problem with `succeed` is that if there are errors, `succeed` will raise an
|
163
|
+
exception.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
xeme = Xeme.new
|
167
|
+
xeme.error 'my-error'
|
168
|
+
xeme.succeed # => raises exception: `succeed': cannot-set-to-success: errors
|
169
|
+
```
|
170
|
+
|
171
|
+
A better option is `try_succeed`. If your script gets to a point, usually at the
|
172
|
+
end of the function or script, that you want to set the xeme to success, but
|
173
|
+
only if there are no errors, use `try_succeed`.
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
xeme = Xeme.new
|
177
|
+
xeme.try_succeed
|
178
|
+
puts xeme.success? # => true
|
179
|
+
```
|
180
|
+
|
181
|
+
If there are errors, `try_succeed` won't raise an exception, but will not set
|
182
|
+
the xeme to failure.
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
xeme = Xeme.new
|
186
|
+
xeme.error 'my-error'
|
187
|
+
xeme.try_succeed
|
188
|
+
puts xeme.success? # => false
|
189
|
+
```
|
190
|
+
|
191
|
+
#### Creating and using messages
|
192
|
+
|
193
|
+
Messages in a xeme provide a way to indicate errors (i.e. fatal errors),
|
194
|
+
warnings (non-fatal errors), notes (not an error at all), and promises (guides
|
195
|
+
to getting the final success or failure of the process). A message is a hash
|
196
|
+
with whatever arbitrary information you want to add. Each type of message has
|
197
|
+
its own method for creating it, an array for storing them, and methods for
|
198
|
+
checking if any exist. The following script creates an error, a warning, a
|
199
|
+
note, and a promise.
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
xeme = Xeme.new
|
203
|
+
xeme.error 'my-error'
|
204
|
+
xeme.warning 'my-warning'
|
205
|
+
xeme.note 'my-note'
|
206
|
+
xeme.promise 'my-promise'
|
207
|
+
```
|
208
|
+
|
209
|
+
`error`, `warning`, and `note` each create a message for their own type.
|
210
|
+
Although it is not required, it's usually a good idea to give a string as the
|
211
|
+
first parameter. That string will be set to the `id` element in the resulting
|
212
|
+
hash, as seen in the example above.
|
213
|
+
|
214
|
+
`errors`, `warnings`, and `notes` return arrays for each type.
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
xeme.errors.each do |e|
|
218
|
+
puts e['id'] # => my-error
|
219
|
+
end
|
220
|
+
|
221
|
+
xeme.warnings.each do |w|
|
222
|
+
puts w['id'] # => my-warning
|
223
|
+
end
|
224
|
+
|
225
|
+
xeme.notes.each do |n|
|
226
|
+
puts n['id'] # => my-note
|
227
|
+
end
|
228
|
+
|
229
|
+
xeme.promises.each do |p|
|
230
|
+
puts p['id'] # => my-promise
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
**Gotcha:** These methods return frozen arrays, *not* the arrays in the xeme.
|
235
|
+
This is because these methods return not only the xeme's own message arrays, but
|
236
|
+
also any nested messages. See [Nesting xemes](#nesting-xemes) below.
|
237
|
+
|
238
|
+
There are several ways to create and populate a message. Choose whichever is
|
239
|
+
preferable to you. One way is demonstrated in the example above. You simply call
|
240
|
+
the appropriate method, passing in an identifier. If all you want to do is
|
241
|
+
create a message with an `id` then that's probably the easiest choice.
|
242
|
+
|
243
|
+
Another way to use a `do` block to add custom information to the message.
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
xeme.error('my-error') do |error|
|
247
|
+
error['database-error'] = 'some database error'
|
248
|
+
error['commands'] = ['a', 'b', 'c']
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
Remember a message is just a hash, so you can add any kind of structure to the
|
253
|
+
hash you want such a strings, booleans, hashes, and arrays.
|
254
|
+
|
255
|
+
Finally, the message command returns the new message. So, if you want, you can
|
256
|
+
assign that return value to a variable and work with the message that way.
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
err = xeme.error('my-error')
|
260
|
+
err['database-error'] = 'some database error'
|
261
|
+
err['commands'] = ['a', 'b', 'c']
|
262
|
+
```
|
263
|
+
|
264
|
+
#### Nesting xemes
|
265
|
+
|
266
|
+
In complex testing situations it can be useful to nest results within other
|
267
|
+
results. To nest a xeme within another xeme, use the `#nest` method:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
xeme = Xeme.new('results')
|
271
|
+
xeme.nest 'child-xeme'
|
272
|
+
```
|
273
|
+
|
274
|
+
You probably want to do something more than just create a nested xeme, so you
|
275
|
+
can use a `do` block to work with the nested xeme:
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
xeme.nest('child-xeme') do |child|
|
279
|
+
child.error 'child-error'
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
You can loop through all xemes, including the outermost xeme and all nested
|
284
|
+
xemes, using the `#all` method.
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
xeme.all.each do |x|
|
288
|
+
puts x.id
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
**Nested messages**
|
293
|
+
|
294
|
+
The `#errors`, `#warnings`, `#notes`, and `#promises` methods return arrays of
|
295
|
+
all messages within the xeme, including the outermost xeme and nested xemes.
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
xeme = Xeme.new
|
299
|
+
xeme.error 'outer-error'
|
300
|
+
|
301
|
+
xeme.nest do |child|
|
302
|
+
child.error 'child-error'
|
303
|
+
end
|
304
|
+
|
305
|
+
puts xeme.errors
|
306
|
+
|
307
|
+
# => {"id"=>"outer-error"}
|
308
|
+
# => {"id"=>"child-error"}
|
309
|
+
```
|
310
|
+
|
311
|
+
If you want to search for messages with a specific `id`, add that id to the
|
312
|
+
messages method:
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
puts xeme.errors('child-error') # => {"id"=>"child-error"}
|
316
|
+
```
|
317
|
+
|
318
|
+
**Flatten**
|
319
|
+
|
320
|
+
Finally, if you want to slurp up all messages into the outermost xeme and delete
|
321
|
+
the nested xemes, use `#flatten`.
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
puts xeme['errors']
|
325
|
+
# => {"id"=>"outer-error"}
|
326
|
+
|
327
|
+
xeme.flatten
|
328
|
+
|
329
|
+
puts xeme['errors']
|
330
|
+
# => {"id"=>"outer-error"}
|
331
|
+
# => {"id"=>"child-error"}
|
332
|
+
```
|
333
|
+
|
334
|
+
|
335
|
+
#### Resolving xemes
|
336
|
+
|
337
|
+
A xeme can contain contradictory information. For example, if `success` is true
|
338
|
+
but there are errors, then the xeme should be considered as failed. If there are
|
339
|
+
promises, then the xeme should not be considered as failed, although `success`
|
340
|
+
may be set as nil.
|
341
|
+
|
342
|
+
The `#resolve` method resolves those contradictions. Generally you won't have to
|
343
|
+
call `#resolve` yourself, but it's worth understanding the rules:
|
344
|
+
|
345
|
+
* A xeme can always be explicitly set to false, regardless of any other
|
346
|
+
considerations. Resolution never changes a `success` of false.
|
347
|
+
|
348
|
+
* If any nested xemes have `success` explicitly set to false, then the outermost
|
349
|
+
xeme will be set to false.
|
350
|
+
|
351
|
+
* If a xeme has errors, or any of it's nested xemes has errors, then it is set
|
352
|
+
to false.
|
353
|
+
|
354
|
+
* If a xeme has promises, or any of its nested xemes do, then it cannot be set
|
355
|
+
to true. If it is already false, then it stays false. Otherwise `success` is set
|
356
|
+
to nil.
|
357
|
+
|
358
|
+
* If any nested xemes have `success` set to nil, then the outermost xeme cannot
|
359
|
+
be set to true.
|
261
360
|
|
262
361
|
## The name
|
263
362
|
|
@@ -267,12 +366,6 @@ also known as Sabine's gull, is a type of gull. See
|
|
267
366
|
[the Wikipedia page](https://en.wikipedia.org/wiki/Sabine's_gull)
|
268
367
|
if you'd like to know more.
|
269
368
|
|
270
|
-
## Install
|
271
|
-
|
272
|
-
```
|
273
|
-
gem install xeme
|
274
|
-
```
|
275
|
-
|
276
369
|
## Author
|
277
370
|
|
278
371
|
Mike O'Sullivan
|
@@ -283,5 +376,4 @@ mike@idocs.com
|
|
283
376
|
| version | date | notes |
|
284
377
|
|---------|--------------|-------------------------------|
|
285
378
|
| 0.1 | Jan 7, 2020 | Initial upload. |
|
286
|
-
| 0
|
287
|
-
| 0.3 | Jan 19, 2020 | Fixed bug in Xeme.from_json() |
|
379
|
+
| 1.0 | May 29, 2023 | Complete overhaul. Not backward compatible. |
|