animoto 0.0.0.alpha3 → 0.0.0.alpha4

Sign up to get free protection for your applications and to get access to all the features.
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