gemical 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +28 -0
- data/bin/gemical +6 -0
- data/lib/gemical/auth.rb +113 -0
- data/lib/gemical/commands/base.rb +44 -0
- data/lib/gemical/commands/bundle.rb +49 -0
- data/lib/gemical/commands/gems.rb +46 -0
- data/lib/gemical/commands/vaults.rb +11 -0
- data/lib/gemical/commands.rb +6 -0
- data/lib/gemical/configuration.rb +55 -0
- data/lib/gemical/connection.rb +78 -0
- data/lib/gemical/format/command_help.erb +25 -0
- data/lib/gemical/format/help.erb +28 -0
- data/lib/gemical/format.rb +8 -0
- data/lib/gemical/import.rb +51 -0
- data/lib/gemical/singleton.rb +22 -0
- data/lib/gemical/vault.rb +10 -0
- data/lib/gemical/version.rb +10 -0
- data/lib/gemical.rb +31 -0
- metadata +129 -0
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
data/lib/gemical/auth.rb
ADDED
@@ -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,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,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
|
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: []
|