mirador 0.1.1 → 0.2.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 +8 -8
- data/lib/mirador.rb +7 -391
- data/lib/mirador/client.rb +120 -0
- data/lib/mirador/error.rb +14 -0
- data/lib/mirador/formatter.rb +101 -0
- data/lib/mirador/processing.rb +80 -0
- data/lib/mirador/result.rb +119 -0
- data/lib/mirador/version.rb +1 -1
- data/test/test_mirador.rb +29 -63
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
ZDU2NmFkMWU5MjZhZGUwNWFmYWE5OWQyZjExZTI3YzhjOWNiODk2Mg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NTI5MzMwZjhiZDAwYmVkMzJkYjZlODE5MjhmYTUwMDI0ZTBmYjA5Nw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZDkzNWZhNzRhYjBmNGU0MWI2NjJmZTljZmMyYzRmMjM0ZWUxYjA4M2M4MGI2
|
10
|
+
NjhlODljNjg1NDNmNTQ2MjVkZDQxMGQ0NDY5MWZiZjUxZTMxYzhmOTg2MGMz
|
11
|
+
YzRjNmZlZGEyYzI3MTJmZDYwMjA3OTg0MzgyNGNiYTk5MGE2NWY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZjFkYzQ2YmI1MjBkMTJhNGM3Y2IyZjM5MWU3YzhmOWIwODBjZDQwZGMzNDkw
|
14
|
+
YTNkZGExOGQ3Y2YzMjljOThkNDBlYTBjZmJlNmIzNjM2NDk4OTE3ZGNkMmY3
|
15
|
+
NDA2OGRmMzc3YTBhNDhmYjZiMzg1YTdhNTFmMWNhZTBlOWZkNDg=
|
data/lib/mirador.rb
CHANGED
@@ -1,396 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'mirador/result'
|
2
|
+
require 'mirador/error'
|
3
|
+
require 'mirador/processing'
|
4
|
+
require 'mirador/formatter'
|
5
|
+
require 'mirador/client'
|
3
6
|
|
4
7
|
module Mirador
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
class ResultList
|
10
|
-
include Enumerable
|
11
|
-
|
12
|
-
def initialize(items=[])
|
13
|
-
@items = {}
|
14
|
-
|
15
|
-
items.each do |x|
|
16
|
-
@items[x.id] = x
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def <<(item)
|
21
|
-
@items[item.id] = item
|
22
|
-
end
|
23
|
-
|
24
|
-
def [](key)
|
25
|
-
if key.is_a? Integer and not @items.has_key? key
|
26
|
-
@items.values[key]
|
27
|
-
else
|
28
|
-
@items[key.to_s]
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_a
|
33
|
-
@items.values
|
34
|
-
end
|
35
|
-
|
36
|
-
def length
|
37
|
-
@items.values.length
|
38
|
-
end
|
39
|
-
|
40
|
-
def update other
|
41
|
-
@items.update(other)
|
42
|
-
end
|
43
|
-
|
44
|
-
def to_h
|
45
|
-
@items
|
46
|
-
end
|
47
|
-
|
48
|
-
def to_json
|
49
|
-
@items.to_json
|
50
|
-
end
|
51
|
-
|
52
|
-
def each &block
|
53
|
-
if block.arity == 1
|
54
|
-
@items.values.each do |x|
|
55
|
-
block.call(x)
|
56
|
-
end
|
57
|
-
else
|
58
|
-
@items.each do |k, v|
|
59
|
-
block.call(k, v)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.parse_results res
|
65
|
-
|
66
|
-
output = {}
|
67
|
-
res.each do |x|
|
68
|
-
r = Result.new(x)
|
69
|
-
output[r.id] = r
|
70
|
-
end
|
71
|
-
|
72
|
-
output
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
class Result
|
78
|
-
attr_accessor :id, :safe, :value, :error
|
79
|
-
|
80
|
-
def initialize data
|
81
|
-
|
82
|
-
if data.has_key? 'errors'
|
83
|
-
@error = data['errors']
|
84
|
-
return
|
85
|
-
end
|
86
|
-
|
87
|
-
@id = data['id']
|
88
|
-
@safe = data['result']['safe']
|
89
|
-
@value = data['result']['value']
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
def to_h
|
94
|
-
{
|
95
|
-
id: @id,
|
96
|
-
safe: @safe,
|
97
|
-
value: @value,
|
98
|
-
}
|
99
|
-
end
|
100
|
-
|
101
|
-
def to_json
|
102
|
-
as_h = self.to_h
|
103
|
-
|
104
|
-
if as_h.respond_to? :to_json
|
105
|
-
as_h.to_json
|
106
|
-
else
|
107
|
-
nil
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def failed?
|
112
|
-
@error != nil
|
113
|
-
end
|
114
|
-
|
115
|
-
def to_s
|
116
|
-
"<Mirador::Result; id: #{ @id }; safe: #{ @safe }; value: #{ @value }/>"
|
117
|
-
end
|
118
|
-
|
119
|
-
def name
|
120
|
-
@id
|
121
|
-
end
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
class Client
|
126
|
-
include HTTParty
|
127
|
-
base_uri 'api.mirador.im'
|
128
|
-
|
129
|
-
default_timeout 20
|
130
|
-
|
131
|
-
MAX_LEN = 4
|
132
|
-
MAX_ID_LEN = 256
|
133
|
-
DATA_URI_PRE = ';base64,'
|
134
|
-
DATA_URI_PRELEN = 8
|
135
|
-
|
136
|
-
def initialize(api_key)
|
137
|
-
@options = { api_key: api_key }
|
138
|
-
|
139
|
-
if block_given?
|
140
|
-
@parser = Proc.new
|
141
|
-
else
|
142
|
-
@parser = nil
|
143
|
-
end
|
144
|
-
|
145
|
-
end
|
146
|
-
|
147
|
-
# metaprogramming extreme
|
148
|
-
[:url, :file, :buffer, :encoded_string, :data_uri].each do |datatype|
|
149
|
-
define_method("classify_#{datatype.to_s}s") do |args, params={}, &block|
|
150
|
-
|
151
|
-
if block != nil
|
152
|
-
old_parser = @parser
|
153
|
-
@parser = block
|
154
|
-
end
|
155
|
-
|
156
|
-
|
157
|
-
res = flexible_request args, params do |item|
|
158
|
-
fmt_items(datatype, item)
|
159
|
-
end
|
160
|
-
|
161
|
-
if block != nil
|
162
|
-
@parser = old_parser
|
163
|
-
end
|
164
|
-
|
165
|
-
|
166
|
-
res
|
167
|
-
end
|
168
|
-
|
169
|
-
define_method("classify_#{datatype.to_s}") do |args, params={}, &block|
|
170
|
-
|
171
|
-
if block != nil
|
172
|
-
old_parser = @parser
|
173
|
-
@parser = block
|
174
|
-
end
|
175
|
-
|
176
|
-
res = self.send("classify_#{datatype.to_s}s", args, params)
|
177
|
-
|
178
|
-
if @parser
|
179
|
-
out = if res != nil then res.values()[0] else nil end
|
180
|
-
else
|
181
|
-
out = if res != nil then res[0] else nil end
|
182
|
-
end
|
183
|
-
|
184
|
-
if block != nil
|
185
|
-
@parser = old_parser
|
186
|
-
end
|
187
|
-
|
188
|
-
out
|
189
|
-
end
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
protected
|
194
|
-
|
195
|
-
def flexible_request(args, params={}, &cb)
|
196
|
-
req = {}
|
197
|
-
|
198
|
-
req = (if args.is_a? Hash
|
199
|
-
|
200
|
-
Hash[args.map do |k, v|
|
201
|
-
process_param(k, v)
|
202
|
-
end]
|
203
|
-
|
204
|
-
elsif args.is_a? String
|
205
|
-
Hash[[process_argument(args)]]
|
206
|
-
|
207
|
-
elsif args and args.length
|
208
|
-
Hash[args.each_with_index.map do |a, idx|
|
209
|
-
process_argument(a, idx)
|
210
|
-
end]
|
211
|
-
|
212
|
-
elsif params
|
213
|
-
Hash[params.map do |k, v|
|
214
|
-
process_param(k, v)
|
215
|
-
end]
|
216
|
-
end)
|
217
|
-
|
218
|
-
chunked_request(req) do |item|
|
219
|
-
formatted = cb.call(item)
|
220
|
-
make_request(formatted)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def process_argument arg, idx=0
|
225
|
-
|
226
|
-
if arg.is_a?(String)
|
227
|
-
if arg.length < MAX_ID_LEN
|
228
|
-
[arg, arg]
|
229
|
-
else
|
230
|
-
[idx, arg]
|
231
|
-
end
|
232
|
-
|
233
|
-
elsif arg.respond_to?(:name) and arg.respond_to?(:read)
|
234
|
-
|
235
|
-
[arg.name, arg]
|
236
|
-
|
237
|
-
elsif arg.respond_to?(:id) and arg.respond_to?(:data)
|
238
|
-
|
239
|
-
[arg.id, arg.data]
|
240
|
-
|
241
|
-
elsif arg.is_a?(Hash)
|
242
|
-
|
243
|
-
if arg.has_key? :id and arg.has_key? :data
|
244
|
-
[arg[:id], arg[:data]]
|
245
|
-
elsif arg.has_key? 'id' and arg.has_key? 'data'
|
246
|
-
[arg['id'], arg['data']]
|
247
|
-
end
|
248
|
-
|
249
|
-
else
|
250
|
-
raise ApiError, "Invalid argument: #{ arg }"
|
251
|
-
end
|
252
|
-
|
253
|
-
end
|
254
|
-
|
255
|
-
# given a parameter passed in,
|
256
|
-
# assuming that its a id => data mapping, return
|
257
|
-
# the correct formatting/check for any fuck ups
|
258
|
-
# @arguments:
|
259
|
-
# k - key
|
260
|
-
# v - value
|
261
|
-
# @returns:
|
262
|
-
# { k => v } pair
|
263
|
-
def process_param k, v
|
264
|
-
|
265
|
-
if v.is_a?(File)
|
266
|
-
[ k, v.read ]
|
267
|
-
elsif k.respond_to?(:to_s) and v.is_a?(String)
|
268
|
-
[ k.to_s, v ]
|
269
|
-
else
|
270
|
-
raise ApiError, "Invalid Argument: #{ k } => #{ v }"
|
271
|
-
end
|
272
|
-
|
273
|
-
end
|
274
|
-
|
275
|
-
# given a request and a block,
|
276
|
-
# call the block X number of times
|
277
|
-
# where X is request.length / MAX_LEN
|
278
|
-
def chunked_request req, &mthd
|
279
|
-
|
280
|
-
if @parser != nil
|
281
|
-
output = {}
|
282
|
-
else
|
283
|
-
output = ResultList.new
|
284
|
-
end
|
285
|
-
|
286
|
-
req.each_slice(MAX_LEN).each do |slice|
|
287
|
-
output.update(mthd.call(slice))
|
288
|
-
end
|
289
|
-
|
290
|
-
return output
|
291
|
-
end
|
292
|
-
|
293
|
-
# basically, transform hash h into a hash
|
294
|
-
# where the key-value pairs are all formatted
|
295
|
-
# by 'fmt-item' (should double the number of key-value
|
296
|
-
# pairs in the hash)
|
297
|
-
def fmt_items name, h
|
298
|
-
out = {}
|
299
|
-
h.each_with_index do |kv, idx|
|
300
|
-
out.update fmt_item(name, idx, kv[0], kv[1])
|
301
|
-
end
|
302
|
-
return out
|
303
|
-
end
|
304
|
-
|
305
|
-
@@name_map = {
|
306
|
-
file: 'image',
|
307
|
-
buffer: 'image',
|
308
|
-
raw: 'image',
|
309
|
-
url: 'url',
|
310
|
-
encoded_string: 'image',
|
311
|
-
data_uri: 'image',
|
312
|
-
}
|
313
|
-
|
314
|
-
@@formatters = {
|
315
|
-
url: Proc.new { |url| url },
|
316
|
-
|
317
|
-
file: Proc.new { |file|
|
318
|
-
|
319
|
-
Base64.encode64(if file.respond_to? :read
|
320
|
-
file.read
|
321
|
-
else
|
322
|
-
File.read(file)
|
323
|
-
end).gsub(/\n/, '')
|
324
|
-
|
325
|
-
},
|
326
|
-
|
327
|
-
buffer: Proc.new { |file|
|
328
|
-
|
329
|
-
Base64.encode64(file).gsub(/\n/, '')
|
330
|
-
|
331
|
-
},
|
332
|
-
|
333
|
-
raw: Proc.new { |file|
|
334
|
-
|
335
|
-
Base64.encode64(file).gsub(/\n/, '')
|
336
|
-
|
337
|
-
},
|
338
|
-
|
339
|
-
encoded_string: Proc.new { |b64str|
|
340
|
-
b64str.gsub(/\n/, '')
|
341
|
-
},
|
342
|
-
|
343
|
-
data_uri: Proc.new { |datauri|
|
344
|
-
datauri.sub(/^.+;base64,/, '').gsub(/\n/,'')
|
345
|
-
},
|
346
|
-
|
347
|
-
}
|
348
|
-
|
349
|
-
# produce a k-v mapping internal to the API,
|
350
|
-
# so that 'name' is the datatype:
|
351
|
-
# e.g., name[idx][id], name[idx][data]
|
352
|
-
def fmt_item name, idx, id, data
|
353
|
-
formatted = @@formatters[name].call(data)
|
354
|
-
datatype = @@name_map[name]
|
355
|
-
{
|
356
|
-
"#{datatype}[#{idx}][id]" => id,
|
357
|
-
"#{datatype}[#{idx}][data]" => formatted,
|
358
|
-
}
|
359
|
-
end
|
360
|
-
|
361
|
-
# base method to actually make the request
|
362
|
-
def make_request params
|
363
|
-
|
364
|
-
res = self.class.post(
|
365
|
-
"/v1/classify",
|
366
|
-
{
|
367
|
-
body: @options.merge(params),
|
368
|
-
headers: {"User-Agent" => "Mirador Client v1.0/Ruby"}
|
369
|
-
}
|
370
|
-
)
|
371
|
-
|
372
|
-
k = 'results'
|
373
|
-
|
374
|
-
if res['errors']
|
375
|
-
|
376
|
-
if not res['result']
|
377
|
-
raise ApiError, res
|
378
|
-
else
|
379
|
-
k = 'result'
|
380
|
-
end
|
381
|
-
|
382
|
-
elsif not res
|
383
|
-
raise ApiError, "no response: #{ res.code }"
|
384
|
-
end
|
385
|
-
|
386
|
-
if @parser != nil
|
387
|
-
return @parser.call(res[k])
|
388
|
-
else
|
389
|
-
|
390
|
-
return ResultList.parse_results res[k]
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
end
|
9
|
+
# do we really want anything in here?
|
10
|
+
# TODO: figure out
|
395
11
|
|
396
12
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Mirador
|
4
|
+
|
5
|
+
class Client
|
6
|
+
include HTTParty
|
7
|
+
include Processing
|
8
|
+
include Formatting
|
9
|
+
|
10
|
+
# the number of items
|
11
|
+
# to actuall send in a request
|
12
|
+
CHUNK_SIZE = 4
|
13
|
+
|
14
|
+
base_uri 'api.mirador.im'
|
15
|
+
default_timeout 20
|
16
|
+
|
17
|
+
format_map(
|
18
|
+
url: :url,
|
19
|
+
default: :image,
|
20
|
+
)
|
21
|
+
|
22
|
+
max_key_size 369
|
23
|
+
|
24
|
+
def initialize(api_key, opt={})
|
25
|
+
raise AuthenticationError.new("api key required") if not api_key
|
26
|
+
|
27
|
+
@options = { api_key: api_key }
|
28
|
+
@parser = opt[:parser] || ResultList
|
29
|
+
@chunk_size = opt[:chunk_size] || CHUNK_SIZE
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
[:url, :file, :buffer, :encoded_string, :data_uri].each do |datatype|
|
34
|
+
|
35
|
+
define_method("classify_#{datatype.to_s}s") do |args, params={}|
|
36
|
+
flexible_request args, params do |item|
|
37
|
+
format_items(datatype, item)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method("classify_#{datatype.to_s}") do |args, params={}|
|
42
|
+
res = self.send("classify_#{datatype.to_s}s", args, params)
|
43
|
+
res and res[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def flexible_request(args, params={}, &cb)
|
51
|
+
req = (if args.is_a? Hash
|
52
|
+
|
53
|
+
Hash[args.map do |k, v|
|
54
|
+
process_param(k, v)
|
55
|
+
end]
|
56
|
+
|
57
|
+
elsif args.is_a? String
|
58
|
+
Hash[[process_argument(args)]]
|
59
|
+
|
60
|
+
elsif args and args.length
|
61
|
+
Hash[args.each_with_index.map do |a, idx|
|
62
|
+
process_argument(a, idx)
|
63
|
+
end]
|
64
|
+
|
65
|
+
elsif params
|
66
|
+
Hash[params.map do |k, v|
|
67
|
+
process_param(k, v)
|
68
|
+
end]
|
69
|
+
end)
|
70
|
+
|
71
|
+
chunked_request(req) do |item|
|
72
|
+
make_request(cb.call(item))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# given a request and a block,
|
78
|
+
# call the block X number of times
|
79
|
+
# where X is request.length / MAX_LEN
|
80
|
+
def chunked_request req, &mthd
|
81
|
+
output = @parser.new
|
82
|
+
|
83
|
+
req.each_slice(@chunk_size).each do |slice|
|
84
|
+
output.update(mthd.call(slice))
|
85
|
+
end
|
86
|
+
|
87
|
+
return output
|
88
|
+
end
|
89
|
+
|
90
|
+
# base method to actually make the request
|
91
|
+
def make_request params
|
92
|
+
|
93
|
+
res = self.class.post(
|
94
|
+
"/v1/classify",
|
95
|
+
{
|
96
|
+
body: @options.merge(params),
|
97
|
+
headers: {"User-Agent" => "Mirador Client v1.0/Ruby"}
|
98
|
+
}
|
99
|
+
)
|
100
|
+
|
101
|
+
k = 'results'
|
102
|
+
|
103
|
+
if res['errors']
|
104
|
+
|
105
|
+
if not res['result']
|
106
|
+
raise ApiError, res
|
107
|
+
else
|
108
|
+
k = 'result'
|
109
|
+
end
|
110
|
+
|
111
|
+
elsif not res
|
112
|
+
raise ApiError, "no response: #{ res.code }"
|
113
|
+
end
|
114
|
+
|
115
|
+
return @parser.parse_results(res[k])
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Mirador
|
4
|
+
|
5
|
+
module Formatting
|
6
|
+
|
7
|
+
@@format_map = {}
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
protected
|
11
|
+
|
12
|
+
def format_map map={}
|
13
|
+
|
14
|
+
# get the default from the map, or use 'image',
|
15
|
+
# which is most likely correct..
|
16
|
+
default = (map.delete(:default) || :image)
|
17
|
+
|
18
|
+
map.default_proc = Proc.new { |h, k|
|
19
|
+
h[k] = default
|
20
|
+
}
|
21
|
+
|
22
|
+
Formatting.class_variable_set(:@@format_map, map)
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.included(base)
|
29
|
+
base.extend(ClassMethods)
|
30
|
+
Formatting.class_variable_set(:@@formatter, Formatter.new)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def format_items dtype, items
|
36
|
+
Hash[items.each_with_index.map do |kv, idx|
|
37
|
+
format_item(dtype, idx, kv[0], kv[1])
|
38
|
+
end.flatten(1)]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def format_item dtype, idx, id, data
|
44
|
+
formatted, dt = get_format(data, dtype.to_sym)
|
45
|
+
|
46
|
+
[
|
47
|
+
["#{ dt }[#{ idx }][id]", id],
|
48
|
+
["#{ dt }[#{ idx }][data]", formatted],
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_format item, dtype
|
53
|
+
dtype = dtype.to_sym
|
54
|
+
|
55
|
+
if not @@formatter.respond_to? dtype
|
56
|
+
raise ApiError, "unsupported datatype: #{ dtype }"
|
57
|
+
end
|
58
|
+
|
59
|
+
return @@formatter.send(dtype, item), @@format_map[dtype]
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
class Formatter
|
65
|
+
DATA_URI_RXP = /^.+;base64,/
|
66
|
+
|
67
|
+
def url item
|
68
|
+
item
|
69
|
+
end
|
70
|
+
|
71
|
+
def buffer item
|
72
|
+
encode_filedata(item)
|
73
|
+
end
|
74
|
+
|
75
|
+
def file item
|
76
|
+
encode_filedata(
|
77
|
+
if item.respond_to? :read
|
78
|
+
item.read
|
79
|
+
else
|
80
|
+
File.read(item)
|
81
|
+
end
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def encoded_string item
|
86
|
+
item.gsub(/\n/, '')
|
87
|
+
end
|
88
|
+
|
89
|
+
def data_uri item
|
90
|
+
item.sub(DATA_URI_RXP, '').gsub(/\n/,'')
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def encode_filedata(data)
|
96
|
+
Base64.encode64(data).gsub(/\n/, '')
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Mirador
|
2
|
+
|
3
|
+
module Processing
|
4
|
+
|
5
|
+
MAX_KEY_SIZE = 256
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
attr_accessor :_max_key_size
|
9
|
+
|
10
|
+
def max_key_size num
|
11
|
+
Processing.class_variable_set(:@@max_key_size, num)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(ClassMethods)
|
18
|
+
Processing.class_variable_set(:@@max_key_size, MAX_KEY_SIZE)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
# given an argument, e.g.,
|
24
|
+
# an item in a *args list,
|
25
|
+
# return the proper datatype-pair
|
26
|
+
# (to be put into a Hash)
|
27
|
+
def process_argument arg, idx=0
|
28
|
+
|
29
|
+
if arg.is_a?(String)
|
30
|
+
if arg.length < @@max_key_size
|
31
|
+
[arg, arg]
|
32
|
+
else
|
33
|
+
[idx, arg]
|
34
|
+
end
|
35
|
+
|
36
|
+
elsif arg.respond_to?(:name) and arg.respond_to?(:read)
|
37
|
+
|
38
|
+
[arg.name, arg]
|
39
|
+
|
40
|
+
elsif arg.respond_to?(:id) and arg.respond_to?(:data)
|
41
|
+
|
42
|
+
[arg.id, arg.data]
|
43
|
+
|
44
|
+
elsif arg.is_a?(Hash)
|
45
|
+
|
46
|
+
if arg.has_key? :id and arg.has_key? :data
|
47
|
+
[arg[:id], arg[:data]]
|
48
|
+
elsif arg.has_key? 'id' and arg.has_key? 'data'
|
49
|
+
[arg['id'], arg['data']]
|
50
|
+
end
|
51
|
+
|
52
|
+
else
|
53
|
+
raise ApiError, "Invalid argument: #{ arg }"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# given a parameter passed in,
|
59
|
+
# assuming that its a id => data mapping, return
|
60
|
+
# the correct formatting/check for any fuck ups
|
61
|
+
# @arguments:
|
62
|
+
# k - key
|
63
|
+
# v - value
|
64
|
+
# @returns:
|
65
|
+
# { k => v } pair
|
66
|
+
def process_param k, v
|
67
|
+
|
68
|
+
if v.is_a?(File)
|
69
|
+
[ k, v.read ]
|
70
|
+
elsif k.respond_to?(:to_s) and v.is_a?(String)
|
71
|
+
[ k.to_s, v ]
|
72
|
+
else
|
73
|
+
raise ApiError, "Invalid Argument: #{ k } => #{ v }"
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Mirador
|
2
|
+
|
3
|
+
class ResultList
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(items=[])
|
7
|
+
@items = {}
|
8
|
+
|
9
|
+
items.each do |x|
|
10
|
+
@items[x.id] = x
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(item)
|
15
|
+
@items[item.id] = item
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
if key.is_a? Integer and not @items.has_key? key
|
20
|
+
@items.values[key]
|
21
|
+
else
|
22
|
+
@items[key.to_s]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_a
|
27
|
+
@items.values
|
28
|
+
end
|
29
|
+
|
30
|
+
def length
|
31
|
+
@items.values.length
|
32
|
+
end
|
33
|
+
|
34
|
+
def update other
|
35
|
+
@items.update(other)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_h
|
39
|
+
@items
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_json
|
43
|
+
@items.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
def each &block
|
47
|
+
if block.arity == 1
|
48
|
+
@items.values.each do |x|
|
49
|
+
block.call(x)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
@items.each do |k, v|
|
53
|
+
block.call(k, v)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.parse_results res
|
59
|
+
|
60
|
+
output = {}
|
61
|
+
res.each do |x|
|
62
|
+
r = Result.new(x)
|
63
|
+
output[r.id] = r
|
64
|
+
end
|
65
|
+
|
66
|
+
output
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
class Result
|
72
|
+
attr_accessor :id, :safe, :value, :error
|
73
|
+
|
74
|
+
def initialize data
|
75
|
+
|
76
|
+
if data.has_key? 'errors'
|
77
|
+
@error = data['errors']
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
@id = data['id']
|
82
|
+
@safe = data['result']['safe']
|
83
|
+
@value = data['result']['value']
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_h
|
88
|
+
{
|
89
|
+
id: @id,
|
90
|
+
safe: @safe,
|
91
|
+
value: @value,
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_json
|
96
|
+
as_h = self.to_h
|
97
|
+
|
98
|
+
if as_h.respond_to? :to_json
|
99
|
+
as_h.to_json
|
100
|
+
else
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def failed?
|
106
|
+
@error != nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_s
|
110
|
+
"<Mirador::Result; id: #{ @id }; safe: #{ @safe }; value: #{ @value }/>"
|
111
|
+
end
|
112
|
+
|
113
|
+
def name
|
114
|
+
@id
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
data/lib/mirador/version.rb
CHANGED
data/test/test_mirador.rb
CHANGED
@@ -2,6 +2,29 @@ require 'test/unit'
|
|
2
2
|
require './lib/mirador'
|
3
3
|
require 'base64'
|
4
4
|
|
5
|
+
class CustomTestParser
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def parse_results res_hash
|
10
|
+
# essentially a no-op
|
11
|
+
res_hash
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :results
|
16
|
+
|
17
|
+
def update partials
|
18
|
+
@results ||= []
|
19
|
+
@results += partials
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](x)
|
23
|
+
return (@results ||= [])[x]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
5
28
|
class MiradorTest < Test::Unit::TestCase
|
6
29
|
|
7
30
|
dirname = File.dirname(__FILE__)
|
@@ -72,6 +95,12 @@ class MiradorTest < Test::Unit::TestCase
|
|
72
95
|
|
73
96
|
end
|
74
97
|
|
98
|
+
def test_custom_parser
|
99
|
+
mc = Mirador::Client.new(ENV['MIRADOR_API_KEY'], parser: CustomTestParser)
|
100
|
+
res = mc.classify_url(NSFW_URL)
|
101
|
+
assert res.is_a?(Hash)
|
102
|
+
end
|
103
|
+
|
75
104
|
def test_hash_call
|
76
105
|
|
77
106
|
res = MM.classify_urls(nsfw: NSFW_URL, sfw: SFW_URL)
|
@@ -159,40 +188,6 @@ class MiradorTest < Test::Unit::TestCase
|
|
159
188
|
|
160
189
|
end
|
161
190
|
|
162
|
-
def test_switch_parser
|
163
|
-
|
164
|
-
res = MM.classify_url 'http://static.mirador.im/test/nsfw.jpg' do |results|
|
165
|
-
|
166
|
-
r = Hash[results.map do |x|
|
167
|
-
[x['id'], x['result']]
|
168
|
-
end]
|
169
|
-
|
170
|
-
r
|
171
|
-
end
|
172
|
-
|
173
|
-
assert res.is_a?(Hash)
|
174
|
-
|
175
|
-
res_norm = MM.classify_url 'http://static.mirador.im/test/sfw.jpg'
|
176
|
-
|
177
|
-
assert (not res_norm.is_a?(Hash))
|
178
|
-
|
179
|
-
end
|
180
|
-
|
181
|
-
def test_multiple_custom_parse
|
182
|
-
|
183
|
-
res = MM.classify_urls('http://static.mirador.im/test/nsfw.jpg', 'http://static.mirador.im/test/sfw.jpg') do |results|
|
184
|
-
|
185
|
-
r = Hash[results.map do |x|
|
186
|
-
[x['id'], x['result']]
|
187
|
-
end]
|
188
|
-
|
189
|
-
r
|
190
|
-
end
|
191
|
-
|
192
|
-
assert res.is_a?(Hash)
|
193
|
-
assert res.has_key?('http://static.mirador.im/test/nsfw.jpg')
|
194
|
-
|
195
|
-
end
|
196
191
|
|
197
192
|
def test_item_error
|
198
193
|
|
@@ -205,33 +200,4 @@ class MiradorTest < Test::Unit::TestCase
|
|
205
200
|
|
206
201
|
end
|
207
202
|
|
208
|
-
def test_custom_parser
|
209
|
-
|
210
|
-
mc = Mirador::Client.new ENV['MIRADOR_API_KEY'] do |results|
|
211
|
-
|
212
|
-
res = Hash[results.map do |x|
|
213
|
-
r = x['result']
|
214
|
-
|
215
|
-
[x['id'], {
|
216
|
-
id: x['id'],
|
217
|
-
breast: r['breast4'],
|
218
|
-
penis: r['penis4'],
|
219
|
-
vagina: r['vagina4'],
|
220
|
-
butt: r['butt4'],
|
221
|
-
}]
|
222
|
-
|
223
|
-
end]
|
224
|
-
|
225
|
-
res
|
226
|
-
end
|
227
|
-
|
228
|
-
res = mc.classify_url 'http://static.mirador.im/test/nsfw.jpg'
|
229
|
-
|
230
|
-
assert res.is_a?(Hash)
|
231
|
-
|
232
|
-
assert res[:breast]
|
233
|
-
assert res[:penis]
|
234
|
-
|
235
|
-
end
|
236
|
-
|
237
203
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mirador
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Jacob
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -46,6 +46,11 @@ files:
|
|
46
46
|
- bin/mirador-client
|
47
47
|
- examples/sinatra-example.rb
|
48
48
|
- lib/mirador.rb
|
49
|
+
- lib/mirador/client.rb
|
50
|
+
- lib/mirador/error.rb
|
51
|
+
- lib/mirador/formatter.rb
|
52
|
+
- lib/mirador/processing.rb
|
53
|
+
- lib/mirador/result.rb
|
49
54
|
- lib/mirador/version.rb
|
50
55
|
- mirador.gemspec
|
51
56
|
- test/images/nsfw.jpg
|