pomade 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,8 @@
1
- require "pomade/version"
2
- require "pomade/publisher"
1
+ require 'pomade/version'
2
+ require 'pomade/exceptions'
3
+ require 'pomade/publisher'
4
+
5
+ ##
6
+ # A ruby library for Pomegranate.
7
+ module Pomade
8
+ end
@@ -0,0 +1,19 @@
1
+ module Pomade
2
+ # Errors thrown from Pomegranate
3
+ class ResponseError < StandardError; end
4
+
5
+ # If an asset's keys do not match up
6
+ class InvalidAssetKeys < StandardError; end
7
+
8
+ # If an asset's type is invalid
9
+ class InvalidAssetType < StandardError; end
10
+
11
+ # If a URL's response is not OK
12
+ class BadAssetValueURL < StandardError; end
13
+
14
+ # If an :image asset's value is not a valid URL
15
+ class InvalidImageValue < StandardError; end
16
+
17
+ # If an :video asset's value is not a valid URL
18
+ class InvalidVideoValue < StandardError; end
19
+ end
@@ -3,35 +3,30 @@ require "ntlm/http"
3
3
  require "nokogiri"
4
4
 
5
5
  module Pomade
6
+ ##
7
+ # Handles all interactions to Pomegranate.
6
8
  class Publisher
7
- # Public: Creates a new instance of Publisher that pushes records to
8
- # Pomegranate.
9
+ ##
10
+ # Creates a new instance of +Publisher+ that pushes records to Pomegranate.
9
11
  #
10
- # Parameters:
11
- #
12
- # subdomain - [string] The subdomain for the Pomegranate instance that
13
- # you'd like to connect to.
14
- # username - [string] The username used for connecting to your instance.
15
- # password - [string] The password used for connecting to your instance.
16
- # client_id - [string] Your client ID.
17
- # opts - [hash] (optional) Additional options.
18
- #
19
- # Available options are:
20
- # host - [string] The host (domain name) that Pomegranate lives on.
21
- # pathname - [string] The path that is used for interacting with
22
- # Pomegranate.
23
- # time_format - [string] (strftime) change the layout of the timestamp
24
- # domain - [string] NTLM login domain.
25
- # debug - [boolean] Turns on debug mode. This will print out
26
- # any activity.
27
- #
28
- # Returns:
12
+ # == Parameters
29
13
  #
30
- # Returns an instance of Pomade::Publisher
14
+ # * *subdomain* _(string)_ - The subdomain for the Pomegranate instance that you'd like to connect to.
15
+ # * *username* _(string)_ - The username used for connecting to your isntance.
16
+ # * *password* _(string)_ - The password used for connecting to your isntance.
17
+ # * *client_id* _(string)_ - Your client ID.
18
+ # * *opts* _(hash, optional)_ - Additional options. Available options are:
19
+ # * *:host* _(string)_ - The host (domain name) that your Pomegranate instance lives on.
20
+ # * *:pathname* _(string)_ - The path that is used for interacting with assets.
21
+ # * *:time_format* _(strftime)_ - Change the layout of the timestamp that is posted to your instance.
22
+ # * *:domain* _(string)_ - NTLM login domain.
31
23
  #
32
- # Example:
24
+ # == Returns
25
+ # An instance of +Pomade::Publisher+
33
26
  #
34
- # @pom = Pom.new('my-subdomain', 'myusername', 'mypassword', 'XX')
27
+ # == Example
28
+ #
29
+ # @pom = Pomade::Publisher.new('my-subdomain', 'myusername', 'mypassword', 'XX')
35
30
  def initialize(subdomain, username, password, client_id, opts = {})
36
31
  @subdomain = subdomain
37
32
  @username = username
@@ -44,42 +39,64 @@ module Pomade
44
39
  @options[:pathname] = opts[:pathname] || '/p/p.svc/Assets/'
45
40
  @options[:time_format] = opts[:time_format] || "%Y-%m-%dT%H:%M:%SZ"
46
41
  @options[:domain] = opts[:domain] || nil
47
- @options[:debug] = opts[:debug] || false
48
42
  end
49
43
 
50
- # Public: Publishes an array of assets to Pomegranate
44
+ ##
45
+ # Publishes an array of assets to Pomegranate and returns the results in a +hash+.
51
46
  #
52
- # Parameters:
47
+ # == Parameters
53
48
  #
