toptranslation_cli 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-spinner'
4
+ require 'tty-progressbar'
5
+ require 'pastel'
6
+
7
+ module ToptranslationCli
8
+ class Push # rubocop:disable Metrics/ClassLength
9
+ using Threaded
10
+
11
+ class << self
12
+ def run
13
+ new.run
14
+ end
15
+ end
16
+
17
+ def initialize
18
+ ToptranslationCli.configuration.load
19
+
20
+ @pastel = Pastel.new
21
+ @spinner_settings = { success_mark: @pastel.green('+'), error_mark: @pastel.red('-') }
22
+ @spinner = TTY::Spinner.new("[#{@pastel.yellow(':spinner')}] :title", @spinner_settings)
23
+ end
24
+
25
+ def run
26
+ @documents = fetch_documents
27
+ changed = changed_files(local_files)
28
+ upload_files(changed)
29
+ rescue RestClient::Forbidden
30
+ @spinner.error('invalid access token')
31
+ exit 1
32
+ end
33
+
34
+ private
35
+
36
+ def verbose?
37
+ ToptranslationCli.configuration.verbose
38
+ end
39
+
40
+ def changed_files(files)
41
+ @spinner.update(title: 'Checking changed files...')
42
+ @spinner.auto_spin
43
+ changed = files.select do |file|
44
+ translation = translation_for_file(file)
45
+ translation_changed?(translation, file)
46
+ end
47
+ @spinner.success(@pastel.green("found #{changed.count} changed file(s)"))
48
+ changed
49
+ end
50
+
51
+ def upload_files(files)
52
+ return if files.empty?
53
+
54
+ grouped_files = files.group_by { |file| file[:placeholder_path] }.values
55
+
56
+ bar = TTY::ProgressBar.new('Uploading [:bar] :percent [:current/:total]', total: grouped_files.flatten.count)
57
+ bar.render
58
+
59
+ grouped_files.each_in_threads(8, true) do |file|
60
+ upload_file(file)
61
+ bar.synchronize { bar.log(file[:path]) }
62
+ bar.advance
63
+ end
64
+ end
65
+
66
+ def translation_for_file(file)
67
+ remote_document = find_document_by_path(@documents, file[:placeholder_path])
68
+ find_translation_by_locale(remote_document&.translations || [], file[:locale])
69
+ end
70
+
71
+ def mark_unchanged(spinner)
72
+ spinner.instance_variable_set(:@success_mark, @pastel.blue('='))
73
+ spinner.success(@pastel.blue('skipping unchanged file'))
74
+ end
75
+
76
+ def fetch_documents
77
+ @spinner.update(title: 'Checking remote documents...')
78
+ @spinner.auto_spin
79
+ documents = project&.documents.to_a
80
+ @spinner.success(@pastel.green("found #{documents.count} document(s)"))
81
+ documents
82
+ end
83
+
84
+ def local_files
85
+ @spinner.update(title: 'Checking local translations...')
86
+ @spinner.auto_spin
87
+ files = ToptranslationCli.configuration.files.flat_map do |path_definition|
88
+ project_locales.flat_map { |locale| file_to_upload(path_definition, locale) }
89
+ end
90
+ @spinner.success(@pastel.green("found #{files.count} file(s)"))
91
+ files
92
+ end
93
+
94
+ def file_to_upload(path_definition, locale)
95
+ placeholder_path = PlaceholderPath.new(path_definition)
96
+ FileFinder.new(path_definition).files(locale.code).flat_map do |path|
97
+ the_placeholder_path = placeholder_path.for_path(path, locale.code)
98
+ {
99
+ path: path,
100
+ placeholder_path: the_placeholder_path,
101
+ locale: locale, sha1: sha1_checksum(path)
102
+ }
103
+ end
104
+ end
105
+
106
+ def upload_file(file)
107
+ project.upload_document(
108
+ file[:path],
109
+ file[:locale].code,
110
+ path: file[:placeholder_path],
111
+ name: File.basename(file[:path])
112
+ )
113
+ end
114
+
115
+ def translation_changed?(translation, file)
116
+ translation.nil? || translation.sha1 != file[:sha1]
117
+ end
118
+
119
+ def sha1_checksum(path)
120
+ Digest::SHA1.file(path).hexdigest
121
+ end
122
+
123
+ def find_document_by_path(documents, path_with_placeholder)
124
+ documents.detect { |document| document.path == path_with_placeholder }
125
+ end
126
+
127
+ def find_translation_by_locale(translations, locale)
128
+ translations.detect { |translation| translation.locale.code == locale.code }
129
+ end
130
+
131
+ def project_locales
132
+ @project_locales ||= project.locales
133
+ end
134
+
135
+ def project
136
+ @project ||= ToptranslationCli.connection.projects.find(ToptranslationCli.configuration.project_identifier)
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ToptranslationCli
4
+ class Status
5
+ class << self
6
+ def run
7
+ ToptranslationCli.configuration.load
8
+
9
+ proj = project
10
+
11
+ local_files = FileFinder.local_files(proj)
12
+ remote_files = FileFinder.remote_files(proj)
13
+
14
+ only_local, only_remote, changed = diff(local_files, remote_files)
15
+
16
+ print_status(only_local, only_remote, changed)
17
+
18
+ (only_local + only_remote + changed).length
19
+ end
20
+
21
+ private
22
+
23
+ def diff(local, remote)
24
+ only_local = local.keys - remote.keys
25
+ only_remote = remote.keys - local.keys
26
+ changed = changed_files(local, remote, only_local, only_remote)
27
+
28
+ [only_local, only_remote, changed]
29
+ end
30
+
31
+ def changed_files(local, remote, only_local, only_remote)
32
+ (local.to_a - remote.to_a | remote.to_a - local.to_a)
33
+ .flat_map(&:first)
34
+ .uniq - only_remote - only_local
35
+ end
36
+
37
+ def print_status(only_local, only_remote, changed)
38
+ print_section 'Local: These documents exist only locally', only_local
39
+ print_section 'Changed: These documents exist both locally and remotely but differ', changed
40
+ print_section 'Remote: These documents exist only remotely', only_remote
41
+ end
42
+
43
+ def print_section(description, paths)
44
+ return if paths.empty?
45
+
46
+ puts <<~SECTION
47
+ #{description}
48
+
49
+ #{paths.sort.map { |path| "\t#{path}" }.join("\n")}
50
+
51
+ SECTION
52
+ end
53
+
54
+ def project
55
+ ToptranslationCli
56
+ .connection
57
+ .projects
58
+ .find(ToptranslationCli.configuration.project_identifier)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ToptranslationCli
4
+ module Threaded
5
+ refine Enumerable do
6
+ def in_chunks(chunks, flatten = false)
7
+ chunk_size = [size, (size / chunks.to_f).ceil].select(&:positive?).min
8
+ chunked = each_slice(chunk_size)
9
+
10
+ chunked = chunked.map(&:flatten) if flatten
11
+ chunked
12
+ end
13
+
14
+ def each_in_threads(num_threads, flatten = false)
15
+ in_chunks(num_threads, flatten).map do |chunk|
16
+ Thread.new do
17
+ chunk.each do |item|
18
+ yield item
19
+ end
20
+ end
21
+ end.each(&:join)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ToptranslationCli
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ # Maintain your gem's version:
7
+ require 'toptranslation_cli/version'
8
+
9
+ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
10
+ s.name = 'toptranslation_cli'
11
+ s.version = ToptranslationCli::VERSION
12
+ s.summary = 'Toptranslation command line client'
13
+ s.description = 'A gem for synching local files with Toptranslation translation service.'
14
+ s.authors = ['Toptranslation GmbH']
15
+ s.email = 'tech@toptranslation.com'
16
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ s.bindir = 'exe'
20
+ s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ s.homepage = 'https://developer.toptranslation.com'
22
+ s.license = 'MIT'
23
+ s.metadata = { 'source_code_uri' => 'https://github.com/Toptranslation/tt_cli' }
24
+ s.required_ruby_version = '>= 2.3'
25
+
26
+ s.add_runtime_dependency 'pastel', '~> 0.7'
27
+ s.add_runtime_dependency 'thor', '~> 0.20'
28
+ s.add_runtime_dependency 'toptranslation_api', '~> 2.5'
29
+ s.add_runtime_dependency 'tty-progressbar', '~> 0.17'
30
+ s.add_runtime_dependency 'tty-prompt', '~> 0.16'
31
+ s.add_runtime_dependency 'tty-spinner', '~> 0.8'
32
+ s.add_development_dependency 'pry'
33
+ s.add_development_dependency 'rake', '~> 10.0'
34
+ s.add_development_dependency 'rspec', '~> 3.4'
35
+ s.add_development_dependency 'rubocop', '~> 0.74.0'
36
+ s.add_development_dependency 'rubocop-rspec', '~> 1.35.0'
37
+ end
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toptranslation_cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Toptranslation GmbH
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-10-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pastel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.20'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.20'
41
+ - !ruby/object:Gem::Dependency
42
+ name: toptranslation_api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-progressbar
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.17'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.17'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-prompt
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.16'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.16'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-spinner
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.8'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.4'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.4'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.74.0
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.74.0
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 1.35.0
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 1.35.0
167
+ description: A gem for synching local files with Toptranslation translation service.
168
+ email: tech@toptranslation.com
169
+ executables:
170
+ - tt
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".github/workflows/tests.yml"
175
+ - ".gitignore"
176
+ - ".rspec"
177
+ - ".rubocop.yml"
178
+ - CHANGELOG.md
179
+ - Gemfile
180
+ - LICENSE
181
+ - README.md
182
+ - Rakefile
183
+ - exe/tt
184
+ - lib/toptranslation_cli.rb
185
+ - lib/toptranslation_cli/check.rb
186
+ - lib/toptranslation_cli/configuration.rb
187
+ - lib/toptranslation_cli/file_finder.rb
188
+ - lib/toptranslation_cli/info.rb
189
+ - lib/toptranslation_cli/initializer.rb
190
+ - lib/toptranslation_cli/placeholder_path.rb
191
+ - lib/toptranslation_cli/pull.rb
192
+ - lib/toptranslation_cli/push.rb
193
+ - lib/toptranslation_cli/status.rb
194
+ - lib/toptranslation_cli/threaded.rb
195
+ - lib/toptranslation_cli/version.rb
196
+ - toptranslation_cli.gemspec
197
+ homepage: https://developer.toptranslation.com
198
+ licenses:
199
+ - MIT
200
+ metadata:
201
+ source_code_uri: https://github.com/Toptranslation/tt_cli
202
+ post_install_message:
203
+ rdoc_options: []
204
+ require_paths:
205
+ - lib
206
+ required_ruby_version: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: '2.3'
211
+ required_rubygems_version: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ requirements: []
217
+ rubygems_version: 3.0.3
218
+ signing_key:
219
+ specification_version: 4
220
+ summary: Toptranslation command line client
221
+ test_files: []