animoto 0.1.1.beta1 → 1.0.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.md +8 -4
- data/lib/animoto.rb +1 -1
- data/lib/animoto/assets/base.rb +1 -1
- data/lib/animoto/assets/footage.rb +1 -1
- data/lib/animoto/assets/image.rb +1 -1
- data/lib/animoto/assets/song.rb +1 -1
- data/lib/animoto/assets/title_card.rb +1 -1
- data/lib/animoto/client.rb +21 -27
- data/lib/animoto/http_engines/base.rb +15 -7
- data/lib/animoto/http_engines/curl_adapter.rb +3 -4
- data/lib/animoto/http_engines/net_http_adapter.rb +3 -5
- data/lib/animoto/http_engines/patron_adapter.rb +2 -4
- data/lib/animoto/http_engines/rest_client_adapter.rb +1 -3
- data/lib/animoto/http_engines/typhoeus_adapter.rb +1 -3
- data/lib/animoto/manifests/base.rb +1 -1
- data/lib/animoto/manifests/directing.rb +1 -1
- data/lib/animoto/manifests/directing_and_rendering.rb +55 -28
- data/lib/animoto/manifests/rendering.rb +7 -6
- data/lib/animoto/resources/base.rb +8 -8
- data/lib/animoto/resources/jobs/base.rb +2 -2
- data/lib/animoto/resources/jobs/directing.rb +7 -2
- data/lib/animoto/resources/jobs/directing_and_rendering.rb +34 -8
- data/lib/animoto/resources/jobs/rendering.rb +17 -3
- data/lib/animoto/resources/storyboard.rb +20 -4
- data/lib/animoto/resources/video.rb +33 -11
- data/lib/animoto/response_parsers/base.rb +13 -8
- data/lib/animoto/response_parsers/json_adapter.rb +2 -4
- data/lib/animoto/response_parsers/yajl_adapter.rb +2 -4
- data/lib/animoto/support/content_type.rb +1 -0
- data/lib/animoto/support/coverable.rb +1 -1
- data/lib/animoto/support/dynamic_class_loader.rb +82 -141
- data/lib/animoto/support/errors.rb +4 -0
- data/lib/animoto/support/hash.rb +25 -0
- data/lib/animoto/support/standard_envelope.rb +70 -19
- data/lib/animoto/support/string.rb +31 -0
- data/lib/animoto/support/visual.rb +1 -1
- data/spec/animoto/client_spec.rb +1 -25
- data/spec/animoto/http_engines/base_spec.rb +1 -1
- data/spec/animoto/resources/base_spec.rb +8 -7
- data/spec/spec_helper.rb +1 -0
- metadata +12 -21
- data/lib/animoto/callbacks/base.rb +0 -45
- data/lib/animoto/callbacks/directing.rb +0 -7
- data/lib/animoto/callbacks/directing_and_rendering.rb +0 -7
- data/lib/animoto/callbacks/rendering.rb +0 -7
- data/spec/animoto/callbacks/base_spec.rb +0 -76
- data/spec/animoto/callbacks/directing_and_rendering_spec.rb +0 -5
- data/spec/animoto/callbacks/directing_spec.rb +0 -5
- data/spec/animoto/callbacks/rendering_spec.rb +0 -5
@@ -19,7 +19,7 @@ module Animoto
|
|
19
19
|
|
20
20
|
# Returns a representation of this visual as a Hash.
|
21
21
|
#
|
22
|
-
# @return [Hash
|
22
|
+
# @return [Hash{String=>Object}] this visual as a Hash
|
23
23
|
def to_hash
|
24
24
|
hash = super rescue {}
|
25
25
|
hash['cover'] = cover? unless @cover.nil?
|
@@ -1,166 +1,107 @@
|
|
1
1
|
module Animoto
|
2
2
|
module Support
|
3
|
-
|
4
|
-
# Sets the search path where classes to be dynamically loaded can be found.
|
5
|
-
# This method is meant to be used in the following manner:
|
6
|
-
# class Thing
|
7
|
-
# extend Animoto::Support::DynamicClassLoader("some/search/path")
|
8
|
-
# end
|
9
|
-
# Behind the scenes, this method sets a temporary instance variable on the
|
10
|
-
# DynamicClassLoader module which will be used (and then removed) the next time
|
11
|
-
# the module is extended into a class. Therefore, don't call this method if you
|
12
|
-
# don't intend to immediately extend the DynamicClassLoader module into the class
|
13
|
-
# that the search path given applies to, else strange behaviors could emerge.
|
14
|
-
#
|
15
|
-
# @param [String] search_path the path where classes to be dynamically loaded are located
|
16
|
-
# @return [Module] the DynamicClassLoader module
|
17
|
-
def self.DynamicClassLoader search_path
|
18
|
-
self::DynamicClassLoader.instance_variable_set(:@carryover_search_path, search_path)
|
19
|
-
self::DynamicClassLoader
|
20
|
-
end
|
21
|
-
|
22
|
-
# This module is a helper for families of "plugins" which rely on external libraries.
|
23
|
-
# It loads classes (and therefore their dependencies) on demand, obviating the need
|
24
|
-
# to require every possible plugin dependency all the time or the need for the end-user
|
25
|
-
# to explicitly require which certain plugin they want to use or know anything about the
|
26
|
-
# specific directory structure where plugins are located.
|
27
|
-
#
|
28
|
-
# A class gets dynamically loaded in one of two ways: either a const_missing hook, or by
|
29
|
-
# symbol-reference. Both methods interpolate the name of the file to load based on
|
30
|
-
# their inputs.
|
31
|
-
#
|
32
|
-
# Referencing a class name under a module that is extended by the DynamicClassLoader will
|
33
|
-
# search in the pre-set search path for a file matching the underscored version of the
|
34
|
-
# class' name. For example, assuming the Thing module is extended with DynamicClassLoader
|
35
|
-
# and its search path is "~/.things/plugins", referencing Thing::KlassName will look for
|
36
|
-
# a file called "klass_name.rb" in "~/.things/plugins", returning the class object if it's
|
37
|
-
# found or raising an exception if it isn't.
|
38
|
-
#
|
39
|
-
# Using a symbol reference is a little trickier. When a symbol is given to the [] method,
|
40
|
-
# the name will be transformed (explained later) and a file in the search path with the
|
41
|
-
# transformed name will be loaded, or an exception will be raised if the file isn't found.
|
42
|
-
#
|
43
|
-
# Name transformations are handled by three methods: symbol_reference_prefix,
|
44
|
-
# symbol_reference_transform, and symbol_reference_prefix. As their names suggest,
|
45
|
-
# symbol_reference_prefix and symbol_reference_suffix add the strings returned to the front
|
46
|
-
# or end of the symbol name. symbol_reference_transform takes the string version of the symbol
|
47
|
-
# and applies an arbitrary transform on it.
|
48
|
-
#
|
49
|
-
# After the file matching the transformed name is found, the class itself still has to
|
50
|
-
# get loaded. While it's easy enough to take a class name and produce an associated file name,
|
51
|
-
# the same is not true in reverse (for example, "HTTPEngine" => "http_engine" is straightforward,
|
52
|
-
# but there's not enough information to turn "http_engine" into "HTTPEngine" without enforcing
|
53
|
-
# class-naming restrictions). To get around this, any class that could get dynamically loaded
|
54
|
-
# should add itself to its containing module's adapter map, using a key that matches the class'
|
55
|
-
# file name.
|
56
3
|
module DynamicClassLoader
|
57
|
-
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
4
|
+
|
5
|
+
# Retrieves the adapter class that the name corresponds to. The name in case- and
|
6
|
+
# punctuation-insensitive, meaning "SomeModule", "some_module", and "somemodule"
|
7
|
+
# are all treated as the same.
|
61
8
|
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
9
|
+
# Note that classes defined as an #adapter for this module will be autoloaded upon
|
10
|
+
# successful reference, so there's no need to ensure that the file defining the class
|
11
|
+
# is already loaded when calling this method.
|
12
|
+
#
|
13
|
+
# @param [String] name the name of the module to access
|
14
|
+
# @return [Class] the class
|
15
|
+
# @raise [NameError] if the class doesn't exist.
|
16
|
+
def [] name
|
17
|
+
if klass = adapter_map[normalize_const_name(name)]
|
18
|
+
const_get(klass)
|
68
19
|
else
|
69
|
-
|
20
|
+
raise NameError, "uninitialized constant #{self.name}::#{name}"
|
70
21
|
end
|
71
22
|
end
|
72
|
-
|
73
|
-
# If a reference is made to a class under this one that hasn't been initialized yet,
|
74
|
-
# this will attempt to require a file with that class' name (underscored). If one is
|
75
|
-
# found, and the file defines the class requested, will return that class object.
|
76
|
-
#
|
77
|
-
# @param [Symbol] const the uninitialized class' name
|
78
|
-
# @return [Class] the class found
|
79
|
-
# @raise [NameError] if the file defining the class isn't found, or if the file required
|
80
|
-
# doesn't define the class.
|
81
|
-
def const_missing const
|
82
|
-
load_missing_file_named underscore_class_name(const.to_s)
|
83
|
-
const_defined?(const) ? const_get(const) : super
|
84
|
-
end
|
85
23
|
|
86
|
-
|
87
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
# Gets or sets the path where this module will look for files it autoloads.
|
88
27
|
#
|
89
|
-
# @param [
|
90
|
-
# @return [
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
adapter_map[engine]
|
28
|
+
# @param [String] path the path; if path is nil, returns the old path
|
29
|
+
# @return [String] the path
|
30
|
+
def dynamic_class_path path = nil
|
31
|
+
@dynamic_class_path = path if path
|
32
|
+
@dynamic_class_path
|
95
33
|
end
|
96
34
|
|
97
|
-
|
98
|
-
|
99
|
-
# When a class is loaded by symbol reference, the return value of this method
|
100
|
-
# will be prepended to the symbol to form the file name. Defaults to a blank
|
101
|
-
# string.
|
35
|
+
# A map of normalized class names to the actual class names.
|
102
36
|
#
|
103
|
-
# @return [String] the
|
104
|
-
def
|
105
|
-
|
37
|
+
# @return [Hash{String=>Symbol}] the map
|
38
|
+
def adapter_map
|
39
|
+
@adapter_map ||= {}
|
106
40
|
end
|
107
|
-
|
108
|
-
#
|
109
|
-
# will
|
110
|
-
#
|
111
|
-
# Note, the word 'symbol' is used, but in reality it's a String and won't need
|
112
|
-
# typecasting.
|
41
|
+
|
42
|
+
# Declare a class as an 'adapter' for this module. Given a name, for example
|
43
|
+
# "ChunkyBacon", it will expect the adapter class to be called ChunkyBaconAdapter
|
44
|
+
# and be defined in "chunky_bacon_adapter.rb" in this module's #dynamic_class_path.
|
113
45
|
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
# When a class is loaded by symbol reference, the return value of this method
|
121
|
-
# will be appended to the symbol to form the file name. Defaults to '_adapter'.
|
46
|
+
# Once a class is declared as an adapter, will set this module to autoload the
|
47
|
+
# file on first reference, and sets a key in the #adapter_map mapping the normalized
|
48
|
+
# name of the adapter (in the case of ChunkyBaconAdapter, the normalized name is
|
49
|
+
# "chunkybacon").
|
122
50
|
#
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
# Requires the file in the search path with the given name (minus trailing '.rb').
|
51
|
+
# The class can be accessed by its normalized name through the #[] method, or simply
|
52
|
+
# referenced directly through a #const_missing hook. Using #const_missing will normalize
|
53
|
+
# the name of the constant and look it up in the #adapter_map, so capitalization (or even
|
54
|
+
# punctuation) need not be exact.
|
129
55
|
#
|
130
|
-
# @
|
56
|
+
# @example Setting up a module and a dynamically-loaded adapter
|
57
|
+
# module FunderfulWonderment
|
58
|
+
# extend Animoto::Support::DynamicClassLoader
|
59
|
+
# adapter 'ChunkyBacon'
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Any reference to FunderfulWonderment::ChunkyBaconAdapter will autoload the class file
|
63
|
+
# and return the ChunkyBaconAdapter. As will FunderfulWonderment::Chunky_bacon_ADAPTER or
|
64
|
+
# any other name whose normalized name maps to "chunkybacon".
|
65
|
+
#
|
66
|
+
# @param [String] name the name of the adapter class, minus the word "Adapter" at the end.
|
131
67
|
# @return [void]
|
132
|
-
# @
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
68
|
+
# @see #[]
|
69
|
+
# @see #const_missing
|
70
|
+
def adapter name
|
71
|
+
klass_name = :"#{name}Adapter"
|
72
|
+
klass_file = klass_name.to_s.underscore
|
73
|
+
self.autoload klass_name, "#{dynamic_class_path}/#{klass_file}"
|
74
|
+
adapter_map[normalize_const_name(klass_file)] = klass_name
|
137
75
|
end
|
138
|
-
|
139
|
-
#
|
140
|
-
# whatever was declared as the search path when this module was extended. The path
|
141
|
-
# should not end with a trailing slash.
|
76
|
+
|
77
|
+
# Normalizes the name of the missing constant and tries to look it up in the #adapter_map.
|
142
78
|
#
|
143
|
-
# @
|
144
|
-
|
145
|
-
|
79
|
+
# @param [Symbol] name the name of the constant, as a symbol
|
80
|
+
# @return [Class] the class the name corresponds to
|
81
|
+
# @raise [NameError] if the class can't be found
|
82
|
+
def const_missing name
|
83
|
+
if klass_name = adapter_map[normalize_const_name(name)]
|
84
|
+
klass = const_get(klass_name)
|
85
|
+
const_set name, klass
|
86
|
+
klass
|
87
|
+
end
|
88
|
+
rescue NameError
|
89
|
+
super
|
146
90
|
end
|
147
91
|
|
148
|
-
#
|
92
|
+
# Normalizes a constant name by downcasing it, stripping off the trailing word
|
93
|
+
# 'adapter', and removing all non-letter characters. Note that the name should
|
94
|
+
# be a valid Ruby constant name.
|
149
95
|
#
|
150
|
-
# @
|
151
|
-
|
152
|
-
@adapter_map ||= {}
|
153
|
-
end
|
154
|
-
|
155
|
-
# Turns a camel-cased class name into an underscored version. Will only affect the base name
|
156
|
-
# of the class, so, for example, Animoto::DirectingAndRenderingJob becomes 'directing_and_rendering_job'
|
96
|
+
# @example
|
97
|
+
# normalize_const_name(:ChunkyBaconAdapter) # => "chunkybacon"
|
157
98
|
#
|
158
|
-
# @param [
|
159
|
-
# @return [String] the
|
160
|
-
def
|
161
|
-
|
162
|
-
klass_name.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
|
99
|
+
# @param [String, Symbol] name the name of the class as a String or Symbol
|
100
|
+
# @return [String] the normalized name
|
101
|
+
def normalize_const_name name
|
102
|
+
name.to_s.downcase[/^([\w_]+?)(?:_?adapter)?$/,1].gsub(/[^a-z]/,'')
|
163
103
|
end
|
104
|
+
|
164
105
|
end
|
165
|
-
end
|
166
|
-
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Animoto
|
2
|
+
module Support
|
3
|
+
module Hash
|
4
|
+
|
5
|
+
# Returns a new hash with only the listed keys from this hash.
|
6
|
+
#
|
7
|
+
# @param [Array<Object>] keys the keys to include
|
8
|
+
# @return [Hash{Object=>Object}] a new hash with only the listed keys
|
9
|
+
def only *keys
|
10
|
+
self.delete_if { |k,v| !keys.include?(k) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a new hash with all keys from this hash except the listed ones.
|
14
|
+
#
|
15
|
+
# @param [Array<Object>] keys the keys to exclude
|
16
|
+
# @return [Hash{Object=>Object}] a new hash without the listed keys
|
17
|
+
def except *keys
|
18
|
+
self.delete_if { |k,v| keys.include?(v) }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Hash.__send__ :include, Animoto::Support::Hash
|
@@ -4,6 +4,9 @@ module Animoto
|
|
4
4
|
|
5
5
|
# When included into a class, also extends ClassMethods, inludes InstanceMethods, and
|
6
6
|
# includes Support::ContentType
|
7
|
+
#
|
8
|
+
# @param [Module] base the module this is being included into
|
9
|
+
# @return [void]
|
7
10
|
def self.included base
|
8
11
|
base.class_eval {
|
9
12
|
include Animoto::Support::StandardEnvelope::InstanceMethods
|
@@ -11,12 +14,31 @@ module Animoto
|
|
11
14
|
include Animoto::Support::ContentType
|
12
15
|
}
|
13
16
|
end
|
17
|
+
|
18
|
+
# Scans the structure of a standard envelope for the 'payload key' and tries to find
|
19
|
+
# the matching class for that key. Returns nil if the class isn't found (instead of,
|
20
|
+
# say, raising a NameError).
|
21
|
+
#
|
22
|
+
# @param [Hash{String=>Object}] envelope a 'standard envelope' hash
|
23
|
+
# @return [Class, nil] the class, or nil if either the payload key or the class couldn't be found
|
24
|
+
def self.find_class_for envelope
|
25
|
+
if payload_key = ((envelope['response'] || {})['payload'] || {}).keys.first
|
26
|
+
klass_name = payload_key.camelize
|
27
|
+
if /(?:Job|Callback)$/ === klass_name
|
28
|
+
Animoto::Jobs::const_get(klass_name) if Animoto::Jobs::const_defined?(klass_name)
|
29
|
+
else
|
30
|
+
Animoto::Resources::const_get(klass_name) if Animoto::Resources::const_defined?(klass_name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue NameError
|
34
|
+
nil
|
35
|
+
end
|
14
36
|
|
15
37
|
module InstanceMethods
|
16
38
|
# Calls the class-level unpack_standard_envelope method.
|
17
|
-
# @return [Hash
|
39
|
+
# @return [Hash{Symbol=>Object}]
|
18
40
|
# @see Animoto::Support::StandardEnvelope::ClassMethods#unpack_standard_envelope
|
19
|
-
def unpack_standard_envelope body
|
41
|
+
def unpack_standard_envelope body
|
20
42
|
self.class.unpack_standard_envelope body
|
21
43
|
end
|
22
44
|
|
@@ -30,18 +52,14 @@ module Animoto
|
|
30
52
|
end
|
31
53
|
|
32
54
|
module ClassMethods
|
33
|
-
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
55
|
+
|
56
|
+
# Get or set the payload key for this class. When building an instance of this
|
57
|
+
# class from a response body, the payload key determines which object in the
|
58
|
+
# response payload holds the attributes for the instance.
|
37
59
|
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# @overload payload_key()
|
42
|
-
# Returns the payload key for this class.
|
43
|
-
#
|
44
|
-
# @return [String] the key
|
60
|
+
# @param [String, nil] key the key to set
|
61
|
+
# @return [String] the payload key (the old key if no argument was given), or
|
62
|
+
# the inferred payload key if not set
|
45
63
|
def payload_key key = nil
|
46
64
|
@payload_key = key if key
|
47
65
|
@payload_key || infer_content_type
|
@@ -49,17 +67,50 @@ module Animoto
|
|
49
67
|
|
50
68
|
protected
|
51
69
|
|
70
|
+
# Extracts the base payload element from the envelope.
|
71
|
+
#
|
72
|
+
# @param [Hash{String=>Object}] body the response body
|
73
|
+
# @return [Hash{String=>Object}] the base payload at $.response.payload
|
74
|
+
def unpack_base_payload body
|
75
|
+
(body['response'] || {})['payload'] || {}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Extracts the base status element from the envelope.
|
79
|
+
#
|
80
|
+
# @param [Hash{String=>Object}] body the response body
|
81
|
+
# @return [Hash{String=>Object}] the status element at $.response.status
|
82
|
+
def unpack_status body
|
83
|
+
(body['response'] || {})['status'] || {}
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns the payload, which is the part of the response classes will find the most
|
87
|
+
# interesting.
|
88
|
+
#
|
89
|
+
# @param [Hash{String=>Object}] body the response body
|
90
|
+
# @return [Hash{String=>Object}] the payload at $.response.payload[payload_key]
|
91
|
+
def unpack_payload body
|
92
|
+
unpack_base_payload(body)[payload_key] || {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the links element of the payload, or an empty hash if it doesn't exist.
|
96
|
+
#
|
97
|
+
# @param [Hash{String=>Object}] body the response body
|
98
|
+
# @return [Hash{String=>Object}] the links at $.response.payload[payload_key].links
|
99
|
+
def unpack_links body
|
100
|
+
unpack_payload(body)['links'] || {}
|
101
|
+
end
|
102
|
+
|
52
103
|
# Extracts common elements from the 'standard envelope' and returns them in a
|
53
104
|
# easier-to-work-with hash.
|
54
105
|
#
|
55
|
-
# @param [Hash
|
56
|
-
# @return [Hash
|
57
|
-
def unpack_standard_envelope body
|
106
|
+
# @param [Hash{String=>Object}] body the body, structured in the 'standard envelope'
|
107
|
+
# @return [Hash{Symbol=>Object}] the nicer hash
|
108
|
+
def unpack_standard_envelope body
|
58
109
|
{
|
59
|
-
:url
|
60
|
-
:errors =>
|
110
|
+
:url => unpack_links(body)['self'],
|
111
|
+
:errors => unpack_status(body)['errors'] || []
|
61
112
|
}
|
62
|
-
end
|
113
|
+
end
|
63
114
|
end
|
64
115
|
end
|
65
116
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Animoto
|
2
|
+
module Support
|
3
|
+
module String
|
4
|
+
|
5
|
+
# Transforms this string from underscore-form to camel case. Capitalizes
|
6
|
+
# the first letter.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# "this_is_a_underscored_string" # => "ThisIsAUnderscoredString"
|
10
|
+
#
|
11
|
+
# @return [String] the camel case form of this string
|
12
|
+
def camelize
|
13
|
+
self.gsub(/(?:^|_)(.)/) { $1.upcase }
|
14
|
+
end
|
15
|
+
|
16
|
+
# Transforms this string from camel case to underscore-form. Downcases the
|
17
|
+
# entire string.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# "ThisIsACamelCasedString" # => "this_is_a_camel_cased_string"
|
21
|
+
#
|
22
|
+
# @return [String] the underscored form of this string
|
23
|
+
def underscore
|
24
|
+
self.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
String.__send__ :include, Animoto::Support::String
|