xoopit-cloud_query 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,7 +1,7 @@
1
- cloudquery
1
+ CloudQuery
2
2
  ==========
3
3
 
4
- Client for Xoopit's cloudquery API
4
+ Client for Xoopit's CloudQuery API
5
5
 
6
6
  Install
7
7
  -------
@@ -11,21 +11,21 @@ care of it. If not, `sudo gem install json rack taf2-curb` will do it.
11
11
 
12
12
  Be sure you've run `gem sources -a http://gems.github.com` once on your system. Then:
13
13
 
14
- sudo gem install xoopit-cloudquery
14
+ sudo gem install xoopit-cloudquery-ruby
15
15
 
16
16
  Simple contacts application example
17
17
  -----------------------------------
18
18
 
19
19
  > require 'rubygems'
20
20
  => true
21
- > require 'cloudquery'
21
+ > require 'cloud_query'
22
22
  => true
23
- > include Cloudquery
23
+ > include CloudQuery
24
24
  => Object
25
25
  > secret = Client.get_secret(<account_name>, <password>)
26
26
  => "your secret appears here"
27
27
  > c = Client.new(:account => '<account_name>', :secret => secret)
28
- => #<Cloudquery::Client:0x10b1b24 @secure=true, @secret="your secret appears here", @account="<account_name>", @document_id_method=nil>
28
+ => #<CloudQuery::Client:0x10b1b24 @secure=true, @secret="your secret appears here", @account="<account_name>", @document_id_method=nil>
29
29
  > c.add_indexes('superheroes')
30
30
  => {"result"=>["kMzzzybpqpY"], "size"=>1, "STATUS"=>200}
31
31
  > c.add_schema(File.open('simple.contact.xml'))
@@ -35,10 +35,10 @@ Simple contacts application example
35
35
  'simple.contact.email' => ['steve.rogers@example.com','captain.america@marvel.com'],
36
36
  'simple.contact.telephone' => ['555-555-5555','123-456-6789'],
37
37
  'simple.contact.address' => ['Lower East Side, NY NY'],
38
- 'simple.contact.birthday' => Date.parse('July 4, 1917'),
38
+ 'simple.contact.birthday' => Time.parse('July 4, 1917').to_i_milliseconds,
39
39
  'simple.contact.note' => 'Captain America!',
40
40
  }
41
- => {"simple.contact.birthday"=>#<Date: 4842827/2,0,2299161>, "simple.contact.address"=>["Lower East Side, NY NY"], "simple.contact.telephone"=>["555-555-5555", "123-456-6789"], "simple.contact.note"=>"Captain America!", "simple.contact.email"=>["steve.rogers@example.com", "captain.america@marvel.com"], "simple.contact.name"=>"Steve Rogers"}
41
+ => {"simple.contact.birthday"=>-1656604800000, "simple.contact.address"=>["Lower East Side, NY NY"], "simple.contact.telephone"=>["555-555-5555", "123-456-6789"], "simple.contact.note"=>"Captain America!", "simple.contact.email"=>["steve.rogers@example.com", "captain.america@marvel.com"], "simple.contact.name"=>"Steve Rogers"}
42
42
  > c.add_documents('superheroes', doc, 'simple.contact')
43
43
  => {"result"=>["nDLCNLPo3oHtxANzG4YBn5kMzzzybpqpY"], "size"=>1, "STATUS"=>201}
44
44
  > docs = [
@@ -47,7 +47,7 @@ Simple contacts application example
47
47
  'simple.contact.email' => ['clark.kent@example.com','superman@dc.com'],
48
48
  'simple.contact.telephone' => ['555-123-1234','555-456-6789'],
49
49
  'simple.contact.address' => ['344 Clinton St., Apt. #3B, Metropolis', 'The Fortess of Solitude, North Pole'],
50
- 'simple.contact.birthday' => Date.parse('June 18, 1938'),
50
+ 'simple.contact.birthday' => Time.parse('June 18, 1938').to_i_milliseconds,
51
51
  'simple.contact.note' => 'Superhuman strength, speed, stamina, durability, senses, intelligence, regeneration, and longevity; super breath, heat vision, x-ray vision and flight. Member of the justice league.'
52
52
  },
53
53
  {
@@ -55,7 +55,7 @@ Simple contacts application example
55
55
  'simple.contact.email' => ['bruce.wayne@example.com','batman@dc.com'],
56
56
  'simple.contact.telephone' => ['555-123-6666','555-456-6666'],
57
57
  'simple.contact.address' => ['1007 Mountain Drive, Gotham', 'The Batcave, Gotham'],
58
- 'simple.contact.birthday' => Date.parse('February 19, 1939'),
58
+ 'simple.contact.birthday' => Time.parse('February 19, 1939').to_i_milliseconds,
59
59
  'simple.contact.note' => 'Sidekick is Robin. Has problems with the Joker. Member of e justice league.'
60
60
  }
61
61
  ]
