animoto 0.0.0.alpha3 → 0.0.0.alpha4

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/lib/animoto/asset.rb CHANGED
@@ -15,11 +15,5 @@ module Animoto
15
15
  { 'source' => @source }
16
16
  end
17
17
 
18
- # Returns a representation of this asset as JSON.
19
- #
20
- # @return [String] this asset as JSON
21
- def to_json
22
- self.to_hash.to_json
23
- end
24
18
  end
25
19
  end
@@ -1,8 +1,5 @@
1
- require 'uri'
2
- require 'net/http'
3
- require 'net/https'
4
- require 'json'
5
1
  require 'yaml'
2
+ require 'uri'
6
3
 
7
4
  require 'animoto/errors'
8
5
  require 'animoto/content_type'
@@ -25,19 +22,18 @@ require 'animoto/job'
25
22
  require 'animoto/directing_and_rendering_job'
26
23
  require 'animoto/directing_job'
27
24
  require 'animoto/rendering_job'
25
+ require 'animoto/dynamic_class_loader'
26
+ require 'animoto/http_engine'
27
+ require 'animoto/response_parser'
28
28
 
29
29
  module Animoto
30
30
  class Client
31
31
  API_ENDPOINT = "https://api2-staging.animoto.com/"
32
32
  API_VERSION = 1
33
33
  BASE_CONTENT_TYPE = "application/vnd.animoto"
34
- HTTP_METHOD_MAP = {
35
- :get => Net::HTTP::Get,
36
- :post => Net::HTTP::Post
37
- }
38
34
 
39
35
  attr_accessor :key, :secret, :endpoint
40
- attr_reader :format
36
+ attr_reader :http_engine, :response_parser
41
37
 
42
38
  # Creates a new Client object which handles credentials, versioning, making requests, and
43
39
  # parsing responses.
@@ -52,27 +48,61 @@ module Animoto
52
48
  # @return [Client]
53
49
  # @raise [ArgumentError] if no credentials are supplied
54
50
  def initialize *args
55
- @debug = ENV['DEBUG']
56
51
  options = args.last.is_a?(Hash) ? args.pop : {}
57
52
  @key = args[0]
58
53
  @secret = args[1]
59
54
  @endpoint = options[:endpoint]
60
-
61
- home_path = File.expand_path '~/.animotorc'
62
- config = if File.exist?(home_path)
63
- YAML.load(File.read(home_path))
64
- elsif File.exist?('/etc/.animotorc')
65
- YAML.load(File.read('/etc/.animotorc'))
55
+ configure_from_rc_file
56
+ @endpoint ||= API_ENDPOINT
57
+ __send__ :http_engine=, options[:http_engine] || :net_http
58
+ __send__ :response_parser=, options[:response_parser] || :json
59
+ end
60
+
61
+ # Set the HTTP engine this client will use.
62
+ #
63
+ # @param [HTTPEngine, Symbol, Class] engine you may pass a
64
+ # HTTPEngine instance to use, or the symbolic name of a adapter to use,
65
+ # or a Class whose instances respond to #request and return a String of
66
+ # the response body
67
+ # @see Animoto::HTTPEngine
68
+ # @return [HTTPEngine] the engine instance
69
+ # @raise [ArgumentError] if given a class without the correct interface
70
+ def http_engine= engine
71
+ @http_engine = case engine
72
+ when Animoto::HTTPEngine
73
+ engine
74
+ when Class
75
+ if engine.instance_methods.include?('request')
76
+ engine.new
77
+ else
78
+ raise ArgumentError
79
+ end
80
+ else
81
+ Animoto::HTTPEngine[engine].new
66
82
  end
67
- @key ||= config['key']
68
- @secret ||= config['secret']
69
- @endpoint ||= config['endpoint']
70
- unless @key && @secret
71
- raise ArgumentError, "You must supply your key and secret"
83
+ end
84
+
85
+ # Set the response parser this client will use.
86
+ #
87
+ # @param [ResponseParser, Symbol, Class] parser you may pass a
88
+ # ResponseParser instance to use, or the symbolic name of a adapter to use,
89
+ # or a Class whose instances respond to #parse, #unparse, and #format.
90
+ # @see Animoto::ResponseParser
91
+ # @return [ResponseParser] the parser instance
92
+ # @raise [ArgumentError] if given a class without the correct interface
93
+ def response_parser= parser
94
+ @response_parser = case parser
95
+ when Animoto::ResponseParser
96
+ parser
97
+ when Class
98
+ if %{format parse unparse}.all? { |m| parser.instance_methods.include? m }
99
+ parser.new
100
+ else
101
+ raise ArgumentError
102
+ end
103
+ else
104
+ Animoto::ResponseParser[parser].new
72
105
  end
73
-
74
- @endpoint ||= API_ENDPOINT
75
- @format = 'json'
76
106
  end
77
107
 
78
108
  # Finds a resource by its URL.
@@ -124,15 +154,40 @@ module Animoto
124
154
 
125
155
  private
126
156
 