54
- # assets - [array] A collection of assets. Each item consits of a hash with
55
- # three keys: { target: "", type: "", value: "" }
49
+ # * *assets* _(array)_ - A collection of assets. Each item consists of a hash with three keys: +:target+, +:type+ and +:value+. The values for keys +:target+ and +:value+ are both strings while the +:type+ key's value is a symbol. Available values are:
50
+ # * +:image+ -- An +IMAGE+ type asset
51
+ # * +:video+ -- A +VIDEO+ type asset
52
+ # * +:text+ -- A +TEXT+ type asset
56
53
  #
57
- # Returns:
54
+ # == Returns
58
55
  #
59
- # A hash containing two keys: record_id and assets.
56
+ # A +hash+ containing two keys: +record_id+ and +assets+.
60
57
  #
61
- # Example:
58
+ # == Example
62
59
  #
63
60
  # records = [
64
- # { target: "XX~USERNAME", type: "TEXT", value: "jakebellacera"},
65
- # { target: "XX~AVATAR", type: "IMAGE", value: "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png"}
61
+ # { target: "XX~username", type: :text, value: "jakebellacera"},
62
+ # { target: "XX~avatar", type: :image, value: "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png"}
66
63
  # ]
64
+ #
67
65
  # @pom.publish(records)
68
- # #=> {
69
- # record_id: "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1",
70
- # assets: [
71
- # {"AssetID"=>"9a24c8e2-1066-42fb-be1c-697c5ead476d", "AssetData"=>"jakebellacera", "AssetType"=>"TEXT", "Target"=>"NS~USERNAME", "Client"=>"XX", "Status"=>"APPROVED", "AssetMeta"=>"", "AssetRecordID"=>"XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"},
72
- # {"AssetID"=>"9a24c8e2-1066-42fb-be1c-697c5ead476d", "AssetData"=>"http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png", "AssetType"=>"IMAGE", "Target"=>"XX~Avatar", "Client"=>"XX", "Status"=>"APPROVED", "AssetMeta"=>"", "AssetRecordID"=>"XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"}
73
- # ]
66
+ # # =>
67
+ # {
68
+ # record_id: "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1",
69
+ # assets: [
70
+ # {
71
+ # "AssetID" => "9a24c8e2-1066-42fb-be1c-697c5ead476d",
72
+ # "AssetData" => "jakebellacera",
73
+ # "AssetType" => "TEXT",
74
+ # "Target" => "XX~username",
75
+ # "Client" => "XX",
76
+ # "Status" => "APPROVED",
77
+ # "AssetMeta" => "",
78
+ # "AssetRecordID" => "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"
79
+ # },
80
+ # {
81
+ # "AssetID" => "9a24c8e2-1066-42fb-be1c-697c5ead476d",
82
+ # "AssetData" => "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png",
83
+ # "AssetType" => "IMAGE",
84
+ # "Target" => "XX~avatar",
85
+ # "Client" => "XX",
86
+ # "Status" => "APPROVED",
87
+ # "AssetMeta" => "",
88
+ # "AssetRecordID" => "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"
74
89
  # }
90
+ # ]
91
+ # }
75
92
  def publish(assets)
76
- puts "Available options: #{@options}" if @options[:debug]
77
- @time = Time.now.strftime(@options[:time_format])
78
93
  @record_id = generate_record_id
94
+ @time = Time.now.strftime(@options[:time_format])
79
95
 
96
+ # Build our XMLs
80
97
  xmls = []
81
- assets.each do |r|
82
- xmls << build_xml(@record_id, r[:target], r[:type].upcase, r[:value])
98
+ validate(assets).each do |r|
99
+ xmls << build_xml(r[:target], r[:type].to_s.upcase, r[:value])
83
100
  end
84
101
 
85
102
  return {
@@ -88,41 +105,90 @@ module Pomade
88
105
  }
89
106
  end
90
107
 
91
- # Public: Generates a record ID
92
- #
93
- # Parameters:
108
+ ##
109
+ # Validates an array of assets.
94
110
  #
95
- # None
111
+ # == Parameters
96
112
  #
97
- # Returns:
113
+ # * *assets* _(array)_ - A collection of assets. Each item consists of a hash with three keys: +:target+, +:type+ and +:value+. The values for keys +:target+ and +:value+ are both strings while the +:type+ key's value is a symbol. Available values are:
114
+ # * +:image+ -- An +IMAGE+ type asset
115
+ # * +:video+ -- A +VIDEO+ type asset
116
+ # * +:text+ -- A +TEXT+ type asset
117
+ #
118
+ # == Returns
119
+ #
120
+ # The +array+ of assets.
98
121
  #