@@ -0,0 +1,370 @@
1
+ module CloudQuery
2
+ class Client
3
+ attr_reader :account
4
+ attr_writer :secret
5
+
6
+ # Create a new instance of the client
7
+ #
8
+ # It's highly recommended to set options <tt>:account</tt>
9
+ # and <tt>:secret</tt>. Creating a client without an account
10
+ # and secret isn't very useful.
11
+ #
12
+ # ==== Acceptable options:
13
+ # :account => <account name> (default => nil)
14
+ # :secret => <API secret> (default => nil)
15
+ # :document_id_method => <method name> (default => nil)
16
+ # :secure => Boolean (use HTTPS, default => true)
17
+ #
18
+ # If +document_id_method+ is set, it will be called on each
19
+ # document as a part of +add_documents+ and +update_documents+
20
+ # which should inject an <tt>'#.#'</tt> key-value pair as a
21
+ # simple way to tie app PKs to doc ids.
22
+ def initialize(options={})
23
+ # unless options[:account] && options[:secret]
24
+ # raise "Client requires :account => <account name> and :secret => <secret>"
25
+ # end
26
+
27
+ @account = options[:account]
28
+ @secret = options[:secret]
29
+
30
+ @secure = options[:secure] != false # must pass false for insecure
31
+
32
+ @document_id_method = options[:document_id_method]
33
+ end
34
+
35
+
36
+ ## Account management
37
+
38
+ # Retrieve the API secret for an +account+, using the +password+ (uses HTTPS)
39
+ def self.get_secret(account, password)
40
+ auth = Request.new(:path => "#{PATH}/auth")
41
+ curl = Curl::Easy.new(auth.url) do |c|
42
+ c.enable_cookies = true
43
+ c.cookiejar = COOKIE_JAR
44
+ end
45
+ params = Rack::Utils.build_query({"name" => account, "password" => password})
46
+ curl.http_post(params)
47
+
48
+ if (curl.response_code/100) == 2
49
+ curl.url = Request.new(:path => "#{PATH}/#{API_PATHS[:account]}/#{account}").url
50
+ curl.http_get
51
+ response = JSON.parse(curl.body_str)
52
+ response['result']['secret']
53
+ else
54
+ STDERR.puts "Error: #{curl.response_code} #{Rack::Utils::HTTP_STATUS_CODES[curl.response_code]}"
55
+ end
56
+ end
57
+
58
+ # change password
59
+ # optionally change the secret
60
+ def self.change_password(account, old_password, new_password, new_secret=nil)
61
+ secret = get_secret(account, old_password)
62
+ c = Client.new(:account => account, :secret => secret, :secure => true)
63
+ a = c.get_account()['result']
64
+ a['password'] = new_password
65
+ a.delete('secret')
66
+ if new_secret != nil
67
+ a['secret'] = new_secret
68
+ end
69
+ c.update_account(a)
70
+ return (new_secret != nil ? new_secret : secret);
71
+ end
72
+
73
+ # Get the account document
74
+ def get_account
75
+ send_request get(account_path)
76
+ end
77
+
78
+ # Update the account document.
79
+ #
80
+ # Use this method to change the API secret:
81
+ # update_account({'secret' => 'your-new-secret'})
82
+ def update_account(account_doc={})
83
+ body = JSON.generate(account_doc)
84
+ send_request put(account_path, body)
85
+ end
86
+
87
+ # Delete the account.
88
+ #
89
+ # ==== BEWARE: THIS WILL ACTUALLY DELETE YOUR ACCOUNT.
90
+ def delete_account!
91
+ send_request delete(account_path)
92
+ end
93
+
94
+
95
+ ## Schema management
96
+
97
+ # Add a schema to the account. +xml+ can be a +String+
98
+ # or +File+-like (responds to <tt>:read</tt>)
99
+ def add_schema(xml)
100
+ body = xml.respond_to?(:read) ? xml.read : xml
101
+ request = post(build_path(API_PATHS[:schema]), body)
102
+ send_request(request, CONTENT_TYPES[:xml])
103
+ end
104
+
105
+ # Delete a schema from the account, by name
106
+ # if cascade is true, all documents with the specified schema will be deleted
107
+ # (use with care)
108
+ def delete_schema(schema_name, cascade=nil)
109
+ c = cascade ? {:cascade => 'true'} : nil
110
+ send_request delete(build_path(
111
+ API_PATHS[:schema],
112
+ Rack::Utils.escape("$.name:\"#{schema_name}\"")
113
+ ), c)
114
+ end
115
+
116
+ # Get the schemas for the account.
117
+ #
118
+ # NOTE: returned format is not the same as accepted for input
119
+ def get_schemas
120
+ send_request get(build_path(API_PATHS[:schema]))
121
+ end
122
+
123
+
124
+ ## Index management
125
+
126
+ # Add one or more indexes to the account, by name or id
127
+ def add_indexes(*indexes)
128
+ body = JSON.generate(indexes.flatten)
129
+ send_request post(build_path(API_PATHS[:indexes]), body)
130
+ end
131
+
132
+ # Delete one or more indexes from the account, by name or id
133
+ # <tt>indexes = '*'</tt> will delete all indexes
134
+ def delete_indexes(*indexes)
135
+ indexes = url_pipe_join(indexes)
136
+ send_request delete(build_path(API_PATHS[:indexes], indexes))
137
+ end
138
+
139
+ # Get the indexes from the account. Returns a list of ids
140
+ def get_indexes
141
+ send_request get(build_path(API_PATHS[:indexes]))
142
+ end
143
+
144
+
145
+ ## Document management
146
+
147
+ # Add documents to the specified +index+
148
+ #
149
+ # <tt>index = name</tt> or +id+, <tt>docs = {}</tt> or +Array+ of <tt>{}</tt>.
150
+ #
151
+ # Documents with key <tt>'#.#'</tt> and an existing value will be updated.
152
+ #
153
+ # If +schemas+ is not +nil+, ensures existence of the
154
+ # specified schemas on each document.
155
+ def add_documents(index, docs, schemas=[], fieldmode=nil)
156
+ fm = fieldmode != nil ? {:fieldmode => fieldmode} : nil
157
+ request = post(
158
+ build_path(API_PATHS[:documents], index, url_pipe_join(schemas), fm),
159
+ JSON.generate(identify_documents(docs))
160
+ )
161
+ send_request request
162
+ end
163
+
164
+ # Update documents in the specified +index+
165
+
166
+ # <tt>index = name</tt> or +id+, <tt>docs = {}</tt> or +Array+ of <tt>{}</tt>.
167
+ #
168
+ # Documents lacking the key <tt>'#.#'</tt> will be created.
169
+ #
170
+ # If +schemas+ is not +nil+, ensures existence of the
171
+ # specified schemas on each document.
172
+ def update_documents(index, docs, schemas=[], fieldmode=nil)
173
+ fm = fieldmode != nil ? {:fieldmode => fieldmode} : nil
174
+ request = put(
175
+ build_path(API_PATHS[:documents], index, url_pipe_join(schemas), fm),
176
+ JSON.generate(identify_documents(docs))
177
+ )
178
+ send_request request
179
+ end
180
+
181
+ # Modify documents in the +index+ matching +query+
182
+ #
183
+ # <tt>modifications = {...data...}</tt> to update all matching
184
+ # documents.
185
+ #
186
+ # If +schemas+ is not +nil+, ensures existence of the
187
+ # specified schemas on each document.
188
+ def modify_documents(index, query, modifications, schemas=[], fieldmode=nil)
189
+ fm = fieldmode != nil ? "?fieldmode=#{fieldmode}" : ""
190
+ request = put(
191
+ build_path(API_PATHS[:documents], index, url_pipe_join(schemas), Rack::Utils.escape(query), fm),
192
+ JSON.generate(modifications)
193
+ )
194
+ send_request request
195
+ end
196
+
197
+ # Delete documents in the +index+ matching +query+
198
+ #
199
+ # query => defaults to '*'
200
+ # index => may be an id, index name, or Array of ids or names.
201
+ #
202
+ # Operates on all indexes if +index+ = +nil+ or <tt>'*'</tt>
203
+ #
204
+ # ==== BEWARE: If +query+ = +nil+ this will delete ALL documents in +index+.
205
+ #
206
+ # ==== Acceptable options:
207
+ # :sort => a string ("[+|-]schema.field"), or a list thereof (default => index-order)
208
+ # :offset => integer offset into the result set (default => 0)
209
+ # :limit => integer limit on number of documents returned per index (default => <no limit>)
210
+ #
211
+ # If +schemas+ is not +nil+, ensures existence of the
212
+ # specified schemas on each document.
213
+ def delete_documents(index, query, options={}, schemas=[])
214
+ if options[:sort]
215
+ options[:sort] = Array(options[:sort]).flatten.join(',')
216
+ end
217
+ request = delete(
218
+ build_path(API_PATHS[:documents],
219
+ url_pipe_join(index),
220
+ url_pipe_join(schemas),
221
+ Rack::Utils.escape(query)
222
+ ),
223
+ options
224
+ )
225
+ send_request request
226
+ end
227
+
228
+ # Get documents matching +query+
229
+ #
230
+ # query => defaults to '*'
231
+ # index => may be an id, index name, or Array of ids or names.
232
+ #
233
+ # Operates on all indexes if +index+ = +nil+ or <tt>'*'</tt>
234
+ #
235
+ # ==== Acceptable options:
236
+ # :fields => a field name, a prefix match (e.g. 'trans*'), or Array of fields (default => '*')
237
+ # :sort => a string ("[+|-]schema.field"), or a list thereof (default => index-order)
238
+ # :offset => integer offset into the result set (default => 0)
239
+ # :limit => integer limit on number of documents returned per index (default => <no limit>)
240
+ # :fieldmode => 'short' or 'long'/nil, if 'short' then field names will be returned in their
241
+ # short form (no schema name prefix), this can cause naming collisions depending
242
+ # on use schema definition and data etc.
243
+ #
244
+ # If +schemas+ is not +nil+, ensures existence of the
245
+ # specified schemas on each document.
246
+ def get_documents(index, query, options={}, schemas=[])
247
+ if fields = options.delete(:fields)
248
+ fields = url_pipe_join(fields)
249
+ end
250
+
251
+ if options[:sort]
252
+ options[:sort] = Array(options[:sort]).flatten.join(',')
253
+ end
254
+
255
+ request = get(
256
+ build_path(API_PATHS[:documents],
257
+ url_pipe_join(index),
258
+ url_pipe_join(schemas),
259
+ url_pipe_join(query),
260
+ fields
261
+ ),
262
+ options
263
+ )
264
+ send_request request
265
+ end
266
+
267
+ # Count documents matching +query+
268
+ #
269
+ # query => defaults to '*'
270
+ # index => may be an id, index name, or Array of ids or names.
271
+ #
272
+ # Operates on all indexes if +index+ = +nil+ or <tt>'*'</tt>
273
+ #
274
+ # If +schemas+ is not +nil+, ensures existence of the
275
+ # specified schemas on each document.
276
+ def count_documents(index, query, schemas)
277
+ get_documents(index, query, {:fields => '@count'}, schemas)
278
+ end
279
+
280
+ private
281
+ def build_path(*path_elements)
282
+ path_elements.flatten.compact.unshift(PATH).join('/')
283
+ end
284
+
285
+ def account_path
286
+ build_path(API_PATHS[:account], @account)
287
+ end
288
+
289
+ def build_request(options={})
290
+ Request.new default_request_params.merge(options)
291
+ end
292
+
293
+ def get(path, params={})
294
+ build_request(:method => 'GET', :path => path, :params => params)
295
+ end
296
+
297
+ def delete(path, params={})
298
+ build_request(:method => 'DELETE', :path => path, :params => params)
299
+ end
300
+
301
+ def post(path, doc, params={})
302
+ build_request(:method => 'POST', :path => path, :body => doc, :params => params)
303
+ end
304
+
305
+ def put(path, doc, params={})
306
+ build_request(:method => 'PUT', :path => path, :body => doc, :params => params)
307
+ end
308
+
309
+ def default_request_params
310
+ {
311
+ :account => @account,
312
+ :secret => @secret,
313
+ :scheme => @secure ? 'https' : 'http',
314
+ }
315
+ end
316
+
317
+ def send_request(request, content_type=nil)
318
+ response = execute_request(request.method, request.url, request.headers, request.body, content_type)
319
+ status_code = response.first
320
+ if (200..299).include?(status_code)
321
+ begin
322
+ result = JSON.parse(response.last)
323
+ rescue JSON::ParserError => e
324
+ result = {"REASON" => e.message}
325
+ end
326
+ else
327
+ result = {"REASON" => "Error: #{status_code} #{Rack::Utils::HTTP_STATUS_CODES[status_code]}"}
328
+ end
329
+ result.merge!({'STATUS' => status_code})
330
+ end
331
+
332
+ def execute_request(method, url, headers, body, content_type=nil)
333
+ content_type ||= CONTENT_TYPES[:json]
334
+ curl = Curl::Easy.new(url) do |c|
335
+ c.headers = headers
336
+ c.headers['Content-Type'] = content_type
337
+ c.encoding = 'gzip'
338
+ end
339
+ case method
340
+ when 'GET'
341
+ curl.http_get
342
+ when 'DELETE'
343
+ curl.http_delete
344
+ when 'POST'
345
+ curl.http_post(body)
346
+ when 'PUT'
347
+ curl.http_put(body)
348
+ end
349
+
350
+ [curl.response_code, curl.header_str, curl.body_str]
351
+ end
352
+
353
+ def url_pipe_join(arr, default_value='*')
354
+ arr = Array(arr).flatten
355
+ if arr.empty?
356
+ default_value
357
+ else
358
+ Rack::Utils.escape(arr.join('|'))
359
+ end
360
+ end
361
+
362
+ def identify_documents(docs)
363
+ [docs] if docs.is_a?(Hash)
364
+ if @document_id_method
365
+ docs.each { |d| d.send(@document_id_method) }
366
+ end
367
+ docs
368
+ end
369
+ end
370
+ end
@@ -0,0 +1,28 @@
1
+ module CloudQuery
2
+ module Crypto
3
+ module Random
4
+ extend self
5
+
6
+ SecureRandom = (defined?(::SecureRandom) && ::SecureRandom) || (defined?(::ActiveSupport::SecureRandom) && ::ActiveSupport::SecureRandom)
7
+ if SecureRandom
8
+ def nonce
9
+ "#{SecureRandom.random_number}.#{Time.now.to_i}"[2..-1]
10
+ end
11
+ else
12
+ def nonce
13
+ "#{rand.to_s}.#{Time.now.to_i}"[2..-1]
14
+ end
15
+ end
16
+ end
17
+
18
+ module URLSafeSHA1
19
+ extend self
20
+
21
+ def sign(*tokens)
22
+ tokens = tokens.flatten
23
+ digest = Digest::SHA1.digest(tokens.join)
24
+ Base64.encode64(digest).chomp.tr('+/', '-_')
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module CloudQuery
2
+ class Field
3
+ attr_accessor :name, :type, :usage
4
+
5
+ def initialize(name, type)
6
+ @name, @type = name, type
7
+ end
8
+
9
+ def to_node(builder)
10
+ builder.field attributes
11
+ end
12
+
13
+ private
14
+
15
+ def attributes
16
+ hash = { :name => @name, :type => @type }
17
+ hash[:usage] = @usage unless @usage.nil?
18
+
19
+ hash
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,64 @@
1
+ module CloudQuery
2
+ class Request
3
+ attr_accessor :method, :headers, :scheme, :host, :port, :path, :params, :body
4
+
5
+ def initialize(options={})
6
+ @method = options[:method] || 'POST'
7
+ @headers = options[:headers] || {}
8
+ @scheme = options[:scheme] || SCHEME
9
+ @host = options[:host] || HOST
10
+ @port = options[:port] || (@scheme == 'https' ? URI::HTTPS::DEFAULT_PORT : URI::HTTP::DEFAULT_PORT)
11
+ @path = options[:path] || PATH
12
+ @params = options[:params] || {}
13
+ if ['PUT', 'DELETE'].include?(@method)
14
+ @params['_method'] = @method
15
+ @method = 'POST'
16
+ end
17
+ @body = options[:body]
18
+
19
+ @account = options[:account]
20
+ @secret = options[:secret]
21
+ end
22
+
23
+ def request_uri(account=@account, secret=@secret)
24
+ query = query_str(signature_params(account))
25
+ uri = if query.empty?
26
+ @path.dup
27
+ else
28
+ "#{@path}?#{query}"
29
+ end
30
+ uri = append_signature(uri, secret) if secret
31
+ uri
32
+ end
33
+
34
+ def url(account=@account, secret=@secret)
35
+ base_uri.merge(request_uri(account, secret)).to_s
36
+ end
37
+
38
+ private
39
+ def append_signature(uri, secret)
40
+ sig = Crypto::URLSafeSHA1.sign(secret, uri)
41
+ x_sig = Rack::Utils.build_query("x_sig" => sig)
42
+ "#{uri}&#{x_sig}"
43
+ end
44
+
45
+ def signature_params(account=@account)
46
+ return {} unless account
47
+ {
48
+ 'x_name' => account,
49
+ 'x_time' => Time.now.to_i_milliseconds,
50
+ 'x_nonce' => CloudQuery::Crypto::Random.nonce,
51
+ 'x_method' => SIGNING_METHOD,
52
+ }
53
+ end
54
+
55
+ def query_str(additional_params={})
56
+ Rack::Utils.build_query(@params.dup.merge(additional_params))
57
+ end
58
+
59
+ def base_uri
60
+ uri_class = (@scheme == 'https' ? URI::HTTPS : URI::HTTP)
61
+ uri_class.build(:scheme => @scheme, :host => @host, :port => @port)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,18 @@
1
+ module CloudQuery
2
+ class Schema
3
+ attr_accessor :name, :fields
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ @fields = []
8
+ end
9
+
10
+ def to_xml
11
+ Nokogiri::XML::Builder.new { |doc|
12
+ doc.schema(:name => @name, :store => 'yes') {
13
+ @fields.each { |field| field.to_node(doc) }
14
+ }
15
+ }.to_xml
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ module CloudQuery
2
+ class X64
3
+ Shift = 6
4
+ Mask = 0x3f
5
+ Munge = 0x30
6
+ NumberOfChars = (64 + Shift - 1)/Shift
7
+ Maximum = 0xffffffffffffffff
8
+ Compliment = 0x8000000000000000
9
+ Characters = '.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'
10
+
11
+ def self.encode(object, characters = Characters, munge = Munge)
12
+ int = integer_from_object(object)
13
+
14
+ chars = []
15
+ while chars.length == 0 || int != 0
16
+ byte = int & Maximum
17
+
18
+ (0...NumberOfChars).each do |i|
19
+ index = byte & Mask
20
+ index |= munge if i == (NumberOfChars - 1)
21
+ chars.unshift characters[index, 1]
22
+ byte = (byte >> Shift) & Maximum
23
+ end
24
+
25
+ int = int >> 64
26
+ end
27
+
28
+ chars.join
29
+ end
30
+
31
+ private
32
+
33
+ def self.integer_from_object(object)
34
+ case object
35
+ when String
36
+ UUIDTools::UUID.parse_raw(Digest::MD5.digest(object)).to_i
37
+ else
38
+ object.to_i
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/cloud_query.rb CHANGED
@@ -6,12 +6,14 @@ require "rack/utils"
6
6
  require "curl"
