fjords-client 0.0.6
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/.gitignore +5 -0
- data/Gemfile +10 -0
- data/Rakefile +12 -0
- data/fjords-client.gemspec +21 -0
- data/lib/fjords-client/fjords-client.rb +391 -0
- data/lib/fjords-client/version.rb +5 -0
- data/lib/fjords-client.rb +4 -0
- data/spec/logged_in_spec.rb +46 -0
- data/spec/spec_helper.rb +16 -0
- metadata +110 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
desc "Run RSpec"
|
6
|
+
RSpec::Core::RakeTask.new do |spec|
|
7
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
8
|
+
spec.rspec_opts = ['--color', '--format nested']
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Run tests"
|
12
|
+
task :test => [:spec]
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/fjords-client/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Thomas Reynolds"]
|
6
|
+
gem.email = ["me@tdreyno.com"]
|
7
|
+
gem.description = %q{Fjords.cc API}
|
8
|
+
gem.summary = %q{Fjords.cc API}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "fjords-client"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Fjords::Client::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency("rest-client", "1.6.7")
|
19
|
+
gem.add_dependency("addressable", "2.3.2")
|
20
|
+
gem.add_dependency("stripe")
|
21
|
+
end
|
@@ -0,0 +1,391 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'addressable/uri'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
require "stripe"
|
9
|
+
Stripe.api_key = 'pk_vfcVMdOHZGAFgqLGSEtlSKzsXeBYI'
|
10
|
+
|
11
|
+
module Fjords
|
12
|
+
class Token
|
13
|
+
attr_reader :token
|
14
|
+
|
15
|
+
def initialize(token)
|
16
|
+
@token = token
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Client
|
21
|
+
|
22
|
+
EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '.*', '*/.*']
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_accessor :host
|
26
|
+
|
27
|
+
def shared
|
28
|
+
@_shared_client ||= new
|
29
|
+
end
|
30
|
+
|
31
|
+
def host
|
32
|
+
@_host ||= "https://api.fjords.cc/v1"
|
33
|
+
end
|
34
|
+
|
35
|
+
def host=(host)
|
36
|
+
@_host = host
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Delegate methods
|
41
|
+
[
|
42
|
+
:login,
|
43
|
+
:logged_in?,
|
44
|
+
:push,
|
45
|
+
:prepare_push,
|
46
|
+
:sites,
|
47
|
+
:delete_site,
|
48
|
+
:signup,
|
49
|
+
:username_available?,
|
50
|
+
:get_stripe_token,
|
51
|
+
:logout,
|
52
|
+
:validate_domain,
|
53
|
+
:bugreport,
|
54
|
+
:info
|
55
|
+
].each do |method|
|
56
|
+
module_eval(%Q{
|
57
|
+
def self.#{method}(*args, &block) # def self.login(*args, &block)
|
58
|
+
shared.__send__(:#{method}, *args, &block) # shared.__send__(:login, *args, &block)
|
59
|
+
end # end
|
60
|
+
})
|
61
|
+
end
|
62
|
+
|
63
|
+
class ClientError < RuntimeError
|
64
|
+
def initialize(e=nil)
|
65
|
+
super()
|
66
|
+
|
67
|
+
@e = e
|
68
|
+
end
|
69
|
+
|
70
|
+
def report_data
|
71
|
+
@e.backtrace
|
72
|
+
end
|
73
|
+
end
|
74
|
+
class Forbidden < ClientError; end
|
75
|
+
class ConnectionRefused < ClientError; end
|
76
|
+
class ResourceNotFound < ClientError; end
|
77
|
+
class LoginInvalid < ClientError; end
|
78
|
+
class NotAuthorizedError < ClientError; end
|
79
|
+
class InternalServerError < ClientError; end
|
80
|
+
class BadPathError < ClientError; end
|
81
|
+
class Conflict < ClientError; end
|
82
|
+
class UploadTooLarge < ClientError; end
|
83
|
+
class NothingToUpload < ClientError; end
|
84
|
+
|
85
|
+
attr_reader :username
|
86
|
+
|
87
|
+
def logged_in?
|
88
|
+
read_token
|
89
|
+
end
|
90
|
+
|
91
|
+
def signup(email, username, password, stripe_token)
|
92
|
+
json = post("/accounts", {
|
93
|
+
:email => email,
|
94
|
+
:username => username,
|
95
|
+
:password => password,
|
96
|
+
:stripe_token => stripe_token
|
97
|
+
})
|
98
|
+
|
99
|
+
token = json['token']
|
100
|
+
|
101
|
+
store_token(token)
|
102
|
+
|
103
|
+
Token.new(token)
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_stripe_token(name_on_card, number, exp_month, exp_year, cvc)
|
107
|
+
Stripe::Token.create(
|
108
|
+
:card => {
|
109
|
+
:name => name_on_card,
|
110
|
+
:number => number.to_s,
|
111
|
+
:exp_month => exp_month.to_i,
|
112
|
+
:exp_year => exp_year.to_i,
|
113
|
+
:cvc => cvc.to_i
|
114
|
+
}
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
def login(username, password)
|
119
|
+
json = post("/login", { :username => username, :password => password })
|
120
|
+
token = json['token']
|
121
|
+
store_token(token)
|
122
|
+
Token.new(token)
|
123
|
+
end
|
124
|
+
|
125
|
+
def info
|
126
|
+
json = get("/info")
|
127
|
+
json
|
128
|
+
end
|
129
|
+
|
130
|
+
def logout
|
131
|
+
remove_token
|
132
|
+
end
|
133
|
+
|
134
|
+
def username_available?(username)
|
135
|
+
get("/users/#{username}")
|
136
|
+
false
|
137
|
+
rescue ResourceNotFound
|
138
|
+
true
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate_domain(domain)
|
142
|
+
post("/site_available", {
|
143
|
+
:domain => domain
|
144
|
+
})
|
145
|
+
rescue Conflict
|
146
|
+
false
|
147
|
+
end
|
148
|
+
|
149
|
+
def delete_site(domain_name)
|
150
|
+
json = delete("/sites", :domain => domain_name)
|
151
|
+
!!json["site"]
|
152
|
+
end
|
153
|
+
|
154
|
+
def prepare_push(path, checksums=nil, domain_file="Fjordsfile")
|
155
|
+
raise BadPathError.new unless File.exists?(path) && File.directory?(path)
|
156
|
+
|
157
|
+
if checksums && checksums["site"] && checksums["site"]["existing_hashes"]
|
158
|
+
existing_hashes = checksums["site"]["existing_hashes"]
|
159
|
+
end
|
160
|
+
|
161
|
+
zipfile, changeless = zip!(path, domain_file, existing_hashes || nil)
|
162
|
+
upload_size = File.size(zipfile)
|
163
|
+
|
164
|
+
if upload_size > (1024*1024*50)
|
165
|
+
raise UploadTooLarge.new
|
166
|
+
end
|
167
|
+
|
168
|
+
if upload_size > 1024*1024
|
169
|
+
upload_size = (upload_size/(1024.0*1024.0)).ceil.to_s + 'M'
|
170
|
+
elsif upload_size > 0
|
171
|
+
upload_size = (upload_size/1024.0).ceil.to_s + 'K'
|
172
|
+
else
|
173
|
+
upload_size = '0K'
|
174
|
+
end
|
175
|
+
|
176
|
+
[zipfile, changeless, upload_size]
|
177
|
+
end
|
178
|
+
|
179
|
+
def push(zipfile, changeless=[], domain=nil, permanent=nil, &block)
|
180
|
+
raise BadPathError.new unless File.exists?(zipfile)
|
181
|
+
|
182
|
+
data = {
|
183
|
+
:site_data => File.new(zipfile, 'rb'),
|
184
|
+
:changeless => changeless
|
185
|
+
}
|
186
|
+
data[:domain] = domain if domain
|
187
|
+
data[:permanent] = permanent if permanent
|
188
|
+
|
189
|
+
json = post("/sites", data)
|
190
|
+
|
191
|
+
wait_until_complete(json['site'], &block)
|
192
|
+
end
|
193
|
+
|
194
|
+
def wait_until_complete(site, &block)
|
195
|
+
sleep(3)
|
196
|
+
|
197
|
+
json = get(site["url"])
|
198
|
+
|
199
|
+
block.call(json['site']) if block_given?
|
200
|
+
|
201
|
+
state = json['site']['state']
|
202
|
+
|
203
|
+
if state != "live"
|
204
|
+
wait_until_complete(site, &block)
|
205
|
+
else
|
206
|
+
json
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def sites
|
211
|
+
json = get("/sites")
|
212
|
+
json['sites']
|
213
|
+
end
|
214
|
+
|
215
|
+
def bugreport(body, attach=nil)
|
216
|
+
params = { :body => body }
|
217
|
+
if attach
|
218
|
+
params[:attach] = File.new(attach, 'r')
|
219
|
+
end
|
220
|
+
|
221
|
+
json = post("/bugreport", params)
|
222
|
+
json['bugreport']
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def get_file_paths(path)
|
228
|
+
Dir.glob("#{path}/**/*").reject do |f|
|
229
|
+
!File.file?(f) || EXCLUSION_GLOBS.any? do |e|
|
230
|
+
File.fnmatch(e, File.basename(f))
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def zip!(path, domain_file, existing_hashes=nil)
|
236
|
+
excludes = EXCLUSION_GLOBS.dup
|
237
|
+
excludes << "#{domain_file}"
|
238
|
+
excludes << "*/#{domain_file}"
|
239
|
+
zipfile = File.expand_path(File.join(Dir.tmpdir, "pack-#{Time.now.to_i}.zip"))
|
240
|
+
changeless = []
|
241
|
+
|
242
|
+
if existing_hashes
|
243
|
+
all_files = get_file_paths(path)
|
244
|
+
unique_files = all_files.dup
|
245
|
+
|
246
|
+
all_files.each do |p|
|
247
|
+
relative_path = Pathname(p).relative_path_from(Pathname(path)).to_s
|
248
|
+
if relative_path == domain_file
|
249
|
+
unique_files.delete(p)
|
250
|
+
elsif existing_hashes[relative_path] == get_md5(p)
|
251
|
+
excludes << relative_path
|
252
|
+
changeless << relative_path
|
253
|
+
unique_files.delete(p)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
if unique_files.length <= 0
|
258
|
+
raise NothingToUpload
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
zip_excludes = excludes.map { |e| "-x \"#{e}\"" }.join(' ')
|
263
|
+
|
264
|
+
Dir.chdir(path) do
|
265
|
+
puts `zip -y -q -r #{zipfile} . #{zip_excludes}`
|
266
|
+
end
|
267
|
+
|
268
|
+
[zipfile, changeless]
|
269
|
+
end
|
270
|
+
|
271
|
+
def require_login!
|
272
|
+
raise NotAuthorizedError unless @user || logged_in?
|
273
|
+
end
|
274
|
+
|
275
|
+
def path(resource_name)
|
276
|
+
"#{self.class.host}#{resource_name}"
|
277
|
+
end
|
278
|
+
|
279
|
+
def get_md5(path)
|
280
|
+
digest = ::Digest::MD5.new
|
281
|
+
|
282
|
+
::File.open(path, 'rb') do |stream|
|
283
|
+
buffer = ""
|
284
|
+
digest.update(buffer) while stream.read(4096, buffer)
|
285
|
+
end
|
286
|
+
|
287
|
+
digest.to_s
|
288
|
+
end
|
289
|
+
|
290
|
+
def get(resource_path, extra_headers={})
|
291
|
+
request(:get, resource_path, nil, extra_headers)
|
292
|
+
end
|
293
|
+
|
294
|
+
def post(resource_path, params={}, extra_headers={})
|
295
|
+
request(:post, resource_path, params, extra_headers)
|
296
|
+
end
|
297
|
+
|
298
|
+
def delete(resource_path, params={}, extra_headers={})
|
299
|
+
request(:delete, resource_path, params, extra_headers)
|
300
|
+
end
|
301
|
+
|
302
|
+
def safe_url(url)
|
303
|
+
url = path(url)
|
304
|
+
Addressable::URI.parse(url).normalize.to_str
|
305
|
+
end
|
306
|
+
|
307
|
+
def request(type, url, params=nil, extra_headers={})
|
308
|
+
headers = {
|
309
|
+
:accept => :json
|
310
|
+
}
|
311
|
+
|
312
|
+
token = self.logged_in?
|
313
|
+
headers[:authorization] = %Q{Token token="#{token.token}"} if token
|
314
|
+
|
315
|
+
headers.merge!(extra_headers)
|
316
|
+
|
317
|
+
req = {
|
318
|
+
:method => type.to_sym,
|
319
|
+
:url => safe_url(url),
|
320
|
+
:headers => headers
|
321
|
+
}
|
322
|
+
req[:payload] = params unless params.nil?
|
323
|
+
|
324
|
+
begin
|
325
|
+
response = RestClient::Request.execute(req)
|
326
|
+
headers[:accept] == :json ? JSON.parse(response) : response
|
327
|
+
rescue RestClient::InternalServerError => e
|
328
|
+
raise InternalServerError.new(e)
|
329
|
+
rescue RestClient::ServerBrokeConnection => e
|
330
|
+
raise InternalServerError.new(e)
|
331
|
+
rescue RestClient::ResourceNotFound => e
|
332
|
+
raise ResourceNotFound.new(e)
|
333
|
+
rescue RestClient::Unauthorized => e
|
334
|
+
raise LoginInvalid.new(e)
|
335
|
+
rescue RestClient::Conflict => e
|
336
|
+
raise Conflict.new(e)
|
337
|
+
rescue RestClient::Forbidden => e
|
338
|
+
raise Forbidden.new(e)
|
339
|
+
rescue Errno::ECONNREFUSED => e
|
340
|
+
raise ConnectionRefused.new(e)
|
341
|
+
rescue Exception => e
|
342
|
+
raise ConnectionRefused.new(e)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
TOKEN_FILE_PATH = "~/.fjords_token"
|
347
|
+
|
348
|
+
def read_token
|
349
|
+
return @token if @token
|
350
|
+
|
351
|
+
token_file = File.expand_path(TOKEN_FILE_PATH)
|
352
|
+
|
353
|
+
return nil unless File.exists?(token_file)
|
354
|
+
|
355
|
+
@token = Token.new(read_file(token_file).strip)
|
356
|
+
end
|
357
|
+
|
358
|
+
def remove_token
|
359
|
+
path = File.expand_path(TOKEN_FILE_PATH)
|
360
|
+
if File.exists?(path)
|
361
|
+
FileUtils.rm(path)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def store_token(token)
|
366
|
+
token_file = File.expand_path(TOKEN_FILE_PATH)
|
367
|
+
write_file(token_file, token)
|
368
|
+
read_token
|
369
|
+
end
|
370
|
+
|
371
|
+
def read_file(file)
|
372
|
+
File.open(file, File::RDONLY) {|f|
|
373
|
+
f.flock(File::LOCK_EX)
|
374
|
+
contents = f.read
|
375
|
+
f.flock(File::LOCK_UN)
|
376
|
+
contents
|
377
|
+
}
|
378
|
+
end
|
379
|
+
|
380
|
+
def write_file(file, contents)
|
381
|
+
File.open(file, File::RDWR | File::CREAT, 0600) {|f|
|
382
|
+
f.flock(File::LOCK_EX)
|
383
|
+
f.rewind
|
384
|
+
f.puts contents
|
385
|
+
f.flush
|
386
|
+
f.truncate(f.pos)
|
387
|
+
f.flock(File::LOCK_UN)
|
388
|
+
}
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
describe "Site listing" do
|
5
|
+
before(:each) do
|
6
|
+
stub_request(:post, "https://api.fjords.cc/v1/users/testy/login").
|
7
|
+
with(:body => {"password"=>"wrong"}).
|
8
|
+
to_return(:status => 401, :body => "")
|
9
|
+
|
10
|
+
stub_request(:post, "https://api.fjords.cc/v1/users/testy/login").
|
11
|
+
with(:body => {"password"=>""}).
|
12
|
+
to_return(:status => 401, :body => "")
|
13
|
+
|
14
|
+
stub_request(:post, "https://api.fjords.cc/v1/users/testy/login").
|
15
|
+
with(:body => {"password"=>"mcgee"}).
|
16
|
+
to_return(:status => 200, :body => {
|
17
|
+
:account => { :username => "testy" },
|
18
|
+
:token => "tokenkey"
|
19
|
+
}.to_json)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be false when not logged in" do
|
23
|
+
Fjords::Client.logged_in?.should be_false
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should catch login invalid error with missing password" do
|
27
|
+
expect {
|
28
|
+
Fjords::Client.login("testy", "")
|
29
|
+
}.to raise_error(Fjords::Client::LoginInvalid)
|
30
|
+
WebMock.should have_requested(:post, "https://api.fjords.cc/v1/users/testy/login").with({ :body =>{"password"=>""} })
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should catch login invalid error with wrong password" do
|
34
|
+
expect {
|
35
|
+
Fjords::Client.login("testy", "wrong")
|
36
|
+
}.to raise_error(Fjords::Client::LoginInvalid)
|
37
|
+
WebMock.should have_requested(:post, "https://api.fjords.cc/v1/users/testy/login").with({ :body =>{"password"=>"wrong"} })
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should be true when a token exists" do
|
41
|
+
Fjords::Client.login("testy", "mcgee")
|
42
|
+
WebMock.should have_requested(:post, "https://api.fjords.cc/v1/users/testy/login").with({ :body =>{"password"=>"mcgee"} })
|
43
|
+
|
44
|
+
Fjords::Client.logged_in?.should be_true
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
|
4
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib', 'fjords-client')
|
5
|
+
|
6
|
+
require 'fjords-client'
|
7
|
+
|
8
|
+
RSpec.configure do |conf|
|
9
|
+
conf.include WebMock::API
|
10
|
+
conf.include WebMock::Matchers
|
11
|
+
|
12
|
+
conf.before(:all) do
|
13
|
+
Fjords::Client.logout
|
14
|
+
# Fjords::Client.host = "https://fake.fjords.cc/v1"
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fjords-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.6
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Reynolds
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rest-client
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.6.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.6.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: addressable
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.3.2
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.3.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: stripe
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Fjords.cc API
|
63
|
+
email:
|
64
|
+
- me@tdreyno.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- Rakefile
|
72
|
+
- fjords-client.gemspec
|
73
|
+
- lib/fjords-client.rb
|
74
|
+
- lib/fjords-client/fjords-client.rb
|
75
|
+
- lib/fjords-client/version.rb
|
76
|
+
- spec/logged_in_spec.rb
|
77
|
+
- spec/spec_helper.rb
|
78
|
+
homepage: ''
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: 1471701448195210841
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
hash: 1471701448195210841
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.23
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Fjords.cc API
|
108
|
+
test_files:
|
109
|
+
- spec/logged_in_spec.rb
|
110
|
+
- spec/spec_helper.rb
|