99
- # Returns a string containing the client_id with a UUID appended to it.
122
+ # == Example
123
+ #
124
+ # records = [
125
+ # { target: "XX~USERNAME", type: :text, value: "jakebellacera"},
126
+ # { target: "XX~AVATAR", type: :image, value: "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png"}
127
+ # ]
128
+ #
129
+ # @pom.validate(records)
130
+ # # =>
131
+ # [
132
+ # { target: "XX~USERNAME", type: :text, value: "jakebellacera"},
133
+ # { target: "XX~AVATAR", type: :image, value: "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png"}
134
+ # ]
135
+ def validate(assets)
136
+ available_keys = [:target, :type, :value].sort
137
+
138
+ assets.each do |a|
139
+ raise InvalidAssetKeys, "Each asset should only contain the keys: :target, :type, and :value." unless (a.keys & available_keys).sort == available_keys
140
+ raise InvalidAssetType, "Invalid asset type. Available choices are: :text, :image and :video." unless [:text, :image, :video].include?(a[:type])
141
+ test(a)
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ ##
148
+ # Generates a +SecureRandom.uuid+ (GUID) with the +client_id+ appended to it.
100
149
  def generate_record_id
101
150
  @client_id + '-' + SecureRandom.uuid
102
151
  end
103
152
 
104
- private
153
+ ##
154
+ # Tests to see if an asset's +:value+ is correct in correlation to its +:type+.
155
+ def test(asset)
156
+ # If the value is a URL...
157
+ if (asset[:type] == :image || asset[:type] == :video) && url?(asset[:value])
158
+ raise BadAssetValueURL, "Please make sure your asset's value is a valid, working URL." unless Net::HTTP.get_response(URI(asset[:value])).code.to_i == 200
159
+ else
160
+ # Since the value was not a URL, we should raise an error for any IMAGE and VIDEO types.
161
+ raise BadImageValue, "assets with an :image type should have an image URL as the value." if asset[:type] == :image
162
+ raise BadVideoValue, "assets with a :video type should have a video URL as the value." if asset[:type] == :video
163
+ end
164
+ end
105
165
 
166
+ ##
167
+ # Posts an XML to the Pomegranate instance and handles the response.
168
+ #
169
+ # *Note:* This method will fail if any requests are rejected.
170
+ #
171
+ # == Parameters
172
+ #
173
+ # * *body* _(string, XML)_ - A Pomegranate XML asset
174
+ #
175
+ # == Returns
176
+ #
177
+ # An +array+ of comiled Pomegranate assets
106
178
  def post(data)
107
179
  response_data = []
108
180
  data.each do |xml|
109
- puts xml if @options[:debug]
110
-
111
181
  req = send_request(xml)
112
182
 
113
- if req[:code] == "201"
114
- puts "===> SUCCESS!" if @options[:debug]
183
+ if req[:code].to_i.between?(200, 201)
115
184
  response_data << req[:data]
116
185
  else
117
186
  if req[:code] == "401"
118
- # TODO: Tell user that we couldn't authenticate
119
- puts "===> ERROR! Authentication" if @options[:debug]
187
+ raise ResponseError, "Could not authenticate with the Pomegranate API. Ensure that your credentials are correct."
120
188
  elsif req[:code] == "400"
121
- # TODO: Tell the user there was a formatting error
122
- puts "===> ERROR! Formatting" if @options[:debug]
189
+ raise ResponseError, "Bad asset value formatting. Please reformat and try again."
123
190
  else
124
- # TODO: Tell the user an unknown error has occured
125
- puts "===> ERROR Unknown" if @options[:debug]
191
+ raise StandardError, "An unknown error has occured."
126
192
  end
127
193
  response_data = false
128
194
  break
@@ -132,12 +198,17 @@ module Pomade
132
198
  response_data
133
199
  end
134
200
 
201
+ ##
202
+ # Sends a request to Pomegranate
203
+ #
204
+ # == Parameters
205
+ #
206
+ # * *body* _(string, XML)_ - A Pomegranate XML asset
135
207
  def send_request(body)
136
208
  status = false
137
209
  data = false
138
210
  code = ""
139
211
 
140
- puts "Initializing request for #{@subdomain + '.' + @options[:host]}" if @options[:debug]
141
212
  Net::HTTP.start("#{@subdomain}.#{@options[:host]}", 80) do |http|
142
213
  req = Net::HTTP::Post.new(@options[:pathname])
143
214
 
