Helipad 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 +101 -0
- data/lib/helipad.rb +457 -0
- data/test/test_helipad.rb +122 -0
- metadata +61 -0
data/README
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Ruby interface to the excellent Helipad[http://pad.helicoid.net/home.html]
|
2
|
+
# online note pad.
|
3
|
+
#
|
4
|
+
# Author: Lonnon Foster (lonnon.foster@gmail.com)
|
5
|
+
#
|
6
|
+
# == Overview
|
7
|
+
#
|
8
|
+
# This package provides three classes for working with
|
9
|
+
# Helipad[http://pad.helicoid.net/home.html]: Helipad, Helipad::Document, and
|
10
|
+
# Helipad::Response.
|
11
|
+
#
|
12
|
+
# The Helipad class does all the heavy lifting. Creating an instance of
|
13
|
+
# Helipad requires your login credentials.
|
14
|
+
#
|
15
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
16
|
+
#
|
17
|
+
# Armed with an instance of Helipad, you can call its methods to interact
|
18
|
+
# with Helipad[http://pad.helicoid.net/home.html] documents.
|
19
|
+
#
|
20
|
+
# The Helipad::Document class holds the data contained in a
|
21
|
+
# Helipad[http://pad.helicoid.net/home.html] document. The get method
|
22
|
+
# returns a Helipad::Document instance. The find, get_all, and get_titles
|
23
|
+
# methods return an Array of Helipad::Document instances.
|
24
|
+
#
|
25
|
+
# The Helipad::Response class holds return data sent by
|
26
|
+
# Helipad[http://pad.helicoid.net/home.html] that describes the success or
|
27
|
+
# failure of various actions. The create, destroy, and update methods
|
28
|
+
# return a Helipad::Response instance.
|
29
|
+
#
|
30
|
+
# == Examples of Use
|
31
|
+
#
|
32
|
+
# All of these examples assume that a Helipad object called +hp+ exists.
|
33
|
+
#
|
34
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
35
|
+
#
|
36
|
+
# === Get an Existing Document
|
37
|
+
#
|
38
|
+
# document = hp.get(3)
|
39
|
+
# puts document.source
|
40
|
+
#
|
41
|
+
# === Get a Document Formatted as HTML
|
42
|
+
#
|
43
|
+
# puts hp.get_html(3)
|
44
|
+
#
|
45
|
+
# === Find Documents
|
46
|
+
#
|
47
|
+
# def how_many(search_term)
|
48
|
+
# documents = hp.find(search_term)
|
49
|
+
# documents.size
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# find_this = "wombats"
|
53
|
+
# puts "#{how_many(find_this)} document(s) were found containing '#{find_this}'."
|
54
|
+
#
|
55
|
+
# === Find Documents by Tags
|
56
|
+
#
|
57
|
+
# documents = hp.find(:tag, "work")
|
58
|
+
# titles = documents.collect { |doc| doc.title }
|
59
|
+
# puts "Documents tagged with 'work':\n #{titles.join("\n ")}"
|
60
|
+
#
|
61
|
+
# === Create a Document
|
62
|
+
#
|
63
|
+
# source = File.read("cake_recipe.txt")
|
64
|
+
# response = hp.create(:title => "Delicious Chocolate Cake",
|
65
|
+
# :tags => "recipe dessert",
|
66
|
+
# :source => source)
|
67
|
+
# puts "Recipe saved" if response.saved?
|
68
|
+
#
|
69
|
+
# === Delete Documents
|
70
|
+
#
|
71
|
+
# doc_ids = hp.find(:tag, "incriminating").collect { |doc| doc.doc_id }
|
72
|
+
# doc_ids.each do |id|
|
73
|
+
# hp.destroy(id)
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# == Legal
|
77
|
+
#
|
78
|
+
# Copyright (c) 2008 Lonnon Foster. All rights reserved.
|
79
|
+
#
|
80
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
81
|
+
# of this software and associated documentation files (the "Software"), to deal
|
82
|
+
# in the Software without restriction, including without limitation the rights
|
83
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
84
|
+
# copies of the Software, and to permit persons to whom the Software is
|
85
|
+
# furnished to do so, subject to the following conditions:
|
86
|
+
#
|
87
|
+
# The above copyright notice and this permission notice shall be included in
|
88
|
+
# all copies or substantial portions of the Software.
|
89
|
+
#
|
90
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
91
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
92
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
93
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
94
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
95
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
96
|
+
# THE SOFTWARE.
|
97
|
+
#
|
98
|
+
# == Thanks
|
99
|
+
#
|
100
|
+
# Special thanks to {Alex Young}[http://alexyoung.org/] at Helicoid[http://helicoid.net]
|
101
|
+
# for creating Helipad[http://pad.helicoid.net/home.html].
|
data/lib/helipad.rb
ADDED
@@ -0,0 +1,457 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2008 Lonnon Foster. All rights reserved.
|
3
|
+
# See README for permissions.
|
4
|
+
#++
|
5
|
+
|
6
|
+
require 'net/http'
|
7
|
+
require 'uri'
|
8
|
+
require 'rexml/document'
|
9
|
+
require 'date'
|
10
|
+
|
11
|
+
# Class Helipad provides a wrapper for the {Helipad XML
|
12
|
+
# API}[http://pad.helicoid.net/document/public/6313d317].
|
13
|
+
#
|
14
|
+
# See the documentation in the file README for an overview.
|
15
|
+
class Helipad
|
16
|
+
# Create a new Helipad object.
|
17
|
+
#
|
18
|
+
# ==== Parameters
|
19
|
+
# +email+ and +password+ are the same credentials you use to log on to your Helipad account.
|
20
|
+
def initialize(email, password)
|
21
|
+
@email = email
|
22
|
+
@password = password
|
23
|
+
raise(ArgumentError, "Email address not specified", caller) if @email.nil?
|
24
|
+
raise(ArgumentError, "Password not specified", caller) if @password.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a new Helipad[http://pad.helicoid.net/home.html] document.
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
# +args+ is a Hash containing properties for the created document.
|
31
|
+
#
|
32
|
+
# * <tt>:title</tt> - Title for the new document. This parameter is
|
33
|
+
# required.
|
34
|
+
# * <tt>:tags</tt> - Space-separated list of tags for the new document
|
35
|
+
# * <tt>:source</tt> - Body of the new document.
|
36
|
+
# Helipad[http://pad.helicoid.net/home.html] understands {Textile markup}[http://pad.helicoid.net/formatting],
|
37
|
+
# which you can use to format the document's text.
|
38
|
+
#
|
39
|
+
# ==== Returns
|
40
|
+
# This method returns a Helipad::Response object, which has the following methods:
|
41
|
+
# * <tt>saved?</tt> - +true+ if the document was created successfully
|
42
|
+
# * +doc_id+ - ID of the newly created document
|
43
|
+
#
|
44
|
+
# ==== Example
|
45
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
46
|
+
# response = hp.create(:title => "Marsupial Inventory",
|
47
|
+
# :tags => "marsupial australia animals",
|
48
|
+
# :source => "|koala|2|\n|kangaroo|8|\n|platypus|1|")
|
49
|
+
# puts "Inventory saved as document #{response.doc_id}" if response.saved?
|
50
|
+
def create(*args)
|
51
|
+
options = args.extract_options!
|
52
|
+
validate_options(options, :create)
|
53
|
+
raise(ArgumentError, "No document options specified", caller) if options.empty?
|
54
|
+
raise(ArgumentError, "Document must have a title", caller) if options[:title].nil?
|
55
|
+
url = URI.parse("http://pad.helicoid.net/document/create")
|
56
|
+
Response.new(send_request(url, build_request(options)))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Delete a Helipad[http://pad.helicoid.net/home.html] document.
|
60
|
+
#
|
61
|
+
# ==== Parameter
|
62
|
+
# * +id+ - ID of the document to delete
|
63
|
+
#
|
64
|
+
# ==== Returns
|
65
|
+
# This method returns a Helipad::Response object, which has the following method:
|
66
|
+
# * <tt>deleted?</tt> - +true+ if the document was deleted successfully
|
67
|
+
#
|
68
|
+
# ==== Example
|
69
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
70
|
+
# response = hp.destroy(81)
|
71
|
+
# puts "Document deleted" if response.deleted?
|
72
|
+
def destroy(id)
|
73
|
+
url = URI.parse("http://pad.helicoid.net/document/#{id}/destroy")
|
74
|
+
Response.new(send_request(url, build_request))
|
75
|
+
end
|
76
|
+
|
77
|
+
# Search for Helipad[http://pad.helicoid.net/home.html] documents by text content or by tags.
|
78
|
+
#
|
79
|
+
# ==== Parameters
|
80
|
+
# The find method searches differently depending on its arguments:
|
81
|
+
# * <tt>find(String)</tt> - Search for the string in the titles and bodies of documents.
|
82
|
+
# * <tt>find(:tag, String)</tt> - Search for documents tagged with the string.
|
83
|
+
#
|
84
|
+
# ==== Returns
|
85
|
+
# This method returns an Array of Helipad::Document objects, or +nil+ if nothing
|
86
|
+
# could be found matching the given search string. See Helipad::Document for more details
|
87
|
+
# about the Document object.
|
88
|
+
#
|
89
|
+
# ==== Examples
|
90
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
91
|
+
# docs = hp.find("wibble")
|
92
|
+
# puts "#{docs.size} documents contain 'wibble'"
|
93
|
+
#
|
94
|
+
# docs = hp.find(:tag, "diary")
|
95
|
+
# puts "First diary entry is titled '#{docs.first.title}'"
|
96
|
+
def find(*args)
|
97
|
+
raise(ArgumentError, "No find arguments supplied", caller) if args.size == 0
|
98
|
+
term = args.extract_search_term!
|
99
|
+
case args.first
|
100
|
+
when :tag then find_by_tag(term)
|
101
|
+
when nil then search(term)
|
102
|
+
else raise(ArgumentError, "Unknown find option '#{args.first}' supplied", caller)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Retrieve a Helipad[http://pad.helicoid.net/home.html] document.
|
107
|
+
#
|
108
|
+
# ==== Parameter
|
109
|
+
# * +id+ - ID of the document to retrieve
|
110
|
+
#
|
111
|
+
# ==== Returns
|
112
|
+
# This method returns a Helipad::Document object, which holds the contents and
|
113
|
+
# properties of the document. See Helipad::Document for more details about the
|
114
|
+
# Document object.
|
115
|
+
#
|
116
|
+
# ==== Example
|
117
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
118
|
+
# document = hp.get(29)
|
119
|
+
# puts "#{document.title} contains #{document.source.length} characters."
|
120
|
+
def get(id)
|
121
|
+
url = URI.parse("http://pad.helicoid.net/document/#{id}/get")
|
122
|
+
Document.new(send_request(url, build_request))
|
123
|
+
end
|
124
|
+
|
125
|
+
# Retrieve all documents on a Helipad[http://pad.helicoid.net/home.html]
|
126
|
+
# account.
|
127
|
+
#
|
128
|
+
# ==== Returns
|
129
|
+
# This method returns an Array of Helipad::Document objects. See
|
130
|
+
# Helipad::Document for more details about the document object.
|
131
|
+
#
|
132
|
+
# ==== Example
|
133
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
134
|
+
# docs = hp.get_all
|
135
|
+
# case docs.size
|
136
|
+
# when > 1000 then puts "Slow down there, Shakespeare!"
|
137
|
+
# when > 100 then puts "That's a respectable amount of writing."
|
138
|
+
# when > 10 then puts "Keep at it!"
|
139
|
+
# else puts "Do you even use your Helipad account?"
|
140
|
+
# end
|
141
|
+
def get_all
|
142
|
+
url = URI.parse("http://pad.helicoid.net/")
|
143
|
+
response = REXML::Document.new(send_request(url, build_request))
|
144
|
+
documents = Array.new
|
145
|
+
REXML::XPath.match(response, "//document").each do |doc|
|
146
|
+
documents.push Document.new(doc)
|
147
|
+
end
|
148
|
+
documents
|
149
|
+
end
|
150
|
+
|
151
|
+
# Retrieve an HTML-formatted version of a Helipad[http://pad.helicoid.net/home.html] document.
|
152
|
+
#
|
153
|
+
# ==== Parameter
|
154
|
+
# * +id+ - ID of the document to retrieve
|
155
|
+
#
|
156
|
+
# ==== Returns
|
157
|
+
# This method returns a String containing the HTML-formatted document.
|
158
|
+
#
|
159
|
+
# ==== Example
|
160
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
161
|
+
# puts hp.get_html(94)
|
162
|
+
def get_html(id)
|
163
|
+
url = URI.parse("http://pad.helicoid.net/document/#{id}/format/html")
|
164
|
+
doc = REXML::Document.new(send_request(url, build_request))
|
165
|
+
REXML::XPath.match(doc, "html/child::text()").join.strip
|
166
|
+
end
|
167
|
+
|
168
|
+
# Retrieve a list of all the document titles in a Helipad[http://pad.helicoid.net/home.html] account.
|
169
|
+
#
|
170
|
+
# ==== Returns
|
171
|
+
# This method returns an Array of Helipad::Document objects that contain titles, but
|
172
|
+
# no document source. See Helipad::Document for more details about the Document object.
|
173
|
+
#
|
174
|
+
# ==== Example
|
175
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
176
|
+
# puts "Table of Contents"
|
177
|
+
# hp.get_titles.each do |doc|
|
178
|
+
# puts doc.title
|
179
|
+
# end
|
180
|
+
def get_titles
|
181
|
+
url = URI.parse("http://pad.helicoid.net/documents/titles")
|
182
|
+
response = REXML::Document.new(send_request(url, build_request))
|
183
|
+
documents = Array.new
|
184
|
+
REXML::XPath.match(response, "//document").each do |doc|
|
185
|
+
documents.push Document.new(doc)
|
186
|
+
end
|
187
|
+
documents
|
188
|
+
end
|
189
|
+
|
190
|
+
# Update an existing Helipad[http://pad.helicoid.net/home.html] document.
|
191
|
+
#
|
192
|
+
# ==== Parameters
|
193
|
+
# +id+ is the ID of the document to be updated.
|
194
|
+
#
|
195
|
+
# +args+ is a Hash containing properties to update in the document. All of the properties
|
196
|
+
# are optional.
|
197
|
+
#
|
198
|
+
# * <tt>:title</tt> - Updated title for the document
|
199
|
+
# * <tt>:tags</tt> - Space-separated list of tags for the document
|
200
|
+
# * <tt>:source</tt> - Updated body of the document.
|
201
|
+
# Helipad[http://pad.helicoid.net/home.html] understands {Textile markup}[http://pad.helicoid.net/formatting],
|
202
|
+
# which you can use to format the document's text.
|
203
|
+
#
|
204
|
+
# ==== Returns
|
205
|
+
# This method returns a Helipad::Response object, which has the following method:
|
206
|
+
# * <tt>saved?</tt> - +true+ if the document was created successfully
|
207
|
+
#
|
208
|
+
# ==== Example
|
209
|
+
# hp = Helipad.new("lonnon@example.com", "password")
|
210
|
+
# response = hp.update(:title => "Marsupial Inventory (amended)",
|
211
|
+
# :source => "|koala|2|\n|kangaroo|2|\n|platypus|19|")
|
212
|
+
# puts "Inventory updated" if response.saved?
|
213
|
+
def update(id, *args)
|
214
|
+
url = URI.parse("http://pad.helicoid.net/document/#{id}/update")
|
215
|
+
options = args.extract_options!
|
216
|
+
validate_options(options, :update)
|
217
|
+
raise(ArgumentError, "No options specified", caller) if options.empty?
|
218
|
+
Response.new(send_request(url, build_request(options)))
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
# Contains the properties and data that make up a Helipad[http://pad.helicoid.net/home.html] document.
|
223
|
+
#
|
224
|
+
# Various Helipad methods create and return Helipad::Document objects; there is
|
225
|
+
# probably little reason to make an instance of Helipad::Document in your own code.
|
226
|
+
#
|
227
|
+
# The class contains a number of read-only methods for retrieving a document's properties. Depending
|
228
|
+
# on which Helipad method created the Helipad::Document object, some of these methods may
|
229
|
+
# not be present. For example, the Helipad.get_titles method leaves out the +source+
|
230
|
+
# attribute.
|
231
|
+
# * +doc_id+ - ID of the document
|
232
|
+
# * +title+ - Title of the document
|
233
|
+
# * +source+ - Body of the document. Helipad[http://pad.helicoid.net/home.html] understands
|
234
|
+
# {Textile markup}[http://pad.helicoid.net/formatting],
|
235
|
+
# which you can use to format the document's text.
|
236
|
+
# * +tags+ - An Array containing the document's tags, each of which is a String
|
237
|
+
# * +created_on+ - A DateTime object containing the creation time of the document
|
238
|
+
# * +updated_on+ - A DateTime object containing the document's last modification time
|
239
|
+
# * +share+ - The URL where the document is shared, or +nil+ if the document is not shared
|
240
|
+
# * <tt>approved?</tt> - +true+ if the document contains a plugin approved by
|
241
|
+
# Helipad[http://pad.helicoid.net/home.html] staff; +false+ otherwise.
|
242
|
+
# * <tt>dangerous?</tt> - I don't know what this means, but it's +true+ if the document's "dangerous"
|
243
|
+
# property is true, and +false+ otherwise.
|
244
|
+
# * +raw_response+ - The raw XML response returned by Helipad[http://pad.helicoid.net/home.html].
|
245
|
+
# This could be useful if, for some reason, you want to parse the results yourself. See the
|
246
|
+
# {Helipad API documentation}[http://pad.helicoid.net/document/public/6313d317] for more
|
247
|
+
# information.
|
248
|
+
class Document
|
249
|
+
def initialize(source) #:nodoc:
|
250
|
+
if source.kind_of? REXML::Element
|
251
|
+
doc = REXML::Document.new(source.to_s)
|
252
|
+
else
|
253
|
+
doc = REXML::Document.new(source)
|
254
|
+
end
|
255
|
+
REXML::XPath.match(doc, "document/*").each do |tag|
|
256
|
+
suffix = ""
|
257
|
+
case tag.name
|
258
|
+
when "approved"
|
259
|
+
name = "approved"
|
260
|
+
suffix = "?"
|
261
|
+
value = tag.text == "true" ? true : false
|
262
|
+
when "created-on"
|
263
|
+
name = "created_on"
|
264
|
+
value = DateTime.parse tag.text
|
265
|
+
when "dangerous"
|
266
|
+
name = "dangerous"
|
267
|
+
suffix = "?"
|
268
|
+
value = tag.text == "true" ? true : false
|
269
|
+
when "share"
|
270
|
+
name = "share"
|
271
|
+
if tag.attributes["nil"] == "true"
|
272
|
+
value = nil
|
273
|
+
else
|
274
|
+
value = "http://pad.helicoid.net/document/public/#{tag.text}"
|
275
|
+
end
|
276
|
+
when "source"
|
277
|
+
name = "source"
|
278
|
+
value = tag.text
|
279
|
+
when "title"
|
280
|
+
name = "title"
|
281
|
+
value = tag.text
|
282
|
+
when "updated-on"
|
283
|
+
name = "updated_on"
|
284
|
+
value = DateTime.parse tag.text
|
285
|
+
when "id"
|
286
|
+
name = "doc_id"
|
287
|
+
value = Integer(tag.text)
|
288
|
+
when "tags"
|
289
|
+
name = "tags"
|
290
|
+
value = Array.new
|
291
|
+
REXML::XPath.match(tag, "tag/name/child::text()").each do |this_tag|
|
292
|
+
value.push this_tag.to_s
|
293
|
+
end
|
294
|
+
else
|
295
|
+
name = tag.name
|
296
|
+
value = tag.text
|
297
|
+
end
|
298
|
+
self.instance_eval %{
|
299
|
+
def self.#{name}#{suffix}
|
300
|
+
@#{name}
|
301
|
+
end
|
302
|
+
@#{name} = value
|
303
|
+
}, __FILE__, __LINE__
|
304
|
+
end
|
305
|
+
self.instance_eval %{
|
306
|
+
def self.raw_response
|
307
|
+
@raw_response
|
308
|
+
end
|
309
|
+
@raw_response = %{#{source}}
|
310
|
+
}, __FILE__, __LINE__
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
# Contains the data returned by Helipad[http://pad.helicoid.net/home.html] in response to certain
|
316
|
+
# API calls.
|
317
|
+
#
|
318
|
+
# Various Helipad methods create and return Helipad::Response objects; there is
|
319
|
+
# probably little reason to make an instance of Helipad::Response in your own code.
|
320
|
+
#
|
321
|
+
# The class contains a number of read-only methods for retrieving a response's properties. Depending
|
322
|
+
# on which Helipad method created the Helipad::Response object, some of these methods may
|
323
|
+
# not be present. For example, the Helipad.update method leaves out the +doc_id+
|
324
|
+
# attribute, and Helipad.destroy doesn't use the <tt>saved?</tt> method.
|
325
|
+
# * +doc_id+ - ID of the document associated with the response. Helipad.create returns this
|
326
|
+
# to let you know the ID of the document it just created.
|
327
|
+
# * <tt>saved?</tt> - +true+ if the document was saved succesfully, otherwise +false+
|
328
|
+
# * <tt>deleted?</tt> - +true+ if the document was deleted successfully, otherwise +false+
|
329
|
+
# * +raw_response+ - The raw XML response returned by Helipad[http://pad.helicoid.net/home.html].
|
330
|
+
# This could be useful if, for some reason, you want to parse the results yourself. See the
|
331
|
+
# {Helipad API documentation}[http://pad.helicoid.net/document/public/6313d317] for more
|
332
|
+
# information.
|
333
|
+
class Response
|
334
|
+
def initialize(raw_response) #:nodoc:
|
335
|
+
doc = REXML::Document.new raw_response
|
336
|
+
REXML::XPath.match(doc, "//*").each do |tag|
|
337
|
+
suffix = ""
|
338
|
+
case tag.name
|
339
|
+
when "saved"
|
340
|
+
name = "saved"
|
341
|
+
suffix = "?"
|
342
|
+
value = tag.text == "true" ? true : false
|
343
|
+
when "deleted"
|
344
|
+
name = "deleted"
|
345
|
+
suffix = "?"
|
346
|
+
value = tag.text == "true" ? true : false
|
347
|
+
when "id"
|
348
|
+
name = "doc_id"
|
349
|
+
value = Integer(tag.text)
|
350
|
+
else
|
351
|
+
name = tag.name
|
352
|
+
value = tag.text
|
353
|
+
end
|
354
|
+
self.instance_eval %{
|
355
|
+
def self.#{name}#{suffix}
|
356
|
+
@#{name}
|
357
|
+
end
|
358
|
+
@#{name} = value
|
359
|
+
}, __FILE__, __LINE__ unless tag.name == "response"
|
360
|
+
end
|
361
|
+
self.instance_eval %{
|
362
|
+
def self.raw_response
|
363
|
+
@raw_response
|
364
|
+
end
|
365
|
+
@raw_response = %{#{raw_response}}
|
366
|
+
}, __FILE__, __LINE__
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
private
|
371
|
+
|
372
|
+
def authentication_block
|
373
|
+
block = %{
|
374
|
+
<authentication>
|
375
|
+
<email>#{@email}</email>
|
376
|
+
<password>#{@password}</password>
|
377
|
+
</authentication>
|
378
|
+
}
|
379
|
+
end
|
380
|
+
|
381
|
+
def build_request(*args)
|
382
|
+
options = args.extract_options!
|
383
|
+
request = "<request>#{authentication_block}"
|
384
|
+
unless options.empty?
|
385
|
+
request << "<document>"
|
386
|
+
options.each_pair do |key, value|
|
387
|
+
request << "<#{key}>#{value}</#{key}>"
|
388
|
+
end
|
389
|
+
request << "</document>"
|
390
|
+
end
|
391
|
+
request << "</request>"
|
392
|
+
end
|
393
|
+
|
394
|
+
def find_by_tag(tag)
|
395
|
+
raise(ArgumentError, "No tag supplied", caller) if tag.nil?
|
396
|
+
url = URI.parse("http://pad.helicoid.net/document/tag/#{URI.escape(tag)}")
|
397
|
+
request = "<request>#{authentication_block}</request>"
|
398
|
+
response = REXML::Document.new(send_request(url, request))
|
399
|
+
documents = Array.new
|
400
|
+
REXML::XPath.match(response, "//document").each do |doc|
|
401
|
+
documents.push Document.new(doc)
|
402
|
+
end
|
403
|
+
documents.size > 0 ? documents : nil
|
404
|
+
end
|
405
|
+
|
406
|
+
def search(term)
|
407
|
+
url = URI.parse("http://pad.helicoid.net/document/search")
|
408
|
+
request = "<request>#{authentication_block}<search>#{term}</search></request>"
|
409
|
+
response = REXML::Document.new(send_request(url, request))
|
410
|
+
documents = Array.new
|
411
|
+
REXML::XPath.match(response, "//document").each do |doc|
|
412
|
+
documents.push Document.new(doc)
|
413
|
+
end
|
414
|
+
documents.size > 0 ? documents : nil
|
415
|
+
end
|
416
|
+
|
417
|
+
def send_request(url, request)
|
418
|
+
response = Net::HTTP.start(url.host, url.port) do |http|
|
419
|
+
http.post(url.path, request,
|
420
|
+
{'Accept' => 'application/xml', 'Content-Type' => 'application/xml'})
|
421
|
+
end
|
422
|
+
case response
|
423
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
424
|
+
response.body
|
425
|
+
else
|
426
|
+
response.error!
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
VALID_CREATE_OPTIONS = [:title, :source, :tags] #:nodoc:
|
431
|
+
VALID_UPDATE_OPTIONS = [:title, :source, :tags] #:nodoc:
|
432
|
+
|
433
|
+
def validate_options(options, action)
|
434
|
+
case action
|
435
|
+
when :create then constant = VALID_CREATE_OPTIONS
|
436
|
+
when :update then constant = VALID_UPDATE_OPTIONS
|
437
|
+
end
|
438
|
+
options.assert_valid_keys(constant)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
class Array #:nodoc: all
|
443
|
+
def extract_options!
|
444
|
+
last.is_a?(::Hash) ? pop : {}
|
445
|
+
end
|
446
|
+
|
447
|
+
def extract_search_term!
|
448
|
+
last.is_a?(::String) ? pop : nil
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
class Hash #:nodoc: all
|
453
|
+
def assert_valid_keys(*valid_keys)
|
454
|
+
unknown_keys = keys - [valid_keys].flatten
|
455
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
456
|
+
end
|
457
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
require 'helipad'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
class TestHelipad < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
raise(ArgumentError, "Usage: #{$0} -- email password", caller) if ARGV.length != 2
|
9
|
+
@email = ARGV[0]
|
10
|
+
@password = ARGV[1]
|
11
|
+
@hp = Helipad.new(@email, @password)
|
12
|
+
@test_string = [Array.new(6){rand(256).chr}.join].pack("m").chomp
|
13
|
+
response = @hp.create(:title => "test #{@test_string}",
|
14
|
+
:tags => "test #{@test_string}",
|
15
|
+
:source => "test #{@test_string}")
|
16
|
+
@test_id = response.doc_id
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
@hp.destroy(@test_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_create
|
24
|
+
response = nil
|
25
|
+
assert_nothing_raised do
|
26
|
+
response = @hp.create(:title => "New document", :tags => "test",
|
27
|
+
:source => "Just a test document")
|
28
|
+
end
|
29
|
+
assert_equal(true, response.saved?, "Document not created.")
|
30
|
+
@hp.destroy(response.doc_id) if response.saved?
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_destroy
|
34
|
+
create_response = @hp.create(:title => "Document for deletion", :tags => "test",
|
35
|
+
:source => "Should be deleted by test case")
|
36
|
+
response = nil
|
37
|
+
assert_nothing_raised do
|
38
|
+
response = @hp.destroy(create_response.doc_id)
|
39
|
+
end
|
40
|
+
assert_equal(true, response.deleted?, "Document not deleted.")
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_find
|
44
|
+
documents = nil
|
45
|
+
assert_nothing_raised do
|
46
|
+
documents = @hp.find(@test_string)
|
47
|
+
end
|
48
|
+
assert_equal("test #{@test_string}", documents[0].title,
|
49
|
+
"By search term: First document title is wrong.")
|
50
|
+
|
51
|
+
documents = @hp.find("32908p9832tn9p85h92gng825g82ngp88p9834p98v348anvs8")
|
52
|
+
assert_nil(documents, "Bogus string should not be found.")
|
53
|
+
|
54
|
+
assert_nothing_raised do
|
55
|
+
documents = @hp.find(:tag, @test_string)
|
56
|
+
end
|
57
|
+
assert_equal("test #{@test_string}", documents[0].title,
|
58
|
+
"By tag: First document title is wrong.")
|
59
|
+
|
60
|
+
documents = @hp.find(:tag, "02vhn45g70h4cgh0872h54gmv072hm45g70hmv0hmv2g20457gh")
|
61
|
+
assert_nil(documents, "Bogus tag should not be found.")
|
62
|
+
|
63
|
+
assert_raise(ArgumentError) {@hp.find}
|
64
|
+
assert_raise(ArgumentError) {@hp.find(:wibble)}
|
65
|
+
assert_raise(ArgumentError) {@hp.find(:tag)}
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_get
|
69
|
+
doc = nil
|
70
|
+
assert_nothing_raised do
|
71
|
+
doc = @hp.get(@test_id)
|
72
|
+
end
|
73
|
+
assert_equal("test #{@test_string}", doc.title, "Title is wrong.")
|
74
|
+
assert_equal("test #{@test_string}", doc.source, "Source is wrong.")
|
75
|
+
assert_equal(["test", @test_string], doc.tags, "Tags are wrong.")
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_get_all
|
79
|
+
documents = nil
|
80
|
+
assert_nothing_raised do
|
81
|
+
documents = @hp.get_all
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_get_html
|
86
|
+
html = nil
|
87
|
+
assert_nothing_raised do
|
88
|
+
html = @hp.get_html(@test_id)
|
89
|
+
end
|
90
|
+
exemplar = %{<h1>test #{@test_string}</h1>
|
91
|
+
<p>test #{@test_string}</p>}
|
92
|
+
assert_equal(exemplar, html, "HTML contents are wrong.")
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_get_titles
|
96
|
+
documents = nil
|
97
|
+
assert_nothing_raised do
|
98
|
+
documents = @hp.get_titles
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_update
|
103
|
+
response = nil
|
104
|
+
assert_raise(ArgumentError) {@hp.update(@test_id)}
|
105
|
+
assert_raise(ArgumentError) {@hp.update(@test_id, :wibble => "freem")}
|
106
|
+
|
107
|
+
response = @hp.update(@test_id, :title => "Modified the title")
|
108
|
+
assert_equal(true, response.saved?, "Document title wasn't updated.")
|
109
|
+
doc = @hp.get(@test_id)
|
110
|
+
assert_equal("Modified the title", doc.title, "Title is wrong.")
|
111
|
+
|
112
|
+
response = @hp.update(@test_id, :source => "Modified, darlin'")
|
113
|
+
assert_equal(true, response.saved?, "Document source wasn't updated.")
|
114
|
+
doc = @hp.get(@test_id)
|
115
|
+
assert_equal("Modified, darlin'", doc.source, "Source is wrong.")
|
116
|
+
|
117
|
+
response = @hp.update(@test_id, :tags => "test stuff")
|
118
|
+
assert_equal(true, response.saved?, "Document tags weren't updated.")
|
119
|
+
doc = @hp.get(@test_id)
|
120
|
+
assert_equal(["test", "stuff"], doc.tags, "Tags are wrong.")
|
121
|
+
end
|
122
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: Helipad
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lonnon Foster
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-07 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: lonnon.foster@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- lib/helipad.rb
|
26
|
+
- README
|
27
|
+
has_rdoc: true
|
28
|
+
homepage: http://nyerm.com/helipad
|
29
|
+
licenses: []
|
30
|
+
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options:
|
33
|
+
- --charset=UTF-8
|
34
|
+
- --title
|
35
|
+
- Helipad Documentation
|
36
|
+
- --main
|
37
|
+
- README
|
38
|
+
- --inline-source
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project: helipad
|
56
|
+
rubygems_version: 1.3.5
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Ruby interface to the excellent Helipad online notepad
|
60
|
+
test_files:
|
61
|
+
- test/test_helipad.rb
|