rosette-client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ca6f02478fbb347742958dd575df86db72b742c6
4
+ data.tar.gz: c43753c2cedbfa451b2c69215df10c82a49d8979
5
+ SHA512:
6
+ metadata.gz: 28b9c44f0d32376b59a3f98e653e59050be2339293de23c8bb6315f4f90b5dac263a3e6e32aedc915a1c916446dc49bd64fc7ac7f5c21560cb052c4e19bf5012
7
+ data.tar.gz: 5a85c962749d9ff7ba04082641ecac2d055822357491b0fab6e904dac377ee0eaee840d13fb09ecbea8b610e212ec3afd4c824f6ce1d3e64c24b6750f2a99829
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pry-nav'
7
+ gem 'rake'
8
+ gem 'threaded'
9
+ end
10
+
11
+ group :test do
12
+ gem 'rspec'
13
+ gem 'tmp-repo'
14
+ end
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ == 1.0.0
2
+
3
+ * Birthday!
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ rosette-core
2
+ ========
3
+
4
+ [![Build Status](https://travis-ci.org/rosette-proj/rosette-client.svg?branch=master)](https://travis-ci.org/rosette-proj/rosette-client.svg?branch=master)
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
4
+
5
+ require 'bundler'
6
+ require 'rspec/core/rake_task'
7
+ require 'rubygems/package_task'
8
+
9
+ require './lib/rosette/client'
10
+
11
+ Bundler::GemHelper.install_tasks
12
+
13
+ task :default => :spec
14
+
15
+ desc 'Run specs'
16
+ RSpec::Core::RakeTask.new do |t|
17
+ t.pattern = './spec/**/*_spec.rb'
18
+ end
data/bin/git-rosette ADDED
@@ -0,0 +1,17 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rosette/client'
4
+
5
+ config_file = File.join(Dir.home, '.rosette/config.yml')
6
+
7
+ api = Rosette::Client::Api.new(
8
+ if File.exist?(config_file)
9
+ YAML.load_file(config_file)
10
+ else
11
+ {}
12
+ end
13
+ )
14
+
15
+ terminal = Rosette::Client::Terminal.new
16
+ repo = Rosette::Client::Repo.new(Dir.getwd)
17
+ Rosette::Client::Cli.new(terminal, api, repo).start(ARGV)
@@ -0,0 +1,128 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'cgi'
4
+ require 'net/http'
5
+ require 'json'
6
+
7
+ module Rosette
8
+ module Client
9
+
10
+ class ApiError < StandardError; end
11
+
12
+ class Api
13
+ DEFAULT_HOST = 'localhost'
14
+ DEFAULT_PORT = 8080
15
+ DEFAULT_VERSION = 'v1'
16
+
17
+ attr_reader :host, :port, :version
18
+
19
+ def initialize(options = {})
20
+ @host = options.fetch(:host, DEFAULT_HOST)
21
+ @port = options.fetch(:port, DEFAULT_PORT)
22
+ @version = options.fetch(:version, DEFAULT_VERSION)
23
+ end
24
+
25
+ def diff(params)
26
+ wrap(make_request(:get, 'git/diff.json', params))
27
+ end
28
+
29
+ def show(params)
30
+ wrap(make_request(:get, 'git/show.json', params))
31
+ end
32
+
33
+ def status(params)
34
+ wrap(make_request(:get, 'git/status.json', params))
35
+ end
36
+
37
+ def commit(params)
38
+ wrap(make_request(:get, 'git/commit.json', params))
39
+ end
40
+
41
+ def snapshot(params)
42
+ wrap(make_request(:get, 'git/snapshot.json', params))
43
+ end
44
+
45
+ def repo_snapshot(params)
46
+ wrap(make_request(:get, 'git/repo_snapshot.json', params))
47
+ end
48
+
49
+ def add_or_update_translation(params)
50
+ wrap(make_request(:post, 'translations/add_or_update.json', params))
51
+ end
52
+
53
+ def export(params)
54
+ wrap(make_request(:get, 'translations/export.json', params))
55
+ end
56
+
57
+ private
58
+
59
+ def wrap(api_response)
60
+ Response.from_api_response(api_response)
61
+ end
62
+
63
+ def base_url
64
+ "http://#{host}:#{port}/#{version}"
65
+ end
66
+
67
+ def make_request(verb, path, params)
68
+ parse_response(
69
+ case verb
70
+ when :post
71
+ url = make_post_url(path, params)
72
+ post(url, params)
73
+ when :get
74
+ url = make_get_url(path, params)
75
+ get(url)
76
+ else
77
+ raise ArgumentError, "unsupported HTTP verb #{verb}."
78
+ end
79
+ )
80
+ rescue => e
81
+ raise ApiError, e.message
82
+ end
83
+
84
+ def parse_response(response)
85
+ JSON.parse(response)
86
+ end
87
+
88
+ def make_param_string(params)
89
+ (params.map do |key, val|
90
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"
91
+ end).join("&")
92
+ end
93
+
94
+ def make_get_url(path, params)
95
+ File.join(base_url, path, "?#{make_param_string(params)}")
96
+ end
97
+
98
+ def make_post_url(path, params)
99
+ File.join(base_url, path)
100
+ end
101
+
102
+ def post(url, params)
103
+ uri = parse_uri(url)
104
+ http = Net::HTTP.new(uri.host, uri.port)
105
+ request = Net::HTTP::Post.new(uri.path)
106
+ request.body = make_param_string(params)
107
+ resp = http.request(request)
108
+ resp.body
109
+ end
110
+
111
+ def get(url)
112
+ resp = Net::HTTP.get_response(parse_uri(url))
113
+ resp.body
114
+ end
115
+
116
+ # hack to handle bug in URI.parse, which doesn't allow subdomains to contain underscores
117
+ def parse_uri(url = nil)
118
+ URI.parse(url)
119
+ rescue URI::InvalidURIError
120
+ host = url.match(".+\:\/\/([^\/]+)")[1]
121
+ uri = URI.parse(url.sub(host, 'dummy-host'))
122
+ uri.instance_variable_set('@host', host)
123
+ uri
124
+ end
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'yaml'
4
+
5
+ module Rosette
6
+ module Client
7
+
8
+ class Cli
9
+ attr_reader :api, :terminal, :repo
10
+
11
+ def initialize(terminal, api, repo)
12
+ @api = api
13
+ @terminal = terminal
14
+ @repo = repo
15
+ end
16
+
17
+ def start(argv)
18
+ if command_const = find_command_const(argv.first)
19
+ command_const.new(api, terminal, repo, argv[1..-1]).execute
20
+ else
21
+ terminal.say("Command '#{argv.first}' not recognized.")
22
+ end
23
+ rescue Rosette::Client::ApiError => e
24
+ terminal.say("An api error occurred: #{e.message}")
25
+ end
26
+
27
+ private
28
+
29
+ def find_command_const(name)
30
+ const = const_name(name)
31
+ if Rosette::Client::Commands.const_defined?(const)
32
+ Rosette::Client::Commands.const_get(const)
33
+ end
34
+ end
35
+
36
+ def const_name(name)
37
+ name.downcase.gsub(/(\A\w|_\w)/) { $1.sub('_', '').upcase } + 'Command'
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs commit <ref>
4
+
5
+ module Rosette
6
+ module Client
7
+ module Commands
8
+
9
+ CommitCommandArgs = Struct.new(:ref) do
10
+ def self.from_argv(argv, repo)
11
+ new(repo.rev_parse(argv[0] || repo.get_head))
12
+ end
13
+ end
14
+
15
+ class CommitCommand < Command
16
+ attr_reader :args
17
+
18
+ def initialize(api, terminal, repo, argv)
19
+ super(api, terminal, repo)
20
+ @args = CommitCommandArgs.from_argv(argv, repo)
21
+ end
22
+
23
+ def execute
24
+ terminal.say("Committing phrases for '#{args.ref}'...")
25
+
26
+ response = api.commit(
27
+ repo_name: derive_repo_name,
28
+ ref: args.ref
29
+ )
30
+
31
+ handle_error(response) do
32
+ terminal.say("Added: #{response.added || 0}")
33
+ terminal.say("Removed: #{response.removed || 0}")
34
+ terminal.say("Modified: #{response.modified || 0}")
35
+ terminal.say('done.')
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,110 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs diff <ref1> [<ref2> | <path>] [-- <path1> <path2> ...]
4
+
5
+ # git rs diff
6
+ # git rs diff master .
7
+ # git rs diff master branch
8
+ # git rs diff master branch .
9
+ # git rs diff master branch -- path/to/files
10
+
11
+ module Rosette
12
+ module Client
13
+ module Commands
14
+
15
+ DiffCommandArgs = Struct.new(:diff_point_ref, :head_ref, :paths) do
16
+ # should probably use a parser for these args, since they're pretty complicated
17
+ # currently no support for options like --name-only, etc
18
+ def self.from_argv(argv, repo)
19
+ diff_point_ref = repo.rev_parse(argv[0] || 'refs/heads/master')
20
+ paths = []
21
+
22
+ if argv[1]
23
+ if File.exist?(argv[1])
24
+ paths << argv[1]
25
+ else
26
+ head_ref = argv[1] unless argv[1] == '--'
27
+ end
28
+ end
29
+
30
+ head_ref ||= repo.rev_parse(repo.get_head)
31
+
32
+ (2..argv.size).each do |i|
33
+ next if argv[i] == '--'
34
+ if argv[i] && File.exist?(argv[i])
35
+ paths << argv[i]
36
+ end
37
+ end
38
+
39
+ new(diff_point_ref, head_ref, paths)
40
+ end
41
+ end
42
+
43
+ class DiffCommand < Command
44
+ attr_reader :args
45
+
46
+ def initialize(api, terminal, repo, argv)
47
+ super(api, terminal, repo)
48
+ @args = DiffCommandArgs.from_argv(argv, repo)
49
+ end
50
+
51
+ def execute
52
+ response = api.diff(
53
+ repo_name: derive_repo_name,
54
+ head_ref: args.head_ref,
55
+ diff_point_ref: args.diff_point_ref,
56
+ paths: args.paths.join(' ')
57
+ )
58
+
59
+ handle_error(response) do |response|
60
+ print_diff(response.attributes)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def add_str_for(change)
67
+ str = "#{change['key']}"
68
+ meta_key = change['meta_key']
69
+
70
+ unless meta_key.empty?
71
+ str += " (#{meta_key})"
72
+ end
73
+ end
74
+
75
+ def remove_str_for(change)
76
+ str = "#{change.fetch('old_key', change['key'])}"
77
+ meta_key = change['meta_key']
78
+
79
+ unless meta_key.empty?
80
+ str += " (#{meta_key})"
81
+ end
82
+ end
83
+
84
+ def print_diff(diff)
85
+ group_diff_by_file(diff).each_pair do |path, states|
86
+ terminal.say("diff --rosette a/#{path} b/#{path}", :white)
87
+
88
+ states.each do |state, changes|
89
+ changes.each do |change|
90
+ add_str = add_str_for(change)
91
+ remove_str = remove_str_for(change)
92
+
93
+ case state
94
+ when 'modified'
95
+ terminal.say("- #{remove_str}", :red)
96
+ terminal.say("+ #{add_str}", :green)
97
+ when 'removed'
98
+ terminal.say("- #{remove_str}", :red)
99
+ when 'added'
100
+ terminal.say("+ #{add_str}", :green)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs snapshot <ref>
4
+
5
+ module Rosette
6
+ module Client
7
+ module Commands
8
+
9
+ RepoSnapshotCommandArgs = Struct.new(:ref) do
10
+ def self.from_argv(argv, repo)
11
+ new(repo.rev_parse(argv[0] || repo.get_head))
12
+ end
13
+ end
14
+
15
+ # a show is really just a diff against your parent (so the inheritance makes sense)
16
+ class RepoSnapshotCommand < Command
17
+ attr_reader :args
18
+
19
+ def initialize(api, terminal, repo, argv)
20
+ super(api, terminal, repo)
21
+ @args = RepoSnapshotCommandArgs.from_argv(argv, repo)
22
+ end
23
+
24
+ def execute
25
+ response = api.repo_snapshot(
26
+ repo_name: derive_repo_name,
27
+ ref: args.ref
28
+ )
29
+
30
+ handle_error(response) do |response|
31
+ print_hash(response.attributes)
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs show <ref>
4
+
5
+ module Rosette
6
+ module Client
7
+ module Commands
8
+
9
+ ShowCommandArgs = Struct.new(:ref) do
10
+ def self.from_argv(argv, repo)
11
+ new(repo.rev_parse(argv[0] || repo.get_head))
12
+ end
13
+ end
14
+
15
+ # a show is really just a diff against your parent (so the inheritance makes sense)
16
+ class ShowCommand < DiffCommand
17
+ attr_reader :args
18
+
19
+ def initialize(api, terminal, repo, argv)
20
+ @api = api
21
+ @terminal = terminal
22
+ @repo = repo
23
+ @args = ShowCommandArgs.from_argv(argv, repo)
24
+ end
25
+
26
+ def execute
27
+ response = api.show(
28
+ repo_name: derive_repo_name,
29
+ ref: args.ref
30
+ )
31
+
32
+ handle_error(response) do |response|
33
+ print_diff(response.attributes)
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs snapshot <ref>
4
+
5
+ module Rosette
6
+ module Client
7
+ module Commands
8
+
9
+ SnapshotCommandArgs = Struct.new(:ref) do
10
+ def self.from_argv(argv, repo)
11
+ new(repo.rev_parse(argv[0] || repo.get_head))
12
+ end
13
+ end
14
+
15
+ # a show is really just a diff against your parent (so the inheritance makes sense)
16
+ class SnapshotCommand < Command
17
+ attr_reader :args
18
+
19
+ def initialize(api, terminal, repo, argv)
20
+ super(api, terminal, repo)
21
+ @args = SnapshotCommandArgs.from_argv(argv, repo)
22
+ end
23
+
24
+ def execute
25
+ response = api.snapshot(
26
+ repo_name: derive_repo_name,
27
+ ref: args.ref
28
+ )
29
+
30
+ handle_error(response) do |response|
31
+ response.attributes.each do |item|
32
+ print_hash(item)
33
+ terminal.say('')
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs status [ref]
4
+
5
+ module Rosette
6
+ module Client
7
+ module Commands
8
+
9
+ StatusCommandArgs = Struct.new(:ref) do
10
+ def self.from_argv(argv, repo)
11
+ new(repo.rev_parse(argv[0] || repo.get_head))
12
+ end
13
+ end
14
+
15
+ # a show is really just a diff against your parent (so the inheritance makes sense)
16
+ class StatusCommand < Command
17
+ attr_reader :args
18
+
19
+ def initialize(api, terminal, repo, argv)
20
+ @api = api
21
+ @terminal = terminal
22
+ @repo = repo
23
+ @args = StatusCommandArgs.from_argv(argv, repo)
24
+ end
25
+
26
+ def execute
27
+ response = api.status(
28
+ repo_name: derive_repo_name,
29
+ ref: args.ref
30
+ )
31
+
32
+ handle_error(response) do |response|
33
+ if response.locales && response.phrase_count
34
+ terminal.say(
35
+ build_locale_table(response.locales, response.phrase_count)
36
+ )
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ HEADER = ['Locale', 'Phrases', 'Translations', 'Percent']
44
+
45
+ def build_locale_table(locales, phrase_count)
46
+ rows = build_locale_rows(locales, phrase_count)
47
+ column_widths = find_column_widths(rows)
48
+ rows = pad_rows(rows, column_widths)
49
+ add_markup(rows)
50
+ end
51
+
52
+ def add_markup(rows)
53
+ rows = rows.map do |row|
54
+ "| #{row.join(' | ')} |"
55
+ end
56
+
57
+ separator = '-' * rows.first.length
58
+ result = rows.join("\n#{separator}\n")
59
+ "#{separator}\n#{result}\n#{separator}"
60
+ end
61
+
62
+ def pad_rows(rows, column_widths)
63
+ rows.map do |row|
64
+ row.map.with_index do |col, index|
65
+ col.ljust(column_widths[index], ' ')
66
+ end
67
+ end
68
+ end
69
+
70
+ def find_column_widths(rows)
71
+ rows.each_with_object([]) do |row, ret|
72
+ row.each_with_index do |col, index|
73
+ ret[index] ||= []
74
+ ret[index] << col.length
75
+ end
76
+ end.map(&:max)
77
+ end
78
+
79
+ def build_locale_rows(locales, phrase_count)
80
+ [HEADER] + locales.map do |locale|
81
+ build_locale_table_row(locale, phrase_count)
82
+ end
83
+ end
84
+
85
+ def build_locale_table_row(locale, phrase_count)
86
+ [
87
+ locale['locale'], phrase_count.to_i.to_s,
88
+ locale['translated_count'].to_i.to_s,
89
+ locale['percent_translated'].to_f.to_s
90
+ ]
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rosette
4
+ module Client
5
+ module Commands
6
+
7
+ autoload :DiffCommand, 'rosette/client/commands/diff_command'
8
+ autoload :DiffCommandArgs, 'rosette/client/commands/diff_command'
9
+ autoload :ShowCommand, 'rosette/client/commands/show_command'
10
+ autoload :ShowCommandArgs, 'rosette/client/commands/show_command'
11
+ autoload :StatusCommand, 'rosette/client/commands/status_command'
12
+ autoload :StatusCommandArgs, 'rosette/client/commands/status_command'
13
+ autoload :CommitCommand, 'rosette/client/commands/commit_command'
14
+ autoload :CommitCommandArgs, 'rosette/client/commands/commit_command'
15
+ autoload :SnapshotCommand, 'rosette/client/commands/snapshot_command'
16
+ autoload :SnapshotCommandArgs, 'rosette/client/commands/snapshot_command'
17
+ autoload :RepoSnapshotCommand, 'rosette/client/commands/repo_snapshot_command'
18
+ autoload :RepoSnapshotCommandArgs, 'rosette/client/commands/repo_snapshot_command'
19
+
20
+ class Command
21
+ attr_reader :api, :terminal, :repo
22
+
23
+ def initialize(api, terminal, repo)
24
+ @api = api
25
+ @terminal = terminal
26
+ @repo = repo
27
+ end
28
+
29
+ protected
30
+
31
+ def print_hash(hash)
32
+ hash.each_pair do |key, value|
33
+ terminal.say("#{key}: #{value}")
34
+ end
35
+ end
36
+
37
+ def handle_error(response)
38
+ if response.error?
39
+ terminal.say(
40
+ [response.error, response.detail].compact.join(': ')
41
+ )
42
+ else
43
+ yield response if block_given?
44
+ end
45
+ end
46
+
47
+ def group_diff_by_file(diff)
48
+ result = Hash.new do |h, i|
49
+ h[i] = Hash.new do |h2, i2|
50
+ h2[i2] = []
51
+ end
52
+ end
53
+
54
+ diff.each_with_object(result) do |(state, items), by_path|
55
+ items.each do |item|
56
+ by_path[item['file']][state] << item
57
+ end
58
+ end
59
+ end
60
+
61
+ def derive_repo_name
62
+ File.basename(`git config --get remote.origin.url`.strip).gsub(/\.git\z/, '')
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end