@@ -147,7 +218,6 @@ module Pomade
147
218
  req.ntlm_auth(@username, @options[:domain], @password)
148
219
 
149
220
  response = http.request(req)
150
- puts response.inspect if @options[:debug]
151
221
 
152
222
  code = response.code
153
223
 
@@ -161,7 +231,9 @@ module Pomade
161
231
  {:code => code, :data => data}
162
232
  end
163
233
 
164
- def build_xml(record_id, target, type, value)
234
+ ##
235
+ # Builds a Pomegranate asset XML file
236
+ def build_xml(target, type, value)
165
237
  <<-EOF.gsub(/^ {8}/, '')
166
238
  <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
167
239
  <entry
@@ -177,10 +249,10 @@ module Pomade
177
249
  <content type="application/xml">
178
250
  <m:properties>
179
251
  <d:AssetID>--</d:AssetID>
180
- <d:AssetData>#{value}</d:AssetData>
252
+ <d:AssetData>#{type === "TEXT" ? escape_xml(value) : value}</d:AssetData>
181
253
  <d:AssetType>#{type}</d:AssetType>
182
254
  <d:AssetMeta></d:AssetMeta>
183
- <d:AssetRecordID>#{record_id}</d:AssetRecordID>
255
+ <d:AssetRecordID>#{@record_id}</d:AssetRecordID>
184
256
  <d:Target>#{target}</d:Target>
185
257
  <d:Client>#{@client_id}</d:Client>
186
258
  <d:Status>APPROVED</d:Status>
@@ -190,6 +262,20 @@ module Pomade
190
262
  EOF
191
263
  end
192
264
 
265
+ ##
266
+ # Escapes any illegal XML characters
267
+ def escape_xml(string)
268
+ string.gsub!("&", "&amp;")
269
+ string.gsub!("<", "&lt;")
270
+ string.gsub!(">", "&gt;")
271
+ string.gsub!("'", "&apos;")
272
+ string.gsub!("\"", "&quot;")
273
+
274
+ return string
275
+ end
276
+
277
+ ##
278
+ # Parses XMLs and returns a hash
193
279
  def parse_xml(xml)
194
280
  parsed_xml = Nokogiri::XML(xml.gsub(/\n|\r| /, ""))
195
281
  data = {}
@@ -198,5 +284,18 @@ module Pomade
198
284
  end
199
285
  data
200
286
  end
287
+
288
+ ##
289
+ # Tests if a string is a URL
290
+ def url?(string)
291
+ begin
292
+ uri = URI.parse(string)
293
+ %w( http https ).include?(uri.scheme)
294
+ rescue URI::BadURIError
295
+ false
296
+ rescue URI::InvalidURIError
297
+ false
298
+ end
299
+ end
201
300
  end
202
301
  end
@@ -1,3 +1,3 @@
1
1
  module Pomade
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/readme.md CHANGED
@@ -33,51 +33,64 @@ These are the available options and their defaults.
33
33
  host: 'timessquare2.com', # [string] The host (domain name) that Pomegranate lives on.
34
34
  pathname: '/p/p.svc/Assets/', # [string] The path that is used for interacting with Pomegranate.
35
35
  time_format: "%Y-%m-%dT%H:%M:%SZ", # [string] (strftime) change the layout of the timestamp.
36
- login_domain: nil, # [string] NTLM login domain.
37
- debug: false # [boolean] Turns on debug mode. This will print out any activity.
36
+ login_domain: nil # [string] NTLM login domain.
38
37
  }
39
38
  ```
40
39
 
41
40
  ### Usage
42
41
 
43
- To publish assets to Pomegranate, simply create a new Publisher instance.
42
+ To publish assets to Pomegranate, simply create a new `Pomade::Publisher` instance.
44
43
 
45
44
  ```ruby
46
45
  @pom = Pomade::Publisher.new('my-subdomain', 'myusername', 'mypassword', 'XX')
47
46
  ```
48
47
 
49
- Next, you'll want to push your assets to Pomegranate. You can do this by building an array of hashes. Each item in the array represents a single asset and they each have three keys: **target**, **type** and **value**. You'll pass this array into the `publisher#push` method.
48
+ Next, you'll want to push your assets to Pomegranate. You can do this by building an array of hashes. Each item in the array represents a single asset and they each have three keys: **:target**, **:type** and **:value**. You'll pass this array into the `publish` method.
50
49
 