157
+ # Sets the API credentials from an .animotorc file. First looks for one in the current
158
+ # directory, then checks ~/.animotorc, then finally /etc/.animotorc.
159
+ #
160
+ # @raise [ArgumentError] if none of the files are found
161
+ def configure_from_rc_file
162
+ catch(:done) do
163
+ current_path = Dir.pwd + '/.animotorc'
164
+ home_path = File.expand_path('~/.animotorc')
165
+ config = if File.exist?(current_path)
166
+ YAML.load(File.read(current_path))
167
+ elsif File.exist?(home_path)
168
+ home_path = File.expand_path '~/.animotorc'
169
+ YAML.load(File.read(home_path))
170
+ elsif File.exist?('/etc/.animotorc')
171
+ YAML.load(File.read('/etc/.animotorc'))
172
+ end
173
+ if config
174
+ @key ||= config['key']
175
+ @secret ||= config['secret']
176
+ @endpoint ||= config['endpoint']
177
+ throw :done if @key && @secret
178
+ end
179
+ raise ArgumentError, "You must supply your key and secret"
180
+ end
181
+ end
182
+
127
183
  # Builds a request to find a resource.
128
184
  #
129
185
  # @param [Class] klass the Resource class you're looking for
130
186
  # @param [String] url the URL of the resource
131
187
  # @param [Hash] options
132
- # @return [Hash] deserialized JSON response body
188
+ # @return [Hash] deserialized response body
133
189
  def find_request klass, url, options = {}
134
- # request(:get, URI.parse(url).path, nil, { "Accept" => content_type_of(klass) }, options)
135
- request(:get, URI.parse(url), nil, { "Accept" => content_type_of(klass) }, options)
190
+ request(:get, url, nil, { "Accept" => content_type_of(klass) }, options)
136
191
  end
137
192
 
138
193
  # Builds a request requiring a manifest.
@@ -140,104 +195,43 @@ module Animoto
140
195
  # @param [Manifest] manifest the manifest being acted on
141
196
  # @param [String] endpoint the endpoint to send the request to
142
197
  # @param [Hash] options
143
- # @return [Hash] deserialized JSON response body
198
+ # @return [Hash] deserialized response body
144
199
  def send_manifest manifest, endpoint, options = {}
145
- # request(:post, endpoint, manifest.to_json, { "Accept" => "application/#{format}", "Content-Type" => content_type_of(manifest) }, options)
146
200
  u = URI.parse(endpoint)
147
201
  u.path = endpoint
148
- request(:post, u, manifest.to_json, { "Accept" => "application/#{format}", "Content-Type" => content_type_of(manifest) }, options)
202
+ request(
203
+ :post,
204
+ u.to_s,
205
+ response_parser.unparse(manifest.to_hash),
206
+ { "Accept" => "application/#{response_parser.format}", "Content-Type" => content_type_of(manifest) },
207
+ options
208
+ )
149
209
  end
150
210
 
151
211
  # Makes a request and parses the response.
152
212
  #
153
213
  # @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
154
- # @param [URI] uri a URI object of the request URI
214
+ # @param [String] url the URL of the request
155
215
  # @param [String, nil] body the request body
156
216
  # @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
157
217
  # specify "Content-Type" => "..." instead of, say, :content_type => "...")
158
218
  # @param [Hash] options
159
- # @return [Hash] deserialized JSON response body
160
- def request method, uri, body, headers = {}, options = {}
161
- http = Net::HTTP.new uri.host, uri.port
162
- http.use_ssl = true
163
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
164
- req = build_request method, uri, body, headers, options
165
- if @debug
166
- puts "********************* REQUEST *******************"
167
- puts "#{req.method} #{uri.to_s} HTTP/#{http.instance_variable_get(:@curr_http_version)}\r\n"
168
- req.each_capitalized { |header, value| puts "#{header}: #{value}\r\n" }
169
- puts "\r\n"
170
- puts req.body unless req.method == 'GET'
219
+ # @return [Hash] deserialized response body
220
+ # @raise [Error]
221
+ def request method, url, body, headers = {}, options = {}
222
+ error = catch(:fail) do
223
+ options = { :username => @key, :password => @secret }.merge(options)
224
+ response = http_engine.request(method, url, body, headers, options)
225
+ return response_parser.parse(response)
171
226
  end
172
- response = http.request(req)
173
- if @debug
174
- puts "********************* RESPONSE *******************"
175
- puts "#{response.code} #{response.message}\r\n"
176
- response.each_capitalized { |header, value| puts "#{header}: #{value}\r\n" }
177
- puts "\r\n"
178
- body = response.body
179
- if body.nil? || body.empty?
180
- puts "(No content)"
181
- else
182
- puts body
183
- end
227
+ if error
228
+ errors = response_parser.parse(error)['response']['status']['errors']
229
+ raise Animoto::Error.new(errors.collect { |e| e['message'] }.join(', '))
230
+ else
231
+ raise Animoto::Error
184
232
  end