7
7
  require "json"
8
8
  require 'nokogiri'
9
+ require 'uuidtools'
9
10
 
10
11
  require 'cloud_query/client'
11
12
  require 'cloud_query/crypto'
12
13
  require 'cloud_query/field'
13
14
  require 'cloud_query/request'
14
15
  require 'cloud_query/schema'
16
+ require 'cloud_query/x64'
15
17
 
16
18
  module CloudQuery
17
19
  SCHEME = "https".freeze
@@ -37,7 +39,7 @@ module CloudQuery
37
39
  end
38
40
 
39
41
  class Time
40
- def to_i_with_milliseconds
42
+ def to_i_milliseconds
41
43
  (to_f * 1000).to_i
42
44
  end
43
45
  end
@@ -89,7 +89,7 @@ if ENV["TEST_REAL_HTTP"]
89
89
  'spec.example.email' => ['steve.rogers@example.com','captain.america@marvel.com'],
90
90
  'spec.example.telephone' => ['555-555-5555','123-456-6789'],
91
91
  'spec.example.address' => ['Lower East Side, NY NY'],
92
- 'spec.example.birthday' => ParseDate.parsedate('July 4, 1917'),
92
+ 'spec.example.birthday' => Date.parse('July 4, 1917'),
93
93
  'spec.example.note' => 'Captain America!',
