hubic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # -*- ruby -*-
2
+
3
+ # Could be useful to fix permission error during local installation
4
+ ENV['RB_USER_INSTALL'] = 'true'
5
+ ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] = "1"
6
+
7
+ source "http://rubygems.org"
8
+
9
+ gemspec
10
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 sdalu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ Hubic
2
+ =====
3
+
4
+ Requirement
5
+ -----------
6
+ You need to retrieve the client id, secret key, and redirection url.
7
+ For that if not alredy done you need to register an application into your account.
8
+ To start the registration process you will need to go to ``My Account``, select ``Your application``, and click on ``Add an application``.
9
+
10
+ Quick example
11
+ -------------
12
+ ```ruby
13
+ require 'hubic'
14
+
15
+ # Configure the client
16
+ Hubic.default_redirect_uri = '** your redirect_uri **'
17
+ Hubic.default_client_id = '** your client_id **'
18
+ Hubic.default_client_secret = '** your client_secret **'
19
+
20
+ # Create a hubic handler for the desired user
21
+ h = Hubic.for_user('** your login **', '** your password **')
22
+
23
+ # Download file hubic-foo.txt and save it to local-foo.txt
24
+ h.download("hubic-foo.txt", "local-foo.txt")
25
+
26
+ # Upload file local-foo.txt and save it to hubic with the name hubic-bar.txt
27
+ h.upload("local-foo.txt", "hubic-bar.txt")
28
+ ```
29
+
30
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # -*- ruby -*-
2
+
3
+ require "rbconfig"
4
+ require "rubygems"
5
+ require "bundler/setup"
6
+
7
+ include Rake::DSL
8
+
9
+
10
+ Bundler::GemHelper.install_tasks
11
+
data/hubic.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.unshift File.expand_path("../lib", __FILE__)
3
+ require "hubic/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hubic"
7
+ s.version = Hubic::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = [ "Stephane D'Alu" ]
10
+ s.email = ["sdalu@sdalu.com" ]
11
+ s.homepage = "http://github.com/sdalu/ruby-hubic"
12
+ s.summary = "Manage your Hubic account from Ruby"
13
+ s.description = "Manage your Hubic account from Ruby"
14
+
15
+ s.add_dependency "faraday", "~>0.9"
16
+ s.add_dependency "nokogiri"
17
+ s.add_development_dependency "rake"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
21
+ s.require_path = 'lib'
22
+ end
data/lib/hubic.rb ADDED
@@ -0,0 +1,219 @@
1
+ # https://api.hubic.com/docs/#/server-side
2
+ # http://docs.openstack.org/api/openstack-object-storage/1.0/content/authentication-examples-curl.html
3
+
4
+
5
+ require 'pathname'
6
+ require 'uri'
7
+ require 'time'
8
+
9
+ require 'json'
10
+ require 'faraday'
11
+ require 'nokogiri'
12
+
13
+ # TODO
14
+ # - verify scope (requested == granted)
15
+ # - better scope handling
16
+ # - deal with state string
17
+ # - refresh token and credentials
18
+
19
+ # X-Storage-Url
20
+ # X-Auth-Token
21
+
22
+ require_relative 'hubic/version'
23
+ require_relative 'hubic/store'
24
+ require_relative 'hubic/openstack'
25
+ require_relative 'hubic/file_ops'
26
+
27
+ class Hubic
28
+ class Error < StandardError
29
+ class Auth < Error
30
+ end
31
+ class NotFound < Error
32
+ end
33
+ end
34
+
35
+
36
+ def self.default_client_id=(client_id)
37
+ @@client_id = client_id
38
+ end
39
+
40
+ def self.default_client_secret=(client_secret)
41
+ @@client_secret = client_secret
42
+ end
43
+
44
+ def self.default_redirect_uri=(redirect_uri)
45
+ @@redirect_uri = redirect_uri
46
+ end
47
+
48
+
49
+ def self.for_user(user, password=nil, store: Store[user],
50
+ &password_requester)
51
+ h = Hubic.new(@@client_id, @@client_secret, @@redirect_uri)
52
+ h.for_user(user, password, store: store, &password_requester)
53
+ h
54
+ end
55
+
56
+
57
+ def initialize(client_id = @@client_id,
58
+ client_secret = @@client_secret,
59
+ redirect_uri = @@redirect_uri)
60
+ @store = nil
61
+ @client_id = client_id
62
+ @client_secret = client_secret
63
+ @redirect_uri = redirect_uri
64
+ @conn = Faraday.new('https://api.hubic.com') do |faraday|
65
+ faraday.request :url_encoded
66
+ faraday.adapter :net_http
67
+ faraday.options.params_encoder = Faraday::FlatParamsEncoder
68
+ end
69
+ @default_container = "default"
70
+ end
71
+
72
+ def for_user(user, password=nil, store: Store[user], &password_requester)
73
+ @store = store
74
+ @refresh_token = @store['refresh_token'] if @store
75
+
76
+ if @refresh_token
77
+ data = refresh_access_token
78
+ @access_token = data[:access_token]
79
+ @expires_at = data[:expires_at ]
80
+ else
81
+ password ||= password_requester.call(user) if password_requester
82
+ if password.nil?
83
+ raise ArgumentError, "password requiered for user authorization"
84
+ end
85
+ code = get_request_code(user, password)
86
+ data = get_access_token(code)
87
+ @access_token = data[:access_token ]
88
+ @expires_at = data[:expires_in ]
89
+ @refresh_token = data[:refresh_token]
90
+ if @store
91
+ @store['refresh_token'] = @refresh_token
92
+ @store.save
93
+ end
94
+ end
95
+ end
96
+
97
+
98
+ def account
99
+ api_hubic(:get, '/1.0/account')
100
+ end
101
+
102
+ def credentials
103
+ api_hubic(:get, '/1.0/account/credentials')
104
+ end
105
+
106
+
107
+
108
+ def [](path=nil, container=@default_container)
109
+ # objects(path, container: container)
110
+ end
111
+
112
+
113
+
114
+ def api_hubic(method, path, params=nil)
115
+ r = @conn.method(method).call(path) do |req|
116
+ req.headers['Authorization'] = "Bearer #{@access_token}"
117
+ req.params = params if params
118
+ end
119
+ JSON.parse(r.body)
120
+ end
121
+
122
+ private
123
+
124
+
125
+ def get_request_code(user, password)
126
+ # Request code (retrieve user confirmation form)
127
+ r = @conn.get '/oauth/auth', {
128
+ :client_id => @client_id,
129
+ :response_type => 'code',
130
+ :redirect_uri => 'http://localhost/',
131
+ :scope => 'account.r,usage.r,links.drw,credentials.r',
132
+ :state => 'random'
133
+ }
134
+
135
+ # Autofill confirmation
136
+ params = {}
137
+ doc = Nokogiri::HTML(r.body)
138
+ doc.css('input').each {|i|
139
+ case i[:name]
140
+ when 'login'
141
+ params[:login ] = user
142
+ next
143
+ when 'user_pwd'
144
+ params[:user_pwd] = password
145
+ next
146
+ end
147
+
148
+ case i[:type]
149
+ when 'checkbox', 'hidden', 'text'
150
+ (params[i[:name]] ||= []) << i[:value] if i[:name]
151
+ end
152
+ }
153
+ if params.empty?
154
+ raise Error, "unable to autofill confirmation form"
155
+ end
156
+
157
+ # Confirm and get code
158
+ r = @conn.post '/oauth/auth', params
159
+ q = Hash[URI.decode_www_form(URI(r[:location]).query)]
160
+
161
+ case r.status
162
+ when 302
163
+ q['code']
164
+ when 400, 401, 500
165
+ raise Error::Auth, "#{q['error']} #{q['error_description']}"
166
+ else
167
+ raise Error::Auth, "unhandled response code (#{r.status})"
168
+ end
169
+ end
170
+
171
+ def get_access_token(code)
172
+ r = @conn.post '/oauth/token', {
173
+ :code => code,
174
+ :redirect_uri => 'http://localhost/',
175
+ :grant_type => 'authorization_code',
176
+ :client_id => @client_id,
177
+ :client_secret => @client_secret
178
+ }
179
+ j = JSON.parse(r.body)
180
+ case r.status
181
+ when 200
182
+ { :acces_token => j['access_token'],
183
+ :expires_at => Time.parse(r[:date]) + j['expires_in'].to_i,
184
+ :refresh_token => j['refresh_token']
185
+ }
186
+ when 400, 401, 500
187
+ raise Error::Auth, "#{j['error']} #{j['error_description']}"
188
+ else
189
+ raise Error::Auth, "unhandled response code (#{r.status})"
190
+ end
191
+ end
192
+
193
+ def refresh_access_token
194
+ if @refresh_token.nil?
195
+ raise Error, "refresh_token was not previously acquiered"
196
+ end
197
+ r = @conn.post '/oauth/token', {
198
+ :refresh_token => @refresh_token,
199
+ :grant_type => 'refresh_token',
200
+ :client_id => @client_id,
201
+ :client_secret => @client_secret
202
+ }
203
+ j = JSON.parse(r.body)
204
+ case r.status
205
+ when 200
206
+ { :access_token => j['access_token'],
207
+ :expires_at => Time.parse(r[:date]) + j['expires_in'].to_i
208
+ }
209
+ when 400, 401, 500
210
+ raise Error::Auth, "#{j['error']} #{j['error_description']}"
211
+ else
212
+ raise Error::Auth, "unhandled response code (#{r.status})"
213
+ end
214
+ end
215
+
216
+ end
217
+
218
+
219
+
@@ -0,0 +1,54 @@
1
+ class Hubic
2
+
3
+ # File operations
4
+ #######################################################################
5
+
6
+ def download(obj, dst=nil, size: nil, offset: 0, &block)
7
+ io = nil
8
+ dst = Pathname(dst) if String === dst
9
+ _dst = case dst
10
+ when Pathname then io = dst.open('w')
11
+ when NilClass then io = StringIO.new
12
+ else dst
13
+ end
14
+
15
+ meta = get_object(obj, _dst, size: size, offset: offset, &block)
16
+
17
+ if (Pathname === dst) && meta[:lastmod]
18
+ dst.utime(meta[:lastmod], meta[:lastmod])
19
+ end
20
+
21
+ if dst.nil?
22
+ then io.flush.string
23
+ else meta
24
+ end
25
+ ensure
26
+ io.close unless io.nil?
27
+ end
28
+
29
+ def upload(src, obj, &block)
30
+ put_object(src, obj, &block)
31
+ end
32
+
33
+ def copy(src, dst)
34
+ raise "not implemented yet"
35
+ end
36
+
37
+ def move(src, dst)
38
+ raise "not implemented yet"
39
+ end
40
+
41
+ def delete(src)
42
+ raise "not implemented yet"
43
+ end
44
+
45
+ def checksum(src)
46
+ raise "not implemented yet"
47
+ end
48
+
49
+
50
+ def list(path = '/', container = @default_container)
51
+ objects(container, path: path)
52
+ end
53
+
54
+ end
@@ -0,0 +1,222 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'json'
5
+
6
+
7
+ class Hubic
8
+ class Container
9
+ attr_reader :name, :count, :bytes, :metadata
10
+ def initialize(hubic, name)
11
+ @hubic = hubic
12
+ @name = name.dup.freeze
13
+ j, h = @hubic.api_openstack(:head, @name)
14
+ @count = h['x-container-object-count'].to_i
15
+ @bytes = h['x-container-bytes-used' ].to_i
16
+ @etag = h['etag' ]
17
+ @metadata = Hash[h.map {|k,v|
18
+ if k =~ /^x-container-meta-(.*)/
19
+ [ $1, v ]
20
+ end
21
+ }.compact].freeze
22
+ end
23
+
24
+ def [](key)
25
+ @metadata[key]
26
+ end
27
+
28
+ def empty?
29
+ @count == 0
30
+ end
31
+
32
+ def destroy!
33
+ j, h = @hubic.api_openstack(:delete, @name)
34
+ j
35
+ end
36
+
37
+ def object(path)
38
+ end
39
+
40
+ def objects()
41
+ end
42
+
43
+
44
+ end
45
+
46
+ class Object
47
+ def initialize(hubic)
48
+ @hubic = hubic
49
+ end
50
+ end
51
+
52
+ def get_object(obj, dst=nil, size: nil, offset: 0, &block)
53
+ container, path, uri = normalize_object(obj)
54
+
55
+ if !(IO === dst) && !dst.respond_to?(:write) &&
56
+ !(Proc === dst) && !dst.respond_to?(:call)
57
+ raise ArgumentError, "unsupported destination"
58
+ end
59
+
60
+ meta = {}
61
+
62
+ hdrs = {}
63
+ hdrs['X-Auth-Token'] = @os[:token]
64
+
65
+ if size
66
+ hdrs['Range'] = sprintf("bytes=%d-%d", offset, offset + size - 1)
67
+ end
68
+
69
+ http = Net::HTTP.new(uri.host, uri.port)
70
+ if uri.scheme == 'https'
71
+ http.use_ssl = true
72
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
73
+ end
74
+ http.start
75
+
76
+
77
+ http.request_get(uri.request_uri, hdrs) {|response|
78
+ case response
79
+ when Net::HTTPSuccess
80
+ when Net::HTTPRedirection
81
+ fail "http redirect is not currently handled"
82
+ when Net::HTTPUnauthorized
83
+ # TODO: Need to refresh token
84
+ else
85
+ fail "resource unavailable: #{uri} (#{response.class})"
86
+ end
87
+
88
+ lastmod = Time.parse(response['last-modified']) rescue nil
89
+ length = response.content_length
90
+ type = response['content-type']
91
+ meta = { :lastmod => lastmod,
92
+ :length => length,
93
+ :type => type
94
+ }
95
+
96
+ if block
97
+ block.call(meta)
98
+ end
99
+
100
+ response.read_body {|segment|
101
+ if IO === dst then dst.write(segment)
102
+ elsif Proc === dst then dst.call(segment)
103
+ elsif dst.respond_to?(:write) then dst.write(segment)
104
+ elsif dst.respond_to?(:call ) then dst.call(segment)
105
+ end
106
+
107
+ if block
108
+ block.call(segment)
109
+ end
110
+ }
111
+ }
112
+ if block
113
+ block.call(:done)
114
+ end
115
+
116
+ meta
117
+ ensure
118
+ http.finish unless http.nil?
119
+ end
120
+
121
+ def put_object(src, obj, &block)
122
+ container, path, uri = normalize_object(obj)
123
+
124
+ r = @conn.put(uri) do |req|
125
+ req.headers['X-Auth-Token'] = @os[:token]
126
+ req.params[:file] = Faraday::UploadIO.new(src, 'text/plain')
127
+ end
128
+ puts r.inspect
129
+ end
130
+
131
+ def objects(container = @default_container,
132
+ path: nil, limit: nil, gt: nil, lt: nil)
133
+ path = path[1..-1] if path && path[0] == ?/
134
+ p = { path: path, limit: limit, marker: gt, end_marker: lt
135
+ }.delete_if {|k,v| v.nil? }
136
+ j, = api_openstack(:get, container, p)
137
+ Hash[j.map {|o| [ o['name'], {
138
+ :hash => o['hash'],
139
+ :lastmod => Time.parse(o['last_modified']),
140
+ :size => o['bytes'].to_i,
141
+ :type => o['content_type']
142
+ } ] } ]
143
+ end
144
+
145
+ def containers
146
+ j, = api_openstack(:get, '/')
147
+ Hash[j.map {|c| [ c['name'], { :size => c['bytes'].to_i,
148
+ :count => c['count'].to_i } ] } ]
149
+ end
150
+
151
+ def container(name)
152
+ Container.new(self, name)
153
+ end
154
+
155
+ def default_container=(name)
156
+ @default_container = name
157
+ end
158
+
159
+ def api_openstack(method, path, params=nil)
160
+ openstack_setup_refresh
161
+
162
+ params ||= {}
163
+ params[:format] ||= :json
164
+
165
+ p = "#{@os[:endpoint]}#{'/' if path[0] != ?/}#{path}"
166
+ r = @conn.method(method).call(p) do |req|
167
+ req.headers['X-Auth-Token'] = @os[:token]
168
+ req.params = params
169
+ end
170
+
171
+ if r.body.nil? || r.body.empty?
172
+ then [ nil, r.headers ]
173
+ else [ JSON.parse(r.body), r.headers ]
174
+ end
175
+ end
176
+
177
+
178
+ def openstack_setup_refresh(force: false)
179
+ return unless force || @os.nil? || @os[:expires] <= Time.now
180
+
181
+ data = self.credentials
182
+ endpoint = data['endpoint']
183
+ token = data['token']
184
+ expires = Time.parse(data['expires'])
185
+
186
+ openstack_setup(endpoint, token, expires)
187
+ end
188
+
189
+ def openstack_setup(endpoint, token, expires)
190
+ conn = Faraday.new do |faraday|
191
+ faraday.request :url_encoded
192
+ faraday.adapter :net_http
193
+ faraday.options.params_encoder = Faraday::FlatParamsEncoder
194
+ end
195
+ @os = {
196
+ :expires => expires,
197
+ :token => token,
198
+ :endpoint => endpoint,
199
+ :conn => conn
200
+ }
201
+ end
202
+
203
+ def normalize_object(obj)
204
+ c, p = case obj
205
+ when String
206
+ [ @default_container, obj ]
207
+ when Hash
208
+ [ obj[:name] || obj[:path],
209
+ (obj[:container] || @default_container).to_s ]
210
+ when Array
211
+ case obj.length
212
+ when 1 then [ @default_container, obj ]
213
+ when 2 then Symbol === obj[1] ? [ obj[1], obj[0] ] : obj
214
+ else raise ArguementError
215
+ end
216
+ end
217
+ c = c.to_s
218
+ p = p[1..-1] if p[0] == ?/
219
+ [ c, p, URI("#{@os[:endpoint]}/#{c}/#{p}") ]
220
+ end
221
+
222
+ end
@@ -0,0 +1,40 @@
1
+ require 'yaml'
2
+
3
+ class Hubic
4
+ class Store
5
+ FILE = "#{ENV['HOME']}/.hubic"
6
+
7
+ extend Forwardable
8
+ def_delegator :@data, :[]
9
+ def_delegator :@data, :[]=
10
+
11
+ def initialize(file = FILE, user = nil)
12
+ @file = file
13
+ @user = user
14
+ @data = Hash(begin
15
+ if data = YAML.load_file(@file)
16
+ @user.nil? ? data : data[@user]
17
+ end
18
+ rescue Errno::ENOENT
19
+ end)
20
+ end
21
+
22
+ def self.[](user)
23
+ self.new(file = FILE, user)
24
+ end
25
+
26
+ def save
27
+ data = if @user
28
+ ( begin
29
+ YAML.load_file(@file)
30
+ rescue Errno::ENOENT
31
+ end || {} ).merge(@user => @data).to_yaml
32
+ else
33
+ @data
34
+ end
35
+ File.open(@file, 'w', 0600) {|io|
36
+ io.write(data.to_yaml)
37
+ }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ class Hubic
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hubic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephane D'Alu
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-05-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.9'
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: '0.9'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
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: Manage your Hubic account from Ruby
63
+ email:
64
+ - sdalu@sdalu.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE
72
+ - README.md
73
+ - Rakefile
74
+ - hubic.gemspec
75
+ - lib/hubic.rb
76
+ - lib/hubic/file_ops.rb
77
+ - lib/hubic/openstack.rb
78
+ - lib/hubic/store.rb
79
+ - lib/hubic/version.rb
80
+ homepage: http://github.com/sdalu/ruby-hubic
81
+ licenses: []
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ segments:
93
+ - 0
94
+ hash: 4363560357882864147
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ segments:
102
+ - 0
103
+ hash: 4363560357882864147
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.29
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Manage your Hubic account from Ruby
110
+ test_files: []