51
50
  ```ruby
52
51
  assets = [
53
- { target: "XX~USERNAME", type: "TEXT", value: "jakebellacera"},
54
- { target: "XX~AVATAR", type: "IMAGE", value: "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png"}
52
+ { target: "XX~username", type: :text, value: "jakebellacera"},
53
+ { target: "XX~avatar", type: :image, value: "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png"}
55
54
  ]
56
55
 
57
56
  record = @pom.publish(assets)
58
57
  ```
59
58
 
60
- The `Publisher#publish` method will return a **record**. A record is a hash with two keys: **record_id** and **assets**. The record_id is a randomly generated UUID string with your client_id prepended to it while the assets array is the posted assets. If assets is false, then the records failed to push to Pomegranate.
59
+ The `publish` method will return a **record**. A record is a hash with two keys: **:record_id** and **:assets**. The `:record_id` is a randomly generated UUID string with your client_id prepended to it while the `:assets` array is the posted assets.
61
60
 
62
61
  ```ruby
63
62
  puts record
64
- #=> {
65
- record_id: "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1",
66
- assets: [
67
- {"AssetID"=>"9a24c8e2-1066-42fb-be1c-697c5ead476d", "AssetData"=>"jakebellacera", "AssetType"=>"TEXT", "Target"=>"NS~USERNAME", "Client"=>"XX", "Status"=>"APPROVED", "AssetMeta"=>"", "AssetRecordID"=>"XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"},
68
- {"AssetID"=>"9a24c8e2-1066-42fb-be1c-697c5ead476d", "AssetData"=>"http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png", "AssetType"=>"IMAGE", "Target"=>"XX~Avatar", "Client"=>"XX", "Status"=>"APPROVED", "AssetMeta"=>"", "AssetRecordID"=>"XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"}
69
- ]
63
+ #=>
64
+ {
65
+ record_id: "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1",
66
+ assets: [
67
+ {
68
+ "AssetID" => "9a24c8e2-1066-42fb-be1c-697c5ead476d",
69
+ "AssetData" => "jakebellacera",
70
+ "AssetType" => "TEXT",
71
+ "Target" => "XX~username",
72
+ "Client" => "XX",
73
+ "Status" => "APPROVED",
74
+ "AssetMeta" => "",
75
+ "AssetRecordID" => "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"
76
+ },
77
+ {
78
+ "AssetID" => "9a24c8e2-1066-42fb-be1c-697c5ead476d",
79
+ "AssetData" => "http://www.gravatar.com/avatar/98363013aa1237798130bc0fd2c4159d.png",
80
+ "AssetType" => "IMAGE",
81
+ "Target" => "XX~avatar",
82
+ "Client" => "XX",
83
+ "Status" => "APPROVED",
84
+ "AssetMeta" => "",
85
+ "AssetRecordID" => "XX-91c8071a-1201-4f99-bc9d-f8d53a947dc1"
70
86
  }
87
+ ]
88
+ }
71
89
  ```
72
90
 
73
- #### Debugging
74
-
75
- Sometimes Pomegranate will not be able to accept your request. If you're getting a 400 error, it's most likely a formatting issue. Since the errors returned by Pomegranate are not very verbose, it's best to run through a simple checklist instead:
91
+ #### Validation
76
92
 
77
- * Ensure that your login info is correct. You can test in your browser by logging in via HTTPS at `<subdomain>.timessquare2.com`. If you'd like to use cURL or something else, connect via NTLM.
78
- * Make sure that the targets and types for each asset are correct. Targets will vary from client to client.
79
- * Try using the `debug` option.
80
- * If all else fails, you can try submitting an [issue](https://github.com/jakebellacera/pomade/issues). Please be specific in your bug report.
93
+ Once you attempt to publish your assets, `Publisher` will attempt to validate your assets. Most of the time it will work, as Publisher will check your URLS for :image and :video types and ensure that they resolve properly. This validation may not find everything and you'll still get a bad response from Pomegranate, if that's the case, please [file a bug](http://github.com/jakebellacera/pomade/issues) with the steps you took to reproduce the problem.
81
94
 
82
95
  ## Contributing
83
96
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pomade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-11 00:00:00.000000000 Z
12
+ date: 2012-09-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ruby-ntlm
@@ -55,6 +55,7 @@ files:
55
55
  - LICENSE.txt
56
56
  - Rakefile
57
57
  - lib/pomade.rb
58
+ - lib/pomade/exceptions.rb
58
59
  - lib/pomade/publisher.rb
59
60
  - lib/pomade/version.rb
60
61
  - pomade.gemspec