googletastic 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +47 -0
- data/Rakefile +103 -0
- data/lib/googletastic/access_rule.rb +45 -0
- data/lib/googletastic/album.rb +22 -0
- data/lib/googletastic/app_engine.rb +103 -0
- data/lib/googletastic/attendee.rb +7 -0
- data/lib/googletastic/base.rb +46 -0
- data/lib/googletastic/calendar.rb +60 -0
- data/lib/googletastic/comment.rb +3 -0
- data/lib/googletastic/document.rb +219 -0
- data/lib/googletastic/event.rb +149 -0
- data/lib/googletastic/ext/file.rb +219 -0
- data/lib/googletastic/ext/pretty_print.xsl +47 -0
- data/lib/googletastic/ext/xml.rb +10 -0
- data/lib/googletastic/ext.rb +1 -0
- data/lib/googletastic/form.rb +131 -0
- data/lib/googletastic/group.rb +44 -0
- data/lib/googletastic/helpers/document.rb +70 -0
- data/lib/googletastic/helpers/event.rb +26 -0
- data/lib/googletastic/helpers/form.rb +102 -0
- data/lib/googletastic/helpers.rb +18 -0
- data/lib/googletastic/image.rb +121 -0
- data/lib/googletastic/mixins/actions.rb +113 -0
- data/lib/googletastic/mixins/attributes.rb +27 -0
- data/lib/googletastic/mixins/finders.rb +78 -0
- data/lib/googletastic/mixins/namespaces.rb +42 -0
- data/lib/googletastic/mixins/parsing.rb +68 -0
- data/lib/googletastic/mixins/requesting.rb +84 -0
- data/lib/googletastic/mixins.rb +5 -0
- data/lib/googletastic/person.rb +41 -0
- data/lib/googletastic/spreadsheet.rb +35 -0
- data/lib/googletastic/sync/document.rb +120 -0
- data/lib/googletastic/sync/form.rb +106 -0
- data/lib/googletastic/sync.rb +46 -0
- data/lib/googletastic/thumbnail.rb +7 -0
- data/lib/googletastic/youtube.rb +25 -0
- data/lib/googletastic.rb +115 -0
- data/spec/benchmarks/document_benchmark.rb +40 -0
- data/spec/config.yml +6 -0
- data/spec/fixtures/data/Doing business in the eMarketPlace.doc +0 -0
- data/spec/fixtures/data/basic.txt +1 -0
- data/spec/fixtures/data/calendar.list.xml +64 -0
- data/spec/fixtures/data/doc_as_html.html +3097 -0
- data/spec/fixtures/data/doc_as_html_html.html +0 -0
- data/spec/fixtures/data/doclist.xml +76 -0
- data/spec/fixtures/data/document.single.xml +30 -0
- data/spec/fixtures/data/end.xml +446 -0
- data/spec/fixtures/data/event.list.xml +62 -0
- data/spec/fixtures/data/form.html +66 -0
- data/spec/fixtures/data/group.list.xml +58 -0
- data/spec/fixtures/data/person.list.xml +53 -0
- data/spec/fixtures/data/photo.list.xml +111 -0
- data/spec/fixtures/data/sample_upload.mp4 +0 -0
- data/spec/fixtures/data/spreadsheet.list.xml +43 -0
- data/spec/fixtures/models/document.rb +7 -0
- data/spec/fixtures/models/event.rb +3 -0
- data/spec/fixtures/models/form.rb +6 -0
- data/spec/fixtures/models/test_model.rb +3 -0
- data/spec/fixtures/results/test.txt +185 -0
- data/spec/googletastic/access_rule_spec.rb +13 -0
- data/spec/googletastic/album_spec.rb +9 -0
- data/spec/googletastic/app_engine_spec.rb +12 -0
- data/spec/googletastic/base_spec.rb +49 -0
- data/spec/googletastic/calendar_spec.rb +11 -0
- data/spec/googletastic/document_spec.rb +163 -0
- data/spec/googletastic/event_spec.rb +34 -0
- data/spec/googletastic/form_spec.rb +43 -0
- data/spec/googletastic/group_spec.rb +9 -0
- data/spec/googletastic/image_spec.rb +9 -0
- data/spec/googletastic/person_spec.rb +9 -0
- data/spec/googletastic/post_spec.rb +46 -0
- data/spec/googletastic/spreadsheet_spec.rb +9 -0
- data/spec/googletastic/youtube_spec.rb +58 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- 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
|