pebbles-uid 0.0.2
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +211 -0
- data/Rakefile +2 -0
- data/lib/pebbles-uid.rb +129 -0
- data/lib/pebbles-uid/conditions.rb +73 -0
- data/lib/pebbles-uid/genus.rb +25 -0
- data/lib/pebbles-uid/labels.rb +45 -0
- data/lib/pebbles-uid/oid.rb +29 -0
- data/lib/pebbles-uid/path.rb +20 -0
- data/lib/pebbles-uid/query.rb +89 -0
- data/lib/pebbles-uid/version.rb +5 -0
- data/lib/pebbles-uid/wildcard.rb +45 -0
- data/lib/pebbles/uid.rb +1 -0
- data/pebbles-uid.gemspec +19 -0
- data/spec/conditions_spec.rb +55 -0
- data/spec/genus_spec.rb +33 -0
- data/spec/labels_spec.rb +37 -0
- data/spec/oid_spec.rb +12 -0
- data/spec/path_spec.rb +24 -0
- data/spec/query_spec.rb +128 -0
- data/spec/uid_spec.rb +130 -0
- data/spec/wildcard_spec.rb +53 -0
- metadata +89 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Katrina Owen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# Pebbles::Uid
|
2
|
+
|
3
|
+
Handle unique identifiers in the Pebblestack universe conveniently.
|
4
|
+
|
5
|
+
## Unique Identifiers
|
6
|
+
|
7
|
+
A valid Uid is in the format `genus[.species]:path[$oid]`.
|
8
|
+
|
9
|
+
### Genus
|
10
|
+
|
11
|
+
The genus is one or more dot-delimited labels.
|
12
|
+
|
13
|
+
The first label uniquely identifies which pebble the resource originated from.
|
14
|
+
|
15
|
+
Any secondary labels are grouped into a `species`, which identify sub-types within a given pebble.
|
16
|
+
|
17
|
+
```
|
18
|
+
post.doc
|
19
|
+
```
|
20
|
+
|
21
|
+
Here the genus is `post`, and the species is `doc`.
|
22
|
+
|
23
|
+
### Path
|
24
|
+
|
25
|
+
The path is a dot-delimited set of labels. The first label represents the realm, and is required.
|
26
|
+
|
27
|
+
A single resource may have multiple paths (and therefore multiple uids). For example:
|
28
|
+
|
29
|
+
```
|
30
|
+
post.card:tourism.norway.fjords$42
|
31
|
+
post.card:tourism.norway.west-coast$42
|
32
|
+
```
|
33
|
+
|
34
|
+
### Oid
|
35
|
+
|
36
|
+
The object identifier can be nearly any string, including another uid.
|
37
|
+
|
38
|
+
The oid _MUST_ be unique for any genus and realm.
|
39
|
+
|
40
|
+
There are currently two characters which cannot be included in an oid:
|
41
|
+
|
42
|
+
`|` (pipe) and `,` (comma).
|
43
|
+
|
44
|
+
Oid is optional when creating a new resource.
|
45
|
+
|
46
|
+
## Queries
|
47
|
+
|
48
|
+
### For one specific resource
|
49
|
+
|
50
|
+
Across the pebblestack universe, you can query for a specific resource by passing a full Uid.
|
51
|
+
|
52
|
+
In addition, the wildcard query `genus:realm.*$oid` will identify a single resource, e.g.:
|
53
|
+
|
54
|
+
```
|
55
|
+
post.card:tourism.*$42
|
56
|
+
```
|
57
|
+
|
58
|
+
If a specific resource is targeted (i.e. genus and oid are both unambiguous), then realm is required.
|
59
|
+
|
60
|
+
### For a collection of resources
|
61
|
+
|
62
|
+
#### Asterisks
|
63
|
+
|
64
|
+
The most permissive query consists of any genus in any path, with any oid (implied):
|
65
|
+
|
66
|
+
```
|
67
|
+
*:*
|
68
|
+
```
|
69
|
+
|
70
|
+
This is the equivalent to:
|
71
|
+
|
72
|
+
```
|
73
|
+
*:*$*
|
74
|
+
```
|
75
|
+
|
76
|
+
Queries may not be made across multiple realms at one time. If realm is not specified in the path, then it is assumed that the application verifies realm if necessary.
|
77
|
+
|
78
|
+
In the context of a genus or a path, you may specify any number of labels and terminate with an asterisk, which means: zero or more labels follow. E.g.:
|
79
|
+
|
80
|
+
```
|
81
|
+
post.*:tourism.europe.*
|
82
|
+
```
|
83
|
+
|
84
|
+
NOTE: wildcard queries on species is not yet supported.
|
85
|
+
|
86
|
+
This will return things like:
|
87
|
+
|
88
|
+
```
|
89
|
+
post:tourism.europe$123
|
90
|
+
post:tourism.europe.france.sightseeing$234
|
91
|
+
post.card:tourism.europe.mountains$345
|
92
|
+
```
|
93
|
+
|
94
|
+
Asterisks may not occur in mid-sequence. In other words, `post:tourism.*.food` will not be accepted.
|
95
|
+
|
96
|
+
In the context of an oid, you either know the oid you want, or you don't. A query where oid is not specified at all is equivalent to an oid represented by an asterisk.
|
97
|
+
|
98
|
+
#### Pipes
|
99
|
+
|
100
|
+
A pipe signifies _or_.
|
101
|
+
|
102
|
+
This can be used in genus at any position:
|
103
|
+
|
104
|
+
```
|
105
|
+
unit|group:*
|
106
|
+
```
|
107
|
+
|
108
|
+
```
|
109
|
+
post.doc|card:*
|
110
|
+
```
|
111
|
+
|
112
|
+
In a path, this can be used in any position except the first, as the realm must be unambiguous.
|
113
|
+
|
114
|
+
Thus:
|
115
|
+
|
116
|
+
```
|
117
|
+
post:realm.label1|label2.something
|
118
|
+
```
|
119
|
+
|
120
|
+
The following is not allowed:
|
121
|
+
|
122
|
+
```
|
123
|
+
post:realm1|realm2.*
|
124
|
+
```
|
125
|
+
|
126
|
+
If you wish to fetch a specific list of objects, you may use the pipe to delimit oids:
|
127
|
+
|
128
|
+
```
|
129
|
+
post:realm.*$a|b|c|d|e|f|g
|
130
|
+
```
|
131
|
+
|
132
|
+
This is equivalent to the NOW deprecated comma-delimited uid query:
|
133
|
+
|
134
|
+
```
|
135
|
+
post:realm.*$a,post:realm.*$b,post:realm.*$c,post:realm.*$d,post:realm.*$e,post:realm.*$f,post:realm.*$g,
|
136
|
+
```
|
137
|
+
|
138
|
+
NOTE: realm is required in this case, because each term in the list must refer to a single resource.
|
139
|
+
|
140
|
+
#### Caret
|
141
|
+
|
142
|
+
In both genus and paths, a caret indicates ancestry up to the specified point:
|
143
|
+
|
144
|
+
```
|
145
|
+
post:realm.europe.^norway.fjords.food
|
146
|
+
```
|
147
|
+
|
148
|
+
This represents everything at the following locations:
|
149
|
+
|
150
|
+
```
|
151
|
+
post:realm.europe
|
152
|
+
post:realm.europe.norway
|
153
|
+
post:realm.europe.norway.fjords
|
154
|
+
post:realm.europe.norway.fjords.food
|
155
|
+
```
|
156
|
+
|
157
|
+
|
158
|
+
## Installation
|
159
|
+
|
160
|
+
Add this line to your application's Gemfile:
|
161
|
+
|
162
|
+
gem 'pebbles-uid'
|
163
|
+
|
164
|
+
And then execute:
|
165
|
+
|
166
|
+
$ bundle
|
167
|
+
|
168
|
+
Or install it yourself as:
|
169
|
+
|
170
|
+
$ gem install pebbles-uid
|
171
|
+
|
172
|
+
## Usage
|
173
|
+
|
174
|
+
### Identifying an existing resource
|
175
|
+
|
176
|
+
uid = Pebbles::Uid.new('post.card:tourism.norway.fjords$1234')
|
177
|
+
|
178
|
+
uid.realm
|
179
|
+
=> "tourism"
|
180
|
+
|
181
|
+
uid.genus
|
182
|
+
=> "post.card"
|
183
|
+
|
184
|
+
uid.species
|
185
|
+
=> "card"
|
186
|
+
|
187
|
+
uid.path
|
188
|
+
=> "tourism.norway.fjords"
|
189
|
+
|
190
|
+
uid.oid
|
191
|
+
=> 1234
|
192
|
+
|
193
|
+
uid.to_s
|
194
|
+
=> 'post.card:tourism.norway.fjords$1234'
|
195
|
+
|
196
|
+
uid.to_hash
|
197
|
+
=> {'genus' => 'post.card', 'path_0' => 'tourism', 'path_1' => 'norway', 'path_2' => 'fjords', 'oid' => '1234'}
|
198
|
+
|
199
|
+
uid.to_hash(:verbose => true, :suffix => '')
|
200
|
+
=> {'genus_0_' => 'post', 'genus_1_' => 'card', 'path_0_' => 'tourism', 'path_1_' => 'norway', 'path_2_' => 'fjords', 'oid_' => '1234'}
|
201
|
+
|
202
|
+
uid.to_hash(:verbose => true, :suffix => 'xyz', :genus => 'klass', :path => 'label' => :oid => 'id')
|
203
|
+
=> {'klass_0_xyz' => 'post', 'klass_1_xyz' => 'card', 'label_0_xyz' => 'tourism', 'label_1_xyz' => 'norway', 'label_2_xyz' => 'fjords', 'id_xyz' => '1234'}
|
204
|
+
|
205
|
+
## Contributing
|
206
|
+
|
207
|
+
1. Fork it
|
208
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
209
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
210
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
211
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/pebbles-uid.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require "pebbles-uid/version"
|
2
|
+
|
3
|
+
require 'pebbles-uid/query'
|
4
|
+
require "pebbles-uid/conditions"
|
5
|
+
require "pebbles-uid/labels"
|
6
|
+
require "pebbles-uid/genus"
|
7
|
+
require "pebbles-uid/path"
|
8
|
+
require "pebbles-uid/oid"
|
9
|
+
|
10
|
+
module Pebbles
|
11
|
+
class Uid
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def query(s)
|
16
|
+
Pebbles::Uid::Query.new(s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(s)
|
20
|
+
/^(?<genus>.*):(?<path>[^\$]*)\$?(?<oid>.*)$/ =~ s
|
21
|
+
[genus, path, oid.empty? ? nil : oid]
|
22
|
+
end
|
23
|
+
|
24
|
+
def genus(s)
|
25
|
+
parse(s)[0]
|
26
|
+
end
|
27
|
+
|
28
|
+
def path(s)
|
29
|
+
parse(s)[1]
|
30
|
+
end
|
31
|
+
|
32
|
+
def oid(s)
|
33
|
+
parse(s)[2]
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_path?(path)
|
37
|
+
return false if path.empty?
|
38
|
+
Path.new(path).valid_with?(/^[a-z0-9_-]+$/)
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid_genus?(genus)
|
42
|
+
return false if genus.empty?
|
43
|
+
Genus.new(genus).valid_with?(/^[a-z0-9_-]+$/)
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid_oid?(oid)
|
47
|
+
return true if !oid || oid.empty?
|
48
|
+
!!(oid =~ /^[^,|]+$/)
|
49
|
+
end
|
50
|
+
|
51
|
+
def cache_key(uid)
|
52
|
+
genus, path, oid = parse(uid)
|
53
|
+
"#{genus}:#{Path.new(path).realm}.*$#{oid}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :genus, :path, :oid
|
58
|
+
def initialize(s)
|
59
|
+
@genus, @path, @oid = self.class.parse(s)
|
60
|
+
unless valid_genus?
|
61
|
+
raise ArgumentError.new("Invalid Uid: #{s}. Genus is invalid.")
|
62
|
+
end
|
63
|
+
unless valid_path?
|
64
|
+
raise ArgumentError.new("Invalid Uid: #{s}. Path is invalid.")
|
65
|
+
end
|
66
|
+
|
67
|
+
if oid == '*'
|
68
|
+
raise ArgumentError.new("Invalid Uid: #{s}. Cannot have wildcard oid.")
|
69
|
+
end
|
70
|
+
|
71
|
+
if genus.empty?
|
72
|
+
raise ArgumentError.new("Invalid Uid: #{s}. Genus is required.")
|
73
|
+
end
|
74
|
+
if path.empty?
|
75
|
+
raise ArgumentError.new("Invalid Uid: #{s}. Realm is required.")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def realm
|
80
|
+
@realm ||= path.split('.').first
|
81
|
+
end
|
82
|
+
|
83
|
+
def species
|
84
|
+
@species ||= genus_labels.species
|
85
|
+
end
|
86
|
+
|
87
|
+
def path_labels
|
88
|
+
@path_labels ||= Path.new(path)
|
89
|
+
end
|
90
|
+
|
91
|
+
def genus_labels
|
92
|
+
@genus_labels ||= Genus.new(genus)
|
93
|
+
end
|
94
|
+
|
95
|
+
def valid?
|
96
|
+
valid_genus? && valid_path? && valid_oid?
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid_genus?
|
100
|
+
self.class.valid_genus?(genus)
|
101
|
+
end
|
102
|
+
|
103
|
+
def valid_path?
|
104
|
+
self.class.valid_path?(path)
|
105
|
+
end
|
106
|
+
|
107
|
+
def valid_oid?
|
108
|
+
self.class.valid_oid?(oid)
|
109
|
+
end
|
110
|
+
|
111
|
+
def oid?
|
112
|
+
!!oid
|
113
|
+
end
|
114
|
+
|
115
|
+
def to_s
|
116
|
+
s = "#{genus}:#{path}"
|
117
|
+
s << "$#{oid}" if oid
|
118
|
+
s
|
119
|
+
end
|
120
|
+
|
121
|
+
def parsed
|
122
|
+
[genus, path, oid].compact
|
123
|
+
end
|
124
|
+
|
125
|
+
def cache_key
|
126
|
+
"#{genus}:#{realm}.*$#{oid}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# A key => value representation of sets of labels
|
2
|
+
# used for elastic search or active record as indexing
|
3
|
+
# or search conditions.
|
4
|
+
module Pebbles
|
5
|
+
class Uid
|
6
|
+
class Conditions
|
7
|
+
|
8
|
+
NO_MARKER = Class.new
|
9
|
+
|
10
|
+
attr_reader :values, :name, :suffix, :stop, :verbose
|
11
|
+
|
12
|
+
def initialize(values, options = {})
|
13
|
+
@values = values
|
14
|
+
@name = options.fetch(:name) { 'label' }
|
15
|
+
@suffix = options.fetch(:suffix) { nil }
|
16
|
+
@verbose = options.fetch(:verbose) { true }
|
17
|
+
@stop = options.fetch(:stop) { NO_MARKER }
|
18
|
+
if values.last == '*'
|
19
|
+
@stop = NO_MARKER
|
20
|
+
@values.pop
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
alias :verbose? :verbose
|
25
|
+
|
26
|
+
def next
|
27
|
+
label(values.length)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_hash
|
31
|
+
return {label => values.join('.')} unless verbose?
|
32
|
+
|
33
|
+
collection = labelize
|
34
|
+
collection.merge!(stop_label) if use_stop_marker?
|
35
|
+
collection
|
36
|
+
end
|
37
|
+
|
38
|
+
def labelize
|
39
|
+
|
40
|
+
optional_part = false
|
41
|
+
labels = values.map do |label|
|
42
|
+
if label =~ /^\^/
|
43
|
+
label.gsub!(/^\^/, '')
|
44
|
+
optional_part = true
|
45
|
+
end
|
46
|
+
|
47
|
+
result = label.include?('|') ? label.split('|') : label
|
48
|
+
result = [result, nil].flatten if optional_part
|
49
|
+
result
|
50
|
+
end
|
51
|
+
|
52
|
+
collection = {}
|
53
|
+
labels.each_with_index do |value, i|
|
54
|
+
collection[label(i)] = value
|
55
|
+
end
|
56
|
+
collection
|
57
|
+
end
|
58
|
+
|
59
|
+
def label(i = nil)
|
60
|
+
[name, i, suffix].compact.join('_')
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop_label
|
64
|
+
{ label(values.length) => stop }
|
65
|
+
end
|
66
|
+
|
67
|
+
def use_stop_marker?
|
68
|
+
stop != NO_MARKER
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pebbles
|
2
|
+
class Uid
|
3
|
+
class Genus < Labels
|
4
|
+
|
5
|
+
def ambiguous?
|
6
|
+
value == '*' || values.empty? || wildcard?
|
7
|
+
end
|
8
|
+
|
9
|
+
def species
|
10
|
+
return if size <= 1
|
11
|
+
|
12
|
+
tail.join('.')
|
13
|
+
end
|
14
|
+
|
15
|
+
def species?
|
16
|
+
species != '*'
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash(options = {})
|
20
|
+
super({:verbose => false, :name => 'genus'}.merge(options))
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Pebbles
|
2
|
+
class Uid
|
3
|
+
class Labels
|
4
|
+
|
5
|
+
attr_reader :values, :prefix
|
6
|
+
def initialize(*values)
|
7
|
+
@values = values.flatten.compact.map {|v| v.split('.') }.flatten
|
8
|
+
end
|
9
|
+
|
10
|
+
def tail
|
11
|
+
values[1..-1]
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_with?(pattern)
|
15
|
+
!empty? && values.all? {|value| value[pattern] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def size
|
19
|
+
values.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_a
|
23
|
+
values
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
values.join('.')
|
28
|
+
end
|
29
|
+
alias :value :to_s
|
30
|
+
|
31
|
+
def empty?
|
32
|
+
values.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def wildcard?
|
36
|
+
!!(value =~ /[\*\|\^]/)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_hash(options = {})
|
40
|
+
Conditions.new(values, options).to_hash
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Pebbles
|
2
|
+
class Uid
|
3
|
+
|
4
|
+
class Oid < Labels
|
5
|
+
|
6
|
+
def ambiguous?
|
7
|
+
value == '*' || value.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
def empty?
|
11
|
+
value.empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
def multiple?
|
15
|
+
!!(value =~ /[\|]/)
|
16
|
+
end
|
17
|
+
|
18
|
+
def wildcard?
|
19
|
+
value == '*' || multiple?
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash(options = {})
|
23
|
+
options.delete(:verbose)
|
24
|
+
super({:verbose => false, :name => 'oid'}.merge(options))
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pebbles
|
2
|
+
class Uid
|
3
|
+
class Path < Labels
|
4
|
+
|
5
|
+
def realm
|
6
|
+
values.first
|
7
|
+
end
|
8
|
+
|
9
|
+
def realm?
|
10
|
+
return false if realm == '*'
|
11
|
+
!!realm
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash(options = {})
|
15
|
+
super({:name => 'path'}.merge(options))
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Pebbles
|
2
|
+
class Uid
|
3
|
+
class Query
|
4
|
+
|
5
|
+
attr_reader :term, :terms, :genus, :path, :oid
|
6
|
+
def initialize(term)
|
7
|
+
@term = term
|
8
|
+
|
9
|
+
if wildcard_query?
|
10
|
+
@terms = [term]
|
11
|
+
@genus, @path, @oid = Pebbles::Uid.parse(term)
|
12
|
+
else
|
13
|
+
@terms = extract_terms
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def for_one?
|
18
|
+
!wildcard_query? && terms.size == 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def list?
|
22
|
+
terms.size != 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def collection?
|
26
|
+
wildcard_query?
|
27
|
+
end
|
28
|
+
|
29
|
+
def path?
|
30
|
+
@path && @path.split('.').reject {|s| s == '*'}.length > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def genus?
|
34
|
+
@genus && @genus != '*'
|
35
|
+
end
|
36
|
+
|
37
|
+
def species?
|
38
|
+
!!Genus.new(genus).species
|
39
|
+
end
|
40
|
+
|
41
|
+
def species
|
42
|
+
Genus.new(genus).species
|
43
|
+
end
|
44
|
+
|
45
|
+
def oid?
|
46
|
+
!!@oid && @oid != '*'
|
47
|
+
end
|
48
|
+
|
49
|
+
def cache_keys
|
50
|
+
terms.map { |t| Pebbles::Uid.cache_key(t) }
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def wildcard_query?
|
56
|
+
return false if term.include?(',')
|
57
|
+
species, _, oid = Pebbles::Uid.parse(term)
|
58
|
+
return true if Genus.new(species).wildcard?
|
59
|
+
return true if oid.nil? || oid.empty? || oid == '*'
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def extract_terms
|
64
|
+
term.split(',').map do |uid|
|
65
|
+
species, path, oid = Pebbles::Uid.parse(uid)
|
66
|
+
species_labels = Genus.new(species)
|
67
|
+
path_labels = Path.new(path)
|
68
|
+
oid_box = Oid.new(oid)
|
69
|
+
|
70
|
+
raise ArgumentError.new('Realm must be specified') unless path_labels.realm?
|
71
|
+
raise ArgumentError.new('Genus must unambiguous') if species_labels.ambiguous?
|
72
|
+
raise ArgumentError.new('Oid must be unambiguous') if oid_box.ambiguous?
|
73
|
+
|
74
|
+
@realm ||= path_labels.realm
|
75
|
+
raise ArgumentError.new('One realm at a time, please') if @realm != path_labels.realm
|
76
|
+
|
77
|
+
if oid_box.multiple?
|
78
|
+
oid.split('|').map do |s|
|
79
|
+
"#{species}:#{path}$#{s}"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
uid
|
83
|
+
end
|
84
|
+
end.flatten
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Pebbles
|
2
|
+
class Uid
|
3
|
+
module Wildcard
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def valid?(path)
|
8
|
+
stars_are_solitary?(path) && single_caret?(path) && pipes_are_interleaved?(path) && carets_are_leading?(path) && stars_are_terminating?(path)
|
9
|
+
end
|
10
|
+
|
11
|
+
# a.*.c passes
|
12
|
+
# a.*b.c does not
|
13
|
+
# A later rule ensures that * always falls at the end of a path
|
14
|
+
def stars_are_solitary?(path)
|
15
|
+
!path.split('.').any? {|s| s.match(/.+\*|\*.+/)}
|
16
|
+
end
|
17
|
+
|
18
|
+
# a.b|c.d passes
|
19
|
+
# a.|b.c does not
|
20
|
+
def pipes_are_interleaved?(path)
|
21
|
+
!path.split('.').any? {|s| s.match(/^\||\|$/)}
|
22
|
+
end
|
23
|
+
|
24
|
+
# a.^b.c passes
|
25
|
+
# a.^c.^d does not
|
26
|
+
def single_caret?(path)
|
27
|
+
path.split('.').select {|s| s.match(/\^/) }.size <= 1
|
28
|
+
end
|
29
|
+
|
30
|
+
# a.^b.c passes
|
31
|
+
# a.b^c.d does not
|
32
|
+
def carets_are_leading?(path)
|
33
|
+
!path.split('.').any? {|s| s.match(/.+\^|\^$/) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# a.b.* passes
|
37
|
+
# *.b.c does not
|
38
|
+
def stars_are_terminating?(path)
|
39
|
+
path !~ /.*\*\.\w/
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/pebbles/uid.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'pebbles-uid'
|
data/pebbles-uid.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/pebbles-uid/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Katrina Owen"]
|
6
|
+
gem.email = ["katrina.owen@gmail.com"]
|
7
|
+
gem.description = %q{Handle pebble UIDs conveniently.}
|
8
|
+
gem.summary = %q{Unique identifiers in the Pebblestack universe, in the format species[.genus]:path$oid, where the path is a dot-delimited set of labels, the first of which (realm) is required.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "pebbles-uid"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Pebbles::Uid::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency "rspec"
|
19
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'pebbles-uid/conditions'
|
2
|
+
|
3
|
+
describe Pebbles::Uid::Conditions do
|
4
|
+
|
5
|
+
subject { Pebbles::Uid::Conditions.new(%w(a b c)) }
|
6
|
+
|
7
|
+
its(:to_hash) { should eq('label_0' => "a", 'label_1' => "b", 'label_2' => "c") }
|
8
|
+
|
9
|
+
describe "customized labels" do
|
10
|
+
subject { Pebbles::Uid::Conditions.new(%w(p r q), :name => 'dot', :suffix => '') }
|
11
|
+
its(:to_hash) { should eq('dot_0_' => 'p', 'dot_1_' => 'r', 'dot_2_' => 'q') }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has a non-verbose mode" do
|
15
|
+
uid = Pebbles::Uid::Conditions.new(%w(k l m), :name => 'stuff', :verbose => false)
|
16
|
+
uid.to_hash.should eq({'stuff' => 'k.l.m'})
|
17
|
+
end
|
18
|
+
|
19
|
+
specify "non-verbose still takes a suffix" do
|
20
|
+
uid = Pebbles::Uid::Conditions.new(%w(k l m), :name => 'stuff', :verbose => false, :suffix => 'xyz')
|
21
|
+
uid.to_hash.should eq({'stuff_xyz' => 'k.l.m'})
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "with a stop label" do
|
25
|
+
subject { Pebbles::Uid::Conditions.new(%w(x y z), :stop => nil) }
|
26
|
+
its(:to_hash) { should eq('label_0' => "x", 'label_1' => "y", 'label_2' => "z", 'label_3' => nil) }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "next label" do
|
30
|
+
subject { Pebbles::Uid::Conditions.new(%w(h j k l), :name => 'vim') }
|
31
|
+
its(:next) { should eq('vim_4') }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "with pipes" do
|
35
|
+
subject { Pebbles::Uid::Conditions.new(%w(a b|c d), :stop => nil) }
|
36
|
+
its(:to_hash) { should eq('label_0' => "a", 'label_1' => ['b', 'c'], 'label_2' => "d", 'label_3' => nil) }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "with an asterisk" do
|
40
|
+
# ignores stop marker if it is terminated by an asterisk
|
41
|
+
subject { Pebbles::Uid::Conditions.new(%w(a b *), :stop => nil) }
|
42
|
+
its(:to_hash) { should eq('label_0' => "a", 'label_1' => 'b') }
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "with an asterisk" do
|
46
|
+
subject { Pebbles::Uid::Conditions.new(%w(a ^b c), :stop => nil) }
|
47
|
+
its(:to_hash) { should eq('label_0' => "a", 'label_1' => ['b', nil], 'label_2' => ['c', nil], 'label_3' => nil) }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "complicated stuff" do
|
51
|
+
subject { Pebbles::Uid::Conditions.new(%w(a ^b|c d *), :stop => nil) }
|
52
|
+
its(:to_hash) { should eq('label_0' => "a", 'label_1' => ['b', 'c', nil], 'label_2' => ['d', nil]) }
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/spec/genus_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'pebbles-uid/labels'
|
2
|
+
require 'pebbles-uid/conditions'
|
3
|
+
require 'pebbles-uid/genus'
|
4
|
+
|
5
|
+
describe Pebbles::Uid::Genus do
|
6
|
+
|
7
|
+
subject { Pebbles::Uid::Genus.new('unicorn') }
|
8
|
+
|
9
|
+
its(:to_s) { should eq('unicorn') }
|
10
|
+
|
11
|
+
its(:species) { should be_nil }
|
12
|
+
its(:to_hash) { should eq('genus' => 'unicorn') }
|
13
|
+
|
14
|
+
context "subtypes" do
|
15
|
+
|
16
|
+
subject { Pebbles::Uid::Genus.new('unicorn', 'dust', 'sparkles') }
|
17
|
+
its(:to_s) { should eq('unicorn.dust.sparkles') }
|
18
|
+
its(:species) { should eq('dust.sparkles') }
|
19
|
+
its(:to_hash) { should eq('genus' => 'unicorn.dust.sparkles') }
|
20
|
+
|
21
|
+
it "doesn't have a species if there's a wildcard" do
|
22
|
+
genus = Pebbles::Uid::Genus.new('unicorn.*')
|
23
|
+
genus.species?.should == false
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can customize the hash" do
|
27
|
+
subject.to_hash(:verbose => true).should eq('genus_0' => 'unicorn', 'genus_1' => 'dust', 'genus_2' => 'sparkles')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it { Pebbles::Uid::Genus.new('unicorn.horn').species.should eq('horn') }
|
32
|
+
|
33
|
+
end
|
data/spec/labels_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'pebbles-uid/conditions'
|
2
|
+
require 'pebbles-uid/labels'
|
3
|
+
|
4
|
+
describe Pebbles::Uid::Labels do
|
5
|
+
|
6
|
+
subject { Pebbles::Uid::Labels.new('a.b.c') }
|
7
|
+
its(:to_s) { should eq('a.b.c') }
|
8
|
+
its(:to_a) { should eq(%w(a b c)) }
|
9
|
+
its(:to_hash) { should eq('label_0' => "a", 'label_1' => "b", 'label_2' => "c") }
|
10
|
+
its(:tail) { should eq(%w(b c)) }
|
11
|
+
|
12
|
+
it "delegates to conditions" do
|
13
|
+
subject.to_hash(:name => 'thing', :verbose => false).should eq({'thing' => 'a.b.c'})
|
14
|
+
end
|
15
|
+
|
16
|
+
it "has a size" do
|
17
|
+
subject.size.should eq(3)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "alternate constructors" do
|
21
|
+
it { Pebbles::Uid::Labels.new('a', 'b', 'c').to_s.should eq('a.b.c') }
|
22
|
+
it { Pebbles::Uid::Labels.new(['a', 'b', 'c']).to_s.should eq('a.b.c') }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Not certain where I want to deal with invalid wildcard paths yet.
|
26
|
+
context "wildcards" do
|
27
|
+
specify { Pebbles::Uid::Labels.new('a.*').wildcard?.should == true }
|
28
|
+
specify { Pebbles::Uid::Labels.new('a.b|c').wildcard?.should == true }
|
29
|
+
specify { Pebbles::Uid::Labels.new('a.^b.c').wildcard?.should == true }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "validation" do
|
33
|
+
it { subject.valid_with?(/[0-9]/).should == false }
|
34
|
+
it { subject.valid_with?(/[a-z]/).should == true }
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/spec/oid_spec.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'pebbles-uid/labels'
|
2
|
+
require 'pebbles-uid/oid'
|
3
|
+
|
4
|
+
describe Pebbles::Uid::Oid do
|
5
|
+
|
6
|
+
describe "wildcard" do
|
7
|
+
specify { Pebbles::Uid::Oid.new('*').wildcard?.should == true }
|
8
|
+
specify { Pebbles::Uid::Oid.new('star*star').wildcard?.should == false }
|
9
|
+
specify { Pebbles::Uid::Oid.new('abc|xyz').wildcard?.should == true }
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/spec/path_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'pebbles-uid/conditions'
|
2
|
+
require 'pebbles-uid/labels'
|
3
|
+
require 'pebbles-uid/path'
|
4
|
+
|
5
|
+
describe Pebbles::Uid::Path do
|
6
|
+
|
7
|
+
subject { Pebbles::Uid::Path.new('magical', 'forrest', 'clearing') }
|
8
|
+
|
9
|
+
its(:realm) { should eq('magical') }
|
10
|
+
its(:realm?) { should == true }
|
11
|
+
its(:to_s) { should eq('magical.forrest.clearing') }
|
12
|
+
its(:to_a) { should eq(%w(magical forrest clearing)) }
|
13
|
+
its(:to_hash) { should eq({'path_0' => 'magical', 'path_1' => 'forrest', 'path_2' => 'clearing'}) }
|
14
|
+
its(:wildcard?) { should == false }
|
15
|
+
|
16
|
+
it "can customize the hash" do
|
17
|
+
subject.to_hash(:name => 'label').should eq({'label_0' => 'magical', 'label_1' => 'forrest', 'label_2' => 'clearing'})
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "wildcard realm" do
|
21
|
+
Pebbles::Uid::Path.new('*').realm?.should == false
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/spec/query_spec.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'pebbles-uid'
|
2
|
+
|
3
|
+
describe Pebbles::Uid::Query do
|
4
|
+
|
5
|
+
context "for a single resource" do
|
6
|
+
let(:term) { "post:area51$abc" }
|
7
|
+
subject { Pebbles::Uid::Query.new(term) }
|
8
|
+
|
9
|
+
its(:term) { should == term }
|
10
|
+
|
11
|
+
its(:for_one?) { should == true }
|
12
|
+
its(:list?) { should == false }
|
13
|
+
its(:collection?) { should == false }
|
14
|
+
its(:cache_keys) { should eq(['post:area51.*$abc']) }
|
15
|
+
|
16
|
+
it "handles a wildcard path if realm is given" do
|
17
|
+
query = Pebbles::Uid::Query.new('post:area51.*$abc')
|
18
|
+
query.for_one?.should == true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "ignores crazy wildcard stuff in the path" do
|
22
|
+
query = Pebbles::Uid::Query.new("post:area51.^a.b.c.*|d$abc")
|
23
|
+
query.for_one?.should == true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "bails without realm" do
|
27
|
+
->{ Pebbles::Uid::Query.new('post:*$abc') }.should raise_error(ArgumentError)
|
28
|
+
->{ Pebbles::Uid::Query.new('post:$abc') }.should raise_error(ArgumentError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "for a list of resources" do
|
33
|
+
let(:term) { "post:area51$abc,post:area51$xyz" }
|
34
|
+
subject { Pebbles::Uid::Query.new(term) }
|
35
|
+
|
36
|
+
its(:for_one?) { should == false }
|
37
|
+
its(:list?) { should == true }
|
38
|
+
its(:collection?) { should == false }
|
39
|
+
|
40
|
+
its(:cache_keys) { should eq(['post:area51.*$abc', 'post:area51.*$xyz']) }
|
41
|
+
|
42
|
+
it "handles a pipe-delimited list of oids" do
|
43
|
+
query = Pebbles::Uid::Query.new("post:area51$abc|xyz")
|
44
|
+
query.terms.should eq(['post:area51$abc', 'post:area51$xyz'])
|
45
|
+
end
|
46
|
+
|
47
|
+
it "ignores crazy wildcard stuff in the path" do
|
48
|
+
query = Pebbles::Uid::Query.new("post:area51.^a.b.c.*|d$abc|xyz")
|
49
|
+
query.list?.should == true
|
50
|
+
query.cache_keys.should eq(['post:area51.*$abc', 'post:area51.*$xyz'])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "bails without realm" do
|
54
|
+
->{ Pebbles::Uid::Query.new('post:*$abc,post:*$xyz') }.should raise_error(ArgumentError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "must have only one realm" do
|
58
|
+
->{ Pebbles::Uid::Query.new('post:area51$abc,post:area52$xyz') }.should raise_error(ArgumentError)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "bails with unspecific genus" do
|
62
|
+
->{ Pebbles::Uid::Query.new('post.*:area51$abc,post:area51$xyz') }.should raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "bails with unspecified oid" do
|
66
|
+
->{ Pebbles::Uid::Query.new('post:area51$*,post:area51$xyz') }.should raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "for a collection of resources" do
|
71
|
+
let(:term) { "post.*:area51.^a.b.c" }
|
72
|
+
subject { Pebbles::Uid::Query.new(term) }
|
73
|
+
|
74
|
+
its(:for_one?) { should == false }
|
75
|
+
its(:list?) { should == false }
|
76
|
+
its(:collection?) { should == true }
|
77
|
+
|
78
|
+
it "handles unspecific oids" do
|
79
|
+
Pebbles::Uid::Query.new('post:area51$*').collection?.should == true
|
80
|
+
Pebbles::Uid::Query.new('post:area51').collection?.should == true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "wildcard queries" do
|
85
|
+
|
86
|
+
context "everything" do
|
87
|
+
subject { Pebbles::Uid::Query.new('*:*') }
|
88
|
+
|
89
|
+
its(:genus?) { should == false }
|
90
|
+
its(:path?) { should == false }
|
91
|
+
its(:oid?) { should == false }
|
92
|
+
end
|
93
|
+
|
94
|
+
context "everything, with any oid" do
|
95
|
+
subject { Pebbles::Uid::Query.new('*:*$*') }
|
96
|
+
|
97
|
+
its(:genus?) { should == false }
|
98
|
+
its(:path?) { should == false }
|
99
|
+
its(:oid?) { should == false }
|
100
|
+
end
|
101
|
+
|
102
|
+
context "a genus" do
|
103
|
+
subject { Pebbles::Uid::Query.new('beast:*$*') }
|
104
|
+
its(:genus?) { should == true }
|
105
|
+
its(:genus) { should eq('beast') }
|
106
|
+
its(:species?) { should == false }
|
107
|
+
end
|
108
|
+
|
109
|
+
context "a species" do
|
110
|
+
subject { Pebbles::Uid::Query.new('beast.mythical.hairy:*$*') }
|
111
|
+
its(:species?) { should == true }
|
112
|
+
its(:species) { should eq('mythical.hairy') }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "a path" do
|
116
|
+
subject { Pebbles::Uid::Query.new('*:area51.*') }
|
117
|
+
its(:path?) { should == true }
|
118
|
+
end
|
119
|
+
|
120
|
+
context "one oid" do
|
121
|
+
subject { Pebbles::Uid::Query.new('*:*$yak') }
|
122
|
+
its(:oid?) { should == true }
|
123
|
+
its(:oid) { should == 'yak' }
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/spec/uid_spec.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'pebbles-uid'
|
2
|
+
|
3
|
+
describe Pebbles::Uid do
|
4
|
+
|
5
|
+
let(:uid) { 'post.card:tourism.norway.fjords$1234' }
|
6
|
+
|
7
|
+
describe "extracting single elements" do
|
8
|
+
specify { Pebbles::Uid.oid(uid).should eq('1234') }
|
9
|
+
specify { Pebbles::Uid.path(uid).should eq('tourism.norway.fjords') }
|
10
|
+
specify { Pebbles::Uid.genus(uid).should eq('post.card') }
|
11
|
+
|
12
|
+
describe "with missing oid" do
|
13
|
+
specify { Pebbles::Uid.oid('post:a.b.c').should eq(nil) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "query" do
|
18
|
+
it "returns a query object" do
|
19
|
+
s = 'post:tourism.*$*'
|
20
|
+
Pebbles::Uid::Query.should_receive(:new).with s
|
21
|
+
query = Pebbles::Uid.query(s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "requirements" do
|
26
|
+
it "must have a genus" do
|
27
|
+
->{ Pebbles::Uid.new(':tourism.norway$1') }.should raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "must have a realm" do
|
31
|
+
->{ Pebbles::Uid.new('post:$1') }.should raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can skip the oid" do
|
35
|
+
->{ Pebbles::Uid.new('post:tourism') }.should_not raise_error
|
36
|
+
end
|
37
|
+
|
38
|
+
it "doesn't accept wildcard genus" do
|
39
|
+
->{ Pebbles::Uid.new('post.*:tourism$1') }.should raise_error(ArgumentError)
|
40
|
+
->{ Pebbles::Uid.new('post|card:tourism$1') }.should raise_error(ArgumentError)
|
41
|
+
->{ Pebbles::Uid.new('post.^b.c:tourism$1') }.should raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "doesn't accept wildcard paths" do
|
45
|
+
->{ Pebbles::Uid.new('post:tourism.*$1') }.should raise_error(ArgumentError)
|
46
|
+
->{ Pebbles::Uid.new('post:tourism|blogging$1') }.should raise_error(ArgumentError)
|
47
|
+
->{ Pebbles::Uid.new('post:tourism.^b.c$1') }.should raise_error(ArgumentError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "doesn't accept wildcard oid" do
|
51
|
+
->{ Pebbles::Uid.new('post:tourism$*') }.should raise_error(ArgumentError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
subject { Pebbles::Uid.new(uid) }
|
56
|
+
|
57
|
+
its(:to_s) { should eq(uid) }
|
58
|
+
its(:realm) { should eq('tourism') }
|
59
|
+
its(:genus) { should eq('post.card') }
|
60
|
+
its(:species) { should eq('card') }
|
61
|
+
its(:path) { should eq('tourism.norway.fjords') }
|
62
|
+
its(:oid) { should eq('1234') }
|
63
|
+
its(:oid?) { should == true }
|
64
|
+
|
65
|
+
its(:cache_key) { should eq('post.card:tourism.*$1234') }
|
66
|
+
|
67
|
+
context "when pending creation" do
|
68
|
+
|
69
|
+
let(:uid) { 'post.doc:universities.europe.norway' }
|
70
|
+
subject { Pebbles::Uid.new(uid) }
|
71
|
+
|
72
|
+
its(:to_s) { should eq(uid) }
|
73
|
+
its(:oid) { should be_nil }
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "paths" do
|
78
|
+
["abc123", "abc.123", "abc.de-f.123"].each do |path|
|
79
|
+
specify "#{path} is a valid path" do
|
80
|
+
Pebbles::Uid.valid_path?(path).should == true
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
["", ".", "..", "abc!"].each do |path|
|
85
|
+
specify "#{path} is not a valid path" do
|
86
|
+
Pebbles::Uid.valid_path?(path).should == false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "genus" do
|
92
|
+
%w(- . _ 8).each do |char|
|
93
|
+
it "accepts #{char} in a genus" do
|
94
|
+
Pebbles::Uid.valid_genus?("uni#{char}corn").should == true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
%w(! % { ? $).each do |char|
|
99
|
+
it "rejects #{char}" do
|
100
|
+
->{ Pebbles::Uid.new("uni#{char}corn:mythical$1") }.should raise_error(ArgumentError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "oids" do
|
106
|
+
it "can be empty" do
|
107
|
+
Pebbles::Uid.new("beast:mythical").oid?.should == false
|
108
|
+
end
|
109
|
+
|
110
|
+
it "cannot contain pipes" do
|
111
|
+
Pebbles::Uid.valid_oid?("abc|xyz").should == false
|
112
|
+
end
|
113
|
+
|
114
|
+
it "cannot contain commas, either" do
|
115
|
+
Pebbles::Uid.valid_oid?("abc,xyz").should == false
|
116
|
+
end
|
117
|
+
|
118
|
+
it "can be empty" do
|
119
|
+
Pebbles::Uid.valid_oid?(nil).should == true
|
120
|
+
end
|
121
|
+
|
122
|
+
it "can be an empty string" do
|
123
|
+
Pebbles::Uid.valid_oid?("").should == true
|
124
|
+
end
|
125
|
+
|
126
|
+
it "is a black box" do
|
127
|
+
Pebbles::Uid.valid_oid?("holy+%^&*s!").should == true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pebbles-uid/wildcard'
|
2
|
+
|
3
|
+
describe Pebbles::Uid::Wildcard do
|
4
|
+
|
5
|
+
# This might not actually be necessary/relevant
|
6
|
+
specify "non-wildcard paths are valid" do
|
7
|
+
Pebbles::Uid::Wildcard.valid?('a.b.d').should == true
|
8
|
+
end
|
9
|
+
|
10
|
+
context "terminating asterisk representing entire label" do
|
11
|
+
[
|
12
|
+
'*', 'a.b.c.*', 'a.b|c.*', 'a.^b.c.*'
|
13
|
+
].each do |path|
|
14
|
+
specify "#{path} is valid" do
|
15
|
+
Pebbles::Uid::Wildcard.valid?(path).should == true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
['*a', 'a*', '*.b', 'a.*.b'].each do |path|
|
20
|
+
specify "#{path} is invalid" do
|
21
|
+
Pebbles::Uid::Wildcard.valid?(path).should == false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "pipes separate labels" do
|
27
|
+
['a|b', 'a.b|c.d', 'a|b.c|d'].each do |path|
|
28
|
+
specify "#{path} is valid" do
|
29
|
+
Pebbles::Uid::Wildcard.valid?(path).should == true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
['|', 'a.|b', 'a.b|', 'a.|b.c', 'a.b|.c'].each do |path|
|
34
|
+
specify "#{path} is invalid" do
|
35
|
+
Pebbles::Uid::Wildcard.valid?(path).should == false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "carets start an ancestry chain" do
|
41
|
+
['^a', '^a.b', 'a.^b.c.*'].each do |path|
|
42
|
+
specify "#{path} is valid" do
|
43
|
+
Pebbles::Uid::Wildcard.valid?(path).should == true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
['^', '^.a', 'a^', 'a^b.c', '^a.^b'].each do |path|
|
48
|
+
specify "#{path} is invalid" do
|
49
|
+
Pebbles::Uid::Wildcard.valid?(path).should == false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pebbles-uid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Katrina Owen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70278976164860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70278976164860
|
25
|
+
description: Handle pebble UIDs conveniently.
|
26
|
+
email:
|
27
|
+
- katrina.owen@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- Gemfile
|
34
|
+
- LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/pebbles-uid.rb
|
38
|
+
- lib/pebbles-uid/conditions.rb
|
39
|
+
- lib/pebbles-uid/genus.rb
|
40
|
+
- lib/pebbles-uid/labels.rb
|
41
|
+
- lib/pebbles-uid/oid.rb
|
42
|
+
- lib/pebbles-uid/path.rb
|
43
|
+
- lib/pebbles-uid/query.rb
|
44
|
+
- lib/pebbles-uid/version.rb
|
45
|
+
- lib/pebbles-uid/wildcard.rb
|
46
|
+
- lib/pebbles/uid.rb
|
47
|
+
- pebbles-uid.gemspec
|
48
|
+
- spec/conditions_spec.rb
|
49
|
+
- spec/genus_spec.rb
|
50
|
+
- spec/labels_spec.rb
|
51
|
+
- spec/oid_spec.rb
|
52
|
+
- spec/path_spec.rb
|
53
|
+
- spec/query_spec.rb
|
54
|
+
- spec/uid_spec.rb
|
55
|
+
- spec/wildcard_spec.rb
|
56
|
+
homepage: ''
|
57
|
+
licenses: []
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.8.15
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Unique identifiers in the Pebblestack universe, in the format species[.genus]:path$oid,
|
80
|
+
where the path is a dot-delimited set of labels, the first of which (realm) is required.
|
81
|
+
test_files:
|
82
|
+
- spec/conditions_spec.rb
|
83
|
+
- spec/genus_spec.rb
|
84
|
+
- spec/labels_spec.rb
|
85
|
+
- spec/oid_spec.rb
|
86
|
+
- spec/path_spec.rb
|
87
|
+
- spec/query_spec.rb
|
88
|
+
- spec/uid_spec.rb
|
89
|
+
- spec/wildcard_spec.rb
|