94
94
  }
95
95
  end
@@ -125,7 +125,7 @@ if ENV["TEST_REAL_HTTP"]
125
125
  'spec.example.telephone' => ['555-123-1234', '555-456-6789'],
126
126
  'spec.example.address' =>
127
127
  ['344 Clinton St., Apt. #3B, Metropolis', 'The Fortess of Solitude, North Pole'],
128
- 'spec.example.birthday' => ParseDate.parsedate('June 18, 1938'),
128
+ 'spec.example.birthday' => Date.parse('June 18, 1938'),
129
129
  'spec.example.note' =>
130
130
  'Superhuman strength, speed, stamina, durability, senses, intelligence, regeneration, and longevity; super breath, heat vision, x-ray vision and flight. Member of the justice league.',
131
131
  },
@@ -135,7 +135,7 @@ if ENV["TEST_REAL_HTTP"]
135
135
  'spec.example.telephone' => ['555-123-6666', '555-456-6666'],
136
136
  'spec.example.address' =>
137
137
  ['1007 Mountain Drive, Gotham', 'The Batcave, Gotham'],
138
- 'spec.example.birthday' => ParseDate.parsedate('February 19, 1939'),
138
+ 'spec.example.birthday' => Date.parse('February 19, 1939'),
139
139
  'spec.example.note' =>
