rosette-client 1.0.0

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.
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