rosette-client 1.0.0 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ca6f02478fbb347742958dd575df86db72b742c6
4
- data.tar.gz: c43753c2cedbfa451b2c69215df10c82a49d8979
3
+ metadata.gz: c5c191cd4c4fab60eb16d3fe33c9b2f0c3e26c65
4
+ data.tar.gz: 3a059a1b64f2bfd6e57d5dea12b26eaccc5825fe
5
5
  SHA512:
6
- metadata.gz: 28b9c44f0d32376b59a3f98e653e59050be2339293de23c8bb6315f4f90b5dac263a3e6e32aedc915a1c916446dc49bd64fc7ac7f5c21560cb052c4e19bf5012
7
- data.tar.gz: 5a85c962749d9ff7ba04082641ecac2d055822357491b0fab6e904dac377ee0eaee840d13fb09ecbea8b610e212ec3afd4c824f6ce1d3e64c24b6750f2a99829
6
+ metadata.gz: 2c1399a9fc2a39f05df85f840e2da3553114087c640372afc72a9cf10f695a5e5e2961292655b84814cd74c671f607f1cb026e0e3b0e31399149550bcc5d3279
7
+ data.tar.gz: d3a0980d7c2f063650a070385bb9e937e13d11696fc7a36973caab37396d556ad95b7a8cd04bc2a030196fb612bda205ed25daf64a01bb3ef670f1960c1cbcba
data/History.txt CHANGED
@@ -1,3 +1,7 @@
1
1
  == 1.0.0
2
2
 
3
- * Birthday!
3
+ * Birthday!
4
+
5
+ == 1.0.1
6
+
7
+ * Added locales endpoint to API.
data/README.md CHANGED
@@ -2,3 +2,57 @@ rosette-core
2
2
  ========
3
3
 
4
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)
5
+
6
+ ## Installation
7
+
8
+ `gem install rosette-client`
9
+
10
+ ## Usage
11
+
12
+ Configure it! Create the file `~/.rosette/config.yml` with configuration options for your Rosette setup:
13
+
14
+ ```yaml
15
+ :host: rosette.mycompany.com
16
+ :port: 8080
17
+ :version: v1
18
+ ```
19
+
20
+ You should now be able to change directory into a git repository and run rosette commands. If you installed Ruby with rbenv, don't forget to run `rbenv rehash` to make rosette-client's executable available.
21
+
22
+ ## Commands
23
+
24
+ `git rosette commit [<ref>]`
25
+
26
+ Causes the given ref to be processed. If ref is omitted, the current `HEAD` is assumed. Phrases will be extracted and stored in the data store.
27
+
28
+ `git rosette diff <ref1> [<ref2> | <path>] [-- <path1> <path2> ...]`
29
+
30
+ Show the diff between two refs. If the second ref is omitted, the current `HEAD` is assumed. You can separate paths from ref arguments with `--` for consistency with the `git diff` command. This command will print phrases that were added, removed, or changed between the two refs.
31
+
32
+ `git rosette show [<ref>]`
33
+
34
+ Print the phrases that were added, removed, or changed in the given ref. If the ref is omitted, the current `HEAD` is assumed.
35
+
36
+ `git rosette status [<ref>]`
37
+
38
+ Print the translation status of the given ref. This includes how many phrases were found and the percentage translated in each supported locale. If ref is omitted, the current `HEAD` is assumed.
39
+
40
+ `git rosette snapshot [<ref>]`
41
+
42
+ Print a snapshot of the phrases for the given ref. If ref is omitted, the current `HEAD` is assumed.
43
+
44
+ `git rosette repo_snapshot [<ref>]`
45
+
46
+ Print the snapshot hash for the given ref. A snapshot is a hash of file paths to commit ids, where each commit id represents the commit the file last changed in. If ref is omitted, the current `HEAD` is assumed.
47
+
48
+ ## Requirements
49
+
50
+ You've gotta be running a properly configured instance of [rosette-server](https://github.com/rosette-proj/rosette-server) to communicate with, but otherwise there are no external requirements.
51
+
52
+ ## Running Tests
53
+
54
+ `bundle exec rake` should do the trick.
55
+
56
+ ## Authors
57
+
58
+ * Cameron C. Dutro: http://github.com/camertron
@@ -5,9 +5,12 @@ require 'rosette/client/repo'
5
5
 
6
6
  module Rosette
7
7
  module Client
8
- autoload :Api, 'rosette/client/api'
9
- autoload :Terminal, 'rosette/client/terminal'
10
- autoload :Commands, 'rosette/client/commands'
11
- autoload :Response, 'rosette/client/response'
8
+ autoload :Api, 'rosette/client/api'
9
+ autoload :Terminal, 'rosette/client/terminal'
10
+ autoload :Writer, 'rosette/client/writer'
11
+ autoload :Commands, 'rosette/client/commands'
12
+ autoload :Response, 'rosette/client/response'
13
+ autoload :HashResponse, 'rosette/client/response'
14
+ autoload :ArrayResponse, 'rosette/client/response'
12
15
  end
13
16
  end
@@ -16,6 +16,13 @@ module Rosette
16
16
 
17
17
  attr_reader :host, :port, :version
18
18
 
19
+ def self.url_join(*args)
20
+ args
21
+ .map { |arg| arg.gsub(/\A\/(.*)\/\z/, '\1') } # remove slashes
22
+ .reject { |arg| arg.empty? } # get rid of empties
23
+ .join('/')
24
+ end
25
+
19
26
  def initialize(options = {})
