googletastic 0.0.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.
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