gemical 0.0.2

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.md ADDED
@@ -0,0 +1,28 @@
1
+ # Gemical command-line client
2
+
3
+ Manage your private Gems in through the command line.
4
+
5
+ ## License
6
+
7
+ (The MIT License)
8
+
9
+ Copyright (c) 2011 Black Square Media Ltd
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining
12
+ a copy of this software and associated documentation files (the
13
+ "Software"), to deal in the Software without restriction, including
14
+ without limitation the rights to use, copy, modify, merge, publish,
15
+ distribute, sublicense, and/or sell copies of the Software, and to
16
+ permit persons to whom the Software is furnished to do so, subject to
17
+ the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be
20
+ included in all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/gemical ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'gemical/import'
@@ -0,0 +1,113 @@
1
+ class Gemical::Auth
2
+ include Gemical::Singleton
3
+
4
+ def configuration
5
+ Gemical.configuration
6
+ end
7
+
8
+ def verify_account!
9
+ @verified_account ||= if credentials
10
+ get_account
11
+ else
12
+ say_ok "\nWelcome to Gemical! You are new, right?"
13
+ say_ok "We need to authenticate your account:"
14
+ collect_credentials
15
+ end
16
+ end
17
+
18
+ def verify_vault!
19
+ verify_account!
20
+
21
+ @verified_vault ||= if primary_vault
22
+ get_vault(primary_vault) || collect_vault("Ooops, you don't seem to have access to '#{primary_vault}' anymore.")
23
+ else
24
+ collect_vault("You haven't specified your primary vault yet.")
25
+ end
26
+ end
27
+
28
+ def vaults
29
+ verify_account!
30
+ @vaults ||= Gemical.connection.get("/vaults").map do |hash|
31
+ Gemical::Vault.new(hash)
32
+ end
33
+ end
34
+
35
+ def credentials
36
+ return nil unless configuration.credentials?
37
+ pair = configuration.credentials.read.split(/\s+/)[0..1]
38
+ pair if pair.size == 2 && pair.all? {|v| string_value?(v) }
39
+ end
40
+
41
+ def basic_auth
42
+ [credentials.last, 'x'] if credentials
43
+ end
44
+
45
+ def primary_vault
46
+ return nil unless configuration.primary_vault?
47
+ value = configuration.primary_vault.read.split(/\s+/).first
48
+ value if string_value?(value)
49
+ end
50
+
51
+ private
52
+
53
+ def get_account
54
+ Gemical.connection.get("/account")
55
+ rescue Gemical::Connection::HTTPUnauthorized
56
+ renew_credentials?
57
+ end
58
+
59
+ def collect_credentials(attempt = 1)
60
+ email = ask('Email: ')
61
+ pass = password
62
+ account = Gemical.connection.get "/account", :basic_auth => [email, pass]
63
+ configuration.write! :credentials, email, account["authentication_token"]
64
+ say_ok "Authentication successful. Thanks!\n"
65
+ account
66
+ rescue Gemical::Connection::HTTPUnauthorized
67
+ if attempt < 3
68
+ say_warning "\nInvalid email and/or password."
69
+ say_warning "Try again:"
70
+ attempt += 1
71
+ retry
72
+ else
73
+ say_error "\nAre your sure you have the right credentials?"
74
+ say_error "Visit http://gemical.com/password/new for instructions."
75
+ exit(1)
76
+ end
77
+ end
78
+
79
+ def renew_credentials?
80
+ say_warning "\nYour stored credentials seem to be outdated."
81
+ if agree("Do you want to re-authenticate your account? ")
82
+ collect_credentials
83
+ else
84
+ say_warning "You can check your stored credentials in #{configuration.credentials}."
85
+ exit(1)
86
+ end
87
+ end
88
+
89
+ def get_vault(name)
90
+ ensure_vaults!
91
+ vaults.find {|v| v == name }
92
+ end
93
+
94
+ def collect_vault(message, save = true)
95
+ ensure_vaults!
96
+ say_warning "\n#{message}"
97
+ name = ask "Which one should it be: ", vaults
98
+ configuration.write! :primary_vault, name
99
+ get_vault(name)
100
+ end
101
+
102
+ def ensure_vaults!
103
+ return unless vaults.empty?
104
+ say_error "You have no vaults associated with your account."
105
+ say_error "Please sign in to your account and create one: http://gemical.com/sign_in."
106
+ exit(1)
107
+ end
108
+
109
+ def string_value?(value)
110
+ value.is_a?(String) && !value.strip.empty?
111
+ end
112
+
113
+ end
@@ -0,0 +1,44 @@
1
+ class Gemical::Commands::Base
2
+
3
+ protected
4
+
5
+ def current_path
6
+ @current_path ||= Pathname.new(File.expand_path('.'))
7
+ end
8
+
9
+ def terminate(message)
10
+ say_error message
11
+ exit(1)
12
+ end
13
+
14
+ def success(message)
15
+ say_ok message
16
+ exit(0)
17
+ end
18
+
19
+ def full_errors(errors, mappings = {})
20
+ errors.map do |attr, messages|
21
+ name = mappings[attr] || attr
22
+ messages.map do |message|
23
+ "#{name} #{message}"
24
+ end.join(', ')
25
+ end.join(', ')
26
+ end
27
+
28
+ def authenticate!
29
+ Gemical.auth.verify_account!
30
+ end
31
+
32
+ def conn
33
+ Gemical.connection
34
+ end
35
+
36
+ def current_vault(options)
37
+ options.vault || primary_vault
38
+ end
39
+
40
+ def primary_vault
41
+ Gemical.auth.verify_vault!
42
+ end
43
+
44
+ end
@@ -0,0 +1,49 @@
1
+ class Gemical::Commands::Bundle < Gemical::Commands::Base
2
+
3
+ class Evaluator
4
+ attr_reader :sources
5
+
6
+ def initialize(file)
7
+ @sources = []
8
+ instance_eval file.read
9
+ end
10
+
11
+ def source(path, *)
12
+ @sources << path
13
+ end
14
+
15
+ def method_missing(sym, *a)
16
+ end
17
+ end
18
+
19
+ def create(args, options)
20
+ gemfile = current_path.join('Gemfile')
21
+ terminate "Unable to find a Gemfile in #{current_path}" unless gemfile.file?
22
+ terminate "Gemfile in #{current_path} is not writable" unless gemfile.writable?
23
+
24
+ evaluation = Evaluator.new(gemfile)
25
+ terminate "Gemfile doesn't seem to be valid" if evaluation.sources.empty?
26
+
27
+ authenticate!
28
+ vault = current_vault(options)
29
+
30
+ if evaluation.sources.any? {|s| s.to_s.include?(vault.token) }
31
+ terminate "Vault '#{vault}' is already sourced in your Gemfile."
32
+ end
33
+
34
+ source = gemfile.open('r')
35
+ output = Tempfile.new "Gemfile"
36
+
37
+ while string = source.gets
38
+ output << string
39
+ if string =~ /^(\s*)source\b/
40
+ output << "#{$1}source 'http://#{vault.token}@bundle.gemical.com'\n"
41
+ end
42
+ end
43
+ output.close
44
+
45
+ FileUtils.cp output.path, source.path
46
+ success "Vault '#{vault}' was added as a source to your Gemfile."
47
+ end
48
+
49
+ end
@@ -0,0 +1,46 @@
1
+ class Gemical::Commands::Gems < Gemical::Commands::Base
2
+
3
+ def index(args, options)
4
+ authenticate!
5
+
6
+ vault = current_vault(options)
7
+ conn.get("/vaults/#{vault}/gems").each do |rubygem|
8
+ versions = rubygem["versions"].map {|v| v["name"] }.sort.reverse
9
+ say "#{rubygem["name"]} (#{versions.join(', ')})"
10
+ end
11
+ rescue Gemical::Connection::HTTPNotFound
12
+ terminate "Sorry, no such vault '#{vault}'."
13
+ end
14
+
15
+ def create(args, options)
16
+ terminate "Please provide a GEM file." unless args.first.is_a?(String)
17
+
18
+ file = current_path.join(args.first)
19
+ terminate "Hmm, no file in #{file}" unless file.file?
20
+
21
+ authenticate!
22
+ vault = current_vault(options)
23
+ response = conn.post("/vaults/#{vault}/gems", :params => { "rubygem[file]" => file.open('rb') })
24
+ success "#{response['original_name']} was successfully added to vault '#{vault}'."
25
+ rescue Gemical::Connection::HTTPNotFound
26
+ terminate "Sorry, no such vault '#{vault}'."
27
+ rescue Gemical::Connection::HTTPUnprocessable => e
28
+ terminate "Sorry, #{full_errors(e.response, 'name' => 'gem')}"
29
+ end
30
+
31
+ def destroy(args, options)
32
+ name, version = args.first(2)
33
+ terminate "Please provide a valid GEM name." unless name.to_s =~ /\A[\w\-]+\z/
34
+ terminate "Please provide a valid VERSION." unless version.to_s =~ /\A[\w\-\.]+\z/
35
+
36
+ authenticate!
37
+ vault = current_vault(options)
38
+ response = conn.delete("/vaults/#{vault}/gems/#{name}/#{version}")
39
+ success "#{name} (#{version}) was successfully removed from #{vault}."
40
+ rescue Gemical::Connection::HTTPNotFound
41
+ terminate "Sorry, unable to find '#{name} (#{version})' in vault '#{vault}'."
42
+ rescue Gemical::Connection::HTTPUnprocessable => e
43
+ "Sorry, #{full_errors(e.response)}"
44
+ end
45
+
46
+ end
@@ -0,0 +1,11 @@
1
+ class Gemical::Commands::Vaults < Gemical::Commands::Base
2
+
3
+ def index(args, options)
4
+ authenticate!
5
+
6
+ Gemical.auth.vaults.each do |vault|
7
+ say vault
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,6 @@
1
+ module Gemical::Commands
2
+ autoload :Base, 'gemical/commands/base'
3
+ autoload :Bundle, 'gemical/commands/bundle'
4
+ autoload :Gems, 'gemical/commands/gems'
5
+ autoload :Vaults, 'gemical/commands/vaults'
6
+ end
@@ -0,0 +1,55 @@
1
+ require 'fileutils'
2
+
3
+ class Gemical::Configuration
4
+ include Gemical::Singleton
5
+
6
+ attr_writer :home
7
+
8
+ def initialize
9
+ @home = ENV['HOME']
10
+ end
11
+
12
+ def root
13
+ @root ||= Pathname.new File.join(@home, ".gemical")
14
+ end
15
+
16
+ def credentials
17
+ root.join('credentials')
18
+ end
19
+
20
+ def primary_vault
21
+ root.join('primary_vault')
22
+ end
23
+
24
+ def root?
25
+ root.directory? && root.readable?
26
+ end
27
+
28
+ def credentials?
29
+ readable_file? credentials
30
+ end
31
+
32
+ def primary_vault?
33
+ readable_file? primary_vault
34
+ end
35
+
36
+ def create_root!
37
+ return if root?
38
+ FileUtils.mkdir_p root
39
+ FileUtils.chmod_R 0700, root
40
+ end
41
+
42
+ def write!(symbol, *lines)
43
+ create_root!
44
+ send(symbol).open "w", 0600 do |file|
45
+ file << (lines + ['']).join("\n")
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def readable_file?(path)
52
+ root? && path.file? && path.readable?
53
+ end
54
+
55
+ end
@@ -0,0 +1,78 @@
1
+ require 'restclient'
2
+ require 'multi_json'
3
+
4
+ class Gemical::Connection
5
+ include Gemical::Singleton
6
+
7
+ HTTPError = Class.new(StandardError)
8
+ HTTPUnauthorized = Class.new(HTTPError)
9
+ HTTPServerError = Class.new(HTTPError)
10
+ HTTPNotFound = Class.new(HTTPError)
11
+
12
+ class HTTPUnprocessable < HTTPError
13
+
14
+ attr_reader :response
15
+
16
+ def initialize(response)
17
+ @response = response
18
+ super "Unprocessable Entity (422)"
19
+ end
20
+
21
+ end
22
+
23
+ attr_accessor :base_url
24
+
25
+ def initialize
26
+ @base_url = ENV['GEMICAL_URL'] || "http://api.gemical.com"
27
+ self.proxy = ENV['http_proxy']
28
+ end
29
+
30
+ def proxy
31
+ RestClient.proxy
32
+ end
33
+
34
+ def proxy=(url)
35
+ RestClient.proxy = url if url
36
+ end
37
+
38
+ def get(path, options = {})
39
+ request :get, path, build_headers(options)
40
+ end
41
+
42
+ def post(path, options = {})
43
+ request :post, path, options.delete(:params), build_headers(options)
44
+ end
45
+
46
+ def delete(path, options = {})
47
+ request :delete, path, build_headers(options)
48
+ end
49
+
50
+ protected
51
+
52
+ def request(method, path, *args)
53
+ url = [base_url, path].join
54
+ MultiJson.decode RestClient.send(method, url, *args)
55
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
56
+ say_error "Ooops. Can't establish a connection to #{base_url}. Are you on-line?"
57
+ exit(1)
58
+ rescue RestClient::UnprocessableEntity => e
59
+ raise HTTPUnprocessable.new MultiJson.decode(e.response.body)
60
+ rescue RestClient::Unauthorized, RestClient::Forbidden => e
61
+ raise HTTPUnauthorized, "Unauthorized request (#{e.http_code})"
62
+ rescue RestClient::ResourceNotFound
63
+ raise HTTPNotFound, "Resource not found (404)"
64
+ rescue RestClient::Exception => e
65
+ raise HTTPServerError, "Service responded with #{e.to_s}"
66
+ end
67
+
68
+ def build_headers(options)
69
+ { :accept => :json }.merge(authorization(options)).merge(options.delete(:headers) || {})
70
+ end
71
+
72
+ def authorization(options)
73
+ pair = options.delete(:basic_auth) || Gemical.auth.basic_auth
74
+ token = [pair.join(':')].pack("m").delete("\r\n")
75
+ { :authorization => "Basic #{token}" }
76
+ end
77
+
78
+ end
@@ -0,0 +1,25 @@
1
+
2
+ Usage:
3
+ <%= @syntax || "gemical #{@name} [options]" %>
4
+ <% if @description || @summary -%>
5
+
6
+ Summary:
7
+ <%= @description || @summary %>
8
+ <% end -%>
9
+ <% unless @options.empty? -%>
10
+
11
+ Options:
12
+ <% for option in @options -%>
13
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] %>
14
+ <% end -%>
15
+ <% end -%>
16
+ <% unless @examples.empty? -%>
17
+
18
+ Examples:
19
+ <% for description, command in @examples -%>
20
+
21
+ # <%= description %>
22
+ <%= command %>
23
+ <% end -%>
24
+ <% end -%>
25
+
@@ -0,0 +1,28 @@
1
+
2
+ <%= program :name %>
3
+
4
+ Commands:
5
+ <% for name, command in @commands.sort -%>
6
+ <% unless alias? name -%>
7
+ <%= "%-20s %s" % [command.name, command.summary || command.description] %>
8
+ <% end -%>
9
+ <% end -%>
10
+ <% unless @aliases.empty? %>
11
+ Aliases:
12
+ <% for alias_name, args in @aliases.sort -%>
13
+ <%= "%-20s %s %s" % [alias_name, command(alias_name).name, args.join(' ')] %>
14
+ <% end -%>
15
+ <% end %>
16
+ <% unless @options.empty? -%>
17
+ Global Options:
18
+ <% for option in @options -%>
19
+ <%= "%-20s %s" % [option[:switches].join(', '), option[:description]] %>
20
+ <% end -%>
21
+ <% end -%>
22
+ <% if program :help -%>
23
+ <% for title, body in program(:help) %>
24
+ <%= title %>:
25
+ <%= body %>
26
+ <% end %>
27
+ <% end -%>
28
+
@@ -0,0 +1,8 @@
1
+ class Gemical::Format < Commander::HelpFormatter::Terminal
2
+
3
+ def template name
4
+ path = File.expand_path("../format/#{name}.erb", __FILE__)
5
+ ERB.new File.read(path), nil, '-'
6
+ end
7
+
8
+ end
@@ -0,0 +1,51 @@
1
+ require 'gemical'
2
+ require 'commander/import'
3
+
4
+ program :version, Gemical::VERSION::STRING
5
+ program :name, 'Gemical command-line client ~ http://gemical.com'
6
+ program :description, '-'
7
+ program :help_formatter, Gemical::Format
8
+
9
+ command :bundle do |c|
10
+ c.summary = 'Bundle a vault with your project'
11
+ c.description = 'This command will add one of your vaults as a source to the Gemfile'
12
+ c.example 'Bundle your primary vault', 'cd my-project; gemical bundle'
13
+ c.example 'Bundle a specific vault', 'cd my-project; gemical bundle --vault my-vault'
14
+ c.option '--vault VAULT', 'Specify a vault'
15
+ c.when_called Gemical::Commands::Bundle, :create
16
+ end
17
+
18
+ command :vaults do |c|
19
+ c.syntax = 'gemical vaults'
20
+ c.summary = 'List all vaults'
21
+ c.description = 'This command will list all vaults you have access to'
22
+ c.description = 'Display all my vaults'
23
+ c.when_called Gemical::Commands::Vaults, :index
24
+ end
25
+
26
+ command :gems do |c|
27
+ c.summary = 'List all gems within a vault'
28
+ c.description = 'This command will list all Gems within a vault (primary, unless specified)'
29
+ c.option '--vault [VAULT]', 'Specify a vault'
30
+ c.when_called Gemical::Commands::Gems, :index
31
+ end
32
+
33
+ command :"gems:add" do |c|
34
+ c.syntax = 'gemical gems:add GEM [options]'
35
+ c.summary = 'Add a gem to a vault'
36
+ c.description = 'This command will upload the specified Gem to one of your vaults (primary, unless specified)'
37
+ c.example 'Push a Gem to your primary vault', 'gemical gems:add my-gem-1.0.0.gem'
38
+ c.example 'Push a Gem to a specific vault', 'gemical gems:add my-gem-1.0.0.gem --vault my-vault'
39
+ c.option '--vault VAULT', 'Specify a vault'
40
+ c.when_called Gemical::Commands::Gems, :create
41
+ end
42
+
43
+ command :"gems:remove" do |c|
44
+ c.syntax = 'gemical gems:remove GEM VERSION [options]'
45
+ c.summary = 'Remove gem from a vault'
46
+ c.description = 'This command will remove the specified Gem version from one of your vaults (primary, unless specified)'
47
+ c.example 'Remove a Gem from your primary vault', 'gemical gems:remove my-gem 1.0.0'
48
+ c.example 'Remove a Gem from a specific vault', 'gemical gems:remove my-gem 1.0.0 --vault my-vault'
49
+ c.option '--vault VAULT', 'Specify a vault'
50
+ c.when_called Gemical::Commands::Gems, :destroy
51
+ end
@@ -0,0 +1,22 @@
1
+ module Gemical::Singleton
2
+
3
+ def self.included(base)
4
+ class << base
5
+ private :new
6
+ end
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def instance
13
+ @instance ||= new
14
+ end
15
+
16
+ def reload!
17
+ @instance = nil
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,10 @@
1
+ class Gemical::Vault < String
2
+
3
+ attr_reader :token
4
+
5
+ def initialize(hash)
6
+ @token = hash["token"]
7
+ super hash["name"]
8
+ end
9
+
10
+ end
@@ -0,0 +1,10 @@
1
+ module Gemical
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 2
6
+ PRE = nil
7
+
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
9
+ end
10
+ end
data/lib/gemical.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'pathname'
3
+ require 'erb'
4
+ require 'commander'
5
+
6
+ include Commander::UI
7
+ include Commander::UI::AskForClass
8
+
9
+ module Gemical
10
+ autoload :Auth, 'gemical/auth'
11
+ autoload :Format, 'gemical/format'
12
+ autoload :Commands, 'gemical/commands'
13
+ autoload :Configuration, 'gemical/configuration'
14
+ autoload :Connection, 'gemical/connection'
15
+ autoload :Singleton, 'gemical/singleton'
16
+ autoload :Vault, 'gemical/vault'
17
+ autoload :VERSION, 'gemical/version'
18
+
19
+ def self.auth
20
+ Gemical::Auth.instance
21
+ end
22
+
23
+ def self.configuration
24
+ Gemical::Configuration.instance
25
+ end
26
+
27
+ def self.connection
28
+ Gemical::Connection.instance
29
+ end
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gemical
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Black Square Media Ltd
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: commander
16
+ requirement: &14809440 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *14809440
25
+ - !ruby/object:Gem::Dependency
26
+ name: rest-client
27
+ requirement: &14808980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *14808980
36
+ - !ruby/object:Gem::Dependency
37
+ name: multi_json
38
+ requirement: &14808520 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *14808520
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &14808080 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *14808080
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &14807560 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *14807560
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: &14807060 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *14807060
80
+ description: Manage your private Gems through the command line.
81
+ email: info@blacksquaremedia.com
82
+ executables:
83
+ - gemical
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - README.md
88
+ - lib/gemical.rb
89
+ - lib/gemical/connection.rb
90
+ - lib/gemical/singleton.rb
91
+ - lib/gemical/format/help.erb
92
+ - lib/gemical/format/command_help.erb
93
+ - lib/gemical/auth.rb
94
+ - lib/gemical/configuration.rb
95
+ - lib/gemical/commands.rb
96
+ - lib/gemical/import.rb
97
+ - lib/gemical/vault.rb
98
+ - lib/gemical/version.rb
99
+ - lib/gemical/format.rb
100
+ - lib/gemical/commands/base.rb
101
+ - lib/gemical/commands/bundle.rb
102
+ - lib/gemical/commands/vaults.rb
103
+ - lib/gemical/commands/gems.rb
104
+ - bin/gemical
105
+ homepage: http://github.com/bsm/gemical
106
+ licenses: []
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: 1.8.7
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: 1.3.6
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.10
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Gemical command-line client.
129
+ test_files: []