185
- read_response response
186
- end
187
-
188
- # Builds the request object.
189
- #
190
- # @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
191
- # @param [String] uri the request path
192
- # @param [String, nil] body the request body
193
- # @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
194
- # specify "Content-Type" => "..." instead of, say, :content_type => "...")
195
- # @param [Hash] options
196
- # @return [Net::HTTPRequest] the request object
197
- def build_request method, uri, body, headers, options
198
- req = HTTP_METHOD_MAP[method].new uri.path
199
- req.body = body
200
- req.initialize_http_header headers
201
- req.basic_auth key, secret
202
- req
203
- end
204
-
205
- # Verifies and parses the response.
206
- #
207
- # @param [Net::HTTPResponse] response the response object
208
- # @return [Hash] deserialized JSON response body
209
- def read_response response
210
- check_status response
211
- parse_response response
212
- end
213
-
214
- # Checks the status of the response to make sure it's successful.
215
- #
216
- # @param [Net::HTTPResponse] response the response object
217
- # @return [nil]
218
- # @raise [Error,RuntimeError] if the response code isn't in the 200 range
219
- def check_status response
220
- unless (200..299).include?(response.code.to_i)
221
- if response.body
222
- begin
223
- json = JSON.parse(response.body)
224
- errors = json['response']['status']['errors']
225
- rescue => e
226
- raise response.message
227
- else
228
- raise Animoto::Error.new(errors.collect { |e| e['message'] }.join(', '))
229
- end
230
- else
231
- raise response.message
232
- end
233
- end
234
- end
235
-
236
- # Parses a JSON response body into a Hash.
237
- # @param [Net::HTTPResponse] response the response object
238
- # @return [Hash] deserialized JSON response body
239
- def parse_response response
240
- JSON.parse(response.body)
233
+ rescue NoMethodError => e
234
+ raise Animoto::Error.new("Invalid response (#{error.inspect})")
241
235
  end
242
236
 
243
237
  # Creates the full content type string given a Resource class or instance
@@ -247,7 +241,7 @@ module Animoto
247
241
  # "application/vnd.animoto.storyboard-v1+json")
248
242
  def content_type_of klass_or_instance
249
243
  klass = klass_or_instance.is_a?(Class) ? klass_or_instance : klass_or_instance.class
250
- "#{BASE_CONTENT_TYPE}.#{klass.content_type}-v#{API_VERSION}+#{format}"
244
+ "#{BASE_CONTENT_TYPE}.#{klass.content_type}-v#{API_VERSION}+#{response_parser.format}"
251
245
  end
252
246
 
253
247
  end
