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
@@ -0,0 +1,44 @@
|
|
1
|
+
module Lingohub::Command
|
2
|
+
class Collaborator < Base
|
3
|
+
def list
|
4
|
+
list = project.collaborators
|
5
|
+
if list.size > 0
|
6
|
+
display "Collaborators:\n"
|
7
|
+
list.each { |c| display("- #{c.display_name} | #{c.email} | #{c.permissions}") }
|
8
|
+
else
|
9
|
+
display "No collaborators found"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def invite
|
14
|
+
email = extract_email_from_args
|
15
|
+
project.invite_collaborator(email)
|
16
|
+
|
17
|
+
display("Invitation sent to #{email}")
|
18
|
+
rescue RestClient::BadRequest
|
19
|
+
display("Error sending invitation to '#{email}'")
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove
|
23
|
+
email = extract_email_from_args
|
24
|
+
collaborator = project.collaborators.find { |c| c.email == email }
|
25
|
+
|
26
|
+
if collaborator.nil?
|
27
|
+
display("Collaborator with email '#{email}' not found")
|
28
|
+
else
|
29
|
+
collaborator.destroy
|
30
|
+
display("Collaborator with email #{email} was removed")
|
31
|
+
end
|
32
|
+
rescue RestClient::BadRequest
|
33
|
+
display("Error removing collaborator with email '#{email}'")
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def extract_email_from_args
|
39
|
+
email = args.shift
|
40
|
+
raise(CommandFailed, "You must specify a invitee email after --email") if email == false
|
41
|
+
email
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Lingohub::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', "let's you (re)login"
|
40
|
+
group.command 'logout', 'logs you out by clearing your current credentials'
|
41
|
+
end
|
42
|
+
|
43
|
+
group 'Project Commands' do |group|
|
44
|
+
group.command 'project:list', 'list your projects'
|
45
|
+
group.command 'project:create <name>', 'create a new project'
|
46
|
+
group.command 'project:info --project <name>', 'show project info, like web url and number of translations'
|
47
|
+
group.command 'project:open --project <name>', 'open the project in a web browser'
|
48
|
+
group.command 'project:rename <oldname> <newname>', 'rename the project'
|
49
|
+
group.command 'project:destroy --project <name>', 'destroy the project permanently'
|
50
|
+
group.space
|
51
|
+
end
|
52
|
+
|
53
|
+
group 'Collaborator Commands' do |group|
|
54
|
+
group.command 'collaborator:list --project <name>', 'list project collaborators'
|
55
|
+
group.command 'collaborator:invite <email> --project <name>', 'invite the collaborator'
|
56
|
+
group.command 'collaborator:remove <email> --project <name>', 'remove the collaborator'
|
57
|
+
group.space
|
58
|
+
end
|
59
|
+
|
60
|
+
group 'Translation Commands' do |group|
|
61
|
+
group.command 'translation:down --all --directory <path> --project <name>', 'download all resource files'
|
62
|
+
group.command 'translation:down <file1> <file2> ... --directory <path> --project <name>', 'download specific resource files'
|
63
|
+
group.command 'translation:down <file> --query <query> --directory <path> --locale <iso2_slug> --project <name>', 'search for translations and download them to file'
|
64
|
+
group.command 'translation:up <file1> <file2> ... --locale <iso2_slug> --project <name>', 'upload specific resource files'
|
65
|
+
group.space
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def index
|
70
|
+
display usage
|
71
|
+
end
|
72
|
+
|
73
|
+
def version
|
74
|
+
display Lingohub::Client.version
|
75
|
+
end
|
76
|
+
|
77
|
+
def usage
|
78
|
+
longest_command_length = self.class.groups.map do |group|
|
79
|
+
group.map { |g| g.first.length }
|
80
|
+
end.flatten.max
|
81
|
+
|
82
|
+
self.class.groups.inject(StringIO.new) do |output, group|
|
83
|
+
output.puts "=== %s" % group.title
|
84
|
+
output.puts
|
85
|
+
|
86
|
+
group.each do |command, description|
|
87
|
+
if command.empty?
|
88
|
+
output.puts
|
89
|
+
else
|
90
|
+
output.puts "%-*s # %s" % [longest_command_length, command, description]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
output.puts
|
95
|
+
output
|
96
|
+
end.string
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
Lingohub::Command::Help.create_default_groups!
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'launchy'
|
3
|
+
|
4
|
+
module Lingohub::Command
|
5
|
+
class Project < Base
|
6
|
+
def login
|
7
|
+
Lingohub::Command.run_internal "auth:reauthorize", args.dup
|
8
|
+
end
|
9
|
+
|
10
|
+
def logout
|
11
|
+
Lingohub::Command.run_internal "auth:delete_credentials", args.dup
|
12
|
+
display "Local credentials cleared."
|
13
|
+
end
|
14
|
+
|
15
|
+
def list
|
16
|
+
list = lingohub.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.strip rescue nil
|
28
|
+
title ||= extract_from_dir_name
|
29
|
+
lingohub.projects.create title
|
30
|
+
display("Created #{title}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def rename
|
34
|
+
oldtitle = args[0].strip rescue raise(CommandFailed, "Invalid old project name")
|
35
|
+
newtitle = args[1].strip rescue raise(CommandFailed, "Invalid new project name")
|
36
|
+
|
37
|
+
project(oldtitle).update(:title => newtitle)
|
38
|
+
display("Project renamed from #{oldtitle} to #{newtitle}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def info
|
42
|
+
display "=== #{project.title}"
|
43
|
+
display "Web URL: #{project.weburl}"
|
44
|
+
display "Owner: #{project.owner}"
|
45
|
+
display "Number of translation: #{project.translations_count}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def open
|
49
|
+
Launchy.open project.weburl
|
50
|
+
end
|
51
|
+
|
52
|
+
def destroy
|
53
|
+
display "=== #{project.title}"
|
54
|
+
display "Web URL: #{project.weburl}"
|
55
|
+
display "Owner: #{project.owner}"
|
56
|
+
|
57
|
+
if confirm_command(project.title)
|
58
|
+
redisplay "Destroying #{project.title} ... "
|
59
|
+
project.destroy
|
60
|
+
display "done"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Lingohub::Command
|
2
|
+
class Translation < Base
|
3
|
+
def down
|
4
|
+
project #project validation
|
5
|
+
|
6
|
+
directory = File.join(Dir.pwd, extract_directory_from_args || "")
|
7
|
+
raise(CommandFailed, "Error downloading translations. Path #{directory} does not exist") unless File.directory?(directory)
|
8
|
+
|
9
|
+
if extract_query_from_args
|
10
|
+
pull_search_results(directory)
|
11
|
+
else
|
12
|
+
pull_resources(directory)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def up
|
17
|
+
project #project validation
|
18
|
+
|
19
|
+
push_resources(args)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def rails_environment?
|
25
|
+
true #TODO
|
26
|
+
end
|
27
|
+
|
28
|
+
def rails_locale_dir
|
29
|
+
Dir.pwd + "/conf/locales"
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_directory_from_args
|
33
|
+
return @directory if defined? @directory
|
34
|
+
@directory = extract_option('--directory', false)
|
35
|
+
raise(CommandFailed, "You must specify a directory after --directory") if @directory == false
|
36
|
+
@directory
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_locale_from_args
|
40
|
+
return @locale if defined? @locale
|
41
|
+
@locale = extract_option('--locale', false)
|
42
|
+
raise(CommandFailed, "You must specify a locale after --locale") if @locale == false
|
43
|
+
@locale
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_all_from_args
|
47
|
+
return @all if defined? @all
|
48
|
+
@all = extract_option('--all', true)
|
49
|
+
raise(CommandFailed, "You have not specify anything after --all") unless @all == true or @all.nil?
|
50
|
+
@all
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_query_from_args
|
54
|
+
return @query if defined? @query
|
55
|
+
@query = extract_option('--query', false)
|
56
|
+
raise(CommandFailed, "You have not specify anything after --query") if @query == false
|
57
|
+
@query
|
58
|
+
end
|
59
|
+
|
60
|
+
def pull_search_results(directory)
|
61
|
+
query = extract_query_from_args
|
62
|
+
locale = extract_locale_from_args
|
63
|
+
filename = args.first
|
64
|
+
|
65
|
+
begin
|
66
|
+
project.pull_search_results(directory, filename, query, locale)
|
67
|
+
display("Search results for '#{query}' saved to #{filename}")
|
68
|
+
rescue
|
69
|
+
display "Error saving search results for '#{query}'. Response: #{$!.response || $!.message}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def pull_resources(directory)
|
74
|
+
files_source = extract_all_from_args ? project.resources.keys : args
|
75
|
+
files_source.each do |file_name|
|
76
|
+
begin
|
77
|
+
project.pull_resource(directory, file_name)
|
78
|
+
display("#{file_name} downloaded")
|
79
|
+
rescue
|
80
|
+
display "Error downloading #{file_name}. Response: #{$!.response || $!.message}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def push_resources(resources)
|
86
|
+
resources.each do |file_name|
|
87
|
+
begin
|
88
|
+
path = File.expand_path(file_name, Dir.pwd)
|
89
|
+
project.push_resource(path, extract_locale_from_args)
|
90
|
+
display("#{file_name} uploaded")
|
91
|
+
rescue
|
92
|
+
display "Error uploading #{file_name}. Response: #{$!.response || $!.message}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Lingohub
|
2
|
+
module Helpers
|
3
|
+
def home_directory
|
4
|
+
running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
|
5
|
+
end
|
6
|
+
|
7
|
+
def running_on_windows?
|
8
|
+
RUBY_PLATFORM =~ /mswin32|mingw32/
|
9
|
+
end
|
10
|
+
|
11
|
+
def running_on_a_mac?
|
12
|
+
RUBY_PLATFORM =~ /-darwin\d/
|
13
|
+
end
|
14
|
+
|
15
|
+
def display(msg, newline=true)
|
16
|
+
if newline
|
17
|
+
puts(msg)
|
18
|
+
else
|
19
|
+
print(msg)
|
20
|
+
STDOUT.flush
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def redisplay(line, line_break = false)
|
25
|
+
display("\r\e[0K#{line}", line_break)
|
26
|
+
end
|
27
|
+
|
28
|
+
def deprecate(version)
|
29
|
+
display "!!! DEPRECATION WARNING: This command will be removed in version #{version}"
|
30
|
+
display ""
|
31
|
+
end
|
32
|
+
|
33
|
+
def error(msg)
|
34
|
+
STDERR.puts(msg)
|
35
|
+
exit 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def confirm(message="Are you sure you wish to continue? (y/n)?")
|
39
|
+
display("#{message} ", false)
|
40
|
+
ask == 'y'
|
41
|
+
end
|
42
|
+
|
43
|
+
def confirm_command(app)
|
44
|
+
# if extract_option('--force')
|
45
|
+
# display("Warning: The --force switch is deprecated, and will be removed in a future release. Use --confirm #{app} instead.")
|
46
|
+
# return true
|
47
|
+
# end
|
48
|
+
|
49
|
+
raise(Lingohub::Command::CommandFailed, "No app specified.\nRun this command from app folder or set it adding --app <app name>") unless app
|
50
|
+
|
51
|
+
confirmed_app = extract_option('--confirm', false)
|
52
|
+
if confirmed_app
|
53
|
+
unless confirmed_app == app
|
54
|
+
raise(Lingohub::Command::CommandFailed, "Confirmed app #{confirmed_app} did not match the selected app #{app}.")
|
55
|
+
end
|
56
|
+
return true
|
57
|
+
else
|
58
|
+
display ""
|
59
|
+
display " ! WARNING: Potentially Destructive Action"
|
60
|
+
display " ! This command will affect the app: #{app}"
|
61
|
+
display " ! To proceed, type \"#{app}\" or re-run this command with --confirm #{app}"
|
62
|
+
display ""
|
63
|
+
display "> ", false
|
64
|
+
if ask != app
|
65
|
+
display " ! Input did not match #{app}. Aborted."
|
66
|
+
false
|
67
|
+
else
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def format_date(date)
|
74
|
+
date = Time.parse(date) if date.is_a?(String)
|
75
|
+
date.strftime("%Y-%m-%d %H:%M %Z")
|
76
|
+
end
|
77
|
+
|
78
|
+
def ask
|
79
|
+
gets.strip
|
80
|
+
end
|
81
|
+
|
82
|
+
def shell(cmd)
|
83
|
+
FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
|
84
|
+
end
|
85
|
+
|
86
|
+
def run_command(command, args=[])
|
87
|
+
Lingohub::Command.run_internal(command, args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def retry_on_exception(*exceptions)
|
91
|
+
retry_count = 0
|
92
|
+
begin
|
93
|
+
yield
|
94
|
+
rescue *exceptions => ex
|
95
|
+
raise ex if retry_count >= 3
|
96
|
+
sleep 3
|
97
|
+
retry_count += 1
|
98
|
+
retry
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def has_git?
|
103
|
+
%x{ git --version }
|
104
|
+
$?.success?
|
105
|
+
end
|
106
|
+
|
107
|
+
def git(args)
|
108
|
+
return "" unless has_git?
|
109
|
+
flattened_args = [args].flatten.compact.join(" ")
|
110
|
+
%x{ git #{flattened_args} 2>&1 }.strip
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
unless String.method_defined?(:shellescape)
|
116
|
+
class String
|
117
|
+
def shellescape
|
118
|
+
empty? ? "''" : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\\\\1').gsub(/\n/, "'\n'")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Lingohub
|
2
|
+
module Models
|
3
|
+
class Collaborator
|
4
|
+
attr_accessor :email, :display_name, :roles
|
5
|
+
|
6
|
+
ROLES_NAMES = { "project_admin" => "Project admin", "developer" => "Developer" }
|
7
|
+
|
8
|
+
def initialize(client, link)
|
9
|
+
@client = client
|
10
|
+
@link = link
|
11
|
+
end
|
12
|
+
|
13
|
+
def destroy
|
14
|
+
@client.delete @link
|
15
|
+
end
|
16
|
+
|
17
|
+
def permissions
|
18
|
+
return "None" if self.roles.nil? or self.roles.empty?
|
19
|
+
|
20
|
+
self.roles.find_all { |role| ROLES_NAMES.has_key?(role) }.join(", ")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|