gcal_mapper 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +32 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +20 -0
- data/Guardfile +19 -0
- data/LICENSE +22 -0
- data/README.md +370 -0
- data/Rakefile +32 -0
- data/bin/ci/file/auth.yaml +7 -0
- data/bin/ci/file/bad_yaml.yaml +0 -0
- data/bin/ci/file/config.yaml +8 -0
- data/bin/ci/file/privatekey.p12 +0 -0
- data/bin/ci/travis_build.sh +4 -0
- data/bin/ci/vcr/GcalMapper_Authentification/access_token_should_exist_with_assertion_auth.yml +49 -0
- data/bin/ci/vcr/GcalMapper_Authentification/should_be_fasle_if_bad_client_email_is_given.yml +47 -0
- data/bin/ci/vcr/GcalMapper_Authentification_Assertion/should_have_access_token_attribute.yml +49 -0
- data/bin/ci/vcr/GcalMapper_Authentification_Assertion/should_have_refresh_token_attribute.yml +49 -0
- data/bin/ci/vcr/GcalMapper_Calendar/should_get_the_calendar_list.yml +190 -0
- data/bin/ci/vcr/GcalMapper_Calendar/should_get_the_events_list.yml +45 -0
- data/bin/ci/vcr/GcalMapper_Calendar/should_raise_error_if_the_calendar_id_isn_t_accessible.yml +56 -0
- data/bin/ci/vcr/GcalMapper_Calendar/should_raise_error_if_the_token_is_bad.yml +60 -0
- data/bin/ci/vcr/GcalMapper_Mapper/should_save_all_events.yml +232 -0
- data/bin/ci/vcr/GcalMapper_Mapper/should_save_events_only_once.yml +232 -0
- data/bin/gcal-mapper +108 -0
- data/gcal_mapper.gemspec +25 -0
- data/lib/gcal_mapper.rb +15 -0
- data/lib/gcal_mapper/authentification.rb +57 -0
- data/lib/gcal_mapper/authentification/assertion.rb +110 -0
- data/lib/gcal_mapper/authentification/base.rb +20 -0
- data/lib/gcal_mapper/authentification/oauth2.rb +68 -0
- data/lib/gcal_mapper/calendar.rb +51 -0
- data/lib/gcal_mapper/configuration.rb +23 -0
- data/lib/gcal_mapper/errors.rb +40 -0
- data/lib/gcal_mapper/mapper.rb +76 -0
- data/lib/gcal_mapper/mapper/active_record.rb +74 -0
- data/lib/gcal_mapper/mapper/dsl.rb +57 -0
- data/lib/gcal_mapper/mapper/simple.rb +97 -0
- data/lib/gcal_mapper/railtie.rb +8 -0
- data/lib/gcal_mapper/rest_request.rb +73 -0
- data/lib/gcal_mapper/sync.rb +159 -0
- data/lib/gcal_mapper/version.rb +4 -0
- data/spec/authentification/assertion_spec.rb +15 -0
- data/spec/authentification/base_spec.rb +18 -0
- data/spec/authentification/oauth2_spec.rb +15 -0
- data/spec/authentification_spec.rb +34 -0
- data/spec/calendar_spec.rb +33 -0
- data/spec/gcal_mapper_spec.rb +5 -0
- data/spec/mapper/active_record_spec.rb +46 -0
- data/spec/mapper/dsl_spec.rb +31 -0
- data/spec/mapper/simple_spec.rb +48 -0
- data/spec/mapper_spec.rb +32 -0
- data/spec/rest_request_spec.rb +5 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/models/event.rb +25 -0
- data/spec/support/models/event_jeu.rb +22 -0
- data/spec/support/schema.rb +28 -0
- data/spec/sync_spec.rb +28 -0
- metadata +200 -0
data/.gitignore
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
*.sqlite3
|
4
|
+
InstalledFiles
|
5
|
+
Gemfile.lock
|
6
|
+
pkg
|
7
|
+
lib/bundler/man
|
8
|
+
spec/reports
|
9
|
+
tmp
|
10
|
+
|
11
|
+
# ignore local config files
|
12
|
+
.bundle
|
13
|
+
.config
|
14
|
+
.rspec
|
15
|
+
.project
|
16
|
+
|
17
|
+
# ignore simplecov path
|
18
|
+
coverage
|
19
|
+
|
20
|
+
# ignore vcr files
|
21
|
+
spec/vcr
|
22
|
+
spec/vcr/**/*
|
23
|
+
spec/file
|
24
|
+
|
25
|
+
# YARD artifacts
|
26
|
+
.yardoc
|
27
|
+
_yardoc
|
28
|
+
doc
|
29
|
+
rdoc
|
30
|
+
|
31
|
+
# google api yaml file
|
32
|
+
google-api.yaml
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
master
|
2
|
+
======
|
3
|
+
|
4
|
+
0.1.0 / 2012-06-06
|
5
|
+
------------------
|
6
|
+
* map google events in your database
|
7
|
+
* use of regex to map only a part of the data
|
8
|
+
* is_empty declaration to use to look in another field is the source is empty
|
9
|
+
* map mutliple calendar at the same time
|
10
|
+
* DSL to simplify configuration
|
11
|
+
* extend "client" class with the synchronization method
|
12
|
+
* Oauth2.0 authentification
|
13
|
+
* Assertion credentials authentification
|
14
|
+
* Compatible with ActiveRecord
|
15
|
+
* Could work without ORM
|
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in gcalmapper.gemspec
|
4
|
+
gemspec
|
5
|
+
gem 'guard-rspec'
|
6
|
+
gem 'guard-spork'
|
7
|
+
gem 'guard-bundler'
|
8
|
+
gem 'libnotify'
|
9
|
+
gem 'rb-readline'
|
10
|
+
gem 'spork', '~> 1.0rc'
|
11
|
+
gem 'yard'
|
12
|
+
gem 'simplecov'
|
13
|
+
gem 'sqlite3'
|
14
|
+
|
15
|
+
# Other development dependancies
|
16
|
+
gem 'rake'
|
17
|
+
|
18
|
+
if RUBY_VERSION == '1.8.7'
|
19
|
+
gem 'json_pure'
|
20
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
guard 'bundler' do
|
4
|
+
watch('Gemfile')
|
5
|
+
watch(/^.+\.gemspec/)
|
6
|
+
end
|
7
|
+
|
8
|
+
guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
|
9
|
+
watch('Gemfile')
|
10
|
+
watch('Gemfile.lock')
|
11
|
+
watch('spec/spec_helper.rb') { :rspec }
|
12
|
+
watch(%r{features/support/}) { :cucumber }
|
13
|
+
end
|
14
|
+
|
15
|
+
guard 'rspec', :version => 2 do
|
16
|
+
watch(%r{^spec/.+_spec\.rb$})
|
17
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
18
|
+
watch('spec/spec_helper.rb') { "spec" }
|
19
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Liquid Concept
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,370 @@
|
|
1
|
+
gcalMapper
|
2
|
+
==========
|
3
|
+
|
4
|
+
[![Build Status](https://secure.travis-ci.org/liquidconcept/gcalmapper.png?branch=master)](http://travis-ci.org/liquidconcept/gcalmapper)
|
5
|
+
|
6
|
+
A library to map Google Calendar events with an ORM.
|
7
|
+
|
8
|
+
Installation
|
9
|
+
------------
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'gcalmapper'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install gcalmapper
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
### Authorization to connect to gcal ##
|
27
|
+
|
28
|
+
To have access to your google calendar go to :
|
29
|
+
|
30
|
+
[Google API Console][1]
|
31
|
+
|
32
|
+
and open a new api account :
|
33
|
+
|
34
|
+
1. click on "Services" on the rigth top menu
|
35
|
+
2. click on the toggle button next to "Calendar API"
|
36
|
+
3. accept the license
|
37
|
+
4. click on "API Access" on the rigth top menu
|
38
|
+
5. click on the blue button "Create an Oauth2.0 Client ID ..."
|
39
|
+
6. provide a name for the API and click next
|
40
|
+
7. chose the type between "service account" and "installed app"
|
41
|
+
8. click "create client id"
|
42
|
+
|
43
|
+
once you have your "installed app" access run :
|
44
|
+
|
45
|
+
$ gcal-mapper --client_id [CLIENT_ID] --client_secret [CLIENT_SECRET]
|
46
|
+
|
47
|
+
it should open a new page, click accept that the API acess your data.
|
48
|
+
this gives you ~/google_auth.yaml copy it on spec/file/google_auth.yaml
|
49
|
+
(you can change path with --file /path/to/the/yaml_file.yaml)
|
50
|
+
(you can change scope with --scope https://www.googleapis.com/another/scope)
|
51
|
+
|
52
|
+
with the service account make sure you save well the private key.
|
53
|
+
You can use service account only if you manage your
|
54
|
+
domain with google. But you have to go to your google apps manager :
|
55
|
+
|
56
|
+
1. go to "Advenced tools" -> "Manage api client acess"
|
57
|
+
2. enter your client id(service account) in "client name"
|
58
|
+
3. enter "http(s)://www.google.com/calendar/feeds/" in "scope"
|
59
|
+
4. click "authorize"
|
60
|
+
|
61
|
+
### exemple ##
|
62
|
+
|
63
|
+
when you manage to have the authorization to access your google calendar create your model
|
64
|
+
|
65
|
+
require 'active_record'
|
66
|
+
|
67
|
+
class Event < ActiveRecord::Base
|
68
|
+
|
69
|
+
include GcalMapper::Mapper::ActiveRecord
|
70
|
+
|
71
|
+
calendar do
|
72
|
+
configure :file => 'path/to/your/yaml/file.yaml'
|
73
|
+
|
74
|
+
calendar 'your_email@gmail.com'
|
75
|
+
|
76
|
+
google_id 'gid'
|
77
|
+
|
78
|
+
field 'name', :source => 'summary'
|
79
|
+
field 'description', :source => 'description',
|
80
|
+
:match => /^category: (.*)$/, :default => 'not categorized'
|
81
|
+
field 'status', :source => 'status'
|
82
|
+
field 'start_at', :source => 'start.dateTime', :if_empty => 'start.date'
|
83
|
+
field 'end_at', :source => 'end.dateTime', :if_empty => 'end.date'
|
84
|
+
field 'created_at', :source => 'created'
|
85
|
+
field 'updated_at', :source => 'updated'
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.synchronize_me
|
89
|
+
self.synchronize_calendar
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
here is an example with Oauth2.0 authentification for installed app, as you can see it'is not a rails model and it work with AtiveRecord.
|
95
|
+
|
96
|
+
you can find an exemple with rails there :
|
97
|
+
|
98
|
+
[GcalMapper Rails exemple][3]
|
99
|
+
|
100
|
+
### configure ##
|
101
|
+
|
102
|
+
`configure` is used to set your crendentials. You can see above an exemple for "installed app" here is one for "service account" :
|
103
|
+
|
104
|
+
configure :file => '/path/to/the/private-key.p12',
|
105
|
+
:client_email => 'service_account_email',
|
106
|
+
:user_email => 'user@yourdomain.com',
|
107
|
+
:password => 'password of your key'
|
108
|
+
|
109
|
+
`:password` is optional the default value is 'notasecret'
|
110
|
+
|
111
|
+
|
112
|
+
### google_id ##
|
113
|
+
|
114
|
+
`google_id` must be present, and you must give it the name of an existing string field in your DB. it store the google event id and must be saved to keep your DB synchronize
|
115
|
+
|
116
|
+
|
117
|
+
### calendar ##
|
118
|
+
|
119
|
+
if you want to synchronize more than one calendar just do someting like :
|
120
|
+
|
121
|
+
calendar 'your_email@gmail.com'
|
122
|
+
calendar 'other_accessible_calendar@gmail.com
|
123
|
+
|
124
|
+
|
125
|
+
### field ##
|
126
|
+
|
127
|
+
the minimal `field` declatration is
|
128
|
+
|
129
|
+
field 'name_of_your_db_column', :source => 'name_of_the_source'
|
130
|
+
|
131
|
+
It is possible to be more precise with the use of `:match` and `:default`
|
132
|
+
|
133
|
+
- `:match => 'regexp'` is used to parse
|
134
|
+
only a part of the google data
|
135
|
+
- `:default => 'default` is used to put default data in case of unmatching data
|
136
|
+
|
137
|
+
Both are optional, but if you use `:match` without `:default`, the default value will be `nil`
|
138
|
+
|
139
|
+
if the data you want to save is not always setted, it'is possible to use `:if_empty`.
|
140
|
+
this will try to take data in another field if the source is empty. in the exemple you
|
141
|
+
can see the usage as date.dateTime is not always present (for exemple long event that only
|
142
|
+
date of start and date of end without hours).
|
143
|
+
|
144
|
+
|
145
|
+
Compatibility
|
146
|
+
-------------
|
147
|
+
|
148
|
+
For now GcalMapper is only compatible with :
|
149
|
+
|
150
|
+
- ActiveRecord
|
151
|
+
|
152
|
+
it can be extended quite easly
|
153
|
+
|
154
|
+
it is possible to use GcalMapper without ORM, juste include
|
155
|
+
|
156
|
+
GcalMapper::Mapper::Simple
|
157
|
+
|
158
|
+
like this :
|
159
|
+
|
160
|
+
class Event
|
161
|
+
attr_accessor :id, :gid, :name, :description, :status, :start_at, :end_at, :created_at, :upated_at
|
162
|
+
include GcalMapper::Mapper::Simple
|
163
|
+
|
164
|
+
calendar do
|
165
|
+
|
166
|
+
configure :file => 'path/to/your/yaml/file.yaml'
|
167
|
+
|
168
|
+
calendar 'your_email@gmail.com'
|
169
|
+
|
170
|
+
google_id 'gid'
|
171
|
+
|
172
|
+
field 'name', :source => 'summary'
|
173
|
+
field 'description', :source => 'description',
|
174
|
+
:match => /^category: (.*)$/, :default => 'not categorized'
|
175
|
+
field 'status', :source => 'status'
|
176
|
+
field 'start_at', :source => 'start.dateTime'
|
177
|
+
field 'end_at', :source => 'end.dateTime'
|
178
|
+
field 'created_at', :source => 'created'
|
179
|
+
field 'updated_at', :source => 'updated'
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.synchronize_me
|
183
|
+
self.synchronize_calendar
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
Source data reference
|
190
|
+
---------------------
|
191
|
+
|
192
|
+
here is the reference for the source data :
|
193
|
+
|
194
|
+
{
|
195
|
+
"kind": "calendar#event",
|
196
|
+
"etag": etag,
|
197
|
+
"id": string,
|
198
|
+
"status": string,
|
199
|
+
"htmlLink": string,
|
200
|
+
"created": datetime,
|
201
|
+
"updated": datetime,
|
202
|
+
"summary": string,
|
203
|
+
"description": string,
|
204
|
+
"location": string,
|
205
|
+
"colorId": string,
|
206
|
+
"creator": {
|
207
|
+
"email": string,
|
208
|
+
"displayName": string
|
209
|
+
},
|
210
|
+
"organizer": {
|
211
|
+
"email": string,
|
212
|
+
"displayName": string
|
213
|
+
},
|
214
|
+
"start": {
|
215
|
+
"date": date,
|
216
|
+
"dateTime": datetime,
|
217
|
+
"timeZone": string
|
218
|
+
},
|
219
|
+
"end": {
|
220
|
+
"date": date,
|
221
|
+
"dateTime": datetime,
|
222
|
+
"timeZone": string
|
223
|
+
},
|
224
|
+
"recurrence": [
|
225
|
+
string
|
226
|
+
],
|
227
|
+
"recurringEventId": string,
|
228
|
+
"originalStartTime": {
|
229
|
+
"date": date,
|
230
|
+
"dateTime": datetime,
|
231
|
+
"timeZone": string
|
232
|
+
},
|
233
|
+
"transparency": string,
|
234
|
+
"visibility": string,
|
235
|
+
"iCalUID": string,
|
236
|
+
"sequence": integer,
|
237
|
+
"attendees": [
|
238
|
+
{
|
239
|
+
"email": string,
|
240
|
+
"displayName": string,
|
241
|
+
"organizer": boolean,
|
242
|
+
"self": boolean,
|
243
|
+
"resource": boolean,
|
244
|
+
"optional": boolean,
|
245
|
+
"responseStatus": string,
|
246
|
+
"comment": string,
|
247
|
+
"additionalGuests": integer
|
248
|
+
}
|
249
|
+
],
|
250
|
+
"attendeesOmitted": boolean,
|
251
|
+
"extendedProperties": {
|
252
|
+
"private": {
|
253
|
+
(key): string
|
254
|
+
},
|
255
|
+
"shared": {
|
256
|
+
(key): string
|
257
|
+
}
|
258
|
+
},
|
259
|
+
"gadget": {
|
260
|
+
"type": string,
|
261
|
+
"title": string,
|
262
|
+
"link": string,
|
263
|
+
"iconLink": string,
|
264
|
+
"width": integer,
|
265
|
+
"height": integer,
|
266
|
+
"display": string,
|
267
|
+
"preferences": {
|
268
|
+
(key): string
|
269
|
+
}
|
270
|
+
},
|
271
|
+
"anyoneCanAddSelf": boolean,
|
272
|
+
"guestsCanInviteOthers": boolean,
|
273
|
+
"guestsCanModify": boolean,
|
274
|
+
"guestsCanSeeOtherGuests": boolean,
|
275
|
+
"privateCopy": boolean,
|
276
|
+
"reminders": {
|
277
|
+
"useDefault": boolean,
|
278
|
+
"overrides": [
|
279
|
+
{
|
280
|
+
"method": string,
|
281
|
+
"minutes": integer
|
282
|
+
}
|
283
|
+
]
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
for now you can access nearly all the data. you can acces all the the first and second degree key.
|
288
|
+
|
289
|
+
if you want the summary :
|
290
|
+
|
291
|
+
field 'summary', :source => 'summary'
|
292
|
+
|
293
|
+
for the creator email :
|
294
|
+
|
295
|
+
field 'creator_email', :source => 'creator.email'
|
296
|
+
|
297
|
+
if you just put `creator` it will give your the string dump of
|
298
|
+
|
299
|
+
"organizer": {
|
300
|
+
"email": string,
|
301
|
+
"displayName": string
|
302
|
+
},
|
303
|
+
|
304
|
+
that will look like :
|
305
|
+
|
306
|
+
"{\"email\"=>\"string\", \"displayName\"=>\"string\"}"
|
307
|
+
|
308
|
+
for things like `attendees` or `extendedProperties`, the only way of saving it is to save the string dump of that
|
309
|
+
|
310
|
+
for `attendees` :
|
311
|
+
|
312
|
+
field 'attendees_dump', :source 'attendees'
|
313
|
+
|
314
|
+
for `extendedProperties` you can do `extendedProperties.private` but `extendedProperties.private.(field)` will not work
|
315
|
+
|
316
|
+
|
317
|
+
Test
|
318
|
+
----
|
319
|
+
|
320
|
+
to launch test you need to have files to do both authentification methodes
|
321
|
+
|
322
|
+
execute, with the data of the installed app
|
323
|
+
|
324
|
+
gcal-mapper --client_id [CLIENT_ID] --client_secret [CLIENT_SECRET]
|
325
|
+
|
326
|
+
copy the file ".p12" to "spec/file/privatekey.p12"
|
327
|
+
copy the file "~/google_auth.yaml" to "spec/file/google_auth.yaml"
|
328
|
+
|
329
|
+
|
330
|
+
then create spec/file/config.yaml, must look like this :
|
331
|
+
|
332
|
+
p12: 'spec/file/privatekey.p12' -> path to your p12 key
|
333
|
+
p12_2: 'spec/file/privatekey2.p12' -> path to a second one, can be the same
|
334
|
+
client_email: '[your service account's email address]'
|
335
|
+
user_email: '[email address that your service account can impersonate]'
|
336
|
+
yaml: 'spec/file/google_auth.yaml' -> or another path
|
337
|
+
yaml_relative: '~/google_auth.yaml' -> or another path, relative
|
338
|
+
bad_yaml: 'spec/file/bad_yaml.yaml' -> create an empty file
|
339
|
+
calendar_id: '[an accesible calendar id, possibly the same at user email]'
|
340
|
+
|
341
|
+
then run
|
342
|
+
|
343
|
+
rake spec
|
344
|
+
|
345
|
+
test's are running!
|
346
|
+
|
347
|
+
### Warning ##
|
348
|
+
|
349
|
+
Ruby 1.8.7 does not support the vcr cassettes generate by 1.9.x, but 1.8.7 cassettes work with 1.9.x.
|
350
|
+
D'ont forget to remove the cassettes before testing with 1.8.7 if you already tested with 1.9.x.
|
351
|
+
|
352
|
+
Contributing
|
353
|
+
------------
|
354
|
+
|
355
|
+
1. Fork it
|
356
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
357
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
358
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
359
|
+
5. Create new Pull Request
|
360
|
+
|
361
|
+
|
362
|
+
Copyright & License
|
363
|
+
-------------------
|
364
|
+
|
365
|
+
Copyright (c) 2012 Liquid Concept. See LICENSE for details.
|
366
|
+
|
367
|
+
|
368
|
+
[1]: https://code.google.com/apis/console
|
369
|
+
[2]: https://developers.google.com/google-apps/calendar/v3/reference/events?hl=fr-FR
|
370
|
+
[3]: https://github.com/ndubuis/gcal_mapper-exemple
|