@@ -0,0 +1,64 @@
1
+ module Animoto
2
+ module DynamicClassLoader
3
+
4
+ # If a reference is made to a class under this one that hasn't been initialized yet,
5
+ # this will attempt to require a file with that class' name (underscored). If one is
6
+ # found, and the file defines the class requested, will return that class object.
7
+ #
8
+ # @param [Symbol] const the uninitialized class' name
9
+ # @return [Class] the class found
10
+ # @raise [NameError] if the file defining the class isn't found, or if the file required
11
+ # doesn't define the class.
12
+ def const_missing const
13
+ engine_name = underscore_class_name(const.to_s)
14
+ file_name = File.dirname(__FILE__) + "/#{search_path}/#{engine_name}.rb"
15
+ if File.exist?(file_name)
16
+ require file_name
17
+ const_defined?(const) ? const_get(const) : super
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ # Returns the adapter class for the given symbol. If the file defining the class
24
+ # hasn't been loaded, will try to load it.
25
+ #
26
+ # @param [Symbol] engine the symbolic name of the adapter
27
+ # @return [Class] the class
28
+ # @raise [NameError] if the class isn't found
29
+ def [] engine
30
+ require(File.dirname(__FILE__) + "/#{search_path}/#{engine.to_s.gsub(/[^\w]/,'.?')}_adapter.rb") unless $".grep(/#{engine.to_s}_adapter/).first
31
+ rescue LoadError
32
+ raise NameError, "Couldn't locate adapter named \"#{engine}\""
33
+ else
34
+ adapter_map[engine]
35
+ end
36
+
37
+ private
38
+
39
+ # Returns the path relative to this file where dynamically loaded files can be found.
40
+ # Defaults to the underscored class name plus 's'.
41
+ #
42
+ # @return [String] the path
43
+ def search_path
44
+ "#{underscore_class_name(self)}s"
45
+ end
46
+
47
+ # Returns a Hash mapping the symbolic names for adapters to their classes.
48
+ #
49
+ # @return [Hash] the map of adapters
50
+ def adapter_map
51
+ @adapter_map ||= {}
52
+ end
53
+
54
+ # Turns a camel-cased class name into an underscored version. Will only affect the base name
55
+ # of the class, so, for example, Animoto::DirectingAndRenderingJob becomes 'directing_and_rendering_job'
56
+ #
57
+ # @param [Class, String] klass a class or name of a class
58
+ # @return [String] the underscored version of the name
59
+ def underscore_class_name klass
60
+ klass_name = klass.is_a?(Class) ? klass.name.split('::').last : klass
61
+ klass_name.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ module Animoto
2
+ class HTTPEngine
3
+ extend DynamicClassLoader
4
+
5
+ # Make a request.
6
+ #
7
+ # @param [Symbol] method the HTTP method to use, should be lower-case (that is, :get
8
+ # instead of :GET)
9
+ # @param [String] url the URL to request
10
+ # @param [String,nil] body the request body
11
+ # @param [Hash<String,String>] headers request headers to send; names will be sent as-is
12
+ # (for example, use keys like "Content-Type" and not :content_type)
13
+ # @param [Hash<Symbol,Object>] options
14
+ # @option options :timeout set a timeout
15
+ # @option options :username the authentication username
16
+ # @option options :password the authentication password
17
+ # @return [String] the response body
18
+ # @raise [NotImplementedError] if called on the abstract class
19
+ def request method, url, body = nil, headers = {}, options = {}
20
+ raise NotImplementedError
21
+ end
22
+
23
+ private
24
+
25
+ # Checks the response and raises an error if the status isn't success.
26
+ #
27
+ # @param [Fixnum] code the HTTP status code
28
+ # @param [String] body the HTTP response body
29
+ # @raise [Animoto::Error] if the status isn't between 200 and 299
30
+ def check_response code, body
31
+ throw(:fail, body) unless (200..299).include?(code)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require 'curl'
2
+
3
+ module Animoto
4
+ class HTTPEngine
5
+ class CurlAdapter < Animoto::HTTPEngine
6
+
7
+ def request method, url, body = nil, headers = {}, options = {}
8
+ curl = build_curl method, url, body, headers, options
9
+ perform curl, method
10
+ check_response curl.response_code, curl.body_str
11
+ curl.body_str
12
+ end
13
+
14
+ private
15
+
16
+ def build_curl method, url, body, headers, options
17
+ ::Curl::Easy.new(url) do |c|
18
+ c.username = options[:username]
19
+ c.password = options[:password]
20
+ c.timeout = options[:timeout]
21
+ c.post_body = body
22
+ headers.each { |header, value| c.headers[header] = value }
23
+ end
24
+ end
25
+
26
+ def perform curl, method
27
+ case method
28
+ when :get
29
+ curl.http_get
30
+ when :post
31
+ curl.http_post(body)
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ adapter_map.merge! :curl => CurlAdapter
38
+ end
39
+ end
@@ -0,0 +1,55 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+
4
+ module Animoto
5
+ class HTTPEngine
6
+ class NetHTTPAdapter < Animoto::HTTPEngine
7
+
8
+ HTTP_METHOD_MAP = {
9
+ :get => Net::HTTP::Get,
10
+ :post => Net::HTTP::Post
11
+ }
12
+
13
+ def request method, url, body = nil, headers = {}, options = {}
14
+ uri = URI.parse(url)
15
+ http = build_http uri
16
+ req = build_request method, uri, body, headers, options
17
+ response = http.request req
18
+ check_response response.code.to_i, response.body
19
+ response.body
20
+ end
21
+
22
+ private
23
+
24
+ # Makes a new HTTP object.
25
+ #
26
+ # @param [URI] uri a URI object of the request URL
27
+ # @return [Net::HTTP] the HTTP object
28
+ def build_http uri
29
+ http = Net::HTTP.new uri.host, uri.port
30
+ http.use_ssl = true
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+ http
33
+ end
34
+
35
+ # Builds the request object.
36
+ #
37
+ # @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
38
+ # @param [String] uri the request path
39
+ # @param [String, nil] body the request body
40
+ # @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
41
+ # specify "Content-Type" => "..." instead of, say, :content_type => "...")
42
+ # @param [Hash] options
43
+ # @return [Net::HTTPRequest] the request object
44
+ def build_request method, uri, body, headers, options
45
+ req = HTTP_METHOD_MAP[method].new uri.path
46
+ req.body = body
47
+ req.initialize_http_header headers
48
+ req.basic_auth options[:username], options[:password]
49
+ req
50
+ end
51
+ end
52
+
53
+ adapter_map.merge! :net_http => NetHTTPAdapter
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ require 'patron'
2
+
3
+ module Animoto
4
+ class HTTPEngine
5
+ class PatronAdapter < Animoto::HTTPEngine
6
+
7
+ def request method, url, body = nil, headers = {}, options = {}
8
+ session = build_session options
9
+ response = session.request method, url, headers, :data => body
10
+ check_response response.status, response.body
11
+ response.body
12
+ end
13
+
14
+ private
15
+
16
+ def build_session options
17
+ session = ::Patron::Session.new
18
+ session.timeout = options[:timeout]
19
+ session.username = options[:username]
20
+ session.password = options[:password]
21
+ session
22
+ end
23
+ end
24
+
25
+ adapter_map.merge! :patron => PatronAdapter
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ require 'restclient'
2
+
3
+ module Animoto
4
+ class HTTPEngine
5
+ class RestClientAdapter < Animoto::HTTPEngine
6
+
7
+ def request method, url, body = nil, headers = {}, options = {}
8
+ response = ::RestClient::Request.execute({
9
+ :method => method,
10
+ :url => url,
11
+ :headers => headers,
12
+ :payload => body,
13
+ :user => options[:username],
14
+ :password => options[:password],
15
+ :timeout => options[:timeout]
16
+ })
17
+ check_response response.code, response.body
18
+ response.body
19
+ end
20
+
21
+ end
22
+
23
+ adapter_map.merge! :rest_client => RestClientAdapter
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ require 'typhoeus'
2
+
3
+ module Animoto
4
+ class HTTPEngine
5
+ class TyphoeusAdapter < Animoto::HTTPEngine
6
+
7
+ def request method, url, body = nil, headers = {}, options = {}
8
+ response = ::Typhoeus::Request.run(url, {
9
+ :method => method,
10
+ :body => body,
11
+ :headers => headers,
12
+ :timeout => options[:timeout],
13
+ :username => options[:username],
14
+ :password => options[:password]
15
+ })
16
+ check_response response.code, response.body
17
+ response.body
18
+ end
19
+
20
+ end
21
+
22
+ adapter_map.merge! :typhoeus => TyphoeusAdapter
23
+ end
24
+ end
@@ -10,12 +10,5 @@ module Animoto
10
10
  {}
