etherpad-lite 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2011 Jordan Hollinger
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,58 @@
1
+ = Etherpad Lite Ruby Client
2
+
3
+ etherpad-lite is a Ruby client for Etherpad Lite's HTTP JSON API. Etherpad Lite is a collaborative editor provided by the Etherpad Foundation (http://etherpad.org).
4
+
5
+ See https://github.com/Pita/etherpad-lite for information on how to install and configure your own Etherpad Lite instance, and read https://github.com/Pita/etherpad-lite/wiki/HTTP-API for an in-depth description of Etherpad Lite's HTTP API.
6
+
7
+ == 1 Installation
8
+ gem install etherpad-lite
9
+
10
+ == 2 Basic usage
11
+ require 'etherpad-lite'
12
+
13
+ # Connect to your Etherpad Lite instance (passing no arguments will connect you to beta.etherpad.org)
14
+ ether = EtherpadLite.connect('http://etherpad-lite.example.com', 'your api key')
15
+
16
+ # Get a Pad (or create one if it doesn't exist)
17
+ pad = ether.pad('my first etherpad lite pad')
18
+
19
+ puts pad.text
20
+ => "Welcome to Etherpad Lite!\n\nThis pad text is synchronized as you type, so that everyone viewing..."
21
+
22
+ # Write your the changes to the Pad
23
+ pad.text = "What hath God wrought?"
24
+
25
+ # There are now 2 revisions!
26
+ puts pad.revison_numbers.size
27
+ => 2
28
+
29
+ # Iterate through each revision
30
+ pad.revisions.each do |pad_rev|
31
+ puts pad_rev.rev
32
+ puts pad_rev.text
33
+ end
34
+
35
+ For advanced functionality (i.e. Groups, Authors, Sessions), read the full docs at http://jordanhollinger.com/docs/ruby-etherpad-lite/. Also, the RSpec tests under /spec may provide some pointers.
36
+
37
+ == 3 Why is the Ruby client so different from the others? What gives you the right?!?
38
+ The PHP and Python clients are extremely thin wrappers of the HTTP API; their method names map directly to urls. The Ruby client offers a slightly higher level of abstration,
39
+ with the goal of making Etherpad Lite integration in Ruby projects as simple, poweful, and dare I say fun, as possible.
40
+
41
+ That said, there is certainly value in supporting the defacto standard set by the PHP and Python clients. With that in mind, the Ruby client is written in two layers,
42
+ one conforming to the other clients, and a higher-level "models" API riding on top of it.
43
+
44
+ In the examples above, the "traditional" client can be accessed with
45
+ client = ether.client
46
+ client.getText('my first etherpad lite pad')
47
+ => {:text => "What hath God wrought?"}
48
+
49
+ Or you can explicitly load only the standard client
50
+ require 'etherpad-lite/client'
51
+ client = EtherpadLite::Client.new('api key', 'http://beta.etherpad.org/api')
52
+
53
+ == 4 License
54
+ Copyright 2011 Jordan Hollinger
55
+ Licensed under the Apache License
56
+
57
+ == 5 Credit
58
+ This Ruby client was inspired by TomNomNom's and devjones's PHP and Python clients, found at https://github.com/TomNomNom/etherpad-lite-client and https://github.com/devjones/PyEtherpadLite.
@@ -0,0 +1,2 @@
1
+ require 'etherpad-lite/client'
2
+ require 'etherpad-lite/models'
@@ -0,0 +1,229 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'json'
5
+
6
+ module EtherpadLite
7
+ # A thin wrapper over Etherpad Lite's HTTP JSON API
8
+ class Client
9
+ API_VERSION = 1
10
+
11
+ CODE_OK = 0
12
+ CODE_INVALID_PARAMETERS = 1
13
+ CODE_INTERNAL_ERROR = 2
14
+ CODE_INVALID_METHOD = 3
15
+ CODE_INVALID_API_KEY = 4
16
+
17
+ attr_reader :uri, :api_key
18
+
19
+ # Path to the system's CA cert paths (for connecting over SSL)
20
+ @@ca_path = nil
21
+
22
+ # Get path to the system's CA certs
23
+ def self.ca_path; @@ca_path; end
24
+
25
+ # Manually set path to the system's CA certs. Use this if the location couldn't be determined automatically.
26
+ def self.ca_path=(path); @@ca_path = path; end
27
+
28
+ # Instantiate a new Etherpad Lite Instance. The url should include the protocol (i.e. http or https).
29
+ def initialize(api_key, url='http://localhost:9001/api')
30
+ @uri = URI.parse(url)
31
+ raise ArgumentError, "#{url} is not a valid url" unless @uri.host and @uri.port
32
+ @api_key = api_key
33
+ connect!
34
+ end
35
+
36
+ # Pad, Group, etc. all use this to send the HTTP API requests.
37
+ def call(method, params={})
38
+ # Build path
39
+ params[:apikey] = @api_key
40
+ params = params.map { |p| p.join('=') }.join('&').gsub(/\s/, '%20')
41
+ path = [@uri.path, API_VERSION, method].compact.join('/') << '?' << params
42
+ # Send request
43
+ get = Net::HTTP::Get.new(path)
44
+ response = @http.request(get)
45
+ handleResult response.body
46
+ end
47
+
48
+ # Groups
49
+ # Pads can belong to a group. There will always be public pads which don't belong to a group.
50
+
51
+ # Creates a new Group
52
+ def createGroup
53
+ call :createGroup
54
+ end
55
+
56
+ # Creates a new Group for groupMapper if one doesn't already exist. Helps you map your application's groups to Etherpad Lite's groups.
57
+ def createGroupIfNotExistsFor(groupMapper)
58
+ call :createGroupIfNotExistsFor, :groupMapper => groupMapper
59
+ end
60
+
61
+ # Deletes a group
62
+ def deleteGroup(groupID)
63
+ call :deleteGroup, :groupID => groupID
64
+ end
65
+
66
+ # Returns all the Pads in the given Group
67
+ def listPads(groupID)
68
+ call :listPads, :groupID => groupID
69
+ end
70
+
71
+ # Creates a new Pad in the given Group
72
+ def createGroupPad(groupID, padName, text=nil)
73
+ params = {:groupID => groupID, :padName => padName}
74
+ params[:text] = text unless text.nil?
75
+ call :createGroupPad, params
76
+ end
77
+
78
+ # Authors
79
+ # These authors are bound to the attributes the users choose (color and name).
80
+
81
+ # Create a new Author
82
+ def createAuthor(name=nil)
83
+ params = {}
84
+ params[:name] = name unless name.nil?
85
+ call :createAuthor, params
86
+ end
87
+
88
+ # Creates a new Author for authorMapper if one doesn't already exist. Helps you map your application's authors to Etherpad Lite's authors.
89
+ def createAuthorIfNotExistsFor(authorMapper, name=nil)
90
+ params = {:authorMapper => authorMapper}
91
+ params[:name] = name unless name.nil?
92
+ call :createAuthorIfNotExistsFor, params
93
+ end
94
+
95
+ # Sessions
96
+ # Sessions can be created between a group and an author. This allows
97
+ # an author to access more than one group. The sessionID will be set as
98
+ # a cookie to the client and is valid until a certian date.
99
+
100
+ # Creates a new Session for the given Author in the given Group
101
+ def createSession(groupID, authorID, validUntil)
102
+ call :createSession, :groupID => groupID, :authorID => authorID, :validUntil => validUntil
103
+ end
104
+
105
+ # Deletes the given Session
106
+ def deleteSession(sessionID)
107
+ call :deleteSession, :sessionID => sessionID
108
+ end
109
+
110
+ # Returns information about the Session
111
+ def getSessionInfo(sessionID)
112
+ call :getSessionInfo, :sessionID => sessionID
113
+ end
114
+
115
+ # Returns all Sessions in the given Group
116
+ def listSessionsOfGroup(groupID)
117
+ call :listSessionsOfGroup, :groupID => groupID
118
+ end
119
+
120
+ # Returns all Sessions belonging to the given Author
121
+ def listSessionsOfAuthor(authorID)
122
+ call :listSessionsOfAuthor, :authorID => authorID
123
+ end
124
+
125
+ # Pad content
126
+ # Pad content can be updated and retrieved through the API
127
+
128
+ # Returns the text of the given Pad. Optionally pass a revision number to get the text for that revision.
129
+ def getText(padID, rev=nil)
130
+ params = {:padID => padID}
131
+ params[:rev] = rev unless rev.nil?
132
+ call :getText, params
133
+ end
134
+
135
+ # Sets the text of the given Pad
136
+ def setText(padID, text)
137
+ call :setText, :padID => padID, :text => text
138
+ end
139
+
140
+ # Pad
141
+ # Group pads are normal pads, but with the name schema
142
+ # GROUPID$PADNAME. A security manager controls access of them and
143
+ # forbids normal pads from including a "$" in the name.
144
+
145
+ # Create a new Pad. Optionally specify the initial text.
146
+ def createPad(padID, text=nil)
147
+ params = {:padID => padID}
148
+ params[:text] = text unless text.nil?
149
+ call :createPad, params
150
+ end
151
+
152
+ # Returns the number of revisions the given Pad contains
153
+ def getRevisionsCount(padID)
154
+ call :getRevisionsCount, :padID => padID
155
+ end
156
+
157
+ # Delete the given Pad
158
+ def deletePad(padID)
159
+ call :deletePad, :padID => padID
160
+ end
161
+
162
+ # Returns the Pad's read-only id
163
+ def getReadOnlyID(padID)
164
+ call :getReadOnlyID, :padID => padID
165
+ end
166
+
167
+ # Sets a boolean for the public status of a Pad
168
+ def setPublicStatus(padID, publicStatus)
169
+ call :setPublicStatus, :padID => padID, :publicStatus => publicStatus
170
+ end
171
+
172
+ # Gets a boolean for the public status of a Pad
173
+ def getPublicStatus(padID)
174
+ call :getPublicStatus, :padID => padID
175
+ end
176
+
177
+ # Sets the password on a pad
178
+ def setPassword(padID, password)
179
+ call :setPassword, :padID => padID, :password => password
180
+ end
181
+
182
+ # Returns true if the Pad has a password, false if not
183
+ def isPasswordProtected(padID)
184
+ call :isPasswordProtected, :padID => padID
185
+ end
186
+
187
+ # Returns true if the connection to the Etherpad Lite instance is using SSL/HTTPS.
188
+ def secure?
189
+ @uri.port == 443
190
+ end
191
+
192
+ protected
193
+
194
+ # Parses the JSON response from the server, returning the data object as a Hash with symbolized keys.
195
+ # If the API response contains an error code, an exception is raised.
196
+ def handleResult(response)
197
+ response = JSON.parse(response, :symbolize_names => true)
198
+ case response[:code]
199
+ when CODE_OK then response[:data]
200
+ when CODE_INVALID_PARAMETERS, CODE_INVALID_API_KEY, CODE_INVALID_METHOD
201
+ raise ArgumentError, response[:message]
202
+ else
203
+ raise StandardError, "An unknown error ocurrced while handling the response: #{response.to_s}"
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ # Initialize the HTTP connection object
210
+ def connect!
211
+ @http = Net::HTTP.new(@uri.host, @uri.port)
212
+ if secure?
213
+ @http.use_ssl = true
214
+ if @@ca_path
215
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
216
+ @http.ca_path = @@ca_path
217
+ else
218
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ # Try to find the system's CA certs
226
+ %w{/etc/ssl/certs /etc/ssl /usr/share/ssl /usr/lib/ssl /System/Library/OpenSSL /usr/local/ssl}.each do |path|
227
+ EtherpadLite::Client.ca_path = path and break if File.exists? path
228
+ end
229
+ $stderr.puts %q|WARNING Unable to find your CA Certificates; HTTPS connections will *not* be verified! You may remedy this with "EtherpadLite::Instance.ca_path = '/path/to/certs'"| unless EtherpadLite::Client.ca_path
@@ -0,0 +1,6 @@
1
+ require 'etherpad-lite/models/padded'
2
+ require 'etherpad-lite/models/instance'
3
+ require 'etherpad-lite/models/pad'
4
+ require 'etherpad-lite/models/group'
5
+ require 'etherpad-lite/models/author'
6
+ require 'etherpad-lite/models/session'
@@ -0,0 +1,53 @@
1
+ module EtherpadLite
2
+ # An Author of Pad content
3
+ class Author
4
+ attr_reader :id, :instance, :name, :mapper
5
+
6
+ # Creates a new Author. Optionally, you may pass the :mapper option your third party system's author id.
7
+ # This will allow you to find the Author again later using the same identifier as your foreign system.
8
+ # If you pass the mapper option, the method behaves like "create author for <mapper> if it doesn't already exist".
9
+ #
10
+ # Options:
11
+ #
12
+ # mapper => uid of Author from another system
13
+ #
14
+ # name => Author's name
15
+ def self.create(instance, options={})
16
+ result = options[:mapper] \
17
+ ? instance.client.createAuthorIfNotExistsFor(options[:mapper], options[:name]) \
18
+ : instance.client.createAuthor(options[:name])
19
+ new instance, result[:authorID], options
20
+ end
21
+
22
+ # Instantiates an Author object (presumed it already exists)
23
+ #
24
+ # Options:
25
+ #
26
+ # mapper => the foreign author id it's mapped to
27
+ #
28
+ # name => the Author's name
29
+ def initialize(instance, id, options={})
30
+ @instance = instance
31
+ @id = id
32
+ @mapper = options[:mapper]
33
+ @name = options[:name]
34
+ end
35
+
36
+ # Create a new session for group that will last length_in_minutes.
37
+ def create_session(group, length_in_min)
38
+ Session.create(@instance, group.id, @id, length_in_min)
39
+ end
40
+
41
+ # Returns all session ids from this Author
42
+ def session_ids
43
+ s = @instance.client.listSessionsOfAuthor(@id) || {}
44
+ s.keys
45
+ end
46
+
47
+ # Returns all sessions from this Author
48
+ def sessions
49
+ s = @instance.client.listSessionsOfAuthor(@id) || {}
50
+ s.map { |id,info| Session.new(@instance, id, info) }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,97 @@
1
+ module EtherpadLite
2
+ # A Group of Pads
3
+ class Group
4
+ GROUP_ID_REGEX = /^g\.[^\$]+/
5
+ include Padded
6
+
7
+ attr_reader :id, :instance, :mapper
8
+
9
+ # Creates a new Group. Optionally, you may pass the :mapper option your third party system's group id.
10
+ # This will allow you to find your Group again later using the same identifier as your foreign system.
11
+ # If you pass the mapper option, the method behaves like "create group for <mapper> if it doesn't already exist".
12
+ #
13
+ # Options:
14
+ #
15
+ # mapper => your foreign group id
16
+ def self.create(instance, options={})
17
+ result = options[:mapper] \
18
+ ? instance.client.createGroupIfNotExistsFor(options[:mapper]) \
19
+ : instance.client.createGroup
20
+ new instance, result[:groupID], options
21
+ end
22
+
23
+ # Instantiates a Group object (presumed it already exists)
24
+ #
25
+ # Options:
26
+ #
27
+ # mapper => the foreign id it's mapped to
28
+ def initialize(instance, id, options={})
29
+ @instance = instance
30
+ @id = id
31
+ @mapper = options[:mapper]
32
+ end
33
+
34
+ # Returns the Pad with the given id, creating it if it doesn't already exist.
35
+ # This requires an HTTP request, so if you *know* the Pad already exists, use Group#get_pad instead.
36
+ def pad(id, options={})
37
+ options[:groupID] = @id
38
+ super groupify_pad_id(id), options
39
+ end
40
+
41
+ # Returns the Pad with the given id (presumed to already exist).
42
+ # Use this instead of Group#pad when you *know* the Pad already exists; it will save an HTTP request.
43
+ def get_pad(id, options={})
44
+ options[:group] = self
45
+ super groupify_pad_id(id), options
46
+ end
47
+
48
+ # Creates and returns a Pad with the given id.
49
+ #
50
+ # Options:
51
+ #
52
+ # text => 'initial Pad text'
53
+ def create_pad(id, options={})
54
+ options[:groupID] = @id
55
+ super groupify_pad_id(id), options
56
+ end
57
+
58
+ # Returns an array of all the Pads in this Group.
59
+ def pads
60
+ pad_ids.map { |id| Pad.new(@instance, id, :group => self) }
61
+ end
62
+
63
+ # Returns an array of all the Pad ids in this Group.
64
+ def pad_ids
65
+ @instance.client.listPads(@id)[:padIDs].keys
66
+ end
67
+
68
+ # Create a new session for author that will last length_in_minutes.
69
+ def create_session(author, length_in_min)
70
+ Session.create(@instance, @id, author.id, length_in_min)
71
+ end
72
+
73
+ # Returns all session ids in this Group
74
+ def session_ids
75
+ s = @instance.client.listSessionsOfGroup(@id) || {}
76
+ s.keys
77
+ end
78
+
79
+ # Returns all sessions in this Group
80
+ def sessions
81
+ s = @instance.client.listSessionsOfGroup(@id) || {}
82
+ s.map { |id,info| Session.new(@instance, id, info) }
83
+ end
84
+
85
+ # Deletes the Group
86
+ def delete
87
+ @instance.client.deleteGroup(@id)
88
+ end
89
+
90
+ private
91
+
92
+ # Prepend the group_id to the pad name
93
+ def groupify_pad_id(pad_id)
94
+ "#{@id}$#{pad_id}"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,94 @@
1
+ module EtherpadLite
2
+ # Aliases to common Etherpad Lite hosts
3
+ HOST_ALIASES = {:local => 'http://localhost:9001',
4
+ :public => 'http://beta.etherpad.org'}
5
+
6
+ # Returns an EtherpadLite::Instance object.
7
+ #
8
+ # ether1 = EtherpadLite.connect('https://etherpad.yoursite.com[https://etherpad.yoursite.com]', 'your api key')
9
+ #
10
+ # ether2 = EtherpadLite.connect(:local, File.new('/file/path/to/APIKEY.txt'))
11
+ #
12
+ # ether3 = EtherpadLite.connect(:public, "beta.etherpad.org's api key")
13
+ def self.connect(host_or_alias, api_key_or_file)
14
+ # Parse the host
15
+ host = if host_or_alias.is_a? Symbol
16
+ raise ArgumentError, %Q|Unknown host alias "#{host_or_alias}"| unless HOST_ALIASES.has_key? host_or_alias
17
+ HOST_ALIASES[host_or_alias]
18
+ else
19
+ host_or_alias
20
+ end
21
+
22
+ # Parse the api key
23
+ if api_key_or_file.is_a? File
24
+ api_key = api_key_or_file.read
25
+ api_key_or_file.close
26
+ else
27
+ api_key = api_key_or_file
28
+ end
29
+
30
+ Instance.new(host, api_key)
31
+ end
32
+
33
+ # An EtherpadLite::Instance provides a hight-level interface to an Etherpad Lite system.
34
+ class Instance
35
+ include Padded
36
+ API_ROOT = 'api'
37
+
38
+ attr_reader :client
39
+
40
+ # Instantiate a new Etherpad Lite Instance. The url should include the protocol (i.e. http or https).
41
+ def initialize(url, api_key)
42
+ @client = Client.new(api_key, url + "/#{API_ROOT}")
43
+ end
44
+
45
+ # Returns, creating if necessary, a Group mapped to your foreign system's group
46
+ def group(mapper)
47
+ create_group(:mapper => mapper)
48
+ end
49
+
50
+ # Returns a Group with the given id (it is presumed to already exist).
51
+ def get_group(id)
52
+ Group.new self, id
53
+ end
54
+
55
+ # Creates a new Group. Optionally, you may pass the :mapper option your third party system's group id.
56
+ # This will allow you to find your Group again later using the same identifier as your foreign system.
57
+ #
58
+ # Options:
59
+ #
60
+ # mapper => your foreign group id
61
+ def create_group(options={})
62
+ Group.create self, options
63
+ end
64
+
65
+ # Returns, creating if necessary, a Author mapped to your foreign system's author
66
+ #
67
+ # Options:
68
+ #
69
+ # name => the Author's name
70
+ def author(mapper, options={})
71
+ options[:mapper] = mapper
72
+ create_author options
73
+ end
74
+
75
+ # Returns an Author with the given id (it is presumed to already exist).
76
+ def get_author(id)
77
+ Author.new self, id
78
+ end
79
+
80
+ # Creates a new Author. Optionally, you may pass the :mapper option your third party system's author id.
81
+ # This will allow you to find the Author again later using the same identifier as your foreign system.
82
+ #
83
+ # Options:
84
+ #
85
+ # mapper => your foreign author id
86
+ #
87
+ # name => the Author's name
88
+ def create_author(options={})
89
+ Author.create self, options
90
+ end
91
+
92
+ def instance; self; end
93
+ end
94
+ end
@@ -0,0 +1,135 @@
1
+ module EtherpadLite
2
+ # An Etherpad Lite Pad
3
+ class Pad
4
+ attr_reader :id, :instance, :rev
5
+
6
+ # Creates and returns a new Pad.
7
+ #
8
+ # Options:
9
+ #
10
+ # text => 'initial Pad text'
11
+ #
12
+ # groupID => group id of Group new Pad should belong to
13
+ def self.create(instance, id, options={})
14
+ if options[:groupID]
15
+ group = Group.new instance, options[:groupID]
16
+ instance.client.createGroupPad(group.id, degroupify_pad_id(id), options[:text])
17
+ else
18
+ group = nil
19
+ instance.client.createPad(id, options[:text])
20
+ end
21
+ new instance, id, :group => group
22
+ end
23
+
24
+ # Remove the group id portion of a Group Pad's id
25
+ def self.degroupify_pad_id(pad_id)
26
+ pad_id.to_s.sub(Group::GROUP_ID_REGEX, '').sub(/^\$/, '')
27
+ end
28
+
29
+ # Instantiate a Pad. It is presumed to already exist (via Pad.create).
30
+ #
31
+ # Options:
32
+ #
33
+ # group
34
+ #
35
+ # rev
36
+ def initialize(instance, id, options={})
37
+ @instance = instance
38
+ @id = id.to_s
39
+ @group = options[:group]
40
+ @rev = options[:rev]
41
+ end
42
+
43
+ # Returns the name of the Pad. For a normal pad, this is the same as it's id. But for a Group Pad,
44
+ # this strips away the group id part of the pad id.
45
+ def name
46
+ @name ||= self.class.degroupify_pad_id(@id)
47
+ end
48
+
49
+ # Returns the group_id of this Pad, if any
50
+ def group_id
51
+ unless @group_id
52
+ match = Group::GROUP_ID_REGEX.match(@id)
53
+ @group_id = match ? match[0] : nil
54
+ end
55
+ @group_id
56
+ end
57
+
58
+ # Returns this Pad's group, if any
59
+ def group
60
+ return nil unless group_id
61
+ @group ||= Group.new(@instance, group_id)
62
+ end
63
+
64
+ # Returns the Pad's text. Unless you specified a :rev when instantiating the Pad, or specify one here, this will return the latest revision.
65
+ #
66
+ # Options:
67
+ #
68
+ # rev => revision_number
69
+ def text(options={})
70
+ @instance.client.getText(@id, options[:rev])[:text]
71
+ end
72
+
73
+ # Writes txt to the Pad. There is no 'save' method; it is written immediately.
74
+ def text=(txt)
75
+ @instance.client.setText(@id, txt)
76
+ end
77
+
78
+ # Returns a Range of all this Pad's revision numbers
79
+ def revision_numbers
80
+ max = @instance.client.getRevisionsCount(@id)[:revisions]
81
+ (0..max).to_a
82
+ end
83
+
84
+ # Returns an array of Pad objects, each with an increasing revision of the text.
85
+ def revisions
86
+ revision_numbers.map { |n| Pad.new(@instance, @id, :rev => n) }
87
+ end
88
+
89
+ # Returns the Pad's read-only id. This is cached.
90
+ def read_only_id
91
+ @read_only_id ||= @instance.client.getReadOnlyID(@id)[:readOnlyID]
92
+ end
93
+
94
+ # Returns true if this is a public Pad (opposite of private).
95
+ # This only applies to Pads belonging to a Group.
96
+ def public?
97
+ @instance.client.getPublicStatus(@id)[:publicStatus]
98
+ end
99
+
100
+ # Set the pad's public status to true or false (opposite of private=)
101
+ # This only applies to Pads belonging to a Group.
102
+ def public=(status)
103
+ @instance.client.setPublicStatus(@id, status)
104
+ end
105
+
106
+ # Returns true if this is a private Pad (opposite of public)
107
+ # This only applies to Pads belonging to a Group.
108
+ def private?
109
+ not public?
110
+ end
111
+
112
+ # Set the pad's private status to true or false (opposite of public=)
113
+ # This only applies to Pads belonging to a Group.
114
+ def private=(status)
115
+ public = !status
116
+ end
117
+
118
+ # Returns true if this Pad has a password, false if not.
119
+ # This only applies to Pads belonging to a Group.
120
+ def password?
121
+ @instance.client.isPasswordProtected(@id)[:isPasswordProtected]
122
+ end
123
+
124
+ # Sets the Pad's password.
125
+ # This only applies to Pads belonging to a Group.
126
+ def password=(new_password)
127
+ @instance.client.setPassword(@id, new_password)
128
+ end
129
+
130
+ # Deletes the Pad
131
+ def delete
132
+ @instance.client.deletePad(@id)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,26 @@
1
+ module EtherpadLite
2
+ # Methods for dealing with pads belonging to something. Both Instance and Group include this, as they each have pads.
3
+ # This will work with any object which has an instance method, returning an EtherpadLite Instance object.
4
+ module Padded
5
+ # Returns the Pad with the given id, creating it if it doesn't already exist.
6
+ # This requires an HTTP request, so if you *know* the Pad already exists, use Instance#get_pad instead.
7
+ def pad(id, options={})
8
+ Pad.create(instance, id, options) rescue Pad.new(instance, id, options)
9
+ end
10
+
11
+ # Returns the Pad with the given id (presumed to already exist).
12
+ # Use this instead of Instance#pad when you *know* the Pad already exists; it will save an HTTP request.
13
+ def get_pad(id, options={})
14
+ Pad.new instance, id, options
15
+ end
16
+
17
+ # Creates and returns a Pad with the given id.
18
+ #
19
+ # Options:
20
+ #
21
+ # text => 'initial Pad text'
22
+ def create_pad(id, options={})
23
+ Pad.create instance, id, options
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ module EtherpadLite
2
+ # An Etherpad Lite Session between a Group and an Author
3
+ class Session
4
+ attr_reader :id, :instance
5
+
6
+ # Creates a new Session between a Group and an Author. The session will expire after length_in_min.
7
+ def self.create(instance, group_id, author_id, length_in_min)
8
+ valid_until = Time.now.to_i + length_in_min * 60
9
+ result = instance.client.createSession(group_id, author_id, valid_until)
10
+ new instance, result[:sessionID]
11
+ end
12
+
13
+ # Instantiates a Session object (presumed to already exist)
14
+ def initialize(instance, id, info=nil)
15
+ @instance = instance
16
+ @id = id
17
+ @info = info
18
+ end
19
+
20
+ # Returns the Session's group id
21
+ def group_id
22
+ get_info[:groupID]
23
+ end
24
+
25
+ # Returns the Session's group
26
+ def group
27
+ @group ||= Group.new @instance, group_id
28
+ end
29
+
30
+ # Returns the Session's author id
31
+ def author_id
32
+ get_info[:authorID]
33
+ end
34
+
35
+ # Returns the Session's author
36
+ def author
37
+ @author ||= Author.new @instance, author_id
38
+ end
39
+
40
+ # Returns the Session's expiration date is a Unix timestamp in seconds
41
+ def valid_until
42
+ get_info[:validUntil]
43
+ end
44
+
45
+ def valid?
46
+ valid_until < Time.now.to_i
47
+ end
48
+
49
+ def expired?
50
+ not valid?
51
+ end
52
+
53
+ # Deletes the Session
54
+ def delete
55
+ @instance.client.deleteSession(@id)
56
+ end
57
+
58
+ private
59
+
60
+ # Request and cache session info from the server
61
+ def get_info
62
+ @info ||= @instance.client.getSessionInfo(@id)
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EtherpadLite::Author do
4
+ before do
5
+ @eth = EtherpadLite.connect TEST_CONFIG[:instances][:http][:url], TEST_CONFIG[:instances][:http][:api_key]
6
+ end
7
+
8
+ it "should be created" do
9
+ author = @eth.create_author
10
+ author.id.nil?.should == false
11
+ end
12
+
13
+ it "should be mapped to 'Author A'" do
14
+ author = @eth.create_author :mapper => 'Author A'
15
+ author.id.nil?.should == false
16
+ end
17
+
18
+ it "should be mapped to 'Author A'" do
19
+ author1 = @eth.create_author :mapper => 'Author A'
20
+ author2 = @eth.author 'Author A'
21
+ # They should be the same
22
+ author1.id.should == author2.id
23
+ end
24
+ end
data/spec/config.yml ADDED
@@ -0,0 +1,9 @@
1
+ :instances:
2
+ :http:
3
+ :url: http://localhost:9003
4
+ :api_key: faia3X2dT0u57Du6io44h4WKvePnUltg
5
+
6
+ # Uncomment to test https connections
7
+ #:https:
8
+ # :url: https://localhost:9001
9
+ # :api_key: api key
@@ -0,0 +1,9 @@
1
+ :instances:
2
+ :http:
3
+ :url: http://localhost:9001
4
+ :api_key: your api key
5
+
6
+ # Uncomment to test https connections
7
+ #:https:
8
+ # :url: https://localhost:9001
9
+ # :api_key: api key
@@ -0,0 +1,102 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EtherpadLite::Group do
4
+ before do
5
+ @eth = EtherpadLite.connect TEST_CONFIG[:instances][:http][:url], TEST_CONFIG[:instances][:http][:api_key]
6
+ end
7
+
8
+ it "should be created" do
9
+ group = @eth.create_group
10
+ group.id.nil?.should == false
11
+ end
12
+
13
+ it "should be mapped to 'Group A'" do
14
+ group = @eth.create_group :mapper => 'Group A'
15
+ group.id.nil?.should == false
16
+ end
17
+
18
+ it "should be mapped to 'Group A'" do
19
+ group1 = @eth.create_group :mapper => 'Group A'
20
+ group2 = @eth.group 'Group A'
21
+ # They should be the same
22
+ group1.id.should == group2.id
23
+ end
24
+
25
+ it "should create a Group Pad" do
26
+ group = @eth.group 'Group A'
27
+ pad = group.pad 'Important Group Stuff'
28
+ pad.id.should == "#{group.id}$Important Group Stuff"
29
+ end
30
+
31
+ it "should create a Group Pad with the right name" do
32
+ group = @eth.group 'Group A'
33
+ pad = group.pad 'Important Group Stuff'
34
+ pad.name.should == "Important Group Stuff"
35
+ end
36
+
37
+ it "should find a Group Pad with the right group" do
38
+ group = @eth.group 'Group A'
39
+ group.get_pad('Important Group Stuff').group_id.should == group.id
40
+ end
41
+
42
+ it "should find a Group Pad with the right id" do
43
+ group = @eth.group 'Group A'
44
+ group.pad_ids.should == [:"#{group.id}$Important Group Stuff"]
45
+ end
46
+
47
+ it "should find a Group Pad with the right name" do
48
+ group = @eth.group 'Group A'
49
+ group.pads.first.name.should == "Important Group Stuff"
50
+ end
51
+
52
+ it "should explicitly create a Group Pad" do
53
+ group = @eth.group 'Group A'
54
+ pad = group.create_pad 'new group pad', :text => 'abc'
55
+ pad.text.should == "abc\n"
56
+ end
57
+
58
+ context 'Group Pad' do
59
+ context 'Privacy' do
60
+ it "should be private" do
61
+ group = @eth.group 'Group A'
62
+ pad = group.get_pad 'new group pad'
63
+ pad.private?.should == true
64
+ end
65
+
66
+ it "should not be public" do
67
+ group = @eth.group 'Group A'
68
+ pad = group.get_pad 'new group pad'
69
+ pad.public?.should == false
70
+ end
71
+
72
+ it "should change to public" do
73
+ group = @eth.group 'Group A'
74
+ pad = group.get_pad 'new group pad'
75
+ pad.public = true
76
+ pad.public?.should == true
77
+ end
78
+
79
+ it "should change to not private" do
80
+ group = @eth.group 'Group A'
81
+ pad = group.get_pad 'new group pad'
82
+ pad.private = false
83
+ pad.private?.should == false
84
+ end
85
+ end
86
+
87
+ context 'Password' do
88
+ it "should not have a password" do
89
+ group = @eth.group 'Group A'
90
+ pad = group.get_pad 'new group pad'
91
+ pad.password?.should == false
92
+ end
93
+
94
+ it "should have a password set" do
95
+ group = @eth.group 'Group A'
96
+ pad = group.get_pad 'new group pad'
97
+ pad.password = 'correct horse battery staple'
98
+ pad.password?.should == true
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EtherpadLite::Instance do
4
+ before do
5
+ @eth = EtherpadLite.connect TEST_CONFIG[:instances][:http][:url], TEST_CONFIG[:instances][:http][:api_key]
6
+ end
7
+
8
+ it "should have the right API key" do
9
+ @eth.client.api_key.should == TEST_CONFIG[:instances][:http][:api_key]
10
+ end
11
+
12
+ it "shouldn't be secure" do
13
+ @eth.client.secure?.should == false
14
+ end
15
+ end
data/spec/pad_spec.rb ADDED
@@ -0,0 +1,81 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EtherpadLite::Pad do
4
+ before do
5
+ @eth = EtherpadLite.connect TEST_CONFIG[:instances][:http][:url], TEST_CONFIG[:instances][:http][:api_key]
6
+ end
7
+
8
+ it "should blow up when querying a non-existing pad" do
9
+ pad = @eth.get_pad 'a non-existant pad'
10
+ begin
11
+ txt = pad.text
12
+ rescue ArgumentError => e
13
+ end
14
+ e.message.should == 'padID does not exist'
15
+ end
16
+
17
+ it "should create a new pad" do
18
+ pad = @eth.create_pad 'my new pad', :text => 'The initial text'
19
+ pad.text.should == "The initial text\n"
20
+ end
21
+
22
+ it "should blow up when creating a Pad that already exists" do
23
+ begin
24
+ pad = @eth.create_pad 'my new pad', :text => 'The initial text'
25
+ rescue ArgumentError => e
26
+ end
27
+ e.message.should == 'padID does already exist'
28
+ end
29
+
30
+ it "should automatically create a Pad" do
31
+ pad = @eth.pad 'another new pad'
32
+ pad.text = "The initial text"
33
+ pad.text.should == "The initial text\n"
34
+ end
35
+
36
+ it "should automatically find a Pad" do
37
+ pad = @eth.pad 'another new pad'
38
+ pad.text.should == "The initial text\n"
39
+ end
40
+
41
+ it "should find a Pad" do
42
+ pad = @eth.get_pad 'another new pad'
43
+ pad.text.should == "The initial text\n"
44
+ end
45
+
46
+ it "should have a read-only id" do
47
+ pad = @eth.get_pad 'another new pad'
48
+ pad.read_only_id.should =~ /^r\.\w+/
49
+ end
50
+
51
+ it "should add revisions" do
52
+ pad = @eth.get_pad 'another new pad'
53
+ pad.text == "New text"
54
+ pad.revision_numbers.last == 2
55
+ end
56
+
57
+ it "should have the first revision" do
58
+ pad = @eth.get_pad 'another new pad'
59
+ pad.text(:rev => 1).should == "The initial text\n"
60
+ end
61
+
62
+ it "should have the first revision" do
63
+ pad = @eth.get_pad 'another new pad'
64
+ pad.revisions[1].text.should == "The initial text\n"
65
+ end
66
+
67
+ it "should have the same name and id" do
68
+ pad = @eth.get_pad 'another new pad'
69
+ pad.name.should == pad.id
70
+ end
71
+
72
+ it "should be initialized as revision 1" do
73
+ pad = @eth.get_pad 'another new pad', :rev => 1
74
+ pad.text.should == "The initial text\n"
75
+ end
76
+
77
+ it "should be deleted" do
78
+ @eth.get_pad('another new pad').delete
79
+ @eth.create_pad('another new pad').id.should_not == nil
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe EtherpadLite::Session do
4
+ before do
5
+ @eth = EtherpadLite.connect TEST_CONFIG[:instances][:http][:url], TEST_CONFIG[:instances][:http][:api_key]
6
+ end
7
+
8
+ it "should be created for a Group" do
9
+ group = @eth.group 'Maycomb'
10
+ author = @eth.author 'Atticus'
11
+ session = group.create_session(author, 20)
12
+ session.valid_until.should == Time.now.to_i + 20 * 60
13
+ end
14
+
15
+ it "should be created for an Author" do
16
+ group = @eth.group 'Maycomb'
17
+ author = @eth.author 'Scout'
18
+ session = author.create_session(group, 15)
19
+ session.valid_until.should == Time.now.to_i + 15 * 60
20
+ end
21
+
22
+ it "should be found in a Group" do
23
+ group = @eth.group 'Maycomb'
24
+ author = @eth.author 'Atticus'
25
+ group.sessions.map(&:author_id).include?(author.id).should == true
26
+ end
27
+
28
+ it "shouldn be found in a Group" do
29
+ group = @eth.group 'Maycomb'
30
+ author = @eth.author 'Atticus'
31
+ author.sessions.map(&:group_id).include?(group.id).should == true
32
+ end
33
+
34
+ it "shouldn't be found in the wrong Group" do
35
+ group = @eth.group 'Other group'
36
+ author = @eth.author 'Scout'
37
+ group.sessions.map(&:author_id).include?(author.id).should == false
38
+ end
39
+
40
+ it "shouldn't be found in the wrong Group" do
41
+ group = @eth.group 'Other group'
42
+ author = @eth.author 'Scout'
43
+ author.sessions.map(&:group_id).include?(group.id).should == false
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ require 'rspec'
2
+
3
+ # Load etherpad-lite
4
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/client'
5
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/models/padded'
6
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/models/instance'
7
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/models/pad'
8
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/models/group'
9
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/models/author'
10
+ require File.dirname(__FILE__) + '/../lib/etherpad-lite/models/session'
11
+
12
+ Rspec.configure do |c|
13
+ c.mock_with :rspec
14
+ end
15
+
16
+ # Load test config
17
+ require 'yaml'
18
+ TEST_CONFIG = YAML.load_file(File.dirname(__FILE__) + '/config.yml')
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: etherpad-lite
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Jordan Hollinger
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-08-30 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: etherpad-lite is a Ruby interface to Etherpad Lite's HTTP JSON API
22
+ email: jordan@jordanhollinger.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - lib/etherpad-lite.rb
31
+ - lib/etherpad-lite/models.rb
32
+ - lib/etherpad-lite/models/session.rb
33
+ - lib/etherpad-lite/models/padded.rb
34
+ - lib/etherpad-lite/models/author.rb
35
+ - lib/etherpad-lite/models/instance.rb
36
+ - lib/etherpad-lite/models/pad.rb
37
+ - lib/etherpad-lite/models/group.rb
38
+ - lib/etherpad-lite/client.rb
39
+ - spec/spec_helper.rb
40
+ - spec/instance_spec.rb
41
+ - spec/session_spec.rb
42
+ - spec/group_spec.rb
43
+ - spec/config.yml
44
+ - spec/config.yml.example
45
+ - spec/author_spec.rb
46
+ - spec/pad_spec.rb
47
+ - README.rdoc
48
+ - LICENSE
49
+ has_rdoc: true
50
+ homepage: http://github.com/jhollinger/etherpad-lite
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.7
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: A Ruby client library for Etherpad Lite
81
+ test_files: []
82
+