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