140
140
  'Sidekick is Robin. Has problems with the Joker. Member of the justice league.',
141
141
  },
@@ -0,0 +1,43 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe CloudQuery::Field do
4
+ it "should initialize with a name and type" do
5
+ field = CloudQuery::Field.new(:name, :string)
6
+
7
+ field.name.should == :name
8
+ field.type.should == :string
9
+ end
10
+
11
+ describe "#.to_node" do
12
+ before :each do
13
+ @field = CloudQuery::Field.new(:name, :string)
14
+ @builder = Nokogiri::XML::Builder.new { |doc|
15
+ @field.to_node(doc)
16
+ }
17
+ end
18
+
19
+ it "should append a field element" do
20
+ @builder.doc.at('field').should_not be_nil
21
+ end
22
+
23
+ it "should set the name attribute" do
24
+ @builder.doc.at('field')['name'].should == 'name'
25
+ end
26
+
27
+ it "should set the type attribute" do
28
+ @builder.doc.at('field')['type'].should == 'string'
29
+ end
30
+
31
+ it "should set the usage attribute if one is provided" do
32
+ @field.usage = 'user'
33
+ @builder = Nokogiri::XML::Builder.new { |doc|
34
+ @field.to_node(doc)
35
+ }
36
+ @builder.doc.at('field')['usage'].should == 'user'
37
+ end
38
+
39
+ it "should not have a usage attribute if not set" do
40
+ @builder.doc.at('field')['usage'].should be_nil
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe CloudQuery::Schema do
4
+ describe "#.to_xml" do
5
+ before :each do
6
+ @schema = CloudQuery::Schema.new('user.schema')
7
+ @schema.fields << CloudQuery::Field.new(:name, :string)
8
+ @schema.fields << CloudQuery::Field.new(:type, :literal)
9
+ @schema.fields << CloudQuery::Field.new(:description, :text)
10
+
11
+ @document = Nokogiri::XML(@schema.to_xml)
12
+ end
13
+
14
+ it "should output a schema element" do
15
+ @document.at('schema').should_not be_nil
16
+ end
17
+
18
+ it "should output the schema name as an attribute" do
19
+ @document.at('schema')['name'].should == 'user.schema'
20
+ end
21
+
22
+ it "should output the store attribute set to yes" do
23
+ @document.at('schema')['store'].should == 'yes'
24
+ end
25
+
26
+ it "should output each of the fields as elements" do
27
+ @schema.fields.each do |field|
28
+ @document.at('schema').css('field').detect { |element|
29
+ element['name'] == field.name.to_s
30
+ }.should_not be_nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe CloudQuery::X64 do
4
+ describe '.encode' do
5
+ # Built with the expectation of parity with the Python equivalent.
6
+ it "should return expected values from strings" do
7
+ CloudQuery::X64.encode('test').should == 'kaEPwp57SCnwfTIcBa8vIq'
8
+ end
9
+
10
+ it "should return expected values from integers" do
11
+ CloudQuery::X64.encode(111).should == 'k........0j'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,38 @@
1
+ require 'jeweler'
2
+ require 'yard'
3
+
4
+ desc 'Generate documentation'
5
+ YARD::Rake::YardocTask.new do |t|
6
+ config = YAML.load(File.read('VERSION.yml'))
7
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
8
+
9
+ t.name = "CloudQuery #{version}"
10
+ t.files = ['lib/**/*.rb']
11
+ end
12
+
13
+ task :rdoc => :yardoc
14
+
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = "cloud_query"
17
+ gem.summary = "Client for Xoopit's CloudQuery API"
18
+ gem.description = "Client for Xoopit's CloudQuery API"
19
+ gem.email = "cloudquery@xoopit.com"
20
+ gem.homepage = "http://github.com/xoopit/cloudquery_ruby"
21
+ gem.authors = ["Xoopit", "Cameron Walters", "nb.io", "Pat Allan"]
22
+ gem.files = FileList[
23
+ "lib/**/*.rb",
24
+ "LICENCE",
25
+ "README.markdown",
26
+ "tasks/**/*.rb"
27
+ ]
28
+ gem.test_files = FileList["spec/**/*_spec.rb"]
29
+ # gem.rubyforge_project = "cloudquery"
30
+
31
+ gem.add_dependency 'rack', '>= 1.0'
32
+ gem.add_dependency 'json', '>= 1.1.4'
33
+ gem.add_dependency 'taf2-curb', '>= 0.2.8.0'
34
+ gem.add_dependency 'nokogiri', '>= 1.3.1'
35
+ gem.add_dependency 'uuidtools', '>= 2.0.0'
36
+
37
+ # See http://www.rubygems.org/read/chapter/20 for additional gemspec settings
38
+ end
data/tasks/testing.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ Spec::Rake::SpecTask.new(:spec) do |t|
4
+ t.spec_files = FileList['spec/**/*_spec.rb']
5
+ t.spec_opts << "-c"
6
+ end
7
+
8
+ Spec::Rake::SpecTask.new(:rcov) do |t|
9
+ t.pattern = 'spec/**/*_spec.rb'
10
+ t.spec_opts << "-c"
11
+
12
+ t.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems']
13
+ t.rcov = true
14
+ end
metadata CHANGED
@@ -1,16 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xoopit-cloud_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
+ - Xoopit
7
8
  - Cameron Walters