20
27
  @host = options.fetch(:host, DEFAULT_HOST)
21
28
  @port = options.fetch(:port, DEFAULT_PORT)
@@ -54,6 +61,10 @@ module Rosette
54
61
  wrap(make_request(:get, 'translations/export.json', params))
55
62
  end
56
63
 
64
+ def locales(params)
65
+ wrap(make_request(:get, 'locales.json', params))
66
+ end
67
+
57
68
  private
58
69
 
59
70
  def wrap(api_response)
@@ -92,11 +103,15 @@ module Rosette
92
103
  end
93
104
 
94
105
  def make_get_url(path, params)
95
- File.join(base_url, path, "?#{make_param_string(params)}")
106
+ url_join(base_url, path, "?#{make_param_string(params)}")
96
107
  end
97
108
 
98
109
  def make_post_url(path, params)
99
- File.join(base_url, path)
110
+ url_join(base_url, path)
111
+ end
112
+
113
+ def url_join(*args)
114
+ self.class.url_join(*args)
100
115
  end
101
116
 
102
117
  def post(url, params)
@@ -6,17 +6,18 @@ module Rosette
6
6
  module Client
7
7
 
8
8
  class Cli
9
- attr_reader :api, :terminal, :repo
9
+ attr_reader :api, :terminal, :writer, :repo
10
10
 
11
- def initialize(terminal, api, repo)
11
+ def initialize(terminal, writer, api, repo)
12
12
  @api = api
13
13
  @terminal = terminal
14
+ @writer = writer
14
15
  @repo = repo
15
16
  end
16
17
 
17
18
  def start(argv)
18
19
  if command_const = find_command_const(argv.first)
19
- command_const.new(api, terminal, repo, argv[1..-1]).execute
20
+ command_const.new(api, terminal, writer, repo, argv[1..-1]).execute
20
21
  else
21
22
  terminal.say("Command '#{argv.first}' not recognized.")
22
23
  end
@@ -16,18 +16,27 @@ module Rosette
16
16
  autoload :SnapshotCommandArgs, 'rosette/client/commands/snapshot_command'
17
17
  autoload :RepoSnapshotCommand, 'rosette/client/commands/repo_snapshot_command'
18
18
  autoload :RepoSnapshotCommandArgs, 'rosette/client/commands/repo_snapshot_command'
19
+ autoload :PullCommand, 'rosette/client/commands/pull_command'
20
+ autoload :PullCommandArgs, 'rosette/client/commands/pull_command'
19
21
 
20
22
  class Command
21
- attr_reader :api, :terminal, :repo
23
+ attr_reader :api, :terminal, :writer, :repo, :args
22
24
 
23
- def initialize(api, terminal, repo)
25
+ def initialize(api, terminal, writer, repo, argv)
24
26
  @api = api
25
27
  @terminal = terminal
28
+ @writer = writer
26
29
  @repo = repo
30
+ @args = parse_args(argv)
27
31
  end
28
32
 
29
33
  protected
30
34
 
35
+ def parse_args(args)
36
+ raise NotImplementedError,
37
+ "#{__method__} must be defined in derived classes"
38
+ end
39
+
31
40
  def print_hash(hash)
32
41
  hash.each_pair do |key, value|
33
42
  terminal.say("#{key}: #{value}")
@@ -13,13 +13,6 @@ module Rosette
13
13
  end
14
14
 
15
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
16
  def execute
24
17
  terminal.say("Committing phrases for '#{args.ref}'...")
25
18
 
@@ -35,6 +28,12 @@ module Rosette
35
28
  terminal.say('done.')
36
29
  end
37
30
  end
31
+
32
+ private
33
+
34
+ def parse_args(args)
35
+ CommitCommandArgs.from_argv(args, repo)
36
+ end
38
37
  end
39
38
 
40
39
  end
@@ -41,13 +41,6 @@ module Rosette
41
41
  end
42
42
 
43
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
44
  def execute
