crocodoc-ruby 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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Instructure
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ # Crocodoc Ruby
2
+
3
+ This is a ruby library for interacting with v2 of the [Crocodoc](https://crocodoc.com) API.
4
+
5
+ ## Installation
6
+
7
+ The gem is packaged as the `crocodoc-ruby` gem, so you could install it with
8
+ a gemfile + bundler:
9
+
10
+ gem 'crocodoc-ruby', :require => 'crocodoc'
11
+
12
+ ## Usage
13
+
14
+ ```ruby
15
+ crocodoc = Crocodoc::API.new(:token => 'your-api-token')
16
+ doc = crocodoc.upload("http://www.example.com/test.doc")
17
+ stat = crocodoc.status(doc['uuid'])
18
+ sesh = crocodoc.session(doc['uuid'])
19
+ url = crocodoc.view(sesh['session'])
20
+ crocodoc.delete(doc['uuid'])
21
+ ```
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{crocodoc-ruby}
3
+ s.version = "0.0.1"
4
+
5
+ s.add_dependency 'json'
6
+
7
+ s.add_development_dependency 'rspec'
8
+ s.add_development_dependency 'ruby-deug'
9
+
10
+ s.authors = ["Instructure"]
11
+ s.date = %q{2012-08-07}
12
+ s.extra_rdoc_files = %W(LICENSE)
13
+ s.files = %W(
14
+ LICENSE
15
+ README.md
16
+ lib/crocodoc.rb
17
+ lib/crocodoc/api.rb
18
+ lib/crocodoc/fake_server.rb
19
+ crocodoc-ruby.gemspec
20
+ )
21
+ s.homepage = %q{http://github.com/instructure/crocodoc-ruby}
22
+ s.require_paths = %W(lib)
23
+ s.summary = %q{Ruby library for interacting with v2 of the Crocodoc API.}
24
+ end
@@ -0,0 +1,12 @@
1
+ require 'cgi'
2
+ require 'net/http'
3
+ require 'net/https'
4
+
5
+ require 'rubygems'
6
+ require 'json'
7
+
8
+ require 'crocodoc/api'
9
+
10
+ module Crocodoc
11
+ class Error < StandardError; end
12
+ end
@@ -0,0 +1,277 @@
1
+ module Crocodoc
2
+ # Public: A small ruby client that wraps the Crocodoc api.
3
+ #
4
+ # Examples
5
+ #
6
+ # Crocodoc::API.new(:token => <token>).status(<uuid>)
7
+ # # => { "uuid": <uuid>, "status": "DONE", "viewable": true }
8
+ class API
9
+ attr_accessor :token, :http, :url
10
+
11
+ # Public: The base part of the url that is the same for all api requests.
12
+ BASE_URL = "https://crocodoc.com/api/v2"
13
+
14
+ # Public: Initialize a Crocodoc api object
15
+ #
16
+ # opts - A hash of options with which to initialize the object
17
+ # :token - The api token to use to authenticate requests. Required.
18
+ #
19
+ # Examples
20
+ # crocodoc = Crocodoc::API.new(:token => <token>)
21
+ # # => <Crocodoc::API:<id>>
22
+ def initialize(opts)
23
+ self.token = opts[:token]
24
+
25
+ # setup the http object for ssl
26
+ @url = URI.parse(BASE_URL)
27
+ @http = Net::HTTP.new(@url.host, @url.port)
28
+ @http.use_ssl = true
29
+ end
30
+
31
+ # -- Documents --
32
+
33
+ # Public: Upload a url or file. Uploading is asynchronous, so this method
34
+ # returns immediately.
35
+ #
36
+ # POST https://crocodoc.com/api/v2/document/upload
37
+ #
38
+ # obj - a url string or file to upload
39
+ #
40
+ # Examples
41
+ #
42
+ # upload("http://www.example.com/test.doc")
43
+ # # => { "uuid": "8e5b0721-26c4-11df-b354-002170de47d3" }
44
+ #
45
+ # Returns a hash containing the uuid of the document and possibly an error
46
+ # explaining why the upload failed.
47
+ def upload(obj)
48
+ params = if obj.is_a?(File)
49
+ { :file => obj }
50
+ raise Crocodoc::Error, "TODO: support raw files"
51
+ else
52
+ { :url => obj.to_s }
53
+ end
54
+
55
+ raw_body = api_call(:post, "document/upload", params)
56
+ JSON.parse(raw_body)
57
+ end
58
+
59
+ # Public: Get the status of a set of documents.
60
+ #
61
+ # GET https://crocodoc.com/api/v2/document/status
62
+ #
63
+ # uuids - a single uuid or an array of uuids
64
+ #
65
+ # Examples
66
+ #
67
+ # status(["6faad04f-5409-4173-87aa-97c1fd1f35ad",
68
+ # "7cf917de-2246-4ac3-adab-791a49454180"])
69
+ # # =>
70
+ # # [
71
+ # # {
72
+ # # "uuid": "7cf917de-2246-4ac3-adab-791a49454180"
73
+ # # "status": "DONE",
74
+ # # "viewable": true,
75
+ # # },
76
+ # # {
77
+ # # "uuid": "6faad04f-5409-4173-87aa-97c1fd1f35ad"
78
+ # # "status": "ERROR",
79
+ # # "viewable": false,
80
+ # # "error": "password protected"
81
+ # # }
82
+ # # ]
83
+ #
84
+ # Returns a single hash or an array of hashes containing the status
85
+ # information for the uuid.
86
+ def status(uuids)
87
+ raw_hash_body = api_call(:get, "document/status", { :uuids => Array(uuids).join(",") })
88
+ hash_body = JSON.parse(raw_hash_body)
89
+ uuids.is_a?(String) ? hash_body.first : hash_body
90
+ end
91
+
92
+ # Public: Delete a document.
93
+ #
94
+ # POST https://crocodoc.com/api/v2/document/delete
95
+ #
96
+ # uuid - a single uuid to delete
97
+ #
98
+ # Examples
99
+ #
100
+ # delete("7cf917de-2246-4ac3-adab-791a49454180")
101
+ # # => true
102
+ #
103
+ # Returns true if the delete was successful
104
+ def delete(uuid)
105
+ raw_body = api_call(:post, "document/delete", { :uuid => uuid })
106
+ raw_body == "true"
107
+ end
108
+
109
+ # -- Sessions --
110
+
111
+ # Public: Create a session, which is a unique id with which you can view
112
+ # the document. Sessions expire 60 minutes after they are generated.
113
+ #
114
+ # POST https://crocodoc.com/api/v2/session/create
115
+ #
116
+ # uuid - The uuid of the document for the session
117
+ # opts - Options for the session (default: {}):
118
+ # :editable - Allows users to create annotations and comments
119
+ # while viewing the document (default: false)
120
+ # :user - A user ID and name joined with a comma (e.g.:
121
+ # 1337,Peter). The ID should be a non-negative
122
+ # signed 32-bit integer (0 <= ID <= 2,147,483,647)
123
+ # and unique for each user across your
124
+ # application's userbase. The user name will be
125
+ # shown in the viewer to attribute annotations and
126
+ # comments to their author. Required if editable is
127
+ # true
128
+ # :filter - Limits which users' annotations and comments are
129
+ # shown. Possible values are: all, none, or
130
+ # a comma-separated list of user IDs as specified
131
+ # in the user field (default: all)
132
+ # :admin - Allows the user to modify or delete any
133
+ # annotations and comments; including those
134
+ # belonging to other users. By default, users may
135
+ # only modify/delete their own annotations or reply
136
+ # to other users' comments. (default: false)
137
+ # :downloadable - Allows the user to download the original
138
+ # document (default: false)
139
+ # :copyprotected - Prevents document text selection. Although
140
+ # copying text will still be technically possible
141
+ # since it's just HTML, enabling this option makes
142
+ # doing so difficult (default: false)
143
+ # :demo - Prevents document changes such as creating,
144
+ # editing, or deleting annotations from being
145
+ # persisted (default: false)
146
+ #
147
+ # Examples
148
+ #
149
+ # session("6faad04f-5409-4173-87aa-97c1fd1f35ad")
150
+ # # => { "session": "CFAmd3Qjm_2ehBI7HyndnXKsDrQXJ7jHCuzcRv" }
151
+ #
152
+ # Returns a hash containing the session uuid
153
+ def session(uuid, opts={})
154
+ raw_body = api_call(:post, "session/create", opts.merge({ :uuid => uuid }))
155
+ JSON.parse(raw_body)
156
+ end
157
+
158
+ # Public: Get the url for the viewer for a session.
159
+ #
160
+ # https://crocodoc.com/view/<session>
161
+ #
162
+ # session - The uuid of the session (see #session)
163
+ #
164
+ # Examples
165
+ # view("CFAmd3Qjm_2ehBI7HyndnXKsDrQXJ7jHCuzcRv_V4FAgbSmaBkF")
166
+ # # => https://crocodoc.com/view/"CFAmd3Qjm_2ehBI7HyndnXKsDrQXJ7jHCuzcRv"
167
+ #
168
+ # Returns a url string for viewing the session
169
+ def view(session_id)
170
+ "https://crocodoc.com/view/#{session_id}"
171
+ end
172
+
173
+ # -- Downloads (TODO) --
174
+
175
+ # GET https://crocodoc.com/api/v2/download/document
176
+ def download(doc)
177
+ raise Crocodoc::Error, "Not implemented"
178
+ end
179
+
180
+ # GET https://crocodoc.com/api/v2/download/thumbnail
181
+ def thumbnail(doc)
182
+ raise Crocodoc::Error, "Not implemented"
183
+ end
184
+
185
+ # GET https://crocodoc.com/api/v2/download/text
186
+ def text(doc)
187
+ raise Crocodoc::Error, "Not implemented"
188
+ end
189
+
190
+ # -- API Glue --
191
+
192
+ # Internal: Setup the api call, format the parameters, send the request,
193
+ # parse the response and return it.
194
+ #
195
+ # method - The http verb to use, currently :get or :post
196
+ # endpoint - The api endpoint to hit. this is the part after
197
+ # crocodoc.com/api/v2. please do not include a beginning slash.
198
+ # params - Parameters to send with the api call, either as a query string
199
+ # (get) or form params (post). Don't worry about including the
200
+ # api token, it will automatically be included with all
201
+ # requests (default: {}).
202
+ #
203
+ # Examples
204
+ #
205
+ # api_call(:post,
206
+ # "document/upload",
207
+ # { :url => "http://www.example.com/test.doc" })
208
+ # # => { "uuid": "8e5b0721-26c4-11df-b354-002170de47d3" }
209
+ #
210
+ # Returns the json parsed response body of the call
211
+ def api_call(method, endpoint, params={})
212
+ # add api token to params
213
+ params.merge!({ :token => self.token })
214
+
215
+ # dispatch to the right method, with the full path (/api/v2 + endpoint)
216
+ request = self.send("format_#{method}", "#{@url.path}/#{endpoint}", params)
217
+ response = @http.request(request)
218
+
219
+ # Possible Responses
220
+ #
221
+ # 200 - (OK) The request was received successfully.
222
+ # 400 - (Bad Request) There was a problem with your request parameters. Check
223
+ # the error field of the response object for info on what was wrong.
224
+ # 401 - (Unauthorized) Your API token in the token parameter was either missing
225
+ # or incorrect.
226
+ # 404 - (Not Found) The API method was not found. Check yor request URL for
227
+ # typos.
228
+ # 405 - (Method Not Allowed) An incorrect HTTP verb (i.e. GET, POST, etc) was
229
+ # used for the request
230
+ # 5XX - There was an error Crocodoc could not recover from. We are generally
231
+ # notified of these automatically. If they are repeatedly received, you
232
+ # should contact Crocodoc Support.
233
+
234
+ unless response.code == '200'
235
+ raise Crocodoc::Error, "HTTP Error #{response.code}: #{response.body}"
236
+ end
237
+ response.body
238
+ end
239
+
240
+
241
+ # Internal: Format and create a Net::HTTP get request, with query
242
+ # parameters.
243
+ #
244
+ # path - the path to get
245
+ # params - the params to add as query params to the path
246
+ #
247
+ # Examples
248
+ #
249
+ # format_get("/api/v2/document/status",
250
+ # { :token => <token>, :uuids => <uuids> })
251
+ # # => <Net::HTTP::Get:<id>> for
252
+ # # "/api/v2/document/status?token=<token>&uuids=<uuids>"
253
+ #
254
+ # Returns a Net::HTTP::Get object for the path with query params
255
+ def format_get(path, params)
256
+ query = params.map { |k,v| "#{k}=#{CGI::escape(v.to_s)}" }.join("&")
257
+ Net::HTTP::Get.new("#{path}?#{query}")
258
+ end
259
+
260
+ # Internal: Format and create a Net::HTTP post request, with form
261
+ # parameters.
262
+ #
263
+ # path - the path to get
264
+ # params - the params to add as form params to the path
265
+ #
266
+ # Examples
267
+ #
268
+ # format_post("/api/v2/document/upload",
269
+ # { :token => <token>, :url => <url> })
270
+ # # => <Net::HTTP::Post:<id>>
271
+ #
272
+ # Returns a Net::HTTP::Post object for the path with form params
273
+ def format_post(path, params)
274
+ Net::HTTP::Post.new(path).tap { |request| request.set_form_data(params) }
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,154 @@
1
+ module Crocodoc
2
+ class FakeServer
3
+ attr_accessor :ids, :docs, :token
4
+ def initialize(opts)
5
+ @ids = 0
6
+ @docs = {}
7
+ @token = opts[:token]
8
+ end
9
+
10
+ def next_uuid
11
+ (@ids += 1).to_s
12
+ end
13
+
14
+ def request(req, body=nil)
15
+ # normalize path and params
16
+ case req.method
17
+ when "GET"
18
+ path, params = req.path.split("?")
19
+ when "POST"
20
+ path = req.path
21
+ params = req.body
22
+ end
23
+
24
+ # verify correct api version
25
+ unless path.start_with? "/api/v2/"
26
+ return Crocodoc::FakeResponse.new("404", "")
27
+ end
28
+ path.gsub!("/api/v2/", "")
29
+
30
+ # parse params
31
+ params = params.split("&")
32
+ params = params.inject({}) do |hsh, p|
33
+ k,v = p.split("=")
34
+ hsh[k.to_s] = CGI::unescape(v)
35
+ hsh
36
+ end
37
+
38
+ # verify token
39
+ unless params['token'] == @token
40
+ return Crocodoc::FakeResponse.new("401", {
41
+ :error => "invalid API token"
42
+ }.to_json)
43
+ end
44
+
45
+ # dispatch
46
+ self.send(path.gsub("/", "_"), req.method, params)
47
+ rescue => exception
48
+ return Crocodoc::FakeResponse.new("500", exception.message)
49
+ end
50
+
51
+ def document_upload(verb, params)
52
+ # Failure cases
53
+ unless verb == "POST"
54
+ return Crocodoc::FakeResponse.new("405", "")
55
+ end
56
+ unless params['url'] || params['file']
57
+ return Crocodoc::FakeResponse.new("400", {
58
+ :error => "no url or file specified"
59
+ }.to_json)
60
+ end
61
+
62
+ # Store doc and succeed
63
+ uuid = next_uuid
64
+ @docs[uuid] = params['url']
65
+ Crocodoc::FakeResponse.new("200", {
66
+ :uuid => uuid
67
+ }.to_json)
68
+ end
69
+
70
+ def document_status(verb, params)
71
+ # Failure cases
72
+ unless verb == "GET"
73
+ return Crocodoc::FakeResponse.new("405", "")
74
+ end
75
+ unless params['uuids']
76
+ return Crocodoc::FakeResponse.new("400", {
77
+ :error => "missing parameter uuids"
78
+ }.to_json)
79
+ end
80
+
81
+ # Build response and succeed
82
+ uuids = params['uuids'].split(",")
83
+ res = uuids.map do |uuid|
84
+ if @docs[uuid]
85
+ {
86
+ :status => "DONE",
87
+ :uuid => uuid,
88
+ :viewable => true
89
+ }
90
+ else
91
+ {
92
+ :uuid => uuid,
93
+ :error => "invalid uuid"
94
+ }
95
+ end
96
+ end
97
+ Crocodoc::FakeResponse.new("200", res.to_json)
98
+ end
99
+
100
+ def document_delete(verb, params)
101
+ # Failure cases
102
+ unless verb == "POST"
103
+ return Crocodoc::FakeResponse.new("405", "")
104
+ end
105
+ unless params['uuid'] && @docs[params['uuid']]
106
+ return Crocodoc::FakeResponse.new("400", {
107
+ :error => "invalid document uuid"
108
+ }.to_json)
109
+ end
110
+
111
+ # Remove doc and succeed
112
+ @docs.delete(params['uuid'])
113
+ Crocodoc::FakeResponse.new("200", "true")
114
+ end
115
+
116
+ def session_create(verb, params)
117
+ # Failure cases
118
+ unless verb == "POST"
119
+ return Crocodoc::FakeResponse.new("405", "")
120
+ end
121
+ unless params['uuid'] && @docs[params['uuid']]
122
+ return Crocodoc::FakeResponse.new("400", {
123
+ :error => "invalid uuid"
124
+ }.to_json)
125
+ end
126
+ if params['editable'] == 'true' && params['user']
127
+ user = params['user'].split(",")
128
+ if user.length != 2 || user.first != user.first.to_i.to_s || user.first.to_i >= (2**31)
129
+ return Crocodoc::FakeResponse.new("400", {
130
+ :error => "invalid user ID"
131
+ }.to_json)
132
+ end
133
+ end
134
+
135
+ # Generate session uuid and succeed
136
+ uuid = next_uuid
137
+ Crocodoc::FakeResponse.new("200", {
138
+ :session => uuid
139
+ }.to_json)
140
+ end
141
+
142
+ def method_missing(m, *args, &block)
143
+ Crocodoc::FakeResponse.new("404", "")
144
+ end
145
+ end
146
+
147
+ class FakeResponse
148
+ attr_accessor :code, :body
149
+ def initialize(code, body)
150
+ @code = code
151
+ @body = body
152
+ end
153
+ end
154
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crocodoc-ruby
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Instructure
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-08-07 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: ruby-deug
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ description:
63
+ email:
64
+ executables: []
65
+
66
+ extensions: []
67
+
68
+ extra_rdoc_files:
69
+ - LICENSE
70
+ files:
71
+ - LICENSE
72
+ - README.md
73
+ - lib/crocodoc.rb
74
+ - lib/crocodoc/api.rb
75
+ - lib/crocodoc/fake_server.rb
76
+ - crocodoc-ruby.gemspec
77
+ homepage: http://github.com/instructure/crocodoc-ruby
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.15
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Ruby library for interacting with v2 of the Crocodoc API.
110
+ test_files: []
111
+
112
+ has_rdoc: