googletastic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/README.textile +47 -0
  2. data/Rakefile +103 -0
  3. data/lib/googletastic/access_rule.rb +45 -0
  4. data/lib/googletastic/album.rb +22 -0
  5. data/lib/googletastic/app_engine.rb +103 -0
  6. data/lib/googletastic/attendee.rb +7 -0
  7. data/lib/googletastic/base.rb +46 -0
  8. data/lib/googletastic/calendar.rb +60 -0
  9. data/lib/googletastic/comment.rb +3 -0
  10. data/lib/googletastic/document.rb +219 -0
  11. data/lib/googletastic/event.rb +149 -0
  12. data/lib/googletastic/ext/file.rb +219 -0
  13. data/lib/googletastic/ext/pretty_print.xsl +47 -0
  14. data/lib/googletastic/ext/xml.rb +10 -0
  15. data/lib/googletastic/ext.rb +1 -0
  16. data/lib/googletastic/form.rb +131 -0
  17. data/lib/googletastic/group.rb +44 -0
  18. data/lib/googletastic/helpers/document.rb +70 -0
  19. data/lib/googletastic/helpers/event.rb +26 -0
  20. data/lib/googletastic/helpers/form.rb +102 -0
  21. data/lib/googletastic/helpers.rb +18 -0
  22. data/lib/googletastic/image.rb +121 -0
  23. data/lib/googletastic/mixins/actions.rb +113 -0
  24. data/lib/googletastic/mixins/attributes.rb +27 -0
  25. data/lib/googletastic/mixins/finders.rb +78 -0
  26. data/lib/googletastic/mixins/namespaces.rb +42 -0
  27. data/lib/googletastic/mixins/parsing.rb +68 -0
  28. data/lib/googletastic/mixins/requesting.rb +84 -0
  29. data/lib/googletastic/mixins.rb +5 -0
  30. data/lib/googletastic/person.rb +41 -0
  31. data/lib/googletastic/spreadsheet.rb +35 -0
  32. data/lib/googletastic/sync/document.rb +120 -0
  33. data/lib/googletastic/sync/form.rb +106 -0
  34. data/lib/googletastic/sync.rb +46 -0
  35. data/lib/googletastic/thumbnail.rb +7 -0
  36. data/lib/googletastic/youtube.rb +25 -0
  37. data/lib/googletastic.rb +115 -0
  38. data/spec/benchmarks/document_benchmark.rb +40 -0
  39. data/spec/config.yml +6 -0
  40. data/spec/fixtures/data/Doing business in the eMarketPlace.doc +0 -0
  41. data/spec/fixtures/data/basic.txt +1 -0
  42. data/spec/fixtures/data/calendar.list.xml +64 -0
  43. data/spec/fixtures/data/doc_as_html.html +3097 -0
  44. data/spec/fixtures/data/doc_as_html_html.html +0 -0
  45. data/spec/fixtures/data/doclist.xml +76 -0
  46. data/spec/fixtures/data/document.single.xml +30 -0
  47. data/spec/fixtures/data/end.xml +446 -0
  48. data/spec/fixtures/data/event.list.xml +62 -0
  49. data/spec/fixtures/data/form.html +66 -0
  50. data/spec/fixtures/data/group.list.xml +58 -0
  51. data/spec/fixtures/data/person.list.xml +53 -0
  52. data/spec/fixtures/data/photo.list.xml +111 -0
  53. data/spec/fixtures/data/sample_upload.mp4 +0 -0
  54. data/spec/fixtures/data/spreadsheet.list.xml +43 -0
  55. data/spec/fixtures/models/document.rb +7 -0
  56. data/spec/fixtures/models/event.rb +3 -0
  57. data/spec/fixtures/models/form.rb +6 -0
  58. data/spec/fixtures/models/test_model.rb +3 -0
  59. data/spec/fixtures/results/test.txt +185 -0
  60. data/spec/googletastic/access_rule_spec.rb +13 -0
  61. data/spec/googletastic/album_spec.rb +9 -0
  62. data/spec/googletastic/app_engine_spec.rb +12 -0
  63. data/spec/googletastic/base_spec.rb +49 -0
  64. data/spec/googletastic/calendar_spec.rb +11 -0
  65. data/spec/googletastic/document_spec.rb +163 -0
  66. data/spec/googletastic/event_spec.rb +34 -0
  67. data/spec/googletastic/form_spec.rb +43 -0
  68. data/spec/googletastic/group_spec.rb +9 -0
  69. data/spec/googletastic/image_spec.rb +9 -0
  70. data/spec/googletastic/person_spec.rb +9 -0
  71. data/spec/googletastic/post_spec.rb +46 -0
  72. data/spec/googletastic/spreadsheet_spec.rb +9 -0
  73. data/spec/googletastic/youtube_spec.rb +58 -0
  74. data/spec/spec.opts +1 -0
  75. data/spec/spec_helper.rb +25 -0
  76. metadata +187 -0
@@ -0,0 +1,149 @@
1
+ class Googletastic::Event < Googletastic::Base
2
+
3
+ attr_accessor :created_at, :updated_at, :title, :description
4
+ attr_accessor :who, :start_time, :end_time, :where, :status, :comments
5
+ attr_accessor :guests_can_join, :guests_can_invite, :guests_can_modify, :guests_can_see_guests, :sequence
6
+ # not yet implemented
7
+ attr_accessor :repeats, :calendar_id
8
+
9
+ def new_record?
10
+ return false if !self.id.nil?
11
+ return true
12
+ # not yet using this
13
+ return self.class.first(:start_time => self.start_time, :end_time => self.end_time).nil?
14
+ end
15
+
16
+ def edit_url
17
+ self.class.edit_url(self.id)
18
+ end
19
+
20
+ class << self
21
+
22
+ def client_class
23
+ "Calendar"
24
+ end
25
+
26
+ def index_url
27
+ "http://www.google.com/calendar/feeds/default/private/full"
28
+ end
29
+
30
+ def edit_url(id)
31
+ "http://www.google.com/calendar/feeds/default/private/full/#{id}"
32
+ end
33
+
34
+ # http://code.google.com/apis/calendar/data/2.0/reference.html#Parameters
35
+ # RFC 3339 timestamp format
36
+ def valid_queries
37
+ {
38
+ :timezone => "ctz",
39
+ :future_only => "futureevents",
40
+ :order => "orderby",
41
+ :collapse_recurring => "singleevents",
42
+ :show_hidden => "showhidden",
43
+ :before => "start-max",
44
+ :after => "start-min",
45
+ :sort => "sortorder" # should be merged with "order"
46
+ }.merge(super)
47
+ end
48
+
49
+ def valid_order?(value)
50
+ %w(lastmodified starttime).include?(value)
51
+ end
52
+
53
+ def valid_sort?(value)
54
+ %w(ascending descending).include?(value)
55
+ end
56
+
57
+ def unmarshall(xml)
58
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
59
+ id = record.xpath("atom:id", ns_tag("atom")).first.text.split("/").last
60
+ title = record.xpath("atom:title", ns_tag("atom")).first.text
61
+ description = record.xpath("atom:content", ns_tag("atom")).first.text
62
+ created_at = record.xpath("atom:published", ns_tag("atom")).first.text
63
+ updated_at = record.xpath("atom:updated", ns_tag("atom")).first.text
64
+
65
+ status = record.xpath("gd:eventStatus", ns_tag("gd")).first["value"].gsub("http://schemas.google.com/g/2005#event.", "")
66
+ where = record.xpath("gd:where", ns_tag("gd")).first["valueString"].to_s
67
+
68
+ who = record.xpath("gd:who", ns_tag("gd")).collect do |who|
69
+ Googletastic::Attendee.new(
70
+ :name => who["valueString"].to_s,
71
+ :email => who["email"],
72
+ :role => who["rel"].gsub("http://schemas.google.com/g/2005#event.", "")
73
+ )
74
+ end
75
+
76
+ time = record.xpath("gd:when", ns_tag("gd")).first
77
+ start_time = time["startTime"].to_s
78
+ end_time = time["endTime"].to_s
79
+
80
+ guests_can_join = record.xpath("gCal:anyoneCanAddSelf", ns_tag("gCal")).first["value"] == "true" ? true : false
81
+ guests_can_invite = record.xpath("gCal:guestsCanInviteOthers", ns_tag("gCal")).first["value"] == "true" ? true : false
82
+ guests_can_modify = record.xpath("gCal:guestsCanModify", ns_tag("gCal")).first["value"] == "true" ? true : false
83
+ guests_can_see_guests = record.xpath("gCal:guestsCanSeeGuests", ns_tag("gCal")).first["value"] == "true" ? true : false
84
+ sequence = record.xpath("gCal:sequence", ns_tag("gCal")).first["value"].to_i
85
+
86
+ Googletastic::Event.new(
87
+ :id => id,
88
+ :title => title,
89
+ :description => description,
90
+ :created_at => created_at,
91
+ :updated_at => updated_at,
92
+ :status => status,
93
+ :where => where,
94
+ :who => who,
95
+ :start_time => start_time,
96
+ :end_time => end_time,
97
+ :guests_can_join => guests_can_join,
98
+ :guests_can_invite => guests_can_invite,
99
+ :guests_can_modify => guests_can_modify,
100
+ :guests_can_see_guests => guests_can_see_guests,
101
+ :sequence => sequence
102
+ )
103
+ end
104
+ records
105
+ end
106
+
107
+ def marshall(record)
108
+ Nokogiri::XML::Builder.new { |xml|
109
+ xml.entry(ns_xml("atom", "gCal", "gd")) {
110
+ if record.id
111
+ xml.id_ {
112
+ xml.text record.id
113
+ }
114
+ end
115
+ if record.created_at
116
+ xml.published {
117
+ xml.text record.created_at
118
+ }
119
+ end
120
+ if record.updated_at
121
+ xml.updated {
122
+ xml.text record.updated_at
123
+ }
124
+ end
125
+ xml.title {
126
+ xml.text record.title
127
+ }
128
+ xml.content {
129
+ xml.text record.description
130
+ }
131
+ record.who.each do |who|
132
+ xml["gd"].who(
133
+ :email => who.email,
134
+ :rel => "http://schemas.google.com/g/2005#event.#{who.role}",
135
+ :valueString => who.name
136
+ ) {
137
+ xml["gd"].attendeeStatus(:value => "http://schemas.google.com/g/2005#event.#{record.status}")
138
+ }
139
+ end unless record.who.nil?
140
+ xml["gd"].where("valueString" => record.where)
141
+
142
+ xml["gd"].when("startTime" => record.start_time, "endTime" => record.end_time)
143
+ }
144
+ }.to_xml
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,219 @@
1
+ require 'find'
2
+ require 'jcode' # For string.each_char
3
+
4
+ class File
5
+
6
+ NON_TEXT_FILE_EXTENSIONS = [
7
+ "jpg", "gif", "jpeg", "pdf", "swf", "swc", "psd", "ai", "ae", "png", "tiff", "mp3",
8
+ "ttf", "otf", "bmp"
9
+ ].collect! { |item| ".#{item}" }#.to_hash unless defined?(NON_TEXT_FILE_EXTENSIONS)
10
+
11
+ # removes file content
12
+ def self.clean(file)
13
+ File.truncate(file, 0)
14
+ end
15
+
16
+ def self.touch(file)
17
+ FileUtils.touch(file) unless File.exists?(file)
18
+ end
19
+
20
+ # sorts the names of files
21
+ def self.sort(files)
22
+ files.sort do |x,y|
23
+ xs = x.before_last("/")
24
+ ys = y.before_last("/")
25
+ xs <=> ys
26
+ end
27
+ end
28
+
29
+ # easier to read method to see if file's empty
30
+ def self.empty?(file)
31
+ File.zero?(file)
32
+ end
33
+
34
+ def self.uniq_dirs(paths)
35
+ dirs = []
36
+ paths.each do |file|
37
+ dirs << file.before_last("/") if file =~ /\//
38
+ end
39
+ dirs.uniq!
40
+ dirs
41
+ end
42
+
43
+ def self.template(target_path, template_path, bind)
44
+ FileUtils.touch(target_path) unless File.exists?(target_path)
45
+ File.clean(target_path)
46
+ File.open(target_path, 'r+') do |f|
47
+ # parse the template file into this
48
+ f.print ERB.new(IO.read(template_path), nil, '-').result(bind)
49
+ end
50
+ end
51
+
52
+ def self.write(file, content)
53
+ File.touch(file)
54
+ File.clean(file)
55
+ File.open(file, 'r+') do |f|
56
+ f.print content
57
+ end
58
+ end
59
+
60
+ def self.has_ext?(name)
61
+ File::NON_TEXT_FILE_EXTENSIONS[name.downcase]
62
+ end
63
+
64
+ # Matches the file name
65
+ def self.matches(target, files_only = true, includes = [".*"], excludes = nil)
66
+ includes ||= [".*"]
67
+ # need to test this to make sure it always works
68
+ includes = includes.collect { |e| e.is_a?(Regexp) ? e : Regexp.new(e) }
69
+ excludes = excludes.collect { |e| e.is_a?(Regexp) ? e : Regexp.new(Regexp.escape(e)) } unless excludes.nil?
70
+ Find.find(target) do |path|
71
+ if excludes and excludes.any? {|e| path.match e }
72
+ next
73
+ elsif FileTest.directory?(path)
74
+ yield path if !files_only
75
+ next
76
+ else #we have a file
77
+ #name = "." + File.basename(path.downcase).split(".").last
78
+ yield path if includes.any? {|e| path.match e }
79
+ end
80
+ end
81
+ end
82
+
83
+ def self.num_lines(target, files_only = true, includes = [".*"], excludes = nil)
84
+ n = 0
85
+ self.matches(target, files_only, includes, excludes) do |path|
86
+ n += File.readlines(path).size
87
+ end
88
+ n
89
+ end
90
+
91
+ def self.num_words(target, files_only = true, includes = [".*"], excludes = nil)
92
+ n = 0
93
+ self.matches(target, files_only, includes, excludes) do |path|
94
+ IO.read(path).scan(/\b\w+\b/) { n += 1 }
95
+ end
96
+ n
97
+ end
98
+
99
+ def self.num_files(target, files_only = true, includes = [".*"], excludes = nil)
100
+ n = 0
101
+ self.matches(target, files_only, includes, excludes) do |path|
102
+ n += 1
103
+ end
104
+ n
105
+ end
106
+
107
+ # moves all files from random places to a single directory
108
+ def self.move_all(target, includes = [".*"], excludes = nil)
109
+
110
+ end
111
+
112
+ def self.list_files(target, files_only = true, includes = [".*"], excludes = nil)
113
+ files = []
114
+ self.matches(target, files_only, includes, excludes) do |path|
115
+ yield path if block_given?
116
+ if !path.nil? and !path.eql?("") then files << path end
117
+ end
118
+ files
119
+ end
120
+
121
+ def self.list_directories(target, includes = [".*"], excludes = nil)
122
+ dirs = []
123
+ self.matches(target, false, includes, excludes) do |path|
124
+ if FileTest.directory?(path)
125
+ yield path if block_given?
126
+ if !path.nil? and !path.eql?("") then dirs << path end
127
+ end
128
+ end
129
+ dirs
130
+ end
131
+
132
+ def self.replace(a, b, target, includes = [".*"], excludes = [], &block)
133
+ a = Regexp.new(Regexp.escape(a))
134
+ self.replace_all(target, includes, excludes) do |line|
135
+ if block_given?
136
+ yield line, a, b
137
+ else # modify lines
138
+ line.gsub!(a, b) unless line.match(a).nil?
139
+ end # truncate to new length
140
+ end
141
+ end
142
+
143
+ def self.replace_all(target, includes = [".*"], excludes = [], &block)
144
+ # files = self.list_files(target, true, includes, excludes).sort do |a,b|
145
+ # Regexp.num_matches(a, "/") <=> Regexp.num_matches(b, "/")
146
+ # end
147
+ self.list_files(target, true, includes, excludes) do |path|
148
+ File.open(path, 'r+') do |f| # open file for update
149
+ lines = f.readlines.collect! { |line| yield path, line if block_given? }
150
+ f.pos = 0 # back to start
151
+ f.print lines # write out modified lines
152
+ f.truncate(f.pos) # truncate to new length
153
+ end
154
+ end
155
+ end
156
+
157
+ def self.rename_all(target, files_only = false, includes = [".*"], excludes = [], &block)
158
+ self.list_files(target, files_only, includes, excludes) do |path|
159
+ dirname = File.dirname(path)
160
+ filetype = File.basename(path).split(".").last
161
+ filename = yield path, File.basename(path).split("/").last if block_given?
162
+ new_path = dirname + "/" + filename
163
+ File.rename(path, new_path)
164
+ end
165
+ end
166
+
167
+ # Inserts lines into a file after a specified pattern's last occurrence.
168
+ # This method first reads the file into memory as a string and checks to see if
169
+ # any of the lines you're trying to add are already there.
170
+ # If they are, it removes them from the inserts.
171
+ # Then it goes through each line, finds the index of the last occurrence of that pattern,
172
+ # and splices your inserts into the lines array, and writes them back to the file.
173
+ def self.insert_lines(target, inserts, pattern, offset = 0, duplicates = false, &block)
174
+ index = -1
175
+ content = IO.read(target)
176
+ accepted_inserts = []
177
+ inserts.each_with_index do |insert, i|
178
+ accepted_inserts << insert unless !duplicates and content.match(insert.strip)
179
+ end
180
+ # accepted_inserts[0] = "\n#{accepted_inserts[0]}" # next line
181
+ File.open(target, 'r+') do |f|
182
+ lines = f.readlines.each_with_index do |line, i|
183
+ yield line if block_given?
184
+ if pattern and line.match(pattern) then index = i end
185
+ end
186
+ index = index == -1 ? lines.length - 1 : index + offset
187
+ lines = lines.splice(accepted_inserts, index)
188
+ f.pos = 0
189
+ f.print lines
190
+ f.truncate(f.pos)
191
+ end
192
+ end
193
+
194
+ def self.remove_lines(target, patterns)
195
+ new_lines = []
196
+ if !patterns.nil? and patterns.length > 0
197
+ patterns.map! {|p| Regexp.escape(p) }
198
+ pattern = Regexp.new(patterns.join("|"))
199
+ File.open(target, 'r+') do |f|
200
+ f.readlines.each { |line| new_lines << line unless line.match(pattern) }
201
+ f.pos = 0
202
+ f.print new_lines
203
+ f.truncate(f.pos)
204
+ end
205
+ end
206
+ new_lines
207
+ end
208
+
209
+ def self.scan(target, pattern, position = 0)
210
+ new_lines = []
211
+ File.open(target, 'r+') do |f|
212
+ lines = f.readlines.each do |line|
213
+ match = line.match(pattern)
214
+ new_lines << match.captures[position] unless match.nil?
215
+ end
216
+ end
217
+ new_lines
218
+ end
219
+ end
@@ -0,0 +1,47 @@
1
+ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
2
+ <xsl:output method="html" indent="yes" encoding="ISO-8859-1"/>
3
+
4
+ <xsl:param name="indent-increment" select="' '"/>
5
+
6
+ <xsl:template name="newline">
7
+ <xsl:text disable-output-escaping="yes">
8
+ </xsl:text>
9
+ </xsl:template>
10
+
11
+ <xsl:template match="comment() | processing-instruction()">
12
+ <xsl:param name="indent" select="''"/>
13
+ <xsl:call-template name="newline"/>
14
+ <xsl:value-of select="$indent"/>
15
+ <xsl:copy />
16
+ </xsl:template>
17
+
18
+ <xsl:template match="text()">
19
+ <xsl:param name="indent" select="''"/>
20
+ <xsl:call-template name="newline"/>
21
+ <xsl:value-of select="$indent"/>
22
+ <xsl:value-of select="normalize-space(.)"/>
23
+ </xsl:template>
24
+
25
+ <xsl:template match="text()[normalize-space(.)='']"/>
26
+
27
+ <xsl:template match="*">
28
+ <xsl:param name="indent" select="''"/>
29
+ <xsl:call-template name="newline"/>
30
+ <xsl:value-of select="$indent"/>
31
+ <xsl:choose>
32
+ <xsl:when test="count(child::*) > 0">
33
+ <xsl:copy>
34
+ <xsl:copy-of select="@*"/>
35
+ <xsl:apply-templates select="*|text()">
36
+ <xsl:with-param name="indent" select="concat ($indent, $indent-increment)"/>
37
+ </xsl:apply-templates>
38
+ <xsl:call-template name="newline"/>
39
+ <xsl:value-of select="$indent"/>
40
+ </xsl:copy>
41
+ </xsl:when>
42
+ <xsl:otherwise>
43
+ <xsl:copy-of select="."/>
44
+ </xsl:otherwise>
45
+ </xsl:choose>
46
+ </xsl:template>
47
+ </xsl:stylesheet>
@@ -0,0 +1,10 @@
1
+ # not sure where to fit this in yet
2
+ module Googletastic::PrettyPrint
3
+ class << self
4
+ def xml(xml)
5
+ pretty_printer = File.join(File.dirname(__FILE__), "pretty_print.xsl")
6
+ xsl = Nokogiri::XSLT(IO.read(pretty_printer))
7
+ xsl.transform(xml).children.to_xml.gsub(/\t/, "")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ require_local "ext/*", __FILE__
@@ -0,0 +1,131 @@
1
+ # from http://github.com/mocra/custom_google_forms
2
+ class Googletastic::Form < Googletastic::Base
3
+
4
+ attr_accessor :title, :body, :redirect_to, :form_key, :form_only
5
+
6
+ def body(options = {}, &block)
7
+ @body ||= get(options, &block)
8
+ @body
9
+ end
10
+
11
+ def submit_url
12
+ self.class.submit_url(self.form_key)
13
+ end
14
+
15
+ def show_url
16
+ self.class.show_url(self.form_key)
17
+ end
18
+
19
+ class << self
20
+
21
+ def client_class
22
+ "Spreadsheets"
23
+ end
24
+
25
+ def index_url
26
+ "http://spreadsheets.google.com/feeds/spreadsheets/private/full"
27
+ end
28
+
29
+ def submit_url(id = "")
30
+ "http://spreadsheets.google.com/formResponse?formkey=#{id}"
31
+ end
32
+
33
+ def show_url(id)
34
+ "http://spreadsheets.google.com/viewform?formkey=#{id}"
35
+ end
36
+
37
+ def unmarshall(xml)
38
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
39
+ id = record.xpath("atom:id", ns_tag("atom")).first.text.gsub("http://spreadsheets.google.com/feeds/spreadsheets/", "")
40
+ title = record.xpath("atom:title", ns_tag("atom")).first.text
41
+ created_at = record.xpath("atom:published", ns_tag("atom")).text
42
+ updated_at = record.xpath("atom:updated", ns_tag("atom")).text
43
+
44
+ # same as spreadsheet
45
+ Googletastic::Form.new(
46
+ :id => id,
47
+ :title => title,
48
+ :updated_at => DateTime.parse(updated_at)
49
+ )
50
+ end
51
+ records
52
+ end
53
+
54
+ end
55
+
56
+ def submit(form_key, params)
57
+ action = submit_url(form_key)
58
+ uri = URI.parse(action)
59
+ req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
60
+ req.form_data = params
61
+ response = Net::HTTP.new(uri.host).start {|h| h.request(req)}
62
+ response
63
+ end
64
+
65
+ # get(:redirect => {:url => "/our-forms", :params => {:one => "hello"}})
66
+ def get(options = {}, &block)
67
+ raise "I NEED A FORM KEY!" unless self.form_key
68
+ response = client.get(show_url)
69
+ if response.is_a?(Net::HTTPSuccess) || response.is_a?(GData::HTTP::Response)
70
+ unmarshall(Nokogiri::HTML(response.body), options, &block)
71
+ else
72
+ response
73
+ end
74
+ end
75
+
76
+ # not class unmarshall, instance unmarshall
77
+ def unmarshall(html, options, &block)
78
+ # title = html.xpath("//h1[@class='ss-form-title']").first.text
79
+ add_redirect(html, options, &block)
80
+
81
+ html.xpath("//textarea").each do |node|
82
+ node.add_child Nokogiri::XML::Text.new("\n", html)
83
+ end
84
+
85
+ if self.form_only
86
+ html.xpath("//form").first.unlink
87
+ else
88
+ html.xpath("//div[@class='ss-footer']").first.unlink
89
+ html.xpath("//script").each {|x| x.unlink }
90
+ html.xpath("//div[@class='ss-form-container']").first.unlink
91
+ end
92
+ end
93
+
94
+ def add_redirect(doc, options, &block)
95
+ action = doc.xpath("//form").first["action"].to_s
96
+ submit_key = action.gsub(self.submit_url, "")
97
+
98
+ form = doc.xpath("//form").first
99
+
100
+ form["enctype"] = "multipart/form-data"
101
+
102
+ # don't have time to build this correctly
103
+ redirect = options[:redirect] || self.redirect_to
104
+ if redirect
105
+ form["action"] = redirect[:url]
106
+ if redirect.has_key?(:params)
107
+ redirect[:params].each do |k,v|
108
+ hidden_node = doc.create_element('input')
109
+ hidden_node["name"] = k.to_s
110
+ hidden_node["type"] = "hidden"
111
+ hidden_node["value"] = v.to_s
112
+ form.children.first.add_previous_sibling(hidden_node)
113
+ end
114
+ end
115
+ end
116
+
117
+ hidden_node = doc.create_element('input')
118
+ hidden_node["name"] = "submit_key"
119
+ hidden_node["type"] = "hidden"
120
+ hidden_node["value"] = submit_key
121
+ form.children.first.add_previous_sibling(hidden_node)
122
+
123
+ put_node = doc.create_element('input')
124
+ put_node["name"] = "_method"
125
+ put_node["type"] = "hidden"
126
+ put_node["value"] = "put"
127
+ form.children.first.add_previous_sibling(put_node)
128
+
129
+ form
130
+ end
131
+ end
@@ -0,0 +1,44 @@
1
+ class Googletastic::Group < Googletastic::Base
2
+
3
+ attr_accessor :title, :description, :system_group
4
+
5
+ class << self
6
+
7
+ def index_url
8
+ "http://www.google.com/m8/feeds/groups/default/full"
9
+ end
10
+
11
+ def client_class
12
+ "Contacts"
13
+ end
14
+
15
+ # http://code.google.com/apis/contacts/docs/2.0/reference.html#Parameters
16
+ def valid_queries
17
+ {
18
+ :order => "orderby",
19
+ :sort => "sortorder",
20
+ :group => "group"
21
+ }.merge(super)
22
+ end
23
+
24
+ def unmarshall(xml)
25
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
26
+ id = record.xpath("atom:id", ns_tag("atom")).first.text
27
+ title = record.xpath("atom:title", ns_tag("atom")).first.text
28
+ description = record.xpath("atom:content", ns_tag("atom")).first.text
29
+ system_group = record.xpath("gContact:systemGroup").first
30
+ system_group = system_group["id"] unless system_group.nil?
31
+
32
+ Googletastic::Group.new(
33
+ :id => id,
34
+ :title => title,
35
+ :description => description,
36
+ :system_group => system_group
37
+ )
38
+ end
39
+ records
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,70 @@
1
+ module Googletastic::Helpers::DocumentModelHelper
2
+
3
+ def self.included(base)
4
+ # defaults
5
+ options = Googletastic[base]
6
+ options[:as] ||= "google_doc"
7
+ options[:foreign_key] ||= "#{options[:as]}_id"
8
+ if options.has_key?(:sync)
9
+ options[:sync] = case options[:sync].class.to_s
10
+ when "Symbol"
11
+ {options[:sync] => options[:sync]}
12
+ when "String"
13
+ {options[:sync].to_sym => options[:sync].to_sym}
14
+ when "Array"
15
+ options[:sync].collect { |v| {v.to_sym => v.to_sym} }
16
+ else
17
+ options[:sync].symbolize_keys
18
+ end
19
+ end
20
+
21
+ # fast access
22
+ as = options[:as]
23
+ foreign_key = options[:foreign_key]
24
+ sync = options[:sync]
25
+
26
+ # eval
27
+ base.class_eval <<-end_eval, __FILE__, __LINE__
28
+ attr_accessor :#{as} # google_doc
29
+ attr_accessor :content
30
+
31
+ def self.find_with_#{as}(*args)
32
+ google_records = Googletastic::Document.all
33
+ foreign_keys = google_records.collect { |record| record.id }
34
+ records = find_all_by_#{foreign_key}(foreign_keys) || []
35
+ record_keys = records.collect { |record| record.#{foreign_key} }
36
+ google_records.each do |google_record|
37
+ if !record_keys.include?(google_record.id)
38
+ record = self.new(
39
+ :#{foreign_key} => google_record.id,
40
+ :created_at => google_record.created_at,
41
+ :updated_at => google_record.updated_at,
42
+ :#{as} => google_record,
43
+ :title => google_record.title
44
+ )
45
+ record.save
46
+ records << record
47
+ end
48
+ end
49
+ records.each do |record|
50
+ record.#{as} = google_records.select { |r| r.id == record.#{foreign_key} }.first
51
+ end
52
+ records
53
+ end
54
+
55
+ if base.is_a?(ActiveRecord::Base)
56
+
57
+ validates_presence_of :#{foreign_key}
58
+ validates_uniqueness_of :#{foreign_key}
59
+ before_save :sync_with_google
60
+
61
+ def sync_with_google
62
+ Hash(#{sync}).each do |mine, theirs|
63
+ self[mine] = self.#{as}[theirs]
64
+ end if Googletastic[self].has_key?(:sync)
65
+ end
66
+
67
+ end
68
+ end_eval
69
+ end
70
+ end