52
45
  response = api.diff(
53
46
  repo_name: derive_repo_name,
@@ -63,6 +56,10 @@ module Rosette
63
56
 
64
57
  private
65
58
 
59
+ def parse_args(args)
60
+ DiffCommandArgs.from_argv(args, repo)
61
+ end
62
+
66
63
  def add_str_for(change)
67
64
  str = "#{change['key']}"
68
65
  meta_key = change['meta_key']
@@ -0,0 +1,115 @@
1
+ # encoding: UTF-8
2
+
3
+ # git rs pull [ref] -f file_pattern -s serializer
4
+
5
+ require 'base64'
6
+ require 'optparse'
7
+
8
+ module Rosette
9
+ module Client
10
+ module Commands
11
+
12
+ PullCommandArgs = Struct.new(:ref, :file_pattern, :serializer) do
13
+ def self.from_argv(argv, repo, terminal)
14
+ options = {}
15
+
16
+ parser = OptionParser.new do |opts|
17
+ desc = "File pattern to use. Can contain these placeholders:\n" +
18
+ "* %{locale.code}: The full locale code, eg. pt-BR\n" +
19
+ "* %{locale.territory}: The locale's territory part, eg. 'BR'" +
20
+ "* %{locale.language}: The locale's language part, eg. 'pt'"
21
+
22
+ opts.on('-f pattern', '--file-pattern pattern', desc) do |pattern|
23
+ pattern.strip!
24
+ options[:file_pattern] = pattern.empty? ? nil : pattern
25
+ end
26
+
27
+ desc = 'The serializer to use. Translations will be requested in ' +
28
+ 'this format.'
29
+
30
+ opts.on('-s serializer', '--serializer serializer', desc) do |serializer|
31
+ serializer.strip!
32
+ options[:serializer] = serializer.empty? ? nil : serializer
33
+ end
34
+ end
35
+
36
+ parser.parse(argv[1..-1])
37
+ options[:ref] = repo.rev_parse(argv[0] || repo.get_head)
38
+ validate_options!(options, terminal)
39
+
40
+ new(
41
+ options[:ref],
42
+ options[:file_pattern],
43
+ options[:serializer]
44
+ )
45
+ end
46
+
47
+ def self.validate_options!(options, terminal)
48
+ unless options.include?(:file_pattern)
49
+ terminal.say('Please supply a file pattern via the -f option')
50
+ exit 1
51
+ end
52
+
53
+ unless options.include?(:serializer)
54
+ terminal.say('Please supply a serializer via the -s option')
55
+ exit 1
56
+ end
57
+ end
58
+ end
59
+
60
+ # a show is really just a diff against your parent (so the inheritance makes sense)
61
+ class PullCommand < Command
62
+ def execute
63
+ locales = api.locales(
64
+ repo_name: derive_repo_name
65
+ )
66
+
67
+ handle_error(locales) do |locales|
68
+ export_locales(locales)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def parse_args(args)
75
+ PullCommandArgs.from_argv(args, repo, terminal)
76
+ end
77
+
78
+ def export_locales(locales)
79
+ locales.each do |locale|
80
+ export_locale(locale)
81
+ end
82
+ end
83
+
84
+ def export_locale(locale)
85
+ response = api.export({
86
+ repo_name: derive_repo_name,
87
+ ref: args.ref,
88
+ locale: locale['code'],
89
+ serializer: args.serializer,
90
+ base_64_encode: true
91
+ })
92
+
93
+ handle_error(response) do |response|
94
+ payload = Base64.decode64(response.payload)
95
+ path = path_for(locale)
96
+ terminal.say("Writing #{path}")
97
+
98
+ writer.open(path, 'w+') do |f|
99
+ f.write(payload)
100
+ end
101
+ end
102
+ end
103
+
104
+ def path_for(locale)
105
+ args.file_pattern % {
106
+ :'locale.code' => locale['code'],
107
+ :'locale.territory' => locale['territory'],
108
+ :'locale.language' => locale['language']
109
+ }
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -14,13 +14,6 @@ module Rosette
14
14
 
15
15
  # a show is really just a diff against your parent (so the inheritance makes sense)
16
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
17
  def execute
25
18
  response = api.repo_snapshot(
26
19
  repo_name: derive_repo_name,
@@ -31,6 +24,12 @@ module Rosette
31
24
  print_hash(response.attributes)
32
25
  end
33
26
  end
27
+
28
+ private
29
+
30
+ def parse_args(args)
31
+ RepoSnapshotCommandArgs.from_argv(args, repo)
32
+ end
34
33
  end
35
34
 
36
35
  end
@@ -14,15 +14,6 @@ module Rosette
14
14
 
15
15
  # a show is really just a diff against your parent (so the inheritance makes sense)
16
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
17
  def execute
27
18
  response = api.show(
28
19
  repo_name: derive_repo_name,
@@ -33,6 +24,12 @@ module Rosette
33
24
  print_diff(response.attributes)
34
25
  end
35
26
  end
27
+
28
+ private
29
+
30
+ def parse_args(args)
31
+ ShowCommandArgs.from_argv(args, repo)
32
+ end
36
33
  end
37
34
 
38
35
  end
@@ -14,13 +14,6 @@ module Rosette
14
14
 
15
15
  # a show is really just a diff against your parent (so the inheritance makes sense)
16
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
17
  def execute
25
18
  response = api.snapshot(
26
19
  repo_name: derive_repo_name,
@@ -34,6 +27,10 @@ module Rosette
34
27
  end
35
28
  end
36
29
  end
30
+
31
+ def parse_args(args)
32
+ SnapshotCommandArgs.from_argv(args, repo)
33
+ end
37
34
  end
38
35
 
39
36
  end
@@ -14,15 +14,6 @@ module Rosette
14
14
 
15
15
  # a show is really just a diff against your parent (so the inheritance makes sense)
16
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
17
  def execute
27
18
  response = api.status(
28
19
  repo_name: derive_repo_name,
@@ -40,6 +31,10 @@ module Rosette
40
31
 
41
32
  private
42
33
 
34
+ def parse_args(args)
35
+ StatusCommandArgs.from_argv(args, repo)
36
+ end
37
+
43
38
  HEADER = ['Locale', 'Phrases', 'Translations', 'Percent']
44
39
 
45
40
  def build_locale_table(locales, phrase_count)
@@ -4,11 +4,18 @@ module Rosette
4
4
  module Client
5
5
 
6
6
  class Response
7
- attr_reader :attributes
8
-
9
- def self.from_api_response(hash)
10
- new(hash)
7
+ def self.from_api_response(hash_or_array)
8
+ case hash_or_array
9
+ when Hash
10
+ HashResponse.new(hash_or_array)
11
+ else
12
+ ArrayResponse.new(hash_or_array)
13
+ end
11
14
  end
15
+ end
16
+
17
+ class HashResponse
18
+ attr_reader :attributes
12
19
 
13
20
  def initialize(hash)
14
21
  @attributes = hash
@@ -34,5 +41,23 @@ module Rosette
34
41
  end
35
42
  end
36
43
 
44
+ class ArrayResponse < Array
45
+ def initialize(array)
46
+ replace(array)
47
+ end
48
+
49
+ def error?
50
+ false
51
+ end
52
+
53
+ def success?
54
+ true
55
+ end
56
+
57
+ def attributes
58
+ self
59
+ end
60
+ end
61
+
37
62
  end
38
63
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rosette
4
4
  module Client
5
- VERSION = "1.0.0"
5
+ VERSION = "1.0.1"
6
6
  end
7
7
  end
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'yaml'
4
+
5
+ module Rosette
6
+ module Client
7
+
8
+ class Writer
9
+ def self.open(path, mode, &block)
10
+ File.open(path, mode, &block)
11
+ end
12
+ end
13
+
14
+ end
15
+ end
data/spec/api_spec.rb CHANGED
@@ -27,28 +27,36 @@ describe Api do
27
27
  let(:api) { api_class.new }
28
28
  let(:params) { { param: 'value' } }
29
29
  let(:endpoint) { method }
30
+ let(:response_type) { HashResponse }
30
31
 
31
32
  before(:each) do
32
- url = "http://localhost:8080/v1/#{path}"
33
+ url = Api.url_join('http://localhost:8080/v1', path)
33
34
 
34
35
  args = case verb
35
36
  when :get
36
- ["#{url}/#{endpoint_override rescue endpoint}.json/?param=value"]
37
+ [Api.url_join(url, "#{endpoint_override rescue endpoint}.json", '?param=value')]
37
38
  else
38
- ["#{url}/#{endpoint_override rescue endpoint}.json", params]
39
+ [Api.url_join(url, "#{endpoint_override rescue endpoint}.json"), params]
39
40
  end
40
41
 
41
42
  allow(api).to receive(verb).with(*args).and_return('{"foo":"bar"}')
42
43
  end
43
44
 
44
45
  it 'wraps the response in a Response object' do
45
- expect(api.send(endpoint, params)).to be_a(Response)
46
+ expect(api.send(endpoint, params)).to be_a(response_type)
46
47
  end
47
48
  end
48
49
 
49
50
  context 'get requests' do
50
51
  let(:verb) { :get }
51
52
 
53
+ describe '#locales' do
54
+ let(:path) { '' }
55
+ let(:method) { :locales }
56
+ let(:response_type) { ArrayResponse }
57
+ it_behaves_like 'an api endpoint'
58
+ end
59
+
52
60
  describe '#diff' do
53
61
  let(:path) { 'git' }
54
62
  let(:method) { :diff }
data/spec/cli_spec.rb CHANGED
@@ -9,7 +9,8 @@ describe Cli do
9
9
  let(:base_repo) { TmpRepo.new }
10
10
  let(:repo) { Repo.new(base_repo.working_dir) }
11
11
  let(:terminal) { FakeTerminal.new }
12
- let(:cli) { Cli.new(terminal, api, repo) }
12
+ let(:writer) { FakeWriter.new }
13
+ let(:cli) { Cli.new(terminal, writer, api, repo) }
13
14
 
14
15
  before(:each) do
15
16
  add_user_to(base_repo)
@@ -21,7 +22,7 @@ describe Cli do
21
22
  base_repo.add_all
22
23
  base_repo.commit('Initial commit')
23
24
 
24
- expect(api).to receive(:commit).and_return(Response.new({ 'foo' => 'bar' }))
25
+ expect(api).to receive(:commit).and_return(Response.from_api_response({ 'foo' => 'bar' }))
25
26
  cli.start(['commit', base_repo.git('rev-parse HEAD').strip])
26
27
  end
27
28
 
@@ -10,9 +10,10 @@ describe CommitCommand do
10
10
  let(:base_repo) { TmpRepo.new }
11
11
  let(:repo) { Repo.new(base_repo.working_dir) }
12
12
  let(:terminal) { FakeTerminal.new }
13
+ let(:writer) { FakeWriter.new }
13
14
  let(:repo_name) { 'my_awesome_repo' }
14
15
  let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
15
- let(:command) { CommitCommand.new(api, terminal, repo, [commit_id]) }
16
+ let(:command) { CommitCommand.new(api, terminal, writer, repo, [commit_id]) }
16
17
 
17
18
  before(:each) do
18
19
  add_user_to(base_repo)
@@ -32,7 +33,7 @@ describe CommitCommand do
32
33
  it 'makes a commit api call' do
33
34
  expect(api).to receive(:commit)
34
35
  .with(repo_name: repo_name, ref: commit_id)
35
- .and_return(Response.new({}))
36
+ .and_return(Response.from_api_response({}))
36
37
 
37
38
  command.execute
38
39
  end
@@ -41,7 +42,7 @@ describe CommitCommand do
41
42
  expect(api).to receive(:commit)
42
43
  .with(repo_name: repo_name, ref: commit_id)
43
44
  .and_return(
44
- Response.new(
45
+ Response.from_api_response(
45
46
  { 'added' => 1, 'removed' => 2, 'modified' => 3 }
46
47
  )
47
48
  )
@@ -57,7 +58,9 @@ describe CommitCommand do
57
58
  expect(api).to receive(:commit)
58
59
  .with(repo_name: repo_name, ref: commit_id)
59
60
  .and_return(
60
- Response.new({ 'error' => 'Jelly beans', 'added' => 1 })
61
+ Response.from_api_response(
62
+ 'error' => 'Jelly beans', 'added' => 1
63
+ )
61
64
  )
62
65
 
63
66
  command.execute
@@ -84,8 +84,15 @@ describe 'diff' do
84
84
 
85
85
  describe DiffCommand do
86
86
  let(:api) { double }
87
- let(:command) { DiffCommand.new(api, terminal, repo, [master_commit_id, new_branch_commit_id]) }
87
+ let(:command) do
88
+ DiffCommand.new(
89
+ api, terminal, writer, repo,
90
+ [master_commit_id, new_branch_commit_id]
91
+ )
92
+ end
93
+
88
94
  let(:terminal) { FakeTerminal.new }
95
+ let(:writer) { FakeWriter.new }
89
96
 
90
97
  describe '#execute' do
91
98
  it 'makes a diff api call' do
@@ -96,7 +103,7 @@ describe 'diff' do
96
103
  diff_point_ref: master_commit_id,
97
104
  paths: ''
98
105
  )
99
- .and_return(Response.new({}))
106
+ .and_return(Response.from_api_response({}))
100
107
 
101
108
  command.execute
102
109
  end
@@ -110,7 +117,7 @@ describe 'diff' do
110
117
  paths: ''
111
118
  )
112
119
  .and_return(
113
- Response.new(
120
+ Response.from_api_response(
114
121
  sample_diff(new_branch_commit_id)
115
122
  )
116
123
  )
@@ -136,7 +143,7 @@ describe 'diff' do
136
143
  paths: ''
137
144
  )
138
145
  .and_return(
139
- Response.new({ 'error' => 'Jelly beans' }.merge(
146
+ Response.from_api_response({ 'error' => 'Jelly beans' }.merge(
140
147
  sample_diff(new_branch_commit_id))
141
148
  )
142
149
  )
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Client
6
+ include Rosette::Client::Commands
7
+
8
+ describe PullCommandArgs do
9
+ let(:base_repo) { TmpRepo.new }
10
+ let(:repo) { Repo.new(base_repo.working_dir) }
11
+ let(:terminal) { FakeTerminal.new }
12
+ let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
13
+ let(:repo_name) { 'my_awesome_repo' }
14
+
15
+ before(:each) do
16
+ add_user_to(base_repo)
17
+ base_repo.git("remote add origin git@github.com/camertron/#{repo_name}")
18
+ base_repo.create_file('file.txt') { |f| f.write('hello, world') }
19
+ base_repo.add_all
20
+ base_repo.commit('Initial commit')
21
+ end
22
+
23
+ describe 'from_argv' do
24
+ it 'exits and prints message when not given a file pattern' do
25
+ expect do
26
+ PullCommandArgs.from_argv(
27
+ [commit_id, '--serializer', 'foo/bar'], repo, terminal
28
+ )
29
+ end.to raise_error(SystemExit)
30
+
31
+ expect(terminal).to have_said(
32
+ 'Please supply a file pattern via the -f option'
33
+ )
34
+ end
35
+
36
+ it 'exits and prints message when not given a serializer' do
37
+ expect do
38
+ PullCommandArgs.from_argv(
39
+ [commit_id, '--file-pattern', 'teapot'], repo, terminal
40
+ )
41
+ end.to raise_error(SystemExit)
42
+
43
+ expect(terminal).to have_said(
44
+ 'Please supply a serializer via the -s option'
45
+ )
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+ require 'base64'
5
+
6
+ include Rosette::Client
7
+ include Rosette::Client::Commands
8
+
9
+ describe PullCommand do
10
+ let(:api) { double }
11
+ let(:base_repo) { TmpRepo.new }
12
+ let(:repo) { Repo.new(base_repo.working_dir) }
13
+ let(:terminal) { FakeTerminal.new }
14
+ let(:writer) { FakeWriter.new }
15
+ let(:repo_name) { 'my_awesome_repo' }
16
+ let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
17
+ let(:serializer) { 'yaml/rails' }
18
+ let(:file_pattern) { 'config/locales/%{locale.code}.yml' }
19
+ let(:command) do
20
+ PullCommand.new(
21
+ api, terminal, writer, repo, [
22
+ commit_id, "-f #{file_pattern}", "-s #{serializer}"
23
+ ]
24
+ )
25
+ end
26
+
27
+ let(:locales) do
28
+ [
29
+ {
30
+ 'code' => 'ja-JP',
31
+ 'language' => 'ja',
32
+ 'territory' => 'JP'
33
+ }, {
34
+ 'code' => 'pt-BR',
35
+ 'language' => 'pt',
36
+ 'territory' => 'BR'
37
+ }
38
+ ]
39
+ end
40
+
41
+ let(:params) do
42
+ {
43
+ repo_name: repo_name,
44
+ ref: commit_id,
45
+ serializer: serializer,
46
+ base_64_encode: true
47
+ }
48
+ end
49
+
50
+ before(:each) do
51
+ add_user_to(base_repo)
52
+ base_repo.git("remote add origin git@github.com/camertron/#{repo_name}")
53
+ base_repo.create_file('file.txt') { |f| f.write('hello, world') }
54
+ base_repo.add_all
55
+ base_repo.commit('Initial commit')
56
+ end
57
+
58
+ around(:each) do |example|
59
+ Dir.chdir(base_repo.working_dir) do
60
+ example.run
61
+ end
62
+ end
63
+
64
+ context 'with a mocked list of locales' do
65
+ before(:each) do
66
+ expect(api).to receive(:locales)
67
+ .with(repo_name: repo_name)
68
+ .and_return(Response.from_api_response(locales))
69
+
70
+ locales.each do |locale|
71
+ expect(api).to receive(:export)
72
+ .with(params.merge(locale: locale['code']))
73
+ .and_return(
74
+ Response.from_api_response({
75
+ 'payload' => Base64.encode64("payload for #{locale['code']}")
76
+ })
77
+ )
78
+ end
79
+ end
80
+
81
+ describe '#execute' do
82
+ it 'exports translations for each locale' do
83
+ command.execute
84
+
85
+ locales.each do |locale|
86
+ file = file_pattern % { :'locale.code' => locale['code'] }
87
+ expect(writer).to have_written_content_for(file)
88
+ expect(writer.contents_of(file)).to eq("payload for #{locale['code']}")
89
+ end
90
+ end
91
+
92
+ context 'with a territory-based file pattern' do
93
+ let(:file_pattern) { 'config/locales/%{locale.territory}.yml' }
94
+
95
+ it 'exports translations for each locale' do
96
+ command.execute
97
+
98
+ locales.each do |locale|
99
+ file = file_pattern % { :'locale.territory' => locale['territory'] }
100
+ expect(writer).to have_written_content_for(file)
101
+ expect(writer.contents_of(file)).to eq("payload for #{locale['code']}")
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'with a language-based file pattern' do
107
+ let(:file_pattern) { 'config/locales/%{locale.language}.yml' }
108
+
109
+ it 'exports translations for each locale' do
110
+ command.execute
111
+
112
+ locales.each do |locale|
113
+ file = file_pattern % { :'locale.language' => locale['language'] }
114
+ expect(writer).to have_written_content_for(file)
115
+ expect(writer.contents_of(file)).to eq("payload for #{locale['code']}")
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -10,9 +10,14 @@ describe RepoSnapshotCommand do
10
10
  let(:base_repo) { TmpRepo.new }
11
11
  let(:repo) { Repo.new(base_repo.working_dir) }
12
12
  let(:terminal) { FakeTerminal.new }
13
+ let(:writer) { FakeWriter.new }
13
14
  let(:repo_name) { 'my_awesome_repo' }
14
15
  let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
15
- let(:command) { RepoSnapshotCommand.new(api, terminal, repo, [commit_id]) }
16
+ let(:command) do
17
+ RepoSnapshotCommand.new(
18
+ api, terminal, writer, repo, [commit_id]
19
+ )
20
+ end
16
21
 
17
22
  before(:each) do
18
23
  add_user_to(base_repo)
@@ -32,7 +37,7 @@ describe RepoSnapshotCommand do
32
37
  it 'makes a repo_snapshot api call' do
33
38
  expect(api).to receive(:repo_snapshot)
34
39
  .with(repo_name: repo_name, ref: commit_id)
35
- .and_return(Response.new({}))
40
+ .and_return(Response.from_api_response({}))
36
41
 
37
42
  command.execute
38
43
  end
@@ -41,7 +46,7 @@ describe RepoSnapshotCommand do
41
46
  expect(api).to receive(:repo_snapshot)
42
47
  .with(repo_name: repo_name, ref: commit_id)
43
48
  .and_return(
44
- Response.new({
49
+ Response.from_api_response({
45
50
  'file1.txt' => 'abc123',
46
51
  'path/file2.txt' => 'def456',
47
52
  'my/awesome/file3.rb' => 'ghi789'
@@ -59,7 +64,7 @@ describe RepoSnapshotCommand do
59
64
  expect(api).to receive(:repo_snapshot)
60
65
  .with(repo_name: repo_name, ref: commit_id)
61
66
  .and_return(
62
- Response.new({ 'error' => 'Jelly beans', 'file1.txt' => 'abc123' })
67
+ Response.from_api_response({ 'error' => 'Jelly beans', 'file1.txt' => 'abc123' })
63
68
  )
64
69
 
65
70
  command.execute
@@ -10,9 +10,10 @@ describe ShowCommand do
10
10
  let(:base_repo) { TmpRepo.new }
11
11
  let(:repo) { Repo.new(base_repo.working_dir) }
12
12
  let(:terminal) { FakeTerminal.new }
13
+ let(:writer) { FakeWriter.new }
13
14
  let(:repo_name) { 'my_awesome_repo' }
14
15
  let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
15
- let(:command) { ShowCommand.new(api, terminal, repo, [commit_id]) }
16
+ let(:command) { ShowCommand.new(api, terminal, writer, repo, [commit_id]) }
16
17
 
17
18
  before(:each) do
18
19
  add_user_to(base_repo)
@@ -32,7 +33,7 @@ describe ShowCommand do
32
33
  it 'makes a commit api call' do
33
34
  expect(api).to receive(:show)
34
35
  .with(repo_name: repo_name, ref: commit_id)
35
- .and_return(Response.new({}))
36
+ .and_return(Response.from_api_response({}))
36
37
 
37
38
  command.execute
38
39
  end
@@ -41,7 +42,7 @@ describe ShowCommand do
41
42
  expect(api).to receive(:show)
42
43
  .with(repo_name: repo_name, ref: commit_id)
43
44
  .and_return(
44
- Response.new(sample_diff(commit_id))
45
+ Response.from_api_response(sample_diff(commit_id))
45
46
  )
46
47
 
47
48
  command.execute
@@ -60,7 +61,9 @@ describe ShowCommand do
60
61
  expect(api).to receive(:show)
61
62
  .with(repo_name: repo_name, ref: commit_id)
62
63
  .and_return(
63
- Response.new({ 'error' => 'Jelly beans' }.merge(sample_diff(commit_id)))
64
+ Response.from_api_response(
65
+ { 'error' => 'Jelly beans' }.merge(sample_diff(commit_id))
66
+ )
64
67
  )
65
68
 
66
69
  command.execute
@@ -10,9 +10,10 @@ describe SnapshotCommand do
10
10
  let(:base_repo) { TmpRepo.new }
11
11
  let(:repo) { Repo.new(base_repo.working_dir) }
12
12
  let(:terminal) { FakeTerminal.new }
13
+ let(:writer) { FakeWriter.new }
13
14
  let(:repo_name) { 'my_awesome_repo' }
14
15
  let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
15
- let(:command) { SnapshotCommand.new(api, terminal, repo, [commit_id]) }
16
+ let(:command) { SnapshotCommand.new(api, terminal, writer, repo, [commit_id]) }
16
17
 
17
18
  before(:each) do
18
19
  add_user_to(base_repo)
@@ -32,7 +33,7 @@ describe SnapshotCommand do
32
33
  it 'makes a snapshot api call' do
33
34
  expect(api).to receive(:snapshot)
34
35
  .with(repo_name: repo_name, ref: commit_id)
35
- .and_return(Response.new({}))
36
+ .and_return(Response.from_api_response({}))
36
37
 
37
38
  command.execute
38
39
  end
@@ -41,7 +42,7 @@ describe SnapshotCommand do
41
42
  expect(api).to receive(:snapshot)
42
43
  .with(repo_name: repo_name, ref: commit_id)
43
44
  .and_return(
44
- Response.new([
45
+ Response.from_api_response([
45
46
  { 'key' => 'Foo', 'commit_id' => 'abc123' },
46
47
  { 'key' => 'Bar', 'commit_id' => 'def456' },
47
48
  { 'key' => 'Baz', 'commit_id' => 'ghi789' }
@@ -62,7 +63,7 @@ describe SnapshotCommand do
62
63
  expect(api).to receive(:snapshot)
63
64
  .with(repo_name: repo_name, ref: commit_id)
64
65
  .and_return(
65
- Response.new({ 'error' => 'Jelly beans' })
66
+ Response.from_api_response({ 'error' => 'Jelly beans' })
66
67
  )
67
68
 
68
69
  command.execute
@@ -10,9 +10,10 @@ describe StatusCommand do
10
10
  let(:base_repo) { TmpRepo.new }
11
11
  let(:repo) { Repo.new(base_repo.working_dir) }
12
12
  let(:terminal) { FakeTerminal.new }
13
+ let(:writer) { FakeWriter.new }
13
14
  let(:repo_name) { 'my_awesome_repo' }
14
15
  let(:commit_id) { base_repo.git('rev-parse HEAD').strip }
15
- let(:command) { StatusCommand.new(api, terminal, repo, [commit_id]) }
16
+ let(:command) { StatusCommand.new(api, terminal, writer, repo, [commit_id]) }
16
17
 
17
18
  before(:each) do
18
19
  add_user_to(base_repo)
@@ -32,7 +33,7 @@ describe StatusCommand do
32
33
  it 'makes a commit api call' do
33
34
  expect(api).to receive(:status)
34
35
  .with(repo_name: repo_name, ref: commit_id)
35
- .and_return(Response.new({}))
36
+ .and_return(Response.from_api_response({}))
36
37
 
37
38
  command.execute
38
39
  end
@@ -41,7 +42,7 @@ describe StatusCommand do
41
42
  expect(api).to receive(:status)
42
43
  .with(repo_name: repo_name, ref: commit_id)
43
44
  .and_return(
44
- Response.new({
45
+ Response.from_api_response({
45
46
  'commit_id' => commit_id,
46
47
  'status' => 'UNTRANSLATED',
47
48
  'phrase_count' => 10,
@@ -67,7 +68,7 @@ describe StatusCommand do
67
68
  expect(api).to receive(:status)
68
69
  .with(repo_name: repo_name, ref: commit_id)
69
70
  .and_return(
70
- Response.new({ 'error' => 'Jelly beans' })
71
+ Response.from_api_response({ 'error' => 'Jelly beans' })
71
72
  )
72
73
 
73
74
  command.execute
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ class FakeWriter
4
+ attr_reader :streams
5
+
6
+ def initialize
7
+ @streams = {}
8
+ end
9
+
10
+ def open(file, mode)
11
+ streams[file] ||= StringIO.new
12
+ yield streams[file]
13
+ end
14
+
15
+ def contents_of(file)
16
+ streams[file].string
17
+ end
18
+
19
+ def has_written_content_for?(file)
20
+ streams.include?(file)
21
+ end
22
+ end
@@ -5,7 +5,34 @@ require 'spec_helper'
5
5
  include Rosette::Client
6
6
 
7
7
  describe Response do
8
- let(:response_class) { Response }
8
+ describe 'from_api_response' do
9
+ it 'accepts a hash of attributes and returns an instance of HashResponse' do
10
+ response = Response.from_api_response({ 'foo' => 'bar' })
11
+ expect(response).to be_a(HashResponse)
12
+ expect(response.attributes).to eq({ 'foo' => 'bar' })
13
+ end
14
+
15
+ it 'accepts an array and returns an instance of ArrayResponse' do
16
+ response = Response.from_api_response(['foo', 'bar'])
17
+ expect(response).to be_a(ArrayResponse)
18
+ expect(response.attributes).to eq(['foo', 'bar'])
19
+ end
20
+ end
21
+ end
22
+
23
+ describe ArrayResponse do
24
+ let(:response_class) { ArrayResponse }
25
+
26
+ describe '#attributes' do
27
+ it 'returns itself' do
28
+ response = response_class.new(['foo', 'bar'])
29
+ expect(response.attributes).to eq(['foo', 'bar'])
30
+ end
31
+ end
32
+ end
33
+
34
+ describe HashResponse do
35
+ let(:response_class) { HashResponse }
9
36
 
10
37
  it 'responds to methods that are also keys in the attributes hash' do
11
38
  response = response_class.new({ 'hello' => 'world' })
@@ -17,14 +44,6 @@ describe Response do
17
44
  expect(response.nothing).to be_nil
18
45
  end
19
46
 
20
- describe 'from_api_response' do
21
- it 'accepts a hash of attributes and returns an instance of Response' do
22
- response = response_class.from_api_response({ 'foo' => 'bar' })
23
- expect(response).to be_a(response_class)
24
- expect(response.attributes).to eq({ 'foo' => 'bar' })
25
- end
26
- end
27
-
28
47
  describe '#error?' do
29
48
  it 'returns true if the attributes hash contains an "error" key' do
30
49
  response = response_class.new({ 'error' => 'jelly beans' })
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,7 @@ require 'pry-nav'
5
5
  require 'rspec'
6
6
  require 'rosette/client'
7
7
  require 'helpers/fake_terminal'
8
+ require 'helpers/fake_writer'
8
9
  require 'tmp-repo'
9
10
 
10
11
  RSpec.configure do |config|
metadata CHANGED
@@ -1,45 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rosette-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-12 00:00:00.000000000 Z
11
+ date: 2015-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: colorize
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - ~>
18
17
  - !ruby/object:Gem::Version
19
18
  version: 0.7.0
20
- type: :runtime
19
+ name: colorize
21
20
  prerelease: false
21
+ type: :runtime
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.7.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: json
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - ~>
32
31
  - !ruby/object:Gem::Version
33
32
  version: 1.8.0
34
- type: :runtime
33
+ name: json
35
34
  prerelease: false
35
+ type: :runtime
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.8.0
41
- description: Git command integration for the Rosette internationalization platform
42
- that manages the translatable content in the source files of a git repository.
41
+ description: Git command integration for the Rosette internationalization platform that manages the translatable content in the source files of a git repository.
43
42
  email:
44
43
  - camertron@gmail.com
45
44
  executables:
@@ -47,33 +46,38 @@ executables:
47
46
  extensions: []
48
47
  extra_rdoc_files: []
49
48
  files:
49
+ - lib/rosette/client.rb
50
50
  - lib/rosette/client/api.rb
51
51
  - lib/rosette/client/cli.rb
52
+ - lib/rosette/client/commands.rb
53
+ - lib/rosette/client/repo.rb
54
+ - lib/rosette/client/response.rb
55
+ - lib/rosette/client/terminal.rb
56
+ - lib/rosette/client/version.rb
57
+ - lib/rosette/client/writer.rb
52
58
  - lib/rosette/client/commands/commit_command.rb
53
59
  - lib/rosette/client/commands/diff_command.rb
60
+ - lib/rosette/client/commands/pull_command.rb
54
61
  - lib/rosette/client/commands/repo_snapshot_command.rb
55
62
  - lib/rosette/client/commands/show_command.rb
56
63
  - lib/rosette/client/commands/snapshot_command.rb
57
64
  - lib/rosette/client/commands/status_command.rb
58
- - lib/rosette/client/commands.rb
59
- - lib/rosette/client/repo.rb
60
- - lib/rosette/client/response.rb
61
- - lib/rosette/client/terminal.rb
62
- - lib/rosette/client/version.rb
63
- - lib/rosette/client.rb
64
65
  - spec/api_spec.rb
65
66
  - spec/cli_spec.rb
67
+ - spec/repo_spec.rb
68
+ - spec/response_spec.rb
69
+ - spec/spec_helper.rb
70
+ - spec/terminal_spec.rb
66
71
  - spec/commands/commit_command_spec.rb
67
72
  - spec/commands/diff_command_spec.rb
73
+ - spec/commands/pull_command_args_spec.rb
74
+ - spec/commands/pull_command_spec.rb
68
75
  - spec/commands/repo_snapshot_command_spec.rb
69
76
  - spec/commands/show_command_spec.rb
70
77
  - spec/commands/snapshot_command_spec.rb
71
78
  - spec/commands/status_command_spec.rb
72
79
  - spec/helpers/fake_terminal.rb
73
- - spec/repo_spec.rb
74
- - spec/response_spec.rb
75
- - spec/spec_helper.rb
76
- - spec/terminal_spec.rb
80
+ - spec/helpers/fake_writer.rb
77
81
  - Gemfile
78
82
  - History.txt
79
83
  - README.md
@@ -83,7 +87,7 @@ files:
83
87
  homepage: http://github.com/camertron
84
88
  licenses: []
85
89
  metadata: {}
86
- post_install_message:
90
+ post_install_message:
87
91
  rdoc_options: []
88
92
  require_paths:
89
93
  - lib
@@ -98,11 +102,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
102
  - !ruby/object:Gem::Version
99
103
  version: '0'
100
104
  requirements: []
101
- rubyforge_project:
102
- rubygems_version: 2.0.14
103
- signing_key:
105
+ rubyforge_project:
106
+ rubygems_version: 2.1.9
107
+ signing_key:
104
108
  specification_version: 4
105
- summary: Git command integration for the Rosette internationalization platform that
106
- manages the translatable content in the source files of a git repository.
109
+ summary: Git command integration for the Rosette internationalization platform that manages the translatable content in the source files of a git repository.
107
110
  test_files: []
108
111
  has_rdoc: true