xoopit-cloud_query 0.1.5 → 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.
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