pomade 0.2.0 → 0.2.1

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.
@@ -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