11
11
  end
12
12
 
13
- # Returns a representation of this manifest as JSON.
14
- #
15
- # @return [String] the manifest as JSON
16
- def to_json
17
- self.to_hash.to_json
18
- end
19
-
20
13
  end
21
14
  end
@@ -50,13 +50,13 @@ module Animoto
50
50
  self.class.payload_key
51
51
  end
52
52
 
53
- # Makes a new instance of this class from a deserialized JSON response body. Note that
53
+ # Makes a new instance of this class from a deserialized response body. Note that
54
54
  # it assumes the hash you're passing is structured correctly and does no format checking
55
55
  # at all, so if the hash is not in the "standard envelope", this method will most likely
56
56
  # raise an error.
57
57
  #
58
58
  # @private
59
- # @param [Hash] body the deserialized JSON response body
59
+ # @param [Hash] body the deserialized response body
60
60
  # @return [Resource] an instance of this class
61
61
  def self.load body
62
62
  new unpack_standard_envelope(body)
@@ -116,7 +116,7 @@ module Animoto
116
116
  # Update this instance with new attributes from the response body.
117
117
  #
118
118
  # @private
119
- # @param [Hash] body deserialized JSON from a response body
119
+ # @param [Hash] body deserialized from a response body
120
120
  # @return [self] this instance, updated
121
121
  def load body = {}
122
122
  instantiate unpack_standard_envelope(body)
@@ -0,0 +1,38 @@
1
+ module Animoto
2
+ class ResponseParser
3
+ extend DynamicClassLoader
4
+
5
+ # Returns the format of this parser class.
6
+ #
7
+ # @return [String] the format
8
+ def self.format
9
+ @format
10
+ end
11
+
12
+ # Returns the format of this parser.
13
+ #
14
+ # @return [String] the format
15
+ def format
16
+ self.class.format
17
+ end
18
+
19
+ # Parses a response body into a usable Hash.
20
+ #
21
+ # @param [String] body the HTTP response body
22
+ # @return [Hash] the parsed response
23
+ # @raise [NotImplementedError] if called on the abstract class
24
+ def parse body
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # Serializes a Hash into the format for this parser.
29
+ #
30
+ # @param [Hash] hash the hash to serialize
31
+ # @return [String] the serialized data
32
+ # @raise [NotImplementedError] if called on the abstract class
33
+ def unparse hash
34
+ raise NotImplementedError
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ require 'json'
2
+
3
+ module Animoto
4
+ class ResponseParser
5
+ class JSONAdapter < Animoto::ResponseParser
6
+
7
+ @format = 'json'
8
+
9
+ def parse body
10
+ ::JSON.parse body
11
+ end
12
+
13
+ def unparse hash
14
+ ::JSON.unparse hash
15
+ end
16
+
17
+ end
18
+
19
+ adapter_map.merge! :json => JSONAdapter
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'yajl'
2
+
3
+ module Animoto
4
+ class ResponseParser
5
+ class YajlAdapter < Animoto::ResponseParser
6
+
7
+ @format = 'json'
8
+
9
+ def parse body
10
+ ::Yajl::Parser.parse body
11
+ end
12
+
13
+ def unparse hash
14
+ ::Yajl::Encoder.encode hash
15
+ end
16
+
17
+ end
18
+
19
+ adapter_map.merge! :yajl => YajlAdapter
20
+ end
21
+ end
@@ -20,7 +20,7 @@ module Animoto
20
20
  {
21
21
  :url => body['response']['payload'][payload_key]['links']['self'],
22
22
  :errors => body['response']['status']['errors'] || []
23
- }
23
+ }
24
24
  end
25
25
  end
26
26
  end
data/lib/animoto.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Animoto
2
2
  def self.version
3
- "0.0.0.alpha3"
3
+ "0.0.0.alpha4"
4
4
  end
5
5
  end
@@ -1,3 +1,4 @@
1
+ require 'base64'
1
2
  require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
3
 
3
4
  describe Animoto::Client do
@@ -42,15 +43,16 @@ describe Animoto::Client do
42
43
 
43
44
  describe "automatically" do
44
45
  before do
46
+ @here_path = File.expand_path("./.animotorc")
45
47
  @home_path = File.expand_path("~/.animotorc")
46
48
  @etc_path = "/etc/.animotorc"
47
49
  @config = "key: joe\nsecret: secret\nendpoint: https://api.animoto.com/"
48
50
  end
49
51
 
50
- describe "when ~/.animotorc exists" do
52
+ describe "when ./.animotorc exists" do
51
53
  before do