8
9
  - nb.io
10
+ - Pat Allan
9
11
  autorequire:
10
12
  bindir: bin
11
13
  cert_chain: []
12
14
 
13
- date: 2009-05-04 00:00:00 -07:00
15
+ date: 2009-07-16 00:00:00 -07:00
14
16
  default_executable:
15
17
  dependencies:
16
18
  - !ruby/object:Gem::Dependency
@@ -43,8 +45,28 @@ dependencies:
43
45
  - !ruby/object:Gem::Version
44
46
  version: 0.2.8.0
45
47
  version:
48
+ - !ruby/object:Gem::Dependency
49
+ name: nokogiri
50
+ type: :runtime
51
+ version_requirement:
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 1.3.1
57
+ version:
58
+ - !ruby/object:Gem::Dependency
59
+ name: uuidtools
60
+ type: :runtime
61
+ version_requirement:
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.0.0
67
+ version:
46
68
  description: Client for Xoopit's CloudQuery API
47
- email: us@nb.io
69
+ email: cloudquery@xoopit.com
48
70
  executables: []
49
71
 
50
72
  extensions: []
@@ -53,17 +75,20 @@ extra_rdoc_files:
53
75
  - LICENSE
54
76
  - README.markdown
55
77
  files:
56
- - LICENSE
57
78
  - README.markdown
