siren 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ module Siren
2
+ module Node
3
+
4
+ def from_json(hash)
5
+ object = self.new
6
+ hash.each do |key, value|
7
+ object.instance_variable_set("@#{key}", value)
8
+ if Reference === value
9
+ value.on(:resolve) do |ref, root, symbols|
10
+ object.instance_variable_set("@#{key}", ref.find(root, symbols, object))
11
+ end
12
+ end
13
+ end
14
+ object
15
+ end
16
+
17
+ @classes = {}
18
+
19
+ def self.extended(base)
20
+ @classes[base.name.split('::').last] = base
21
+ end
22
+
23
+ def self.from_json(hash)
24
+ hash = Siren.parse(hash) if String === hash
25
+ return hash unless Hash === hash && hash[TYPE_FIELD]
26
+ klass = find_class(hash[TYPE_FIELD])
27
+ klass ? klass.from_json(hash) : hash
28
+ end
29
+
30
+ def self.find_class(name)
31
+ name = name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
32
+ @classes[name]
33
+ end
34
+
35
+ end
36
+ end
37
+
@@ -0,0 +1,205 @@
1
+ # Based on http://www.json.org/json_parse.js (public domain)
2
+
3
+ module Siren
4
+ class Parser
5
+
6
+ include Walker
7
+
8
+ ESCAPEE = {
9
+ '"' => '"',
10
+ '\\' => '\\',
11
+ '/' => '/',
12
+ 'b' => '\b',
13
+ 'f' => '\f',
14
+ 'n' => '\n',
15
+ 'r' => '\r',
16
+ 't' => '\t'
17
+ }
18
+
19
+ attr_reader :at, # The index of the current character
20
+ :ch # The current character
21
+
22
+ def parse(source, &reviver)
23
+ @text = source.dup
24
+ @at, @ch = 0, ' '
25
+ result = value!
26
+ white!
27
+ error! "Syntax error" if @ch
28
+
29
+ walk(result, &reviver)
30
+ end
31
+
32
+ private
33
+
34
+ def next!(c = nil)
35
+ # If a c parameter is provided, verify that it matches the current character.
36
+ error! "Expected '#{c}' instead of '#{@ch}'" if c && c != @ch
37
+
38
+ # Get the next character. When there are no more characters,
39
+ # return the empty string.
40
+ @ch = @text[at].chr rescue nil
41
+ @at += 1
42
+ @ch
43
+ end
44
+
45
+ # Parse a number value.
46
+ def number!
47
+ string = ''
48
+ if @ch == '-'
49
+ string = '-'
50
+ next!('-')
51
+ end
52
+ while @ch >= '0' && @ch <= '9'
53
+ string += @ch
54
+ next!
55
+ end
56
+ if @ch == '.'
57
+ string += '.'
58
+ while next! && @ch >= '0' && @ch <= '9'
59
+ string += @ch
60
+ end
61
+ end
62
+ if @ch == 'e' || @ch == 'E'
63
+ string += @ch
64
+ next!
65
+ if @ch == '-' || @ch == '+'
66
+ string += @ch
67
+ next!
68
+ end
69
+ while @ch >= '0' && @ch <= '9'
70
+ string += @ch
71
+ next!
72
+ end
73
+ end
74
+ string.to_f
75
+ rescue
76
+ error! "Bad number"
77
+ end
78
+
79
+ #Parse a string value.
80
+ def string!
81
+ string = ''
82
+ # When parsing for string values, we must look for " and \ characters.
83
+ if @ch == '"'
84
+ while next!
85
+ if @ch == '"'
86
+ next!
87
+ return string
88
+ elsif ch == '\\'
89
+ next!
90
+ if @ch == 'u'
91
+ uffff = 0
92
+ 4.times do
93
+ hex = next!.to_i(16)
94
+ uffff = uffff * 16 + hex
95
+ end
96
+ string += uffff.chr
97
+ elsif String === ESCAPPE[@ch]
98
+ string += ESCAPPE[@ch]
99
+ else
100
+ break
101
+ end
102
+ else
103
+ string += @ch
104
+ end
105
+ end
106
+ end
107
+ error! "Bad string"
108
+ end
109
+
110
+ # Skip whitespace.
111
+ def white!
112
+ next! while @ch && @ch <= ' '
113
+ end
114
+
115
+ # true, false, undefined or null.
116
+ def word!
117
+ case @ch
118
+ when 't'
119
+ %w(t r u e).each { |c| next!(c) }
120
+ return true
121
+ when 'f'
122
+ %w(f a l s e).each { |c| next!(c) }
123
+ return nil
124
+ when 'n'
125
+ %w(n u l l).each { |c| next!(c) }
126
+ return nil
127
+ end
128
+ error! "Unexpected '#{@ch}'"
129
+ end
130
+
131
+ # Parse an array value.
132
+ def array!
133
+ array = []
134
+
135
+ if @ch == '['
136
+ next!('[')
137
+ white!
138
+ if @ch == ']'
139
+ next!(']')
140
+ return array # empty array
141
+ end
142
+ while @ch
143
+ array << value!
144
+ white!
145
+ if @ch == ']'
146
+ next!(']')
147
+ return array
148
+ end
149
+ next!(',')
150
+ white!
151
+ end
152
+ end
153
+ error! "Bad array"
154
+ end
155
+
156
+ # Parse an object value.
157
+ def object!
158
+ object = {}
159
+
160
+ if @ch == '{'
161
+ next!('{')
162
+ white!
163
+ if @ch == '}'
164
+ next!('}')
165
+ return object # empty object
166
+ end
167
+ while @ch
168
+ key = string!
169
+ white!
170
+ next!(':')
171
+ error! 'Duplicate key "#{key}"' if object.has_key?(key)
172
+ object[key] = value!
173
+ white!
174
+ if @ch == '}'
175
+ next!('}')
176
+ return object
177
+ end
178
+ next!(',')
179
+ white!
180
+ end
181
+ end
182
+ error! "Bad object"
183
+ end
184
+
185
+ # Parse a JSON value. It could be an object, an array, a string, a number,
186
+ # or a word.
187
+ def value!
188
+ white!
189
+ case @ch
190
+ when '{' then object!
191
+ when '[' then array!
192
+ when '"' then string!
193
+ when '-' then number!
194
+ else @ch >= '0' && @ch <= '9' ? number! : word!
195
+ end
196
+ end
197
+
198
+ # Call error when something is wrong.
199
+ def error!(message = nil)
200
+ raise Exception
201
+ end
202
+
203
+ end
204
+ end
205
+
@@ -0,0 +1,30 @@
1
+ module Siren
2
+ class Reference
3
+
4
+ include Eventful
5
+
6
+ def self.flush!
7
+ @@cache = {}
8
+ end
9
+
10
+ def self.resolve!(root, symbols)
11
+ @@cache.each { |id, ref| ref.resolve!(root, symbols) }
12
+ end
13
+
14
+ def initialize(hash)
15
+ @query = Siren.compile_query(hash[REF_FIELD])
16
+ @@cache ||= {}
17
+ @@cache[hash.__id__] = self
18
+ end
19
+
20
+ def resolve!(root, symbols)
21
+ fire(:resolve, root, symbols)
22
+ end
23
+
24
+ def find(root, symbols, current)
25
+ @query.value(root, symbols, current)
26
+ end
27
+
28
+ end
29
+ end
30
+
@@ -0,0 +1,33 @@
1
+ module Siren
2
+ module Walker
3
+
4
+ # If there is a reviver function, we recursively walk the new structure,
5
+ # passing each name/value pair to the reviver function for possible
6
+ # transformation, starting with a temporary root object that holds the result
7
+ # in an empty key. If there is not a reviver function, we simply return the
8
+ # result.
9
+ def walk(data, &reviver)
10
+ data = parse(data) if String === data
11
+ return data unless block_given?
12
+
13
+ walker = lambda do |holder, key|
14
+ value = holder[key]
15
+
16
+ Siren.each(value) do |k, val|
17
+ v = walker.call(value, k)
18
+ if v.nil?
19
+ holder.delete(k)
20
+ else
21
+ value[k] = v
22
+ end
23
+ end
24
+
25
+ reviver.call(holder, key, value)
26
+ end
27
+
28
+ walker.call({"" => data}, "")
29
+ end
30
+
31
+ end
32
+ end
33
+
@@ -0,0 +1,11 @@
1
+ {
2
+ "people": [
3
+ {"name": "John", "age": 23},
4
+ {"name": "Paul", "age": 21},
5
+ {"name": "George", "age": 12},
6
+ {"name": "Ringo", "age": 17}
7
+ ],
8
+
9
+ "adults": {"$ref": "$.people[? @.age >= 18 ]"}
10
+ }
11
+
@@ -0,0 +1,14 @@
1
+ class Car
2
+ extend Siren::Node
3
+
4
+ attr_reader :brand
5
+
6
+ def initialize(brand = nil)
7
+ @brand = brand
8
+ end
9
+
10
+ def ==(other)
11
+ @brand == other.brand
12
+ end
13
+ end
14
+
@@ -0,0 +1,7 @@
1
+ [
2
+ {"first": "Thom", "last": "Yorke"},
3
+ {"first": "Jonny", "last": "Greenwood"},
4
+ {"first": "Ed", "last": "O'Brien"},
5
+ {"first": "Colin", "last": "Greenwood"},
6
+ {"first": "Phil", "last": "Selway"}
7
+ ]
@@ -0,0 +1,34 @@
1
+ {
2
+ "people": [
3
+ {
4
+ "type": "person",
5
+ "cars": [
6
+ {
7
+ "type": "car",
8
+ "brand": "ford"
9
+ }
10
+ ]
11
+ },
12
+
13
+ {
14
+ "type": "person",
15
+ "cars": [
16
+ {
17
+ "type": "Car",
18
+ "brand": "ferrari"
19
+ },
20
+
21
+ {
22
+ "type": "car",
23
+ "brand": "bentley"
24
+ },
25
+
26
+ {
27
+ "type": "car",
28
+ "brand": "zonda"
29
+ }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+
@@ -0,0 +1,15 @@
1
+ class Person
2
+ extend Siren::Node
3
+
4
+ attr_reader :id, :cars, :favourite, :handle
5
+
6
+ def initialize(*cars)
7
+ @cars = cars.map { |brand| Car.new(brand) }
8
+ end
9
+
10
+ def ==(other)
11
+ @cars.size == other.cars.size &&
12
+ @cars.all? { |car| other.cars.include?(car) }
13
+ end
14
+ end
15
+
@@ -0,0 +1,26 @@
1
+ [
2
+ {
3
+ "id": "mike",
4
+ "favourite": {"$ref": "mike"}
5
+ },
6
+
7
+ {
8
+ "type": "person",
9
+ "id": "bob",
10
+ "favourite": {"$ref": "bob"}
11
+ },
12
+
13
+ {
14
+ "type": "person",
15
+ "id": "romeo",
16
+ "handle": {"$ref": "@.id"},
17
+ "favourite": {"$ref": "juliet"}
18
+ },
19
+
20
+ {
21
+ "type": "person",
22
+ "id": "juliet",
23
+ "favourite": {"$ref": "romeo"}
24
+ }
25
+ ]
26
+
@@ -0,0 +1,32 @@
1
+ { "store": {
2
+ "book": [
3
+ { "category": "reference",
4
+ "author": "Nigel Rees",
5
+ "title": "Sayings of the Century",
6
+ "price": 8.95
7
+ },
8
+ { "category": "fiction",
9
+ "author": "Evelyn Waugh",
10
+ "title": "Sword of Honour",
11
+ "price": 12.99
12
+ },
13
+ { "category": "fiction",
14
+ "author": "Herman Melville",
15
+ "title": "Moby Dick",
16
+ "isbn": "0-553-21311-3",
17
+ "price": 8.99
18
+ },
19
+ { "category": "fiction",
20
+ "author": "J. R. R. Tolkien",
21
+ "title": "The Lord of the Rings",
22
+ "isbn": "0-395-19395-8",
23
+ "price": 22.99
24
+ }
25
+ ],
26
+ "bicycle": {
27
+ "color": "red",
28
+ "price": 19.95
29
+ }
30
+ }
31
+ }
32
+