lingohub 0.0.4
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/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
|