52
- File.stubs(:exist?).with(@home_path).returns(true)
53
- File.stubs(:read).with(@home_path).returns(@config)
54
+ File.stubs(:exist?).with(@here_path).returns(true)
55
+ File.stubs(:read).with(@here_path).returns(@config)
54
56
  end
55
57
 
56
58
  it "should configure itself based on the options in ~/.animotorc" do
@@ -61,28 +63,47 @@ describe Animoto::Client do
61
63
  end
62
64
  end
63
65
 
64
- describe "when ~/.animotorc doesn't exist" do
66
+ describe "when ./.animotorc doesn't exist" do
65
67
  before do
66
- File.stubs(:exist?).with(@home_path).returns(false)
68
+ File.stubs(:exist?).with(@here_path).returns(false)
67
69
  end
68
70
 
69
- describe "when /etc/.animotorc exists" do
71
+ describe "when ~/.animotorc exists" do
70
72
  before do
71
- File.stubs(:exist?).with(@etc_path).returns(true)
72
- File.stubs(:read).with(@etc_path).returns(@config)
73
+ File.stubs(:exist?).with(@home_path).returns(true)
74
+ File.stubs(:read).with(@home_path).returns(@config)
73
75
  end
74
-
75
- it "should configure itself based on the options in /etc/.animotorc" do
76
+
77
+ it "should configure itself based on the options in ~/.animotorc" do
76
78
  c = Animoto::Client.new
77
79
  c.key.should == "joe"
78
80
  c.secret.should == "secret"
79
81
  c.endpoint.should == "https://api.animoto.com/"
80
82
  end
81
83
  end
84
+
85
+ describe "when ~/.animotorc doesn't exist" do
86
+ before do
87
+ File.stubs(:exist?).with(@home_path).returns(false)
88
+ end
89
+
90
+ describe "when /etc/.animotorc exists" do
91
+ before do
92
+ File.stubs(:exist?).with(@etc_path).returns(true)
93
+ File.stubs(:read).with(@etc_path).returns(@config)
94
+ end
95
+
96
+ it "should configure itself based on the options in /etc/.animotorc" do
97
+ c = Animoto::Client.new
98
+ c.key.should == "joe"
99
+ c.secret.should == "secret"
100
+ end
101
+ end
82
102
 
83
- describe "when /etc/.animotorc doesn't exist" do
84
- it "should raise an error" do
85
- lambda { Animoto::Client.new }.should raise_error
103
+ describe "when /etc/.animotorc doesn't exist" do
104
+ it "should raise an error" do
105
+ lambda { Animoto::Client.new }.should raise_error
106
+ end
86
107
  end
87
108
  end
88
109
  end
@@ -91,9 +112,10 @@ describe Animoto::Client do
91
112
 
92
113
  describe "finding an instance by identifier" do
93
114
  before do
94
- @url = "https://api.animoto.com/storyboards/1"
95
- @body = {'response'=>{'status'=>{'code'=>200}},'payload'=>{'storyboard'=>{'links'=>{'self'=>@url}}}}
96
- stub_request(:get, @url).to_return(:body => @body.to_json, :status => [200,"OK"])
115
+ @url = "https://joe:secret@api.animoto.com/storyboards/1"
116
+ hash = {'response'=>{'status'=>{'code'=>200},'payload'=>{'storyboard'=>{'links'=>{'self'=>@url,'preview'=>'http://animoto.com/preview/1.mp4'},'metadata'=>{'duration'=>100,'visuals_count'=>1}}}}}
117
+ body = client.response_parser.unparse(hash)
118
+ stub_request(:get, @url).to_return(:body => body, :status => [200,"OK"])
97
119
  end
98
120
 
99
121
  it "should make a GET request to the given url" do
@@ -103,7 +125,7 @@ describe Animoto::Client do
103
125
 
104
126
  it "should ask for a response in the proper format" do
105
127
  client.find(Animoto::Storyboard, @url)
106
- WebMock.should have_requested(:get, @url).with(:headers => { 'Accept' => "application/vnd.animoto.storyboard-v1+json"})
128
+ WebMock.should have_requested(:get, @url).with(:headers => { 'Accept' => "application/vnd.animoto.storyboard-v1+json" })
107
129
  end
108
130
 
109
131
  it "should not sent a request body" do
@@ -118,10 +140,11 @@ describe Animoto::Client do
118
140
 
119
141
  describe "reloading an instance" do
120
142
  before do
121
- @url = 'https://api.animoto.com/jobs/directing/1'
143
+ @url = 'https://joe:secret@api.animoto.com/jobs/directing/1'
122
144
  @job = Animoto::DirectingJob.new :state => 'initial', :url => @url
123
- @body = {'response'=>{'status'=>{'code'=>200}},'payload'=>{'directing_job'=>{'state'=>'retrieving_assets','links'=>{'self'=>@url}}}}
124
- stub_request(:get, @url).to_return(:body => @body.to_json, :status => [200,"OK"])
145
+ hash = {'response'=>{'status'=>{'code'=>200},'payload'=>{'directing_job'=>{'state'=>'retrieving_assets','links'=>{'self'=>@url,'storyboard'=>'http://api.animoto.com/storyboards/1'}}}}}
146
+ body = client.response_parser.unparse(hash)
147
+ stub_request(:get, @url).to_return(:body => body, :status => [200,"OK"])
125
148
  @job.state.should == 'initial' # sanity check
