linguist_ruby 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +0 -0
- data/LICENSE +20 -0
- data/README.md +0 -0
- data/REST.md +0 -0
- data/ROADMAP.md +0 -0
- data/bin/linguist +14 -0
- data/lib/linguist/client.rb +120 -0
- data/lib/linguist/command.rb +98 -0
- data/lib/linguist/commands/app.rb +72 -0
- data/lib/linguist/commands/auth.rb +146 -0
- data/lib/linguist/commands/base.rb +118 -0
- data/lib/linguist/commands/help.rb +90 -0
- data/lib/linguist/commands/translations.rb +30 -0
- data/lib/linguist/commands/version.rb +7 -0
- data/lib/linguist/helpers.rb +121 -0
- data/lib/linguist/models/project.rb +81 -0
- data/lib/linguist/models/projects.rb +34 -0
- data/lib/linguist/models/resource.rb +26 -0
- data/lib/linguist/models/user.rb +3 -0
- data/lib/linguist/version.rb +3 -0
- data/lib/linguist.rb +3 -0
- data/lib/vendor/okjson.rb +556 -0
- metadata +154 -0
data/CHANGELOG.md
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Carl Lerche, Yehuda Katz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
File without changes
|
data/REST.md
ADDED
File without changes
|
data/ROADMAP.md
ADDED
File without changes
|
data/bin/linguist
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'linguist'
|
7
|
+
require 'linguist/command'
|
8
|
+
|
9
|
+
args = ARGV.dup
|
10
|
+
ARGV.clear
|
11
|
+
command = args.shift.strip rescue 'help'
|
12
|
+
|
13
|
+
Linguist::Command.run(command, args)
|
14
|
+
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'uri'
|
3
|
+
require 'time'
|
4
|
+
require 'linguist/version'
|
5
|
+
require 'vendor/okjson'
|
6
|
+
require 'json'
|
7
|
+
require 'linguist/models/projects'
|
8
|
+
|
9
|
+
# A Ruby class to call the Linguist REST API. You might use this if you want to
|
10
|
+
# manage your Linguist apps from within a Ruby program, such as Capistrano.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# require 'linguist'
|
15
|
+
# linguist = Linguist::Client.new('me@example.com', 'mypass')
|
16
|
+
# linguist.create('myapp')
|
17
|
+
#
|
18
|
+
class Linguist::Client
|
19
|
+
|
20
|
+
def self.version
|
21
|
+
Linguist::VERSION
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.gem_version_string
|
25
|
+
"linguist-gem/#{version}"
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :host, :user, :auth_token
|
29
|
+
|
30
|
+
def self.auth(user, password, host='lingui.st')
|
31
|
+
client = new(user, password, host)
|
32
|
+
OkJson.decode client.post('/sessions', { :email => user, :password => password }, :accept => 'json').to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(user, auth_token, host='lingui.st')
|
36
|
+
@user = user
|
37
|
+
@auth_token = auth_token
|
38
|
+
@host = host
|
39
|
+
end
|
40
|
+
|
41
|
+
def project(title)
|
42
|
+
project = self.projects[title]
|
43
|
+
raise(CommandFailed, "=== You aren't associated for a project named '#{title}'") if project.nil?
|
44
|
+
project
|
45
|
+
end
|
46
|
+
|
47
|
+
def projects
|
48
|
+
return Linguist::Models::Projects.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get(uri, extra_headers={ }) # :nodoc:
|
52
|
+
process(:get, uri, extra_headers)
|
53
|
+
end
|
54
|
+
|
55
|
+
def post(uri, payload="", extra_headers={ }) # :nodoc:
|
56
|
+
process(:post, uri, extra_headers, payload)
|
57
|
+
end
|
58
|
+
|
59
|
+
def put(uri, payload, extra_headers={ }) # :nodoc:
|
60
|
+
process(:put, uri, extra_headers, payload)
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete(uri, extra_headers={ }) # :nodoc:
|
64
|
+
process(:delete, uri, extra_headers)
|
65
|
+
end
|
66
|
+
|
67
|
+
def process(method, uri, extra_headers={ }, payload=nil)
|
68
|
+
headers = linguist_headers.merge(extra_headers)
|
69
|
+
# payload = auth_params.merge(payload)
|
70
|
+
args = [method, payload, headers].compact
|
71
|
+
response = resource(uri).send(*args)
|
72
|
+
|
73
|
+
puts "RESPONSE #{response}"
|
74
|
+
|
75
|
+
# extract_warning(response)
|
76
|
+
response
|
77
|
+
end
|
78
|
+
|
79
|
+
def resource(uri)
|
80
|
+
|
81
|
+
RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
|
82
|
+
if uri =~ /^https?/
|
83
|
+
# RestClient::Resource.new(uri, user, auth_token)
|
84
|
+
RestClient::Resource.new(uri)
|
85
|
+
elsif host =~ /^https?/
|
86
|
+
# RestClient::Resource.new(host, user, auth_token)[uri]
|
87
|
+
RestClient::Resource.new(host)[uri]
|
88
|
+
else
|
89
|
+
# RestClient::Resource.new("https://api.#{host}", user, password)[uri]
|
90
|
+
# RestClient::Resource.new("http://localhost:3000/api/v1", user, auth_token)[uri]
|
91
|
+
RestClient::Resource.new("http://localhost:3000/api/v1")[uri]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_warning(response)
|
96
|
+
# return unless response
|
97
|
+
# if response.headers[:x_heroku_warning] && @warning_callback
|
98
|
+
# warning = response.headers[:x_heroku_warning]
|
99
|
+
# @displayed_warnings ||= { }
|
100
|
+
# unless @displayed_warnings[warning]
|
101
|
+
# @warning_callback.call(warning)
|
102
|
+
# @displayed_warnings[warning] = true
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
end
|
106
|
+
|
107
|
+
def linguist_headers # :nodoc:
|
108
|
+
{
|
109
|
+
'X-Linguist-API-Version' => '1',
|
110
|
+
'X-Linguist-User-Email' => user,
|
111
|
+
'X-Linguist-User-Auth-Token' => auth_token,
|
112
|
+
'User-Agent' => self.class.gem_version_string,
|
113
|
+
'X-Ruby-Version' => RUBY_VERSION,
|
114
|
+
'X-Ruby-Platform' => RUBY_PLATFORM,
|
115
|
+
'content_type' => 'json',
|
116
|
+
'accept' => 'json'
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'linguist/helpers'
|
2
|
+
require 'linguist/commands/base'
|
3
|
+
|
4
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
5
|
+
|
6
|
+
module Linguist
|
7
|
+
module Command
|
8
|
+
class InvalidCommand < RuntimeError; end
|
9
|
+
class CommandFailed < RuntimeError; end
|
10
|
+
|
11
|
+
extend Linguist::Helpers
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def run(command, args, retries=0)
|
16
|
+
begin
|
17
|
+
run_internal 'auth:reauthorize', args.dup if retries > 0
|
18
|
+
run_internal(command, args.dup)
|
19
|
+
rescue InvalidCommand
|
20
|
+
error "Unknown command. Run 'linguist help' for usage information."
|
21
|
+
# rescue RestClient::Unauthorized
|
22
|
+
# if retries < 3
|
23
|
+
# STDERR.puts "Authentication failure"
|
24
|
+
# run(command, args, retries+1)
|
25
|
+
# else
|
26
|
+
# error "Authentication failure"
|
27
|
+
# end
|
28
|
+
# rescue RestClient::ResourceNotFound => e
|
29
|
+
# error extract_not_found(e.http_body)
|
30
|
+
# rescue RestClient::RequestFailed => e
|
31
|
+
# error extract_error(e.http_body) unless e.http_code == 402
|
32
|
+
# retry if run_internal('account:confirm_billing', args.dup)
|
33
|
+
# rescue RestClient::RequestTimeout
|
34
|
+
# error "API request timed out. Please try again, or contact support@lingui.st if this issue persists."
|
35
|
+
rescue CommandFailed => e
|
36
|
+
error e.message
|
37
|
+
rescue Interrupt => e
|
38
|
+
error "\n[canceled]"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def run_internal(command, args, linguist=nil)
|
43
|
+
klass, method = parse(command)
|
44
|
+
runner = klass.new(args, linguist)
|
45
|
+
raise InvalidCommand unless runner.respond_to?(method)
|
46
|
+
runner.send(method)
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(command)
|
50
|
+
parts = command.split(':')
|
51
|
+
case parts.size
|
52
|
+
when 1
|
53
|
+
begin
|
54
|
+
return eval("Linguist::Command::#{command.capitalize}"), :index
|
55
|
+
rescue NameError, NoMethodError
|
56
|
+
return Linguist::Command::App, command.to_sym
|
57
|
+
end
|
58
|
+
else
|
59
|
+
begin
|
60
|
+
const = Linguist::Command
|
61
|
+
command = parts.pop
|
62
|
+
parts.each { |part| const = const.const_get(part.capitalize) }
|
63
|
+
return const, command.to_sym
|
64
|
+
rescue NameError
|
65
|
+
raise InvalidCommand
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# def extract_not_found(body)
|
71
|
+
# body =~ /^[\w\s]+ not found$/ ? body : "Resource not found"
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# def extract_error(body)
|
75
|
+
# msg = parse_error_xml(body) || parse_error_json(body) || parse_error_plain(body) || 'Internal server error'
|
76
|
+
# msg.split("\n").map { |line| ' ! ' + line }.join("\n")
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# def parse_error_xml(body)
|
80
|
+
# xml_errors = REXML::Document.new(body).elements.to_a("//errors/error")
|
81
|
+
# msg = xml_errors.map { |a| a.text }.join(" / ")
|
82
|
+
# return msg unless msg.empty?
|
83
|
+
# rescue Exception
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# def parse_error_json(body)
|
87
|
+
# json = OkJson.decode(body.to_s)
|
88
|
+
# json['error']
|
89
|
+
# rescue OkJson::ParserError
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# def parse_error_plain(body)
|
93
|
+
# return unless body.respond_to?(:headers) && body.headers[:content_type].include?("text/plain")
|
94
|
+
# body.to_s
|
95
|
+
# end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'launchy'
|
3
|
+
|
4
|
+
module Linguist::Command
|
5
|
+
class App < Base
|
6
|
+
def login
|
7
|
+
Linguist::Command.run_internal "auth:reauthorize", args.dup
|
8
|
+
end
|
9
|
+
|
10
|
+
def logout
|
11
|
+
Linguist::Command.run_internal "auth:delete_credentials", args.dup
|
12
|
+
display "Local credentials cleared."
|
13
|
+
end
|
14
|
+
|
15
|
+
def list
|
16
|
+
list = linguist.projects.all
|
17
|
+
if list.size > 0
|
18
|
+
display "Projects:\n" + list.keys.map { |name|
|
19
|
+
"- #{name}"
|
20
|
+
}.join("\n")
|
21
|
+
else
|
22
|
+
display "You have no projects."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def create
|
27
|
+
title = args.shift.downcase.strip rescue nil
|
28
|
+
title ||= extract_from_dir_name
|
29
|
+
linguist.projects.create title
|
30
|
+
display("Created #{title}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def rename
|
34
|
+
newtitle = args.shift.downcase.strip rescue ''
|
35
|
+
oldtitle = project.title
|
36
|
+
|
37
|
+
raise(CommandFailed, "Invalid name.") if newtitle == ''
|
38
|
+
|
39
|
+
project.update(:title => newtitle)
|
40
|
+
display("Renaming project from #{oldtitle} to #{newtitle}")
|
41
|
+
|
42
|
+
display app_urls(newtitle)
|
43
|
+
end
|
44
|
+
|
45
|
+
def info
|
46
|
+
display "=== #{project.title}"
|
47
|
+
display "Web URL: #{project.weburl}"
|
48
|
+
display "Owner: #{project.owner}"
|
49
|
+
display "Number of translation: #{project.translations_count}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def open
|
53
|
+
project = linguist.project
|
54
|
+
url = project.weburl
|
55
|
+
Launchy.open url
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy
|
59
|
+
display "=== #{project.title}"
|
60
|
+
display "Web URL: #{project.weburl}"
|
61
|
+
display "Owner: #{project.owner}"
|
62
|
+
|
63
|
+
if confirm_command(project_title)
|
64
|
+
redisplay "Destroying #{project_title} ... "
|
65
|
+
project.destroy
|
66
|
+
display "done"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require "linguist/client"
|
2
|
+
|
3
|
+
module Linguist::Command
|
4
|
+
class Auth < Base
|
5
|
+
attr_accessor :credentials
|
6
|
+
|
7
|
+
def client
|
8
|
+
@client ||= init_linguist
|
9
|
+
end
|
10
|
+
|
11
|
+
def init_linguist
|
12
|
+
client = Linguist::Client.new(user, auth_token, host)
|
13
|
+
# client.on_warning { |msg| self.display("\n#{msg}\n\n") }
|
14
|
+
client
|
15
|
+
end
|
16
|
+
|
17
|
+
def host
|
18
|
+
ENV['LINGUIST_HOST'] || 'lingui.st'
|
19
|
+
end
|
20
|
+
|
21
|
+
# just a stub; will raise if not authenticated
|
22
|
+
def check
|
23
|
+
client.list
|
24
|
+
end
|
25
|
+
|
26
|
+
def reauthorize
|
27
|
+
@credentials = ask_for_and_save_credentials
|
28
|
+
end
|
29
|
+
|
30
|
+
def user # :nodoc:
|
31
|
+
get_credentials
|
32
|
+
@credentials[0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def auth_token # :nodoc:
|
36
|
+
get_credentials
|
37
|
+
@credentials[1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def credentials_file
|
41
|
+
"#{home_directory}/.linguist/credentials"
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_credentials # :nodoc:
|
45
|
+
return if @credentials
|
46
|
+
unless @credentials = read_credentials
|
47
|
+
ask_for_and_save_credentials
|
48
|
+
end
|
49
|
+
@credentials
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_credentials
|
53
|
+
File.exists?(credentials_file) and File.read(credentials_file).split("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def echo_off
|
57
|
+
system "stty -echo"
|
58
|
+
end
|
59
|
+
|
60
|
+
def echo_on
|
61
|
+
system "stty echo"
|
62
|
+
end
|
63
|
+
|
64
|
+
def ask_for_credentials
|
65
|
+
# puts "Enter your Linguist credentials."
|
66
|
+
|
67
|
+
print "Email: "
|
68
|
+
user = "hjuskewycz@hemju.com"#ask
|
69
|
+
|
70
|
+
print "Password: "
|
71
|
+
password = "testtest"#running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
72
|
+
api_key = Linguist::Client.auth(user, password, host)['api_key']
|
73
|
+
|
74
|
+
[user, api_key]
|
75
|
+
end
|
76
|
+
|
77
|
+
def ask_for_password_on_windows
|
78
|
+
require "Win32API"
|
79
|
+
char = nil
|
80
|
+
password = ''
|
81
|
+
|
82
|
+
while char = Win32API.new("crtdll", "_getch", [], "L").Call do
|
83
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
84
|
+
if char == 127 || char == 8 # backspace and delete
|
85
|
+
password.slice!(-1, 1)
|
86
|
+
else
|
87
|
+
# windows might throw a -1 at us so make sure to handle RangeError
|
88
|
+
(password << char.chr) rescue RangeError
|
89
|
+
end
|
90
|
+
end
|
91
|
+
puts
|
92
|
+
return password
|
93
|
+
end
|
94
|
+
|
95
|
+
def ask_for_password
|
96
|
+
echo_off
|
97
|
+
password = ask
|
98
|
+
puts
|
99
|
+
echo_on
|
100
|
+
return password
|
101
|
+
end
|
102
|
+
|
103
|
+
def ask_for_and_save_credentials
|
104
|
+
begin
|
105
|
+
@credentials = ask_for_credentials
|
106
|
+
write_credentials
|
107
|
+
check
|
108
|
+
rescue ::RestClient::Unauthorized, ::RestClient::ResourceNotFound => e
|
109
|
+
puts "EXCEPTION #{e}"
|
110
|
+
delete_credentials
|
111
|
+
@client = nil
|
112
|
+
@credentials = nil
|
113
|
+
display "Authentication failed."
|
114
|
+
retry if retry_login?
|
115
|
+
exit 1
|
116
|
+
rescue Exception => e
|
117
|
+
delete_credentials
|
118
|
+
raise e
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def retry_login?
|
123
|
+
@login_attempts ||= 0
|
124
|
+
@login_attempts += 1
|
125
|
+
@login_attempts < 3
|
126
|
+
end
|
127
|
+
|
128
|
+
def write_credentials
|
129
|
+
FileUtils.mkdir_p(File.dirname(credentials_file))
|
130
|
+
f = File.open(credentials_file, 'w')
|
131
|
+
f.chmod(0600)
|
132
|
+
f.puts self.credentials
|
133
|
+
f.close
|
134
|
+
set_credentials_permissions
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_credentials_permissions
|
138
|
+
FileUtils.chmod 0700, File.dirname(credentials_file)
|
139
|
+
FileUtils.chmod 0600, credentials_file
|
140
|
+
end
|
141
|
+
|
142
|
+
def delete_credentials
|
143
|
+
# FileUtils.rm_f(credentials_file)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Linguist::Command
|
4
|
+
class Base
|
5
|
+
include Linguist::Helpers
|
6
|
+
|
7
|
+
attr_accessor :args
|
8
|
+
attr_reader :autodetected_app
|
9
|
+
|
10
|
+
def initialize(args, linguist=nil)
|
11
|
+
@args = args
|
12
|
+
@linguist = linguist
|
13
|
+
@autodetected_app = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def linguist
|
17
|
+
@linguist ||= Linguist::Command.run_internal('auth:client', args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_app(force=true)
|
21
|
+
app = extract_option('--app', false)
|
22
|
+
raise(CommandFailed, "You must specify an app name after --app") if app == false
|
23
|
+
unless app
|
24
|
+
app = extract_app_from_git || extract_from_dir_name ||
|
25
|
+
raise(CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") if force
|
26
|
+
@autodetected_app = true
|
27
|
+
end
|
28
|
+
app
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_from_dir_name
|
32
|
+
dir = Dir.pwd
|
33
|
+
File.basename(dir)
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_app_from_git
|
37
|
+
dir = Dir.pwd
|
38
|
+
return unless remotes = git_remotes(dir)
|
39
|
+
|
40
|
+
if remote = extract_option('--remote')
|
41
|
+
remotes[remote]
|
42
|
+
elsif remote = extract_app_from_git_config
|
43
|
+
remotes[remote]
|
44
|
+
else
|
45
|
+
apps = remotes.values.uniq
|
46
|
+
return apps.first if apps.size == 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_app_from_git_config
|
51
|
+
remote = git("config heroku.remote")
|
52
|
+
remote == "" ? nil : remote
|
53
|
+
end
|
54
|
+
|
55
|
+
def git_remotes(base_dir=Dir.pwd)
|
56
|
+
remotes = { }
|
57
|
+
original_dir = Dir.pwd
|
58
|
+
Dir.chdir(base_dir)
|
59
|
+
|
60
|
+
# TODO
|
61
|
+
# git("remote -v").split("\n").each do |remote|
|
62
|
+
# name, url, method = remote.split(/\s/)
|
63
|
+
# if url =~ /^git@#{heroku.host}:([\w\d-]+)\.git$/
|
64
|
+
# remotes[name] = $1
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
|
68
|
+
Dir.chdir(original_dir)
|
69
|
+
remotes
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_option(options, default=true)
|
73
|
+
values = options.is_a?(Array) ? options : [options]
|
74
|
+
return unless opt_index = args.select { |a| values.include? a }.first
|
75
|
+
opt_position = args.index(opt_index) + 1
|
76
|
+
if args.size > opt_position && opt_value = args[opt_position]
|
77
|
+
if opt_value.include?('--')
|
78
|
+
opt_value = nil
|
79
|
+
else
|
80
|
+
args.delete_at(opt_position)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
opt_value ||= default
|
84
|
+
args.delete(opt_index)
|
85
|
+
block_given? ? yield(opt_value) : opt_value
|
86
|
+
end
|
87
|
+
|
88
|
+
def git_url(name)
|
89
|
+
"git@#{heroku.host}:#{name}.git"
|
90
|
+
end
|
91
|
+
|
92
|
+
def app_urls(name)
|
93
|
+
# "#{web_url(name)} | #{git_url(name)}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def escape(value)
|
97
|
+
linguist.escape(value)
|
98
|
+
end
|
99
|
+
|
100
|
+
def project(title=nil)
|
101
|
+
title ||= project_title
|
102
|
+
@project ||= linguist.project(title)
|
103
|
+
end
|
104
|
+
|
105
|
+
def project_title
|
106
|
+
(args.first && !args.first =~ /^\-\-/) ? args.first : extract_app
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class BaseWithApp < Base
|
111
|
+
attr_accessor :app
|
112
|
+
|
113
|
+
def initialize(args, linguist=nil)
|
114
|
+
super(args, linguist)
|
115
|
+
@app ||= extract_app
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Linguist::Command
|
2
|
+
class Help < Base
|
3
|
+
class HelpGroup < Array
|
4
|
+
|
5
|
+
attr_reader :title
|
6
|
+
|
7
|
+
def initialize(title)
|
8
|
+
@title = title
|
9
|
+
end
|
10
|
+
|
11
|
+
def command(name, description)
|
12
|
+
self << [name, description]
|
13
|
+
end
|
14
|
+
|
15
|
+
def space
|
16
|
+
self << ['', '']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.groups
|
21
|
+
@groups ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.group(title, &block)
|
25
|
+
groups << begin
|
26
|
+
group = HelpGroup.new(title)
|
27
|
+
yield group
|
28
|
+
group
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.create_default_groups!
|
33
|
+
return if @defaults_created
|
34
|
+
@defaults_created = true
|
35
|
+
group 'General Commands' do |group|
|
36
|
+
group.command 'help', 'show this usage'
|
37
|
+
group.command 'version', 'show the gem version'
|
38
|
+
group.space
|
39
|
+
# group.command 'login', 'log in with your linguist credentials'
|
40
|
+
# group.command 'logout', 'clear local authentication credentials'
|
41
|
+
# group.space
|
42
|
+
group.command 'list', 'list your projects'
|
43
|
+
# group.command 'create [<name>]', 'create a new app'
|
44
|
+
group.command 'info', 'show app info, like web url and number of translations'
|
45
|
+
group.command 'open', 'open the app in a web browser'
|
46
|
+
group.command 'rename <newname>', 'rename the app'
|
47
|
+
group.command 'destroy', 'destroy the app permanently'
|
48
|
+
group.space
|
49
|
+
end
|
50
|
+
|
51
|
+
# group 'Plugins' do |group|
|
52
|
+
# group.command 'plugins', 'list installed plugins'
|
53
|
+
# group.command 'plugins:install <url>', 'install the plugin from the specified git url'
|
54
|
+
# group.command 'plugins:uninstall <url/name>', 'remove the specified plugin'
|
55
|
+
# end
|
56
|
+
end
|
57
|
+
|
58
|
+
def index
|
59
|
+
display usage
|
60
|
+
end
|
61
|
+
|
62
|
+
def version
|
63
|
+
display Linguist::Client.version
|
64
|
+
end
|
65
|
+
|
66
|
+
def usage
|
67
|
+
longest_command_length = self.class.groups.map do |group|
|
68
|
+
group.map { |g| g.first.length }
|
69
|
+
end.flatten.max
|
70
|
+
|
71
|
+
self.class.groups.inject(StringIO.new) do |output, group|
|
72
|
+
output.puts "=== %s" % group.title
|
73
|
+
output.puts
|
74
|
+
|
75
|
+
group.each do |command, description|
|
76
|
+
if command.empty?
|
77
|
+
output.puts
|
78
|
+
else
|
79
|
+
output.puts "%-*s # %s" % [longest_command_length, command, description]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
output.puts
|
84
|
+
output
|
85
|
+
end.string
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Linguist::Command::Help.create_default_groups!
|