58
- - Rakefile
59
- - VERSION.yml
60
- - lib/cloudquery.rb
61
79
  - lib/cloud_query.rb
62
- - spec/cloudquery_spec.rb
63
- - spec/example_schema.xml
64
- - spec/spec_helper.rb
80
+ - lib/cloud_query/client.rb
81
+ - lib/cloud_query/crypto.rb
82
+ - lib/cloud_query/field.rb
83
+ - lib/cloud_query/request.rb
84
+ - lib/cloud_query/schema.rb
85
+ - lib/cloud_query/x64.rb
86
+ - lib/cloudquery.rb
87
+ - tasks/distribution.rb
88
+ - tasks/testing.rb
89
+ - LICENSE
65
90
  has_rdoc: true
66
- homepage: http://github.com/nbio/cloudquery
91
+ homepage: http://github.com/xoopit/cloudquery_ruby
67
92
  post_install_message:
68
93
  rdoc_options:
69
94
  - --charset=UTF-8
@@ -87,7 +112,9 @@ rubyforge_project:
87
112
  rubygems_version: 1.2.0
88
113
  signing_key:
89
114
  specification_version: 2
90
- summary: Client for Xoopit's cloudquery API
115
+ summary: Client for Xoopit's CloudQuery API
91
116
  test_files:
