googletastic 0.0.4 → 0.0.5

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