126
149
  end
127
150
 
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Animoto::HTTPEngine do
4
+
5
+ describe "autoloading subclasses" do
6
+ before do
7
+ Animoto::HTTPEngine.const_defined?(:BeefHearts).should be_false
8
+ end
9
+
10
+
11
+
12
+ after do
13
+ Animoto::HTTPEngine.remove_const(:BeefHearts)
14
+ end
15
+ end
16
+
17
+ describe "making a request" do
18
+ before do
19
+ @engine = Animoto::HTTPEngine.new
20
+ end
21
+
22
+ it "should raise an implementation error" do
23
+ lambda { @engine.request(:get, "http://www.example.com/thing") }.should raise_error(NotImplementedError)
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Animoto::ResponseParser::JSONAdapter do
4
+
5
+ before do
6
+ @parser = Animoto::ResponseParser::JSONAdapter.new
7
+ end
8
+
9
+ it "should be JSON format" do
10
+ @parser.format.should == 'json'
11
+ end
12
+
13
+ describe "parsing" do
14
+ before do
15
+ @json = %Q!{"result":{"rank":1,"score":1.9,"tags":["hooray","for","dolphins"],"message":"woohoo"}}!
16
+ @hash = @parser.parse(@json)
17
+ end
18
+
19
+ it "should return a hash" do
20
+ @hash.should be_an_instance_of(Hash)
21
+ end
22
+
23
+ it "should turn the object root into a hash" do
24
+ @hash.should have_key('result')
25
+ end
26
+
27
+ it "should turn array elements into an array" do
28
+ @hash['result'].should have_key('tags')
29
+ @hash['result']['tags'].should be_an_instance_of(Array)
30
+ end
31
+
32
+ it "should preserve the order of elements in an array" do
33
+ @hash['result']['tags'].should == ["hooray", "for", "dolphins"]
34
+ end
35
+
36
+ it "should turn each object attribute into a key/value pair in the hash" do
37
+ @hash['result'].should have_key('rank')
38
+ @hash['result']['rank'].should be_an_instance_of(Fixnum)
39
+ end
40
+
41
+ it "should turn attributes with strictly numeric values into integers" do
42
+ @hash['result']['rank'].should eql(1)
43
+ end
44
+
45
+ it "should turn attributes with content representing floats into floats" do
46
+ @hash['result']['score'].should eql(1.9)
47
+ end
48
+
49
+ it "should turn strings into strings" do
50
+ @hash['result']['message'].should be_an_instance_of(String)
51
+ @hash['result']['message'].should == "woohoo"
52
+ end
53
+ end
54
+
55
+ describe "unparsing" do
56
+ before do
57
+ @obj = {
58
+ 'result' => {
59
+ 'tags' => [ 'hooray', 'for', 'dolphins' ],
60
+ 'message' => 'woohoo',
61
+ 'rank' => 1,
62
+ 'score' => 1.9
63
+ }
64
+ }
65
+ @json_str = @parser.unparse(@obj)
66
+ @json = ::JSON.parse(@json_str)
67
+ end
68
+
69
+ it "should turn hashes into objects with attributes" do
70
+ @json.should have_key('result')
71
+ @json['result'].should_not be_empty
72
+ end
73
+
74
+ it "should turn arrays into arrays" do
75
+ @json['result'].should have_key('tags')
76
+ @json['result']['tags'].should be_an_instance_of(Array)
77
+ end
78
+
79
+ it "should turn strings into text content" do
80
+ @json['result'].should have_key('message')
81
+ @json['result']['message'].should == "woohoo"
82
+ end
83
+
84
+ it "should turn integers into integers" do
85
+ @json_str.should =~ /"rank"\s*:\s*1/
86
+ end
87
+
88
+ it "should turn floats into floats" do
89
+ @json_str.should =~ /"score"\s*:\s*1\.9/
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,93 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Animoto::ResponseParser::YajlAdapter do
4
+
5
+ before do
6
+ @parser = Animoto::ResponseParser::YajlAdapter.new
7
+ end
8
+
9
+ it "should be JSON format" do
10
+ @parser.format.should == 'json'
11
+ end
12
+
13
+ describe "parsing" do
14
+ before do
15
+ @json = %Q!{"result":{"rank":1,"score":1.9,"tags":["hooray","for","dolphins"],"message":"woohoo"}}!
16
+ @hash = @parser.parse(@json)
17
+ end
18
+
19
+ it "should return a hash" do
20
+ @hash.should be_an_instance_of(Hash)
21
+ end
22
+
23
+ it "should turn the object root into a hash" do
24
+ @hash.should have_key('result')
25
+ end
26
+
27
+ it "should turn array elements into an array" do
28
+ @hash['result'].should have_key('tags')
29
+ @hash['result']['tags'].should be_an_instance_of(Array)
30
+ end
31
+
32
+ it "should preserve the order of elements in an array" do
33
+ @hash['result']['tags'].should == ["hooray", "for", "dolphins"]
34
+ end
35
+
36
+ it "should turn each object attribute into a key/value pair in the hash" do
37
+ @hash['result'].should have_key('rank')
38
+ @hash['result']['rank'].should be_an_instance_of(Fixnum)
39
+ end
40
+
41
+ it "should turn attributes with strictly numeric values into integers" do
42
+ @hash['result']['rank'].should eql(1)
43
+ end
44
+
45
+ it "should turn attributes with content representing floats into floats" do
46
+ @hash['result']['score'].should eql(1.9)
47
+ end
48
+
49
+ it "should turn strings into strings" do
50
+ @hash['result']['message'].should be_an_instance_of(String)
51
+ @hash['result']['message'].should == "woohoo"
52
+ end
53
+ end
54
+
55
+ describe "unparsing" do
56
+ before do
57
+ @obj = {
58
+ 'result' => {
59
+ 'tags' => [ 'hooray', 'for', 'dolphins' ],
60
+ 'message' => 'woohoo',
61
+ 'rank' => 1,
62
+ 'score' => 1.9
63
+ }
64
+ }
65
+ @json_str = @parser.unparse(@obj)
66
+ @json = ::JSON.parse(@json_str)
67
+ end
68
+
69
+ it "should turn hashes into objects with attributes" do
70
+ @json.should have_key('result')
71
+ @json['result'].should_not be_empty
72
+ end
73
+
74
+ it "should turn arrays into arrays" do
75
+ @json['result'].should have_key('tags')
76
+ @json['result']['tags'].should be_an_instance_of(Array)
77
+ end
78
+
79
+ it "should turn strings into text content" do
80
+ @json['result'].should have_key('message')
81
+ @json['result']['message'].should == "woohoo"
82
+ end
83
+
84
+ it "should turn integers into integers" do
85
+ @json_str.should =~ /"rank"\s*:\s*1/
86
+ end
87
+
88
+ it "should turn floats into floats" do
89
+ @json_str.should =~ /"score"\s*:\s*1\.9/
90
+ end
91
+ end
92
+
93
+ end
data/spec/spec_helper.rb CHANGED
@@ -7,4 +7,4 @@ Spec::Runner.configure do |config|
7
7
  config.include WebMock
