lingohub 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +4 -0
- data/LICENSE +20 -0
- data/README.md +10 -0
- data/REST.md +0 -0
- data/ROADMAP.md +0 -0
- data/bin/lingohub +14 -0
- data/lib/lingohub.rb +16 -0
- data/lib/lingohub/client.rb +106 -0
- data/lib/lingohub/command.rb +98 -0
- data/lib/lingohub/commands/auth.rb +146 -0
- data/lib/lingohub/commands/base.rb +119 -0
- data/lib/lingohub/commands/collaborator.rb +44 -0
- data/lib/lingohub/commands/help.rb +101 -0
- data/lib/lingohub/commands/project.rb +66 -0
- data/lib/lingohub/commands/translation.rb +97 -0
- data/lib/lingohub/commands/version.rb +7 -0
- data/lib/lingohub/helpers.rb +121 -0
- data/lib/lingohub/models/collaborator.rb +25 -0
- data/lib/lingohub/models/project.rb +130 -0
- data/lib/lingohub/models/projects.rb +34 -0
- data/lib/lingohub/models/resource.rb +15 -0
- data/lib/lingohub/models/user.rb +3 -0
- data/lib/lingohub/rails3/railtie.rb +14 -0
- data/lib/lingohub/version.rb +4 -0
- data/lib/patches/rails3/i18n/i18n.rb +66 -0
- data/lib/vendor/okjson.rb +556 -0
- metadata +233 -0
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011-2012 lingohub GmbH
|
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
data/REST.md
ADDED
File without changes
|
data/ROADMAP.md
ADDED
File without changes
|
data/bin/lingohub
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 'lingohub'
|
7
|
+
require 'lingohub/command'
|
8
|
+
|
9
|
+
args = ARGV.dup
|
10
|
+
ARGV.clear
|
11
|
+
command = args.shift.strip rescue 'help'
|
12
|
+
|
13
|
+
Lingohub::Command.run(command, args)
|
14
|
+
|
data/lib/lingohub.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "lingohub/client"
|
2
|
+
require "lingohub/rails3/railtie" if defined?(Rails)
|
3
|
+
|
4
|
+
module Lingohub
|
5
|
+
class << self
|
6
|
+
attr_accessor :environments, :protocol, :host, :username, :project
|
7
|
+
|
8
|
+
def configure
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_value?(value)
|
13
|
+
value.start_with?(":")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'uri'
|
3
|
+
require 'time'
|
4
|
+
require 'lingohub/version'
|
5
|
+
require 'vendor/okjson'
|
6
|
+
require 'json'
|
7
|
+
require 'lingohub/models/projects'
|
8
|
+
|
9
|
+
# A Ruby class to call the lingohub REST API. You might use this if you want to
|
10
|
+
# manage your lingohub apps from within a Ruby program, such as Capistrano.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# require 'lingohub'
|
15
|
+
# lingohub = Lingohub::Client.new('me@example.com', 'mypass')
|
16
|
+
# lingohub.create('myapp')
|
17
|
+
#
|
18
|
+
class Lingohub::Client
|
19
|
+
|
20
|
+
def self.version
|
21
|
+
Lingohub::VERSION
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.gem_version_string
|
25
|
+
"lingohub-gem/#{version}"
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :host, :user, :password
|
29
|
+
|
30
|
+
def self.auth(options)
|
31
|
+
client = new(options)
|
32
|
+
OkJson.decode client.post('/sessions', {}, :accept => 'json').to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options)
|
36
|
+
@user = options[:username]
|
37
|
+
@password = options[:password]
|
38
|
+
@auth_token = options[:auth_token]
|
39
|
+
@host = options[:host] || 'lingohub.com'
|
40
|
+
end
|
41
|
+
|
42
|
+
def credentials
|
43
|
+
@auth_token.nil? ? {:username => @user, :password => @password} : {:username => @auth_token, :password => ""}
|
44
|
+
end
|
45
|
+
|
46
|
+
def project(title)
|
47
|
+
project = self.projects[title]
|
48
|
+
raise(Lingohub::Command::CommandFailed, "=== You aren't associated for a project named '#{title}'") if project.nil?
|
49
|
+
project
|
50
|
+
end
|
51
|
+
|
52
|
+
def projects
|
53
|
+
return Lingohub::Models::Projects.new(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def get(uri, extra_headers={ }) # :nodoc:
|
57
|
+
process(:get, uri, extra_headers)
|
58
|
+
end
|
59
|
+
|
60
|
+
def post(uri, payload="", extra_headers={ }) # :nodoc:
|
61
|
+
process(:post, uri, extra_headers, payload)
|
62
|
+
end
|
63
|
+
|
64
|
+
def put(uri, payload, extra_headers={ }) # :nodoc:
|
65
|
+
process(:put, uri, extra_headers, payload)
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete(uri, extra_headers={ }) # :nodoc:
|
69
|
+
process(:delete, uri, extra_headers)
|
70
|
+
end
|
71
|
+
|
72
|
+
def process(method, uri, extra_headers={ }, payload=nil)
|
73
|
+
headers = lingohub_headers.merge(extra_headers)
|
74
|
+
# payload = auth_params.merge(payload)
|
75
|
+
args = [method, payload, headers].compact
|
76
|
+
response = resource(uri, credentials).send(*args)
|
77
|
+
|
78
|
+
response
|
79
|
+
end
|
80
|
+
|
81
|
+
def resource(uri, credentials)
|
82
|
+
RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
|
83
|
+
if uri =~ /^https?/
|
84
|
+
RestClient::Resource.new(uri, :user => credentials[:username], :password => credentials[:password])
|
85
|
+
else
|
86
|
+
host_uri = host =~ /^https?/ ? "#{host}/#{api_uri_part}" : "https://#{host}/#{api_uri_part}"
|
87
|
+
RestClient::Resource.new(host_uri, :user => credentials[:username], :password => credentials[:password])[uri]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def api_uri_part
|
92
|
+
"api/#{Lingohub::API_VERSION}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def lingohub_headers # :nodoc:
|
96
|
+
{
|
97
|
+
'X-lingohub-API-Version' => '1',
|
98
|
+
'User-Agent' => self.class.gem_version_string,
|
99
|
+
'X-Ruby-Version' => RUBY_VERSION,
|
100
|
+
'X-Ruby-Platform' => RUBY_PLATFORM,
|
101
|
+
'content_type' => 'json',
|
102
|
+
'accept' => 'json'
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'lingohub/helpers'
|
2
|
+
require 'lingohub/commands/base'
|
3
|
+
|
4
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
5
|
+
|
6
|
+
module Lingohub
|
7
|
+
module Command
|
8
|
+
class InvalidCommand < RuntimeError; end
|
9
|
+
class CommandFailed < RuntimeError; end
|
10
|
+
|
11
|
+
extend Lingohub::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 'lingohub 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 team@lingohub.com 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, lingohub=nil)
|
43
|
+
klass, method = parse(command)
|
44
|
+
runner = klass.new(args, lingohub)
|
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("Lingohub::Command::#{command.capitalize}"), :index
|
55
|
+
rescue NameError, NoMethodError
|
56
|
+
return Lingohub::Command::Project, command.to_sym
|
57
|
+
end
|
58
|
+
else
|
59
|
+
begin
|
60
|
+
const = Lingohub::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,146 @@
|
|
1
|
+
require "lingohub/client"
|
2
|
+
|
3
|
+
module Lingohub::Command
|
4
|
+
class Auth < Base
|
5
|
+
attr_accessor :credentials
|
6
|
+
|
7
|
+
def client
|
8
|
+
@client ||= init_lingohub
|
9
|
+
end
|
10
|
+
|
11
|
+
def init_lingohub
|
12
|
+
client = Lingohub::Client.new(:username => user, :auth_token => auth_token, :host => host)
|
13
|
+
# client.on_warning { |msg| self.display("\n#{msg}\n\n") }
|
14
|
+
client
|
15
|
+
end
|
16
|
+
|
17
|
+
def host
|
18
|
+
ENV['lingohub_HOST'] || 'lingohub.com'
|
19
|
+
end
|
20
|
+
|
21
|
+
# just a stub; will raise if not authenticated
|
22
|
+
def check
|
23
|
+
client.projects.all
|
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}/.lingohub/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 Lingohub credentials."
|
66
|
+
|
67
|
+
print "Email: "
|
68
|
+
user = ask
|
69
|
+
|
70
|
+
print "Password: "
|
71
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
72
|
+
api_key = Lingohub::Client.auth(:username => user, :password => password, :host => 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,119 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Lingohub::Command
|
4
|
+
class Base
|
5
|
+
include Lingohub::Helpers
|
6
|
+
|
7
|
+
attr_accessor :args
|
8
|
+
attr_reader :autodetected_app
|
9
|
+
|
10
|
+
def initialize(args, lingohub=nil)
|
11
|
+
@args = args
|
12
|
+
@lingohub = lingohub
|
13
|
+
@autodetected_project_name = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def lingohub
|
17
|
+
@lingohub ||= Lingohub::Command.run_internal('auth:client', args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def project_title(force=true)
|
21
|
+
project_title = extract_project_title_from_args
|
22
|
+
unless project_title
|
23
|
+
project_title = extract_project_title_from_git || extract_project_title_from_dir_name ||
|
24
|
+
raise(CommandFailed, "No project specified.\nRun this command from project folder or set it adding --project <title>") if force
|
25
|
+
@autodetected_project_name = true
|
26
|
+
end
|
27
|
+
project_title
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_project_title_from_args
|
31
|
+
project_title = extract_option('--project', false)
|
32
|
+
raise(CommandFailed, "You must specify a project title after --project") if project_title == false
|
33
|
+
project_title
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_project_title_from_dir_name
|
37
|
+
dir = Dir.pwd
|
38
|
+
File.basename(dir)
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_project_title_from_git
|
42
|
+
dir = Dir.pwd
|
43
|
+
return unless remotes = git_remotes(dir)
|
44
|
+
|
45
|
+
if remote = extract_option('--remote')
|
46
|
+
remotes[remote]
|
47
|
+
elsif remote = extract_app_from_git_config
|
48
|
+
remotes[remote]
|
49
|
+
else
|
50
|
+
apps = remotes.values.uniq
|
51
|
+
return apps.first if apps.size == 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_app_from_git_config
|
56
|
+
remote = git("config heroku.remote")
|
57
|
+
remote == "" ? nil : remote
|
58
|
+
end
|
59
|
+
|
60
|
+
def git_remotes(base_dir=Dir.pwd)
|
61
|
+
remotes = { }
|
62
|
+
original_dir = Dir.pwd
|
63
|
+
Dir.chdir(base_dir)
|
64
|
+
|
65
|
+
# TODO
|
66
|
+
# git("remote -v").split("\n").each do |remote|
|
67
|
+
# name, url, method = remote.split(/\s/)
|
68
|
+
# if url =~ /^git@#{heroku.host}:([\w\d-]+)\.git$/
|
69
|
+
# remotes[name] = $1
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
|
73
|
+
Dir.chdir(original_dir)
|
74
|
+
remotes
|
75
|
+
end
|
76
|
+
|
77
|
+
def extract_option(options, default=true)
|
78
|
+
values = options.is_a?(Array) ? options : [options]
|
79
|
+
return unless opt_index = args.select { |a| values.include? a }.first
|
80
|
+
opt_position = args.index(opt_index) + 1
|
81
|
+
if args.size > opt_position && opt_value = args[opt_position]
|
82
|
+
if opt_value.include?('--')
|
83
|
+
opt_value = nil
|
84
|
+
else
|
85
|
+
args.delete_at(opt_position)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
opt_value ||= default
|
89
|
+
args.delete(opt_index)
|
90
|
+
block_given? ? yield(opt_value) : opt_value
|
91
|
+
end
|
92
|
+
|
93
|
+
def git_url(name)
|
94
|
+
"git@#{heroku.host}:#{name}.git"
|
95
|
+
end
|
96
|
+
|
97
|
+
def app_urls(name)
|
98
|
+
# "#{web_url(name)} | #{git_url(name)}"
|
99
|
+
end
|
100
|
+
|
101
|
+
def escape(value)
|
102
|
+
lingohub.escape(value)
|
103
|
+
end
|
104
|
+
|
105
|
+
def project(title=nil)
|
106
|
+
title ||= project_title
|
107
|
+
@project ||= lingohub.project(title)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class BaseWithApp < Base
|
112
|
+
attr_accessor :app
|
113
|
+
|
114
|
+
def initialize(args, lingohub=nil)
|
115
|
+
super(args, lingohub)
|
116
|
+
@app ||= extract_app
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|