lokalise_manager 1.1.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6dbbda54dc255ed4ec13547b954213e41ab38c90d1520ffe3762d36dc2917f8
4
- data.tar.gz: a341a91e4fea6ff76e0de87e5efa9660f5a0c5a7659656fbcfa14600fc4937d8
3
+ metadata.gz: f3a7da094cb7939fd6826c89afbbbc525dff59b553a23fba7b49ed38046e68b9
4
+ data.tar.gz: c0a323464c864d12042f126d765a483adc128a4397604d012cdeadee31003fcf
5
5
  SHA512:
6
- metadata.gz: 2f5f277f431596f70ee52f45186a972dcd507d1be08f6fc71bd04117556ca803efbe3e8b0d6a123ec6f81b959a5a1c99d9aa63a3189a4700a195fb823ca766e2
7
- data.tar.gz: c64848ee98ee90cd77507bae719048ac425c60b7f16581b52de6ed6ab2301976487f6999027eab729244c4d3e8b27af1d5d6875c74d8b3d7cbd35112e0ededf1
6
+ metadata.gz: 79c0b5a93767b5ffa8cd55dbf6591a90cafbb586043e8e75ca040761b8fa882882971783358e6d342afab96e298d34c67ca5d414d376c909fa564e8ad03d2466
7
+ data.tar.gz: ed2ad8e59fb6e15db8ba7c60de48e808c2eab0ff94b8f287c54bb2e618e1c930d5d3244a2483a6e61e69b7094943a05db59ea54ef32fc3596ef344f5956553ee
data/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0 (27-Jan-22)
4
+
5
+ * **Breaking change**: `export!` will now return an array of objects responding to the following methods:
6
+ + `success` — usually returns `true` (to learn more, check documentation for the `:raise_on_export_fail` option below)
7
+ + `process` — returns an object (an instance of the `Lokalise::Resources::QueuedProcess`) representing a [queued background process](https://lokalise.github.io/ruby-lokalise-api/api/queued-processes) as uploading is done in the background on Lokalise. You can use this object to check the process status (whether the uploading is completed or not).
8
+ + `path` — returns an instance of the `Pathname` class which represent the file being uploaded.
9
+ * Here's an example:
10
+
11
+ ```ruby
12
+ def uploaded?(process)
13
+ 5.times do # try to check the status 5 times
14
+ process = process.reload_data # load new data
15
+ return(true) if process.status == 'finished' # return true is the upload has finished
16
+ sleep 1 # wait for 1 second, adjust this number with regards to the upload size
17
+ end
18
+
19
+ false # if all 5 checks failed, return false (probably something is wrong)
20
+ end
21
+
22
+ processes = exporter.export!
23
+ puts "Checking status for the #{processes[0].path} file"
24
+ uploaded? processes[0].process
25
+ ```
26
+
27
+ * Introduced a new option `raise_on_export_fail` (`boolean`) which is `true` by default. When this option is enabled, LokaliseManager will re-raise any exceptions that happened during the file uploading. When this option is disabled, the exporting process will continue even if something goes wrong. In this case you'll probably need to check the result yourself and make the necessary actions. For example:
28
+
29
+ ```ruby
30
+ processes = exporter.export!
31
+
32
+ processes.each do |proc_data|
33
+ if proc_data.success
34
+ # Everything is good, the uploading is queued
35
+ puts "#{proc_data.path} is sent to Lokalise!"
36
+ process = proc_data.process
37
+ puts "Current process status is #{process.status}"
38
+ else
39
+ # Something bad has happened
40
+ puts "Could not send #{proc_data.path} to Lokalise"
41
+ puts "Error #{proc_data.error.class}: #{proc_data.error.message}"
42
+ # Or you could re-raise this exception:
43
+ # raise proc_data.error.class
44
+ end
45
+ end
46
+ ```
47
+
48
+ ## 2.0.0 (27-Jan-22)
49
+
50
+ * `export!` method is now taking advantage of multi-threading (as Lokalise API allows to send requests in parallel since January 2022)
51
+ * Test with Ruby 3.1.0
52
+ * Other minor fixes
53
+
54
+ ## 1.2.1 (26-Nov-21)
55
+
56
+ * Use refinements instead of monkey patching to add hash methods
57
+ * Don't use `OpenStruct` anymore to store config opts
58
+ * Minor fixes
59
+
60
+ ## 1.2.0 (26-Oct-21)
61
+
62
+ * Add a new option `:silent_mode` which is `false` by default. When silent mode is enabled, no debug info will be printed out to `$stdout`. The only exception are the "safe mode" messages — you'll still be prompted to continue if the target directory is not empty.
63
+ * Use `#deep_merge` instead of a simple merge when processing options.
64
+
3
65
  ## 1.1.0 (25-Oct-21)
4
66
 
5
67
  * Add a new option `:use_oauth2_token` which is `false` by default. When enabled, you'll be able to provide a token obtained via [OAuth 2 flow](https://docs.lokalise.com/en/articles/5574713-oauth-2) rather than generated via Lokalise profile. The token should still be provided via the `:api_token` option:
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # LokaliseManager
2
2
 
3
3
  ![Gem](https://img.shields.io/gem/v/lokalise_manager)
4
- [![Build Status](https://travis-ci.com/bodrovis/lokalise_manager.svg?branch=master)](https://travis-ci.com/github/bodrovis/lokalise_manager)
4
+ ![CI](https://github.com/bodrovis/lokalise_manager/actions/workflows/ci.yml/badge.svg)
5
5
  [![Test Coverage](https://codecov.io/gh/bodrovis/lokalise_manager/graph/badge.svg)](https://codecov.io/gh/bodrovis/lokalise_manager)
6
6
  ![Downloads total](https://img.shields.io/gem/dt/lokalise_manager)
7
7
 
@@ -15,6 +15,8 @@ If you are looking for a Rails integration, please check [lokalise_rails](https:
15
15
 
16
16
  This gem requires Ruby 2.5+. You will also need to [setup a Lokalise account](https://app.lokalise.com/signup) and create a [translation project](https://docs.lokalise.com/en/articles/1400460-projects). Finally, you will need to generate a [read/write API token](https://docs.lokalise.com/en/articles/1929556-api-tokens) at your Lokalise profile.
17
17
 
18
+ Alternatively, you can utilize a token obtained via OAuth 2 flow. When using such a token, you'll have to set `:use_oauth2_token` option to `true` (see below).
19
+
18
20
  ### Installation
19
21
 
20
22
  Add the gem to your `Gemfile`:
@@ -63,7 +65,13 @@ To upload your translation files from a local directory (defaults to `locales/`)
63
65
  processes = exporter.export!
64
66
  ```
65
67
 
66
- `processes` will contain an array of queued background processes as uploading is done in the background on Lokalise. You can perform periodic checks to read the status of the process. Here's a very simple example:
68
+ `processes` will contain an array of objects responding to the following methods:
69
+
70
+ * `success` — usually returns `true` (to learn more, check documentation for the `:raise_on_export_fail` option below)
71
+ * `process` — returns an object (an instance of the `Lokalise::Resources::QueuedProcess`) representing a [queued background process](https://lokalise.github.io/ruby-lokalise-api/api/queued-processes) as uploading is done in the background on Lokalise.
72
+ * `path` — returns an instance of the `Pathname` class which represent the file being uploaded.
73
+
74
+ You can perform periodic checks to read the status of the process. Here's a very simple example:
67
75
 
68
76
  ```ruby
69
77
  def uploaded?(process)
@@ -77,7 +85,8 @@ def uploaded?(process)
77
85
  end
78
86
 
79
87
  processes = exporter.export!
80
- uploaded? processes[0]
88
+ puts "Checking status for the #{processes[0].path} file"
89
+ uploaded? processes[0].process
81
90
  ```
82
91
 
83
92
  Please don't forget that Lokalise API has rate limiting and you cannot send more than six requests per second.
@@ -92,6 +101,7 @@ Please don't forget that Lokalise API has rate limiting and you cannot send more
92
101
  * `locales_path` (`string`) — path to the directory with your translation files. Defaults to `"#{Dir.getwd}/locales"`.
93
102
  * `branch` (`string`) — Lokalise project branch to use. Defaults to `""` (no branch is provided).
94
103
  * `timeouts` (`hash`) — set [request timeouts for the Lokalise API client](https://lokalise.github.io/ruby-lokalise-api/additional_info/customization#setting-timeouts). By default, requests have no timeouts: `{open_timeout: nil, timeout: nil}`. Both values are in seconds.
104
+ * `silent_mode` (`boolean`) — whether you would like to output debugging information to `$stdout`. By default, after a task is performed, a short notification message will be printed out to the terminal. When set to `false`, notifications won't be printed. Please note that currently `import_safe_mode` has higher priority. Even if you enable `silent_mode`, and the `import_safe_mode` is enabled as well, you will be prompted to confirm the import operation if the target directory is not empty.
95
105
 
96
106
  ### Import config
97
107
 
@@ -159,7 +169,29 @@ In this case the `export_opts` will have `detect_icu_plurals` set to `true` and
159
169
  c.skip_file_export = ->(file) { f.split[1].to_s.include?('fr') }
160
170
  ```
161
171
 
162
- * `max_retries_export` (`integer`) — this option is introduced to properly handle Lokalise API rate limiting. If the HTTP status code 429 (too many requests) has been received, LokaliseRails will apply an exponential backoff mechanism with a very simple formula: `2 ** retries` (initially `retries` is `0`). If the maximum number of retries has been reached, a `Lokalise::Error::TooManyRequests` exception will be raised and the export operation will be halted. By default, LokaliseRails will make up to `5` retries which potentially means `1 + 2 + 4 + 8 + 16 + 32 = 63` seconds of waiting time. If the `max_retries_export` is less than `1`, LokaliseRails will not perform any retries and give up immediately after receiving error 429.
172
+ * `max_retries_export` (`integer`) — this option is introduced to properly handle Lokalise API rate limiting. If the HTTP status code 429 (too many requests) has been received, LokaliseRails will apply an exponential backoff mechanism with a very simple formula: `2 ** retries` (initially `retries` is `0`). If the maximum number of retries has been reached, a `Lokalise::Error::TooManyRequests` exception will be raised and the export operation will be halted. By default, LokaliseManager will make up to `5` retries which potentially means `1 + 2 + 4 + 8 + 16 + 32 = 63` seconds of waiting time. If the `max_retries_export` is less than `1`, LokaliseRails will not perform any retries and give up immediately after receiving error 429.
173
+ * `raise_on_export_fail` (`boolean`) — default is `true`. When this option is enabled, LokaliseManager will re-raise any exceptions that happened during the file uploading. In other words, if any uploading thread raised an exception, your exporting process will exit with an exception. Suppose, you are uploading 12 translation files; these files will be split in 2 groups with 6 files each, and each group will be uploaded in parallel (using threads). However, suppose some exception happens when uploading the first group. By default this exception will be re-raised for the whole process and the script will never try to upload the second group. If you would like to continue uploading even if an exception happened, set the `raise_on_export_fail` to `false`. In this case the `export!` method will return an array with scheduled processes and with information about processes that were not successfully scheduled. This information is represented as an object with three methods: `path` (contains an instance of the `Pathname` class which says which file could not be uploaded), `error` (the actual exception), and `success` (returns `false`). So, you can use the following snippet to check your processes:
174
+
175
+ ```ruby
176
+ processes = exporter.export!
177
+
178
+ processes.each do |proc_data|
179
+ if proc_data.success
180
+ # Everything is good, the uploading is queued
181
+ puts "#{proc_data.path} is sent to Lokalise!"
182
+ process = proc_data.process
183
+ puts "Current process status is #{process.status}"
184
+ else
185
+ # Something bad has happened
186
+ puts "Could not send #{proc_data.path} to Lokalise"
187
+ puts "Error #{proc_data.error.class}: #{proc_data.error.message}"
188
+ # Or you could re-raise this exception:
189
+ # raise proc_data.error.class
190
+ end
191
+ end
192
+ ```
193
+
194
+ * For example, you could collect all the files that were uploaded successfully, re-create the exporter object with the `skip_file_export` option (skipping all files that were successfully imported), and re-run the whole exporting process once again.
163
195
 
164
196
  ### Config to work with formats other than YAML
165
197
 
@@ -268,4 +300,4 @@ importer.import!
268
300
 
269
301
  ## License
270
302
 
271
- Copyright (c) [Lokalise team](http://lokalise.com), [Ilya Bodrov](http://bodrovis.tech). License type is [MIT](https://github.com/bodrovis/lokalise_manager/blob/master/LICENSE).
303
+ Copyright (c) [Lokalise team](http://lokalise.com), [Ilya Bodrov](http://bodrovis.tech). License type is [MIT](https://github.com/bodrovis/lokalise_manager/blob/master/LICENSE).
@@ -7,13 +7,24 @@ module LokaliseManager
7
7
  attr_writer :import_opts, :import_safe_mode, :export_opts, :locales_path,
8
8
  :file_ext_regexp, :skip_file_export, :branch, :timeouts,
9
9
  :translations_loader, :translations_converter, :lang_iso_inferer,
10
- :max_retries_export, :max_retries_import, :use_oauth2_token
10
+ :max_retries_export, :max_retries_import, :use_oauth2_token, :silent_mode,
11
+ :raise_on_export_fail
11
12
 
12
13
  # Main interface to provide configuration options
13
14
  def config
14
15
  yield self
15
16
  end
16
17
 
18
+ # When enabled, will re-raise any exception that happens during file exporting
19
+ def raise_on_export_fail
20
+ @raise_on_export_fail || true
21
+ end
22
+
23
+ # When enabled, won't print any debugging info to $stdout
24
+ def silent_mode
25
+ @silent_mode || false
26
+ end
27
+
17
28
  # When enabled, will use OAuth 2 Lokalise client and will require to provide a token obtained via OAuth 2 flow
18
29
  # rather than via Lokalise profile
19
30
  def use_oauth2_token
@@ -2,11 +2,12 @@
2
2
 
3
3
  require 'ruby-lokalise-api'
4
4
  require 'pathname'
5
- require 'ostruct'
6
5
 
7
6
  module LokaliseManager
8
7
  module TaskDefinitions
9
8
  class Base
9
+ using LokaliseManager::Utils::HashUtils
10
+
10
11
  attr_accessor :config
11
12
 
12
13
  # Creates a new importer or exporter. It accepts custom config and merges it
@@ -23,7 +24,11 @@ module LokaliseManager
23
24
  opts[reader.to_sym] = global_config.send(reader)
24
25
  end
25
26
 
26
- @config = OpenStruct.new primary_opts.merge(custom_opts)
27
+ all_opts = primary_opts.deep_merge(custom_opts)
28
+
29
+ config_klass = Struct.new(*all_opts.keys, keyword_init: true)
30
+
31
+ @config = config_klass.new all_opts
27
32
  end
28
33
 
29
34
  # Creates a Lokalise API client
@@ -33,12 +38,13 @@ module LokaliseManager
33
38
  client_opts = [config.api_token, {enable_compression: true}.merge(config.timeouts)]
34
39
  client_method = config.use_oauth2_token ? :oauth_client : :client
35
40
 
36
- @api_client ||= ::Lokalise.send(client_method, *client_opts)
41
+ @api_client = ::Lokalise.send(client_method, *client_opts)
37
42
  end
38
43
 
39
44
  # Resets API client
40
45
  def reset_api_client!
41
- Lokalise.reset_client!
46
+ ::Lokalise.reset_client!
47
+ ::Lokalise.reset_oauth_client!
42
48
  @api_client = nil
43
49
  end
44
50
 
@@ -5,6 +5,10 @@ require 'base64'
5
5
  module LokaliseManager
6
6
  module TaskDefinitions
7
7
  class Exporter < Base
8
+ using LokaliseManager::Utils::ArrayUtils
9
+
10
+ MAX_THREADS = 6
11
+
8
12
  # Performs translation file export to Lokalise and returns an array of queued processes
9
13
  #
10
14
  # @return [Array]
@@ -12,31 +16,50 @@ module LokaliseManager
12
16
  check_options_errors!
13
17
 
14
18
  queued_processes = []
15
- each_file do |full_path, relative_path|
16
- queued_processes << do_upload(full_path, relative_path)
17
- rescue StandardError => e
18
- raise e.class, "Error while trying to upload #{full_path}: #{e.message}"
19
+
20
+ all_files.in_groups_of(MAX_THREADS) do |files_group|
21
+ parallel_upload(files_group).each do |thr|
22
+ raise_on_fail(thr) if config.raise_on_export_fail
23
+
24
+ queued_processes.push thr
25
+ end
19
26
  end
20
27
 
21
- $stdout.print 'Task complete!'
28
+ $stdout.print('Task complete!') unless config.silent_mode
22
29
 
23
30
  queued_processes
24
31
  end
25
32
 
26
33
  private
27
34
 
35
+ def parallel_upload(files_group)
36
+ files_group.compact.map do |file_data|
37
+ do_upload(*file_data)
38
+ end.map(&:value)
39
+ end
40
+
41
+ def raise_on_fail(thread)
42
+ raise(thread.error.class, "Error while trying to upload #{thread.path}: #{thread.error.message}") unless thread.success
43
+ end
44
+
28
45
  # Performs the actual file uploading to Lokalise. If the API rate limit is exceeed,
29
46
  # applies exponential backoff
30
47
  def do_upload(f_path, r_path)
31
- with_exp_backoff(config.max_retries_export) do
32
- api_client.upload_file project_id_with_branch, opts(f_path, r_path)
48
+ proc_klass = Struct.new(:success, :process, :path, :error, keyword_init: true)
49
+
50
+ Thread.new do
51
+ process = with_exp_backoff(config.max_retries_export) do
52
+ api_client.upload_file project_id_with_branch, opts(f_path, r_path)
53
+ end
54
+ proc_klass.new success: true, process: process, path: f_path
55
+ rescue StandardError => e
56
+ proc_klass.new success: false, path: f_path, error: e
33
57
  end
34
58
  end
35
59
 
36
- # Processes each translation file in the specified directory
37
- def each_file
38
- return unless block_given?
39
-
60
+ # Gets translation files from the specified directory
61
+ def all_files
62
+ files = []
40
63
  loc_path = config.locales_path
41
64
  Dir["#{loc_path}/**/*"].sort.each do |f|
42
65
  full_path = Pathname.new f
@@ -45,8 +68,9 @@ module LokaliseManager
45
68
 
46
69
  relative_path = full_path.relative_path_from Pathname.new(loc_path)
47
70
 
48
- yield full_path, relative_path
71
+ files << [full_path, relative_path]
49
72
  end
73
+ files
50
74
  end
51
75
 
52
76
  # Generates export options
@@ -14,13 +14,13 @@ module LokaliseManager
14
14
  check_options_errors!
15
15
 
16
16
  unless proceed_when_safe_mode?
17
- $stdout.print 'Task cancelled!'
17
+ $stdout.print('Task cancelled!') unless config.silent_mode
18
18
  return false
19
19
  end
20
20
 
21
21
  open_and_process_zip download_files['bundle_url']
22
22
 
23
- $stdout.print 'Task complete!'
23
+ $stdout.print('Task complete!') unless config.silent_mode
24
24
  true
25
25
  end
26
26
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Taken from https://github.com/rails/rails/blob/6bfc637659248df5d6719a86d2981b52662d9b50/activesupport/lib/active_support/core_ext/array/grouping.rb
4
+
5
+ module LokaliseManager
6
+ module Utils
7
+ module ArrayUtils
8
+ refine Array do
9
+ def in_groups_of(number, fill_with = nil, &block)
10
+ if number.to_i <= 0
11
+ raise ArgumentError,
12
+ "Group size must be a positive integer, was #{number.inspect}"
13
+ end
14
+
15
+ if fill_with == false
16
+ collection = self
17
+ else
18
+ padding = (number - (size % number)) % number
19
+ collection = dup.concat(Array.new(padding, fill_with))
20
+ end
21
+
22
+ collection.each_slice(number, &block)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Taken from https://github.com/rails/rails/blob/83217025a171593547d1268651b446d3533e2019/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
4
+
5
+ module LokaliseManager
6
+ module Utils
7
+ module HashUtils
8
+ refine Hash do
9
+ def deep_merge(other_hash, &block)
10
+ dup.deep_merge!(other_hash, &block)
11
+ end
12
+
13
+ # Same as +deep_merge+, but modifies +self+.
14
+ def deep_merge!(other_hash, &block)
15
+ merge!(other_hash) do |key, this_val, other_val|
16
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
17
+ this_val.deep_merge(other_val, &block)
18
+ elsif block
19
+ yield(key, this_val, other_val)
20
+ else
21
+ other_val
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LokaliseManager
4
- VERSION = '1.1.0'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'yaml'
4
4
 
5
+ require 'lokalise_manager/utils/hash_utils'
6
+ require 'lokalise_manager/utils/array_utils'
7
+
5
8
  require 'lokalise_manager/version'
6
9
  require 'lokalise_manager/error'
7
10
  require 'lokalise_manager/global_config'
@@ -32,7 +32,10 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency 'rspec', '~> 3.6'
33
33
  spec.add_development_dependency 'rubocop', '~> 1.0'
34
34
  spec.add_development_dependency 'rubocop-performance', '~> 1.5'
35
- spec.add_development_dependency 'rubocop-rspec', '~> 2.5.0'
35
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.6'
36
36
  spec.add_development_dependency 'simplecov', '~> 0.16'
37
37
  spec.add_development_dependency 'vcr', '~> 6.0'
38
+ spec.metadata = {
39
+ 'rubygems_mfa_required' => 'true'
40
+ }
38
41
  end
@@ -14,6 +14,16 @@ describe LokaliseManager::GlobalConfig do
14
14
  fake_class.project_id = '123.abc'
15
15
  end
16
16
 
17
+ it 'is possible to set raise_on_export_fail' do
18
+ allow(fake_class).to receive(:raise_on_export_fail=).with(false)
19
+ fake_class.raise_on_export_fail = false
20
+ end
21
+
22
+ it 'is possible to set silent_mode' do
23
+ allow(fake_class).to receive(:silent_mode=).with(true)
24
+ fake_class.silent_mode = true
25
+ end
26
+
17
27
  it 'is possible to set use_oauth2_token' do
18
28
  allow(fake_class).to receive(:use_oauth2_token=).with(true)
19
29
  fake_class.use_oauth2_token = true
@@ -24,7 +24,7 @@ describe LokaliseManager::TaskDefinitions::Base do
24
24
  specify '.reset_client!' do
25
25
  expect(described_object.api_client).to be_an_instance_of(Lokalise::Client)
26
26
  described_object.reset_api_client!
27
- current_client = described_object.instance_variable_get '@api_client'
27
+ current_client = described_object.instance_variable_get :@api_client
28
28
  expect(current_client).to be_nil
29
29
  end
30
30
 
@@ -24,26 +24,52 @@ describe LokaliseManager::TaskDefinitions::Exporter do
24
24
 
25
25
  describe '.export!' do
26
26
  it 'sends a proper API request and handles rate limiting' do
27
- process = VCR.use_cassette('upload_files_multiple') do
28
- described_object.export!
29
- end.first
27
+ process = nil
28
+
29
+ VCR.use_cassette('upload_files_multiple') do
30
+ expect(-> { process = described_object.export!.first.process }).to output(/complete!/).to_stdout
31
+ end
30
32
 
31
33
  expect(process.project_id).to eq(project_id)
32
34
  expect(process.status).to eq('queued')
33
35
  end
34
36
 
35
37
  it 'handles too many requests' do
38
+ allow(described_object.config).to receive(:max_retries_export).and_return(1)
39
+ allow(described_object).to receive(:sleep).and_return(0)
40
+
41
+ fake_client = instance_double('Lokalise::Client')
42
+ allow(fake_client).to receive(:token).with(any_args).and_return('fake_token')
43
+ allow(fake_client).to receive(:upload_file).with(any_args).and_raise(Lokalise::Error::TooManyRequests)
44
+ allow(described_object).to receive(:api_client).and_return(fake_client)
45
+
46
+ expect(-> { described_object.export! }).to raise_error(Lokalise::Error::TooManyRequests, /Gave up after 1 retries/i)
47
+
48
+ expect(described_object).to have_received(:sleep).exactly(6).times
49
+ expect(described_object).to have_received(:api_client).at_least(12).times
50
+ expect(fake_client).to have_received(:upload_file).exactly(12).times
51
+ end
52
+
53
+ it 'handles too many requests but does not re-raise anything when raise_on_export_fail is false' do
54
+ allow(described_object.config).to receive(:max_retries_export).and_return(1)
55
+ allow(described_object.config).to receive(:raise_on_export_fail).and_return(false)
36
56
  allow(described_object).to receive(:sleep).and_return(0)
37
57
 
38
58
  fake_client = instance_double('Lokalise::Client')
59
+ allow(fake_client).to receive(:token).with(any_args).and_return('fake_token')
39
60
  allow(fake_client).to receive(:upload_file).with(any_args).and_raise(Lokalise::Error::TooManyRequests)
40
61
  allow(described_object).to receive(:api_client).and_return(fake_client)
62
+ processes = []
63
+ expect(-> { processes = described_object.export! }).not_to raise_error
41
64
 
42
- expect(-> { described_object.export! }).to raise_error(Lokalise::Error::TooManyRequests, /Gave up after 2 retries/i)
65
+ expect(processes[0].path.to_s).to include('en_0')
66
+ expect(processes[0].success).to be false
67
+ expect(processes[1].error.class).to eq(Lokalise::Error::TooManyRequests)
68
+ expect(processes.count).to eq(7)
43
69
 
44
- expect(described_object).to have_received(:sleep).exactly(2).times
45
- expect(described_object).to have_received(:api_client).exactly(3).times
46
- expect(fake_client).to have_received(:upload_file).exactly(3).times
70
+ expect(described_object).to have_received(:sleep).exactly(7).times
71
+ expect(described_object).to have_received(:api_client).at_least(14).times
72
+ expect(fake_client).to have_received(:upload_file).exactly(14).times
47
73
  end
48
74
  end
49
75
  end
@@ -58,10 +84,23 @@ describe LokaliseManager::TaskDefinitions::Exporter do
58
84
  end
59
85
 
60
86
  describe '.export!' do
87
+ it 'sends a proper API request but does not output anything when silent_mode is enabled' do
88
+ allow(described_object.config).to receive(:silent_mode).and_return(true)
89
+
90
+ process = nil
91
+
92
+ VCR.use_cassette('upload_files') do
93
+ expect(-> { process = described_object.export!.first.process }).not_to output(/complete!/).to_stdout
94
+ end
95
+
96
+ expect(process.status).to eq('queued')
97
+ expect(described_object.config).to have_received(:silent_mode).at_most(1).times
98
+ end
99
+
61
100
  it 'sends a proper API request' do
62
101
  process = VCR.use_cassette('upload_files') do
63
102
  described_object.export!
64
- end.first
103
+ end.first.process
65
104
 
66
105
  expect(process.project_id).to eq(project_id)
67
106
  expect(process.status).to eq('queued')
@@ -70,11 +109,16 @@ describe LokaliseManager::TaskDefinitions::Exporter do
70
109
  it 'sends a proper API request when a different branch is provided' do
71
110
  allow(described_object.config).to receive(:branch).and_return('develop')
72
111
 
73
- process = VCR.use_cassette('upload_files_branch') do
112
+ process_data = VCR.use_cassette('upload_files_branch') do
74
113
  described_object.export!
75
114
  end.first
76
115
 
77
116
  expect(described_object.config).to have_received(:branch).at_most(2).times
117
+ expect(process_data.success).to be true
118
+ expect(process_data.path.to_s).to include('en.yml')
119
+
120
+ process = process_data.process
121
+ expect(process).to be_an_instance_of(Lokalise::Resources::QueuedProcess)
78
122
  expect(process.project_id).to eq(project_id)
79
123
  expect(process.status).to eq('queued')
80
124
  end
@@ -93,9 +137,9 @@ describe LokaliseManager::TaskDefinitions::Exporter do
93
137
  end
94
138
  end
95
139
 
96
- describe '.each_file' do
140
+ describe '#all_files' do
97
141
  it 'yield proper arguments' do
98
- expect { |b| described_object.send(:each_file, &b) }.to yield_with_args(
142
+ expect(described_object.send(:all_files).first).to include(
99
143
  Pathname.new(path),
100
144
  Pathname.new(relative_name)
101
145
  )
@@ -165,30 +209,29 @@ describe LokaliseManager::TaskDefinitions::Exporter do
165
209
  end
166
210
  end
167
211
 
168
- describe '.each_file' do
169
- it 'yields every translation file' do
170
- expect { |b| described_object.send(:each_file, &b) }.to yield_successive_args(
171
- [
172
- Pathname.new(path),
173
- Pathname.new(relative_name)
174
- ],
175
- [
176
- Pathname.new(path_ru),
177
- Pathname.new(filename_ru)
178
- ]
212
+ describe '#each_file' do
213
+ it 'returns all files' do
214
+ files = described_object.send(:all_files)
215
+ expect(files[0]).to include(
216
+ Pathname.new(path),
217
+ Pathname.new(relative_name)
218
+ )
219
+ expect(files[1]).to include(
220
+ Pathname.new(path_ru),
221
+ Pathname.new(filename_ru)
179
222
  )
180
223
  end
181
224
 
182
- it 'does not yield files that have to be skipped' do
225
+ it 'does not return files that have to be skipped' do
183
226
  allow(described_object.config).to receive(:skip_file_export).twice.and_return(
184
227
  ->(f) { f.split[1].to_s.include?('ru') }
185
228
  )
186
- expect { |b| described_object.send(:each_file, &b) }.to yield_successive_args(
187
- [
188
- Pathname.new(path),
189
- Pathname.new(relative_name)
190
- ]
229
+ files = described_object.send(:all_files)
230
+ expect(files[0]).to include(
231
+ Pathname.new(path),
232
+ Pathname.new(relative_name)
191
233
  )
234
+ expect(files.count).to eq(1)
192
235
 
193
236
  expect(described_object.config).to have_received(:skip_file_export).twice
194
237
  end
@@ -9,12 +9,16 @@ describe LokaliseManager::TaskDefinitions::Importer do
9
9
  let(:loc_path) { described_object.config.locales_path }
10
10
  let(:project_id) { ENV['LOKALISE_PROJECT_ID'] }
11
11
  let(:local_trans) { "#{Dir.getwd}/spec/fixtures/trans.zip" }
12
- let(:faulty_trans) { "#{Dir.getwd}/spec/fixtures/faulty_trans.zip" }
13
12
 
14
- describe '.open_and_process_zip' do
13
+ describe '#open_and_process_zip' do
15
14
  it 're-raises errors during file processing' do
16
- expect(-> { described_object.send(:open_and_process_zip, faulty_trans) }).
17
- to raise_error(Psych::DisallowedClass, /Error when trying to process fail\.yml/)
15
+ entry = double
16
+ allow(entry).to receive(:name).and_return('fail.yml')
17
+ allow(described_object).to receive(:data_from).with(entry).and_raise(EncodingError)
18
+ expect(-> { described_object.send(:process!, entry) }).
19
+ to raise_error(EncodingError, /Error when trying to process fail\.yml/)
20
+
21
+ expect(described_object).to have_received(:data_from)
18
22
  end
19
23
 
20
24
  it 're-raises errors during file opening' do
@@ -23,7 +27,7 @@ describe LokaliseManager::TaskDefinitions::Importer do
23
27
  end
24
28
  end
25
29
 
26
- describe '.download_files' do
30
+ describe '#download_files' do
27
31
  it 'returns a proper download URL' do
28
32
  response = VCR.use_cassette('download_files') do
29
33
  described_object.send :download_files
@@ -103,15 +107,31 @@ describe LokaliseManager::TaskDefinitions::Importer do
103
107
  end
104
108
 
105
109
  it 'runs import successfully' do
106
- result = VCR.use_cassette('import') do
107
- described_object.import!
110
+ result = nil
111
+
112
+ VCR.use_cassette('import') do
113
+ expect(-> { result = described_object.import! }).to output(/complete!/).to_stdout
108
114
  end
109
115
 
110
116
  expect(result).to be true
111
117
 
112
- expect(count_translations).to eq(8)
113
- expect_file_exist loc_path, 'en.yml'
114
- expect_file_exist loc_path, 'ru.yml'
118
+ expect(count_translations).to eq(24)
119
+ expect_file_exist loc_path, 'en_1.yml'
120
+ expect_file_exist loc_path, 'ru_2.yml'
121
+ end
122
+
123
+ it 'runs import successfully but does not provide any output when silent_mode is enabled' do
124
+ allow(described_object.config).to receive(:silent_mode).and_return(true)
125
+ result = nil
126
+
127
+ VCR.use_cassette('import') do
128
+ expect(-> { result = described_object.import! }).not_to output(/complete!/).to_stdout
129
+ end
130
+
131
+ expect(result).to be true
132
+ expect_file_exist loc_path, 'en_1.yml'
133
+ expect_file_exist loc_path, 'ru_2.yml'
134
+ expect(described_object.config).to have_received(:silent_mode).at_most(1).times
115
135
  end
116
136
  end
117
137
 
@@ -157,9 +177,21 @@ describe LokaliseManager::TaskDefinitions::Importer do
157
177
  it 'import halts when a user chooses not to proceed' do
158
178
  allow(safe_mode_obj).to receive(:download_files).at_most(0).times
159
179
  allow($stdin).to receive(:gets).and_return('N')
160
- expect(-> { safe_mode_obj.import! }).to output(/is not empty/).to_stdout
180
+ expect(-> { safe_mode_obj.import! }).to output(/cancelled/).to_stdout
181
+
182
+ expect(safe_mode_obj).not_to have_received(:download_files)
183
+ expect($stdin).to have_received(:gets)
184
+ expect(count_translations).to eq(1)
185
+ end
186
+
187
+ it 'import halts when a user chooses not to proceed and debug info is not printed out when silent_mode is enabled' do
188
+ allow(safe_mode_obj.config).to receive(:silent_mode).and_return(true)
189
+ allow(safe_mode_obj).to receive(:download_files).at_most(0).times
190
+ allow($stdin).to receive(:gets).and_return('N')
191
+ expect(-> { safe_mode_obj.import! }).not_to output(/cancelled/).to_stdout
161
192
 
162
193
  expect(safe_mode_obj).not_to have_received(:download_files)
194
+ expect(safe_mode_obj.config).to have_received(:silent_mode)
163
195
  expect($stdin).to have_received(:gets)
164
196
  expect(count_translations).to eq(1)
165
197
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe LokaliseManager::Utils::ArrayUtils do
4
+ using described_class
5
+ let(:arr) { (1..8).to_a }
6
+
7
+ describe '#in_groups_of' do
8
+ it 'raises an exception when the number is less than 1' do
9
+ expect(-> { arr.in_groups_of(-1) }).to raise_error(ArgumentError)
10
+ end
11
+
12
+ it 'uses collection itself if fill_with is false' do
13
+ enum = arr.in_groups_of(5, false)
14
+ enum.next
15
+ expect(enum.next.count).to eq(3)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe LokaliseManager::Utils::HashUtils do
4
+ using described_class
5
+ let(:h1) { {a: 100, b: 200, c: {c1: 100}} }
6
+ let(:h2) { {b: 250, c: {c1: 200}} }
7
+
8
+ specify '#deep_merge' do
9
+ result = h1.deep_merge(h2) { |_key, this_val, other_val| this_val + other_val }
10
+ expect(result[:b]).to eq(450)
11
+ expect(result[:c][:c1]).to eq(300)
12
+ end
13
+ end
data/spec/support/vcr.rb CHANGED
@@ -6,6 +6,8 @@ VCR.configure do |c|
6
6
  c.ignore_hosts 'codeclimate.com'
7
7
  c.hook_into :faraday
8
8
  c.cassette_library_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'vcr_cassettes')
9
- c.filter_sensitive_data('<LOKALISE_TOKEN>') { ENV.fetch('LOKALISE_API_TOKEN') }
9
+ c.filter_sensitive_data('<LOKALISE_TOKEN>') do |_i|
10
+ ENV.fetch('LOKALISE_API_TOKEN')
11
+ end
10
12
  c.configure_rspec_metadata!
11
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lokalise_manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ilya Bodrov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-25 00:00:00.000000000 Z
11
+ date: 2022-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-lokalise-api
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 2.5.0
131
+ version: '2.6'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 2.5.0
138
+ version: '2.6'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: simplecov
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -187,6 +187,8 @@ files:
187
187
  - lib/lokalise_manager/task_definitions/base.rb
188
188
  - lib/lokalise_manager/task_definitions/exporter.rb
189
189
  - lib/lokalise_manager/task_definitions/importer.rb
190
+ - lib/lokalise_manager/utils/array_utils.rb
191
+ - lib/lokalise_manager/utils/hash_utils.rb
190
192
  - lib/lokalise_manager/version.rb
191
193
  - lokalise_manager.gemspec
192
194
  - spec/lib/lokalise_manager/global_config_spec.rb
@@ -194,6 +196,8 @@ files:
194
196
  - spec/lib/lokalise_manager/task_definitions/exporter_spec.rb
195
197
  - spec/lib/lokalise_manager/task_definitions/importer_spec.rb
196
198
  - spec/lib/lokalise_manager_spec.rb
199
+ - spec/lib/utils/array_utils_spec.rb
200
+ - spec/lib/utils/hash_utils_spec.rb
197
201
  - spec/spec_helper.rb
198
202
  - spec/support/file_manager.rb
199
203
  - spec/support/spec_addons.rb
@@ -201,7 +205,8 @@ files:
201
205
  homepage: https://github.com/bodrovis/lokalise_manager
202
206
  licenses:
203
207
  - MIT
204
- metadata: {}
208
+ metadata:
209
+ rubygems_mfa_required: 'true'
205
210
  post_install_message:
206
211
  rdoc_options: []
207
212
  require_paths:
@@ -217,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
222
  - !ruby/object:Gem::Version
218
223
  version: '0'
219
224
  requirements: []
220
- rubygems_version: 3.2.26
225
+ rubygems_version: 3.3.5
221
226
  signing_key:
222
227
  specification_version: 4
223
228
  summary: Lokalise integration for Ruby
@@ -227,6 +232,8 @@ test_files:
227
232
  - spec/lib/lokalise_manager/task_definitions/exporter_spec.rb
228
233
  - spec/lib/lokalise_manager/task_definitions/importer_spec.rb
229
234
  - spec/lib/lokalise_manager_spec.rb
235
+ - spec/lib/utils/array_utils_spec.rb
236
+ - spec/lib/utils/hash_utils_spec.rb
230
237
  - spec/spec_helper.rb
231
238
  - spec/support/file_manager.rb
232
239
  - spec/support/spec_addons.rb