8
8
  end
9
9
 
10
- require File.dirname(__FILE__) + '/../lib/animoto/client'
10
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/animoto/client')
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: animoto
3
3
  version: !ruby/object:Gem::Version
4
- hash: -1710980559
4
+ hash: -1710980402
5
5
  prerelease: true
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
9
  - 0
10
- - alpha3
11
- version: 0.0.0.alpha3
10
+ - alpha4
11
+ version: 0.0.0.alpha4
12
12
  platform: ruby
13
13
  authors:
14
14
  - Animoto
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-09-07 00:00:00 -04:00
19
+ date: 2010-09-08 00:00:00 -04:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -53,14 +53,24 @@ files:
53
53
  - ./lib/animoto/directing_and_rendering_manifest.rb
54
54
  - ./lib/animoto/directing_job.rb
55
55
  - ./lib/animoto/directing_manifest.rb
56
+ - ./lib/animoto/dynamic_class_loader.rb
56
57
  - ./lib/animoto/errors.rb
57
58
  - ./lib/animoto/footage.rb
59
+ - ./lib/animoto/http_engine.rb
60
+ - ./lib/animoto/http_engines/curl_adapter.rb
61
+ - ./lib/animoto/http_engines/net_http_adapter.rb
62
+ - ./lib/animoto/http_engines/patron_adapter.rb
63
+ - ./lib/animoto/http_engines/rest_client_adapter.rb
64
+ - ./lib/animoto/http_engines/typhoeus_adapter.rb
58
65
  - ./lib/animoto/image.rb
59
66
  - ./lib/animoto/job.rb
60
67
  - ./lib/animoto/manifest.rb
61
68
  - ./lib/animoto/rendering_job.rb
62
69
  - ./lib/animoto/rendering_manifest.rb
63
70
  - ./lib/animoto/resource.rb
71
+ - ./lib/animoto/response_parser.rb
72
+ - ./lib/animoto/response_parsers/json_adapter.rb
73
+ - ./lib/animoto/response_parsers/yajl_adapter.rb
64
74
  - ./lib/animoto/song.rb
65
75
  - ./lib/animoto/standard_envelope.rb
66
76
  - ./lib/animoto/storyboard.rb
@@ -76,11 +86,14 @@ files:
76
86
  - ./spec/animoto/directing_job_spec.rb
77
87
  - ./spec/animoto/directing_manifest_spec.rb
78
88
  - ./spec/animoto/footage_spec.rb
89
+ - ./spec/animoto/http_engine_spec.rb
79
90
  - ./spec/animoto/image_spec.rb
80
91
  - ./spec/animoto/job_spec.rb
81
92
  - ./spec/animoto/rendering_job_spec.rb
82
93
  - ./spec/animoto/rendering_manifest_spec.rb
83
94
  - ./spec/animoto/resource_spec.rb
95
+ - ./spec/animoto/response_parsers/json_adapter_spec.rb
96
+ - ./spec/animoto/response_parsers/yajl_adapter_spec.rb
84
97
  - ./spec/animoto/song_spec.rb
85
98
  - ./spec/animoto/standard_envelope_spec.rb
86
99
  - ./spec/animoto/storyboard_spec.rb