googletastic 0.0.4 → 0.0.5

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/Rakefile CHANGED
@@ -31,7 +31,7 @@ spec = Gem::Specification.new do |s|
31
31
  s.rubyforge_project = "googletastic"
32
32
  s.platform = Gem::Platform::RUBY
33
33
  s.files = %w(README.textile Rakefile) +
34
- Dir["{googletastic,lib,spec}/**/*"] -
34
+ Dir["{googletastic,lib,spec,rails}/**/*"] -
35
35
  Dir["spec/tmp"]
36
36
  s.extra_rdoc_files = %w(README.textile)
37
37
  s.require_path = "lib"
@@ -68,7 +68,12 @@ end
68
68
 
69
69
  desc "Install the gem locally"
70
70
  task :install => [:package] do
71
- sh %{sudo gem install pkg/googletastic-#{Googletastic::VERSION} --no-ri --no-rdoc}
71
+ sh %{sudo gem install pkg/#{spec.name}-#{spec.version} --no-ri --no-rdoc}
72
+ end
73
+
74
+ desc "Publish gem to rubygems"
75
+ task :publish => [:package] do
76
+ %x[gem push pkg/#{spec.name}-#{spec.version}.gem]
72
77
  end
73
78
 
74
79
  desc "Run all specs"
@@ -59,7 +59,7 @@ end
59
59
 
60
60
  module Googletastic
61
61
  # :stopdoc:
62
- VERSION = '0.0.4'
62
+ VERSION = '0.0.5'
63
63
  # :startdoc
64
64
  class << self; attr_accessor :keys, :clients, :options; end
65
65
 
@@ -3,11 +3,17 @@ class Googletastic::Form < Googletastic::Base
3
3
 
4
4
  FORM_KEY_EXPRESSION = /formkey["|']\s*:["|']\s*([^"|']+)"/ unless defined?(FORM_KEY_EXPRESSION)
5
5
 
6
- attr_accessor :title, :body, :redirect_to, :form_key, :form_only, :authenticity_token, :spreadsheet
6
+ attr_accessor :title, :body, :form_key, :description, :fields, :properties
7
+ attr_accessor :authenticity_token, :spreadsheet, :form_only
7
8
 
8
- def body(options = {}, &block)
9
- @body ||= get(options, &block)
10
- @body
9
+ # uses mechanize to hackily find form key
10
+ def form_key
11
+ unless @form_key
12
+ puts "Getting form key for '#{self.title}'"
13
+ @form_key = self.class.find_form_key(self.id)
14
+ end
15
+
16
+ @form_key
11
17
  end
12
18
 
13
19
  def submit_url
@@ -18,18 +24,25 @@ class Googletastic::Form < Googletastic::Base
18
24
  self.class.show_url(self.form_key)
19
25
  end
20
26
 
21
- def form_key
22
- @form_key ||= get_form_key
27
+ def from_google_tag(key)
28
+ self.class.from_google_tag(key)
23
29
  end
24
30
 
25
- def properties
26
- body
27
- @properties
31
+ def to_google_tag(key)
32
+ self.class.to_google_tag(key)
28
33
  end
29
34
 
30
35
  # this you want to call JUST BEFORE you render in the view
31
36
  # body still gives you the nokogiri element
32
37
  def render
38
+ add_redirect(body, options, &block)
39
+
40
+ body.xpath("//textarea").each do |node|
41
+ node.add_child Nokogiri::XML::Text.new("\n", body)
42
+ node.remove_attribute("rows")
43
+ node.remove_attribute("cols")
44
+ end
45
+
33
46
  if self.form_only
34
47
  result = body.xpath("//form").first.unlink
35
48
  else
@@ -101,6 +114,25 @@ class Googletastic::Form < Googletastic::Base
101
114
  "http://spreadsheets.google.com/viewform?formkey=#{id}"
102
115
  end
103
116
 
117
+ def tag_map
118
+ @tag_map ||= {
119
+ "text" => "text",
120
+ "paragraph-text" => "textarea",
121
+ "grid" => "grid",
122
+ "scale" => "range",
123
+ "radio" => "radio",
124
+ "checkbox" => "checkbox"
125
+ }
126
+ end
127
+
128
+ def from_google_tag(key)
129
+ tag_map[key]
130
+ end
131
+
132
+ def to_google_tag(key)
133
+ tag_map[tag_map.values.detect {|i| i == key.to_s}]
134
+ end
135
+
104
136
  def unmarshall(xml)
105
137
  records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
106
138
  #id = record.xpath("atom:id", ns_tag("atom")).first.text.gsub("http://spreadsheets.google.com/feeds/spreadsheets/", "")
@@ -111,131 +143,195 @@ class Googletastic::Form < Googletastic::Base
111
143
  title = record.xpath("atom:title", ns_tag("atom")).first.text
112
144
  created_at = record.xpath("atom:published", ns_tag("atom")).text
113
145
  updated_at = record.xpath("atom:updated", ns_tag("atom")).text
114
-
146
+
115
147
  # same as spreadsheet
116
148
  Googletastic::Form.new(
117
- :id => id,
118
- :title => title,
149
+ :id => id,
150
+ :title => title,
119
151
  :updated_at => DateTime.parse(updated_at)
120
152
  )
121
153
  end
122
154
  records
123
155
  end
156
+
157
+ def agent(force = false)
158
+ if force || !@agent
159
+ @agent = defined?(Mechanize) ? Mechanize.new : WWW::Mechanize.new
160
+ # google wants recent browsers!
161
+ # http://docs.google.com/support/bin/answer.py?answer=37560&hl=en
162
+ @agent.user_agent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; ru-ru) AppleWebKit/533.2+ (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10"
163
+ login_form = @agent.get(mechanize_url(id)).forms.first
164
+ login_form.Email = Googletastic.credentials[:username].split("@").first # don't want emails
165
+ login_form.Passwd = Googletastic.credentials[:password]
166
+ @agent.submit(login_form)
167
+ end
168
+
169
+ @agent
170
+ end
124
171
 
125
- end
126
-
127
- def get_form_key
128
- begin
129
- agent = defined?(Mechanize) ? Mechanize.new : WWW::Mechanize.new
130
- # google wants recent browsers!
131
- # http://docs.google.com/support/bin/answer.py?answer=37560&hl=en
132
- agent.user_agent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; ru-ru) AppleWebKit/533.2+ (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10"
172
+ def mechanize_url(id)
133
173
  url = "http://spreadsheets.google.com/"
134
174
  # for spreadsheet, we need the domain!
135
175
  if Googletastic.credentials[:domain]
136
176
  url << "a/#{Googletastic.credentials[:domain]}/"
137
177
  end
138
- url << "ccc?key=#{self.id}&hl=en&pli=1"
139
- login_form = agent.get(url).forms.first
140
- login_form.Email = Googletastic.credentials[:username].split("@").first # don't want emails
141
- login_form.Passwd = Googletastic.credentials[:password]
142
- page = agent.submit(login_form)
143
- match = page.body.match(FORM_KEY_EXPRESSION)
144
- if page.meta.first and (page.title == "Redirecting" || match.nil?)
145
- page = page.meta.first.click
178
+ url << "ccc?key=#{id}&hl=en&pli=1"
179
+ url
180
+ end
181
+
182
+ # optimize this to login only once
183
+ def find_form_key(id, force = false)
184
+ result = nil
185
+ begin
186
+ page = agent(force).get(mechanize_url(id))
146
187
  match = page.body.match(FORM_KEY_EXPRESSION)
188
+ if page.meta.first and (page.title == "Redirecting" || match.nil?)
189
+ page = page.meta.first.click
190
+ match = page.body.match(FORM_KEY_EXPRESSION)
191
+ end
192
+ result = match.captures.first if match
193
+ rescue Exception => e #
194
+ puts "ERROR: #{e.to_s}"
195
+ result = find_form_key(id, true) unless force # try again, unless we have already tried again
196
+ end
197
+ result
198
+ end
199
+
200
+ # returns {:fields => fields, :body => body, :properties => properties, :description => description}
201
+ def parse(html, options, &block)
202
+ title = html.xpath("//h1[@class='ss-form-title']").first.text
203
+ description = html.css(".ss-form-desc").first
204
+ description = description ? description.children.to_html : ""
205
+
206
+ properties = {}
207
+ fields = []
208
+
209
+ html.css("div.ss-item").each do |item|
210
+ tag = ""
211
+ required = false
212
+ classes = item["class"].split(/\s+/)
213
+ classes.each do |clazz|
214
+ if clazz =~ /ss-(text|paragraph-text|grid|scale|radio|checkbox)/
215
+ tag = from_google_tag($1)
216
+ elsif clazz =~ /ss-item-required/
217
+ required = true
218
+ end
219
+ end
220
+ entry = item.xpath("div[@class='ss-form-entry']").first
221
+ label = entry.xpath("label[@class='ss-q-title']").first
222
+ next unless label
223
+ title = label.text.gsub(/:[^:]+$/, "").gsub(/\n+.*/, "") # remove trailing :, then trailing newline stuff
224
+ help = entry.xpath("label[@class='ss-q-help']").first.text
225
+ single = (item["class"].to_s =~ /ss-(grid|scale)/).nil?
226
+ type = single ? "single" : "group"
227
+ column = label.text.downcase.gsub(/[^a-z0-9\-]/, "")
228
+ input_name = label["for"].gsub("_", ".").squeeze("\.") + "." + type
229
+
230
+ value = []
231
+ if tag =~ /(radio|checkbox|range|text)/
232
+ item.css("* input[type=#{$1}]").each do |input|
233
+ value << input["value"]
234
+ end
235
+ elsif tag =~ /(select)/
236
+ item.css("* #{$1} option").each do |option|
237
+ value << option["value"]
238
+ end
239
+ end
240
+ # still need to handle defaults
241
+ keys = {
242
+ :id => input_name.gsub(/[^\d]/, ""),
243
+ :key => column,
244
+ :tag => tag,
245
+ :title => title,
246
+ :help => help,
247
+ :required => required,
248
+ :value => value # need defaults somehow
249
+ }
250
+ if tag == "range"
251
+ keys[:left_label] = item.css("* td.ss-leftlabel").text
252
+ keys[:right_label] = item.css("* td.ss-rightlabel").text
253
+ end
254
+ fields << keys
255
+ properties[column] = input_name
147
256
  end
148
- formkey = match.captures.first if match
149
- rescue Exception => e
150
- puts "ERROR: #{e.to_s}"
151
- nil
257
+
258
+ {:fields => fields, :body => html, :properties => properties, :description => description}
152
259
  end
260
+
261
+ def add_redirect(doc, options, &block)
262
+ action = doc.xpath("//form").first
263
+ return if action.nil?
264
+ action = action["action"].to_s
265
+ submit_key = action.gsub(self.submit_url, "")
266
+
267
+ form = doc.xpath("//form").first
268
+
269
+ form["enctype"] = "multipart/form-data"
270
+
271
+ # don't have time to build this correctly
272
+ redirect = options[:redirect]
273
+ if redirect
274
+ form["action"] = redirect[:url]
275
+ if redirect.has_key?(:params)
276
+ redirect[:params].each do |k,v|
277
+ hidden_node = doc.create_element('input')
278
+ hidden_node["name"] = k.to_s
279
+ hidden_node["type"] = "hidden"
280
+ hidden_node["value"] = v.to_s
281
+ form.children.first.add_previous_sibling(hidden_node)
282
+ end
283
+ end
284
+ end
285
+
286
+ hidden_node = doc.create_element('input')
287
+ hidden_node["name"] = "submit_key"
288
+ hidden_node["type"] = "hidden"
289
+ hidden_node["value"] = submit_key
290
+ form.children.first.add_previous_sibling(hidden_node)
291
+
292
+ put_node = doc.create_element('input')
293
+ put_node["name"] = "_method"
294
+ put_node["type"] = "hidden"
295
+ put_node["value"] = "put"
296
+ form.children.first.add_previous_sibling(put_node)
297
+
298
+ form
299
+ end
300
+
153
301
  end
154
-
155
- def submit(form_key, params, options)
156
- uri = URI.parse(submit_url)
157
- req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
158
- req.form_data = params
159
- response = Net::HTTP.new(uri.host).start {|h| h.request(req)}
160
- if response.is_a?(Net::HTTPSuccess) || response.is_a?(GData::HTTP::Response)
161
- unmarshall(Nokogiri::HTML(response.body), options).to_html
162
- else
163
- response.body
302
+
303
+ # construct(:redirect => {:url => "/our-forms", :params => {:one => "hello"}})
304
+ def construct(options = {}, &block)
305
+ return if body
306
+ raise "I NEED A FORM KEY (#{self.title.to_s})!" unless self.form_key
307
+ parts = fetch(options, &block)
308
+ parts.each do |k, v|
309
+ self.send("#{k}=", v)
164
310
  end
311
+ parts.delete(:body)
312
+ parts[:form_key] = form_key
313
+ self.attributes.merge!(parts)
314
+ body
165
315
  end
166
316
 
167
- # get(:redirect => {:url => "/our-forms", :params => {:one => "hello"}})
168
- def get(options = {}, &block)
169
- raise "I NEED A FORM KEY!" unless self.form_key
317
+ def fetch(options = {}, &block)
170
318
  response = client.get(show_url)
171
319
  if response.is_a?(Net::HTTPSuccess) || response.is_a?(GData::HTTP::Response)
172
- unmarshall(Nokogiri::HTML(response.body), options, &block)
320
+ self.class.parse(Nokogiri::HTML(response.body), options, &block)
173
321
  else
174
322
  response
175
323
  end
176
324
  end
177
325
 
178
- # not class unmarshall, instance unmarshall
179
- def unmarshall(html, options, &block)
180
- # title = html.xpath("//h1[@class='ss-form-title']").first.text
181
- add_redirect(html, options, &block)
182
-
183
- html.xpath("//textarea").each do |node|
184
- node.add_child Nokogiri::XML::Text.new("\n", html)
185
- end
186
-
187
- @properties = {}
188
- html.css("div.ss-item").each do |item|
189
- entry = item.xpath("div[@class='ss-form-entry']").first
190
- label = entry.xpath("label[@class='ss-q-title']").first
191
- next unless label
192
- single = (item["class"].to_s =~ /ss-(grid|scale)/).nil?
193
- type = single ? "single" : "group"
194
- column = label.text.downcase.gsub(/[^a-z0-9\-]/, "")
195
- input_name = label["for"].gsub("_", ".").squeeze("\.") + "." + type
196
- @properties[column] = input_name
197
- end
198
-
199
- html
200
- end
201
-
202
- def add_redirect(doc, options, &block)
203
- action = doc.xpath("//form").first
204
- return if action.nil?
205
- action = action["action"].to_s
206
- submit_key = action.gsub(self.submit_url, "")
207
-
208
- form = doc.xpath("//form").first
209
-
210
- form["enctype"] = "multipart/form-data"
211
-
212
- # don't have time to build this correctly
213
- redirect = options[:redirect] || self.redirect_to
214
- if redirect
215
- form["action"] = redirect[:url]
216
- if redirect.has_key?(:params)
217
- redirect[:params].each do |k,v|
218
- hidden_node = doc.create_element('input')
219
- hidden_node["name"] = k.to_s
220
- hidden_node["type"] = "hidden"
221
- hidden_node["value"] = v.to_s
222
- form.children.first.add_previous_sibling(hidden_node)
223
- end
224
- end
326
+ def submit(form_key, params, options)
327
+ uri = URI.parse(submit_url)
328
+ req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
329
+ req.form_data = params
330
+ response = Net::HTTP.new(uri.host).start {|h| h.request(req)}
331
+ if response.is_a?(Net::HTTPSuccess) || response.is_a?(GData::HTTP::Response)
332
+ self.class.parse(Nokogiri::HTML(response.body), options).to_html
333
+ else
334
+ response.body
225
335
  end
226
-
227
- hidden_node = doc.create_element('input')
228
- hidden_node["name"] = "submit_key"
229
- hidden_node["type"] = "hidden"
230
- hidden_node["value"] = submit_key
231
- form.children.first.add_previous_sibling(hidden_node)
232
-
233
- put_node = doc.create_element('input')
234
- put_node["name"] = "_method"
235
- put_node["type"] = "hidden"
236
- put_node["value"] = "put"
237
- form.children.first.add_previous_sibling(put_node)
238
-
239
- form
240
336
  end
241
337
  end
@@ -0,0 +1 @@
1
+ require "googletastic"
@@ -21,25 +21,32 @@ describe Googletastic::Form do
21
21
 
22
22
  it "should respond_to?(:find_with_google_form) dynamic class methods" do
23
23
  Form.respond_to?(:find_with_google_form).should == true
24
- pending
25
24
  end
26
25
  end
27
26
 
28
27
  describe "mechanize" do
29
28
  it "it should get the formkey via mechanize" do
30
29
  form = Googletastic::Form.first
31
- formkey = form.get_form_key
32
- puts "FORMKEY: #{formkey}"
30
+ formkey = form.form_key
31
+ puts "Form Key: #{formkey.to_s}"
33
32
  formkey.should_not be_nil
34
33
  end
35
34
 
36
35
  it "should remove unnecessary html from form" do
37
36
  form = Googletastic::Form.first
38
- form.form_key = form.get_form_key
37
+ form.construct
39
38
  body = form.body
40
- puts "BODY!: #{body.to_s}"
41
39
  body.should_not == nil
42
40
  end
41
+
42
+ it "should get 'properties', 'fields', and 'description'" do
43
+ form = Googletastic::Form.first
44
+ form.construct
45
+ form.properties.should_not == nil
46
+ form.fields.should_not == nil
47
+ form.description.should_not == nil
48
+ puts form.inspect
49
+ end
43
50
  end
44
51
 
45
52
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 4
9
- version: 0.0.4
8
+ - 5
9
+ version: 0.0.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - Lance Pollard
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-22 00:00:00 -07:00
17
+ date: 2010-03-22 20:12:47 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -158,6 +158,7 @@ files:
158
158
  - spec/googletastic/youtube_spec.rb
159
159
  - spec/spec.opts
160
160
  - spec/spec_helper.rb
161
+ - rails/init.rb
161
162
  has_rdoc: true
162
163
  homepage: http://github.com/viatropos/googletastic
163
164
  licenses: []