92
117
  - spec/cloudquery_spec.rb
93
- - spec/spec_helper.rb
118
+ - spec/lib/cloud_query/field_spec.rb
119
+ - spec/lib/cloud_query/schema_spec.rb
120
+ - spec/lib/cloud_query/x64_spec.rb
data/Rakefile DELETED
@@ -1,80 +0,0 @@
1
- require 'rake'
2
-
3
- begin
4
- require 'jeweler'
5
- Jeweler::Tasks.new do |gem|
6
- gem.name = "cloud_query"
7
- gem.summary = "Client for Xoopit's CloudQuery API"
8
- gem.email = "us@nb.io"
9
- gem.homepage = "http://github.com/xoopit/cloudquery_ruby"
10
- gem.description = "Client for Xoopit's CloudQuery API"
11
- gem.authors = ["Cameron Walters", "nb.io"]
12
- gem.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
13
- # gem.rubyforge_project = "cloudquery"
14
- gem.add_dependency('rack', ">= 1.0")
15
- gem.add_dependency('json', ">= 1.1.4")
16
- gem.add_dependency('taf2-curb', ">= 0.2.8.0")
17
-
18
-
19
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
- end
21
- rescue LoadError
22
- puts "Jeweler not available. Install it with: sudo gem install jeweler"
23
- end
24
-
25
- require 'spec/rake/spectask'
26
- Spec::Rake::SpecTask.new(:spec) do |spec|
27
- spec.libs << 'lib' << 'spec'
28
- spec.spec_files = FileList['spec/**/*_spec.rb']
29
- spec.spec_opts << "-c"
30
- end
31
-
32
- Spec::Rake::SpecTask.new(:rcov) do |spec|
33
- spec.libs << 'lib' << 'spec'
34
- spec.pattern = 'spec/**/*_spec.rb'
35
- spec.rcov = true
36
- end
37
-
38
-
39
- task :default => :spec
40
-
41
- require 'rake/rdoctask'
42
- Rake::RDocTask.new do |rdoc|
43
- if File.exist?('VERSION.yml')
44
- config = YAML.load(File.read('VERSION.yml'))
45
- version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
46
- else
47
- version = ""
48
- end
49
-
50
- rdoc.rdoc_dir = 'rdoc'
51
- rdoc.title = "CloudQuery #{version}"
52
- rdoc.rdoc_files.include('README*')
53
- rdoc.rdoc_files.include('lib/**/*.rb')
54
- end
55
-
56
- # begin
57
- # require 'rake/contrib/sshpublisher'
58
- # namespace :rubyforge do
59
- #
60
- # desc "Release gem and RDoc documentation to RubyForge"
61
- # task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
62
- #
63
- # namespace :release do
64
- # desc "Publish RDoc to RubyForge."
65
- # task :docs => [:rdoc] do
66
- # config = YAML.load(
67
- # File.read(File.expand_path('~/.rubyforge/user-config.yml'))
68
- # )
69
- #
70
- # host = "#{config['username']}@rubyforge.org"
71
- # remote_dir = "/var/www/gforge-projects/cloudquery/"
72
- # local_dir = 'rdoc'
73
- #
74
- # Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
75
- # end
76
- # end
77
- # end
78
- # rescue LoadError
79
- # puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
80
- # end
data/VERSION.yml DELETED
@@ -1,4 +0,0 @@
1
- ---
2
- :major: 0
3
- :minor: 1
4
- :patch: 5
@@ -1,26 +0,0 @@
1
- <schema name="spec.example" store="yes">
2
- <!-- The full name of the contact -->
3
- <field name="name"
4
- type="string"
5
- analyzer="LCWhitespaceAnalyzer"
6
- usage="user" />
7
- <!-- The email addresses. A json array: email address -->
8
- <field name="email"
9
- type="string"
10
- usage="user" />
11
- <!-- The phone numbers. A json array: phone number -->
12
- <field name="telephone"
13
- type="string"
14
- usage="user" />
15
- <!-- The addresses. A json array: address -->
16
- <field name="address"
17
- type="string"
18
- usage="user" />
19
- <!-- The birthday of the contact-->
20
- <field name="birthday"
21
- type="date" />
22
- <!-- A note for the contact-->
23
- <field name="note"
24
- type="text"
25
- usage="user" />
26
- </schema>
data/spec/spec_helper.rb DELETED
@@ -1,11 +0,0 @@
1
- require 'spec'
2
-
3
- $LOAD_PATH.unshift(File.dirname(__FILE__))
4
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
- require 'cloud_query'
6
- require 'parsedate'
7
- require 'pp'
8
-
9
- Spec::Runner.configure do |config|
10
-
11
- end