i18n-tasks 0.7.13 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +11 -0
  3. data/Gemfile +5 -3
  4. data/README.md +27 -126
  5. data/bin/i18n-tasks +2 -45
  6. data/config/locales/en.yml +11 -14
  7. data/config/locales/ru.yml +0 -3
  8. data/i18n-tasks.gemspec +0 -1
  9. data/lib/i18n/tasks.rb +14 -2
  10. data/lib/i18n/tasks/cli.rb +195 -0
  11. data/lib/i18n/tasks/command/collection.rb +2 -2
  12. data/lib/i18n/tasks/command/commander.rb +4 -24
  13. data/lib/i18n/tasks/command/commands/data.rb +17 -19
  14. data/lib/i18n/tasks/command/commands/eq_base.rb +2 -2
  15. data/lib/i18n/tasks/command/commands/health.rb +2 -2
  16. data/lib/i18n/tasks/command/commands/meta.rb +2 -2
  17. data/lib/i18n/tasks/command/commands/missing.rb +22 -15
  18. data/lib/i18n/tasks/command/commands/tree.rb +31 -35
  19. data/lib/i18n/tasks/command/commands/usages.rb +10 -11
  20. data/lib/i18n/tasks/command/commands/xlsx.rb +3 -3
  21. data/lib/i18n/tasks/command/dsl.rb +22 -7
  22. data/lib/i18n/tasks/command/option_parsers/enum.rb +53 -0
  23. data/lib/i18n/tasks/command/option_parsers/locale.rb +48 -0
  24. data/lib/i18n/tasks/command/options/common.rb +21 -31
  25. data/lib/i18n/tasks/command/options/data.rb +91 -0
  26. data/lib/i18n/tasks/command/options/locales.rb +22 -42
  27. data/lib/i18n/tasks/data/file_system_base.rb +2 -2
  28. data/lib/i18n/tasks/data/tree/node.rb +3 -3
  29. data/lib/i18n/tasks/data/tree/nodes.rb +3 -2
  30. data/lib/i18n/tasks/data/tree/siblings.rb +0 -1
  31. data/lib/i18n/tasks/logging.rb +9 -5
  32. data/lib/i18n/tasks/scanners/base_scanner.rb +6 -7
  33. data/lib/i18n/tasks/scanners/relative_keys.rb +1 -1
  34. data/lib/i18n/tasks/version.rb +1 -1
  35. data/spec/commands/data_commands_spec.rb +4 -4
  36. data/spec/commands/tree_commands_spec.rb +5 -5
  37. data/spec/google_translate_spec.rb +2 -3
  38. data/spec/i18n_spec.rb +0 -1
  39. data/spec/i18n_tasks_spec.rb +44 -23
  40. data/spec/readme_spec.rb +1 -1
  41. data/spec/support/test_codebase.rb +21 -10
  42. data/templates/config/i18n-tasks.yml +51 -39
  43. data/templates/rspec/i18n_spec.rb +0 -1
  44. metadata +6 -23
  45. data/lib/i18n/tasks/command/dsl/cmd.rb +0 -19
  46. data/lib/i18n/tasks/command/dsl/cmd_opt.rb +0 -19
  47. data/lib/i18n/tasks/command/dsl/enum_opt.rb +0 -45
  48. data/lib/i18n/tasks/command/options/enum_opt.rb +0 -50
  49. data/lib/i18n/tasks/command/options/list_opt.rb +0 -11
  50. data/lib/i18n/tasks/command/options/trees.rb +0 -81
  51. data/lib/i18n/tasks/slop_command.rb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f23b45a33ecdc2b1ff1ce24733bbfc7561e64462
4
- data.tar.gz: 4b09900b186afbf6ee67276cca45847f0d8e15f5
3
+ metadata.gz: 4ffa593b45f4a5d40746da85f93879fffce47423
4
+ data.tar.gz: 689a92c8047df2b9b56638e2a9f3b9c46c43e115
5
5
  SHA512:
6
- metadata.gz: 8b1cca5e5a2f5ede2defb56fe16bf9f15ba1546c2b610ebaeb282538611a800ad93f8cfb7521b13888cbecda2ca73e2718e2624250f989ffd0a7ad7350a557cf
7
- data.tar.gz: d42a79921370c760f5fc557c9344d47e921580abf198fb00e19138474825b609f80104965eb7fc753de3ecacbf01a5e46390e8898e44c5a71c6806b2524c24ba
6
+ metadata.gz: 6d988d11c0b8690cb5596845a61b818eb9aa0702c6b84075c65b99a9fe0670c2435dfdc746d1cb5b92fb1f6929e9fc73b9bf7e8e8c76ab199a7c2517bfda782d
7
+ data.tar.gz: b071f96e5d5b912c95848ea6b363185b26a216fc968ae9657e1d28950b753f0933b9fc9c8f29e105a1ff2e8a4a324fc706ed33e53128c9074411980830628d2f
data/CHANGES.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.8.0
2
+
3
+ * Parse command line arguments with `optparse`. Remove dependency on Slop.
4
+ Simplified commands DSL: options are mostly passed directly to optparse.
5
+ * `search.relative_roots` default changed from from `%w(app/views)` to
6
+ `%w(app/views app/controllers app/helpers app/presenters)`.
7
+ * `add-missing` now adds keys detected in source to all locales (previously just base) [#134](https://github.com/glebm/i18n-tasks/issues/134).
8
+ * The default spec template no long requires `spec_helper` by default [Daniel Levenson](https://github.com/dleve123) [#135](https://github.com/glebm/i18n-tasks/pull/135).
9
+ * `search.exclude` now appends to and not overrides the default exclude list. More extensions excluded by default:
10
+ *.css, *.sass, *.scss, *.less, *.yml, and *.json. [#137](https://github.com/glebm/i18n-tasks/issues/137).
11
+
1
12
  ## 0.7.13
2
13
 
3
14
  * Fix relative keys when controller name consists of more than one word by [Yuji Nakayama](https://github.com/yujinakayama) [#132](https://github.com/glebm/i18n-tasks/pull/132).
data/Gemfile CHANGED
@@ -3,9 +3,11 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in i18n-tasks.gemspec
4
4
  gemspec
5
5
 
6
- group :development do
7
- gem 'byebug', platforms: [:mri_21, :mri_22], require: false
8
- gem 'rubinius-debugger', platform: :rbx, require: false
6
+ unless ENV['TRAVIS']
7
+ group :development do
8
+ gem 'byebug', platforms: [:mri_21, :mri_22], require: false
9
+ gem 'rubinius-debugger', platform: :rbx, require: false
10
+ end
9
11
  end
10
12
 
11
13
  gem 'codeclimate-test-reporter', group: :test, require: nil
data/README.md CHANGED
@@ -22,7 +22,7 @@ i18n-tasks can be used with any project using the ruby [i18n gem][i18n-gem] (def
22
22
  Add it to the Gemfile:
23
23
 
24
24
  ```ruby
25
- gem 'i18n-tasks', '~> 0.7.13'
25
+ gem 'i18n-tasks', '~> 0.8.0'
26
26
  ```
27
27
 
28
28
  Copy default [configuration file](#configuration) (optional):
@@ -177,7 +177,7 @@ For now, you can disable dynamic key inference by passing `-s` or `--strict` to
177
177
  Configuration is read from `config/i18n-tasks.yml` or `config/i18n-tasks.yml.erb`.
178
178
  Inspect configuration with `i18n-tasks config`.
179
179
 
180
- Install the default config file with:
180
+ Install the [default config file][config] with:
181
181
 
182
182
  ```console
183
183
  $ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
@@ -188,60 +188,16 @@ Settings are compatible with Rails by default.
188
188
  ### Locales
189
189
 
190
190
  By default, `base_locale` is set to `en` and `locales` are inferred from the paths to data files.
191
- You can override these in the config:
192
-
193
- ```yaml
194
- # config/i18n-tasks.yml
195
- base_locale: en
196
- locales: [es, fr] # This includes base_locale by default
197
- ```
198
-
199
- `internal_locale` controls the language i18n-tasks reports in. Locales available are `en` and `ru` (pull request to add more!).
200
-
201
- ```yaml
202
- internal_locale: en
203
- ```
191
+ You can override these in the [config][config].
204
192
 
205
193
  ### Storage
206
194
 
207
195
  The default data adapter supports YAML and JSON files.
208
196
 
209
- ```yaml
210
- # config/i18n-tasks.yml
211
- data:
212
- # configure YAML / JSON serializer options
213
- # passed directly to load / dump / parse / serialize.
214
- yaml:
215
- write:
216
- # do not wrap lines at 80 characters (override default)
217
- line_width: -1
218
- ```
219
-
220
197
  #### Multiple locale files
221
198
 
222
- Use `data` options to work with locale data spread over multiple files.
223
-
224
- `data.read` accepts a list of file globs to read from per-locale:
225
-
226
- ```
227
- # config/i18n-tasks.yml
228
- data:
229
- read:
230
- # read from namespaced files, e.g. simple_form.en.yml
231
- - 'config/locales/*.%{locale}.yml'
232
- # read from a gem (config is parsed with ERB first, then YAML)
233
- - "<%= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
234
- # default
235
- - 'config/locales/%{locale}.yml'
236
- ```
237
-
238
- #### Key pattern syntax
239
-
240
- | syntax | description |
241
- |:------------:|:----------------------------------------------------------|
242
- | `*` | matches everything |
243
- | `:` | matches a single key |
244
- | `{a, b.c}` | match any in set, can use `:` and `*`, match is captured |
199
+ i18n-tasks can manage multiple translation files and read translations from other gems.
200
+ To find out more the `data` options in the [config][config].
245
201
 
246
202
  For writing to locale files i18n-tasks provides 2 options.
247
203
 
@@ -266,7 +222,7 @@ data:
266
222
 
267
223
  Conservative router keeps the keys where they are found, or infers the path from base locale.
268
224
  If the key is completely new, conservative router will fall back to pattern router behaviour.
269
- Conservative router is the default router.
225
+ Conservative router is the **default** router.
270
226
 
271
227
  ```
272
228
  data:
@@ -276,55 +232,26 @@ data:
276
232
  - 'config/locales/%{locale}.yml'
277
233
  ```
278
234
 
279
- #### Custom adapters
280
-
281
- If you store data somewhere but in the filesystem, e.g. in the database or mongodb, you can implement a custom adapter.
282
- Implement [a handful of methods][adapter-example], then set `data.adapter` to the class name; the rest of the `data` config is passed to the initializer.
235
+ ##### Key pattern syntax
283
236
 
284
- ```yaml
285
- # config/i18n-tasks.yml
286
- data:
287
- # file_system is the default adapter, you can provide a custom class name here:
288
- adapter: file_system
289
- ```
237
+ A special syntax similar to file glob patterns is used throughout i18n-tasks to match translation keys:
290
238
 
291
- ### Usage search
239
+ | syntax | description |
240
+ |:------------:|:----------------------------------------------------------|
241
+ | `*` | matches everything |
242
+ | `:` | matches a single key |
243
+ | `{a, b.c}` | match any in set, can use `:` and `*`, match is captured |
292
244
 
293
245
 
294
- Configure usage search in `config/i18n-tasks.yml`:
246
+ #### Custom adapters
295
247
 
296
- ```yaml
297
- # config/i18n-tasks.yml
298
- # i18n usage search in source
299
- search:
300
- # search these directories (relative to your Rails.root directory, default: 'app/')
301
- paths:
302
- - 'app/'
303
- - 'vendor/'
304
- # paths for relative key resolution:
305
- relative_roots:
306
- # default:
307
- - app/views
308
- # add a custom one:
309
- - app/views-mobile
310
- # include only files matching this glob pattern (default: blank = include all files)
311
- include:
312
- - '*.rb'
313
- - '*.html.*'
314
- - '*.text.*'
315
- # explicitly exclude files (default: exclude common binary files)
316
- exclude:
317
- - '*.js'
318
- # you can override the default key regex pattern:
319
- pattern: "\\bt[( ]\\s*(:?\".+?\"|:?'.+?'|:\\w+)"
320
- # comments are ignored by default
321
- ignore_lines:
322
- - "^\\s*[#/](?!\\si18n-tasks-use)"
323
- ```
248
+ If you store data somewhere but in the filesystem, e.g. in the database or mongodb, you can implement a custom adapter.
249
+ If you have implemented a custom adapter please share it on [the wiki][wiki].
324
250
 
325
- It is also possible to use a custom key usage scanner by setting `search.scanner` to a class name.
326
- See this basic [pattern scanner](/lib/i18n/tasks/scanners/pattern_scanner.rb) for reference.
251
+ ### Usage search
327
252
 
253
+ See the `search` section in the [config file][config] for all available configuration options.
254
+ An example of a custom scanner can be found here: https://github.com/glebm/i18n-tasks/issues/138#issuecomment-87255708.
328
255
 
329
256
  ### Fine-tuning
330
257
 
@@ -335,33 +262,8 @@ Add hints to static analysis with magic comment hints (lines starting with `(#|/
335
262
  User.model_name.human
336
263
  ```
337
264
 
338
- You can also explicitly ignore keys appearing in locale files:
339
-
340
- ```yaml
341
- # config/i18n-tasks.yml
342
- # do not report these keys as unused
343
- ignore_unused:
344
- - category.*.db_name
345
-
346
- # do not report these keys as missing (both on blank value and no key)
347
- ignore_missing:
348
- - devise.errors.unauthorized # ignore this key
349
- - pagination.views.* # ignore the whole pattern
350
- # E.g to ignore all Rails number / currency keys:
351
- - 'number.{format, percentage.format, precision.format, human.format, currency.format}.{strip_insignificant_zeros,significant,delimiter}'
352
- - 'time.{pm,am}'
353
-
354
- # do not report these keys when they have the same value as the base locale version
355
- ignore_eq_base:
356
- all:
357
- - common.ok
358
- es,fr:
359
- - common.brand
360
-
361
- # do not report these keys ever
362
- ignore:
363
- - kaminari.*
364
- ```
265
+ You can also explicitly ignore keys appearing in locale files via `ignore*` settings.
266
+ See the [config file][config] to find out more.
365
267
 
366
268
  <a name="translation-config"></a>
367
269
  ### Google Translate
@@ -383,7 +285,7 @@ translation:
383
285
  api_key: <Google Translate API key>
384
286
  ```
385
287
 
386
- ## Interactive Console
288
+ ## Interactive console
387
289
 
388
290
  `i18n-tasks irb` starts an IRB session in i18n-tasks context. Type `guide` for more information.
389
291
 
@@ -395,12 +297,7 @@ Export missing and unused data to XLSX:
395
297
  $ i18n-tasks xlsx-report
396
298
  ```
397
299
 
398
- ### HTML
399
-
400
- While i18n-tasks does not provide an HTML version of the report, you can add [one like this](https://gist.github.com/glebm/bdd3ab6d12d915f0c81b).
401
-
402
-
403
- ## Add New Tasks
300
+ ## Add new tasks
404
301
 
405
302
  Tasks that come with the gem are defined in [lib/i18n/tasks/command/commands](lib/i18n/tasks/command/commands).
406
303
 
@@ -430,6 +327,8 @@ Run with:
430
327
  $ i18n-tasks my-task
431
328
  ```
432
329
 
330
+ See more examples of custom tasks [on the wiki](https://github.com/glebm/i18n-tasks/wiki#custom-tasks).
331
+
433
332
  [MIT license]: /LICENSE.txt
434
333
  [travis]: https://travis-ci.org/glebm/i18n-tasks
435
334
  [badge-travis]: http://img.shields.io/travis/glebm/i18n-tasks.svg
@@ -439,6 +338,8 @@ $ i18n-tasks my-task
439
338
  [badge-gemnasium]: https://gemnasium.com/glebm/i18n-tasks.svg
440
339
  [code-climate]: https://codeclimate.com/github/glebm/i18n-tasks
441
340
  [badge-code-climate]: http://img.shields.io/codeclimate/github/glebm/i18n-tasks.svg
341
+ [config]: https://github.com/glebm/i18n-tasks/blob/master/templates/config/i18n-tasks.yml
342
+ [wiki]: https://github.com/glebm/i18n-tasks/wiki "i18n-tasks wiki"
442
343
  [i18n-gem]: https://github.com/svenfuchs/i18n "svenfuchs/i18n on Github"
443
344
  [screenshot-find]: https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-usages.png "i18n-tasks find output screenshot"
444
345
  [adapter-example]: https://github.com/glebm/i18n-tasks/blob/master/lib/i18n/tasks/data/file_system_base.rb
data/bin/i18n-tasks CHANGED
@@ -8,49 +8,6 @@ if i18n_gem_config.respond_to?(:enforce_available_locales=) && i18n_gem_config.e
8
8
  i18n_gem_config.enforce_available_locales = true
9
9
  end
10
10
 
11
- require 'i18n/tasks'
12
- require 'i18n/tasks/commands'
13
- require 'slop'
11
+ require 'i18n/tasks/cli'
14
12
 
15
- err = proc { |message, exit_code|
16
- if $stderr.isatty
17
- $stderr.puts Term::ANSIColor.yellow('i18n-tasks: ' + message)
18
- else
19
- $stderr.puts message
20
- end
21
- exit exit_code
22
- }
23
-
24
- begin
25
- ran = false
26
- commander = ::I18n::Tasks::Commands
27
- instance = commander.new
28
- instance.set_internal_locale!
29
- slop_adapter = ::I18n::Tasks::SlopCommand
30
- args = ARGV.dup
31
- args = ['--help'] if args.empty?
32
- Slop.parse(args, help: true) do
33
- on('-v', '--version', 'Print the version') {
34
- puts I18n::Tasks::VERSION
35
- exit
36
- }
37
- commander.cmds.each do |name, attr|
38
- slop_dsl = slop_adapter.slop_command(name, attr) { |_name, opts|
39
- begin
40
- ran = true
41
- instance.safe_run name, opts
42
- rescue Errno::EPIPE
43
- # ignore Errno::EPIPE which is throw when pipe breaks, e.g.:
44
- # i18n-tasks missing | head
45
- exit 1
46
- end
47
- }
48
- instance_exec(&slop_dsl)
49
- end
50
- end
51
- rescue Slop::Error => e
52
- err.call(e.message, 64)
53
- end
54
-
55
-
56
- err.call("Command unknown: #{args[0]}", 64) if !ran && args[0]
13
+ I18n::Tasks::CLI.start(ARGV)
@@ -7,20 +7,19 @@ en:
7
7
  other: Added %{count} keys
8
8
  cmd:
9
9
  args:
10
- default_all: 'Default: all'
11
10
  default_text: 'Default: %{value}'
12
11
  desc:
13
12
  confirm: Confirm automatically
14
- data_format: 'Data format: %{valid_text}. %{default_text}.'
13
+ data_format: 'Data format: %{valid_text}.'
15
14
  key_pattern: Filter by key pattern (e.g. 'common.*')
16
15
  key_pattern_to_rename: Full key (pattern) to rename. Required
17
- locale: 'Locale. Default: base'
18
- locale_to_translate_from: 'Locale to translate from (default: base)'
19
- locales_filter: 'Comma-separated list of locale(s) to process. Default: all. Special: base.'
20
- missing_types: 'Filter by types: %{valid}. Default: all'
16
+ locale: Locale
17
+ locale_to_translate_from: Locale to translate from
18
+ locales_filter: "Locale(s) to process. Special: base"
19
+ missing_types: 'Filter by types: %{valid}'
21
20
  new_key_name: New name, interpolates original name as %{key}. Required
22
21
  nostdin: Do not read from stdin
23
- out_format: 'Output format: %{valid_text}. %{default_text}.'
22
+ out_format: 'Output format: %{valid_text}'
24
23
  pattern_router: 'Use pattern router: keys moved per config data.write'
25
24
  strict: Do not infer dynamic key usage such as `t("category.\#{category.name}")`
26
25
  value: 'Value. Interpolates: %{value}, %{human_key}, %{value_or_human_key}'
@@ -54,18 +53,16 @@ en:
54
53
  - Well done!
55
54
  - Perfect!
56
55
  enum_list_opt:
57
- desc: 'Comma-separated list of: %{valid_text}. %{default_text}'
58
56
  invalid: "%{invalid} is not in: %{valid}."
59
57
  enum_opt:
60
- desc: "%{valid_text}. %{default_text}"
61
58
  invalid: "%{invalid} is not one of: %{valid}."
62
59
  errors:
63
- invalid_format: 'Unknown format %{invalid}. Valid: %{valid}.'
64
- invalid_locale: Invalid locale %{invalid}
60
+ invalid_format: 'invalid format: %{invalid}. valid: %{valid}.'
61
+ invalid_locale: 'invalid locale: %{invalid}'
65
62
  invalid_missing_type:
66
- one: 'Unknown type %{invalid}. Valid: %{valid}.'
67
- other: 'Unknown types: %{invalid}. Valid: %{valid}.'
68
- pass_forest: Pass locale forest
63
+ one: 'invalid type: %{invalid}. valid: %{valid}.'
64
+ other: 'unknown types: %{invalid}. valid: %{valid}.'
65
+ pass_forest: pass locale forest
69
66
  common:
70
67
  base_value: Base Value
71
68
  continue_q: Continue?
@@ -5,7 +5,6 @@ ru:
5
5
  added: "Добавлены ключи (%{count})"
6
6
  cmd:
7
7
  args:
8
- default_all: "По умолчанию: все"
9
8
  default_text: "По умолчанию: %{value}"
10
9
  desc:
11
10
  confirm: "Подтвердить автоматом"
@@ -53,10 +52,8 @@ ru:
53
52
  - "Отлично!"
54
53
  - "Прекрасно!"
55
54
  enum_list_opt:
56
- desc: "Разделенных запятыми список: %{valid_text}. %{default_text}"
57
55
  invalid: "%{invalid} не в: %{valid}."
58
56
  enum_opt:
59
- desc: "%{valid_text}. %{default_text}"
60
57
  invalid: "%{invalid} не является одним из: %{valid}."
61
58
  errors:
62
59
  invalid_format: "Неизвестный формат %{invalid}. Форматы: %{valid}."
data/i18n-tasks.gemspec CHANGED
@@ -39,7 +39,6 @@ TEXT
39
39
  s.add_dependency 'term-ansicolor'
40
40
  s.add_dependency 'terminal-table'
41
41
  s.add_dependency 'highline'
42
- s.add_dependency 'slop', '~> 3.5'
43
42
  s.add_dependency 'i18n'
44
43
  s.add_development_dependency 'axlsx', '~> 2.0'
45
44
  s.add_development_dependency 'bundler', '~> 1.3'
data/lib/i18n/tasks.rb CHANGED
@@ -2,11 +2,22 @@
2
2
  # define all the modules to be able to use ::
3
3
  module I18n
4
4
  module Tasks
5
+ class << self
6
+ def gem_path
7
+ File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
8
+ end
5
9
 
6
- def self.gem_path
7
- File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
10
+ def verbose?
11
+ @verbose
12
+ end
13
+
14
+ def verbose=(value)
15
+ @verbose = value
16
+ end
8
17
  end
9
18
 
19
+ @verbose = !!ENV['VERBOSE']
20
+
10
21
  module Data
11
22
  end
12
23
  end
@@ -15,6 +26,7 @@ end
15
26
 
16
27
  require 'active_support/core_ext/hash'
17
28
  require 'active_support/core_ext/string'
29
+ require 'active_support/core_ext/array/access'
18
30
  require 'active_support/core_ext/module/delegation'
19
31
  require 'active_support/core_ext/object/try'
20
32
  require 'active_support/core_ext/object/blank'
@@ -0,0 +1,195 @@
1
+ require 'i18n/tasks'
2
+ require 'i18n/tasks/commands'
3
+ require 'optparse'
4
+
5
+ class I18n::Tasks::CLI
6
+ include ::I18n::Tasks::Logging
7
+
8
+ def self.start(argv)
9
+ new.start(argv)
10
+ end
11
+
12
+ def initialize
13
+ end
14
+
15
+ def start(argv)
16
+ auto_output_coloring do
17
+ begin
18
+ run(argv)
19
+ rescue OptionParser::ParseError => e
20
+ error e.message, 64
21
+ rescue I18n::Tasks::CommandError => e
22
+ begin
23
+ error e.message, 78
24
+ ensure
25
+ log_verbose e.backtrace * "\n"
26
+ end
27
+ rescue Errno::EPIPE
28
+ # ignore Errno::EPIPE which is throw when pipe breaks, e.g.:
29
+ # i18n-tasks missing | head
30
+ exit 1
31
+ end
32
+ end
33
+ rescue ExecutionError => e
34
+ exit e.exit_code
35
+ end
36
+
37
+ def run(argv)
38
+ name, *options = parse!(argv.dup)
39
+ context.run(name, *options)
40
+ end
41
+
42
+ def context
43
+ @context ||= ::I18n::Tasks::Commands.new.tap(&:set_internal_locale!)
44
+ end
45
+
46
+ def commands
47
+ @commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr('_', '-') }
48
+ end
49
+
50
+ private
51
+
52
+ def parse!(argv)
53
+ command = parse_command! argv
54
+ options = optparse! command, argv
55
+ parse_options! options, command, argv
56
+ [command.tr('-', '_'), options.update(arguments: argv)]
57
+ end
58
+
59
+ def optparse!(command, argv)
60
+ if command
61
+ optparse_command!(command, argv)
62
+ else
63
+ optparse_no_command!(argv)
64
+ end
65
+ end
66
+
67
+ def optparse_command!(command, argv)
68
+ cmd_conf = commands[command]
69
+ flags = cmd_conf[:args].dup
70
+ options = {}
71
+ OptionParser.new("Usage: #{program_name} #{command} [options] #{cmd_conf[:pos]}".strip) do |op|
72
+ flags.each do |flag|
73
+ op.on(*optparse_args(flag)) { |v| options[option_name(flag)] = v }
74
+ end
75
+ verbose_option op
76
+ help_option op
77
+ end.parse!(argv)
78
+ options
79
+ end
80
+
81
+ def optparse_no_command!(argv)
82
+ argv << '--help' if argv.empty?
83
+ OptionParser.new("Usage: #{program_name} [command] [options]") do |op|
84
+ op.on('-v', '--version', 'Print the version') do
85
+ puts I18n::Tasks::VERSION
86
+ exit
87
+ end
88
+ help_option op
89
+ commands_summary op
90
+ end.parse!(argv)
91
+ end
92
+
93
+ def allow_help_arg_first!(argv)
94
+ # allow `i18n-tasks --help command` in addition to `i18n-tasks command --help`
95
+ argv[0], argv[1] = argv[1], argv[0] if %w(-h --help).include?(argv[0]) && argv[1] && !argv[1].start_with?('-')
96
+ end
97
+
98
+ def parse_command!(argv)
99
+ allow_help_arg_first! argv
100
+ if argv[0] && !argv[0].start_with?('-')
101
+ if commands.keys.include?(argv[0])
102
+ argv.shift
103
+ else
104
+ error "unknown command: #{argv[0]}", 64
105
+ end
106
+ end
107
+ end
108
+
109
+ def verbose_option(op)
110
+ op.on('--verbose', 'Verbose output') {
111
+ ::I18n::Tasks.verbose = true
112
+ }
113
+ end
114
+
115
+ def help_option(op)
116
+ op.on('-h', '--help', 'Show this message') do
117
+ $stderr.puts op
118
+ exit
119
+ end
120
+ end
121
+
122
+ # @param [OptionParser] op
123
+ def commands_summary(op)
124
+ op.separator ''
125
+ op.separator 'Available commands:'
126
+ op.separator ''
127
+ commands.each do |cmd, cmd_conf|
128
+ op.separator " #{cmd.ljust(op.summary_width + 1, ' ')}#{try_call cmd_conf[:desc]}"
129
+ end
130
+ op.separator ''
131
+ op.separator 'See `i18n-tasks <command> --help` for more information on a specific command.'
132
+ end
133
+
134
+ def optparse_args(flag)
135
+ args = flag.dup
136
+ args.map! { |v| try_call v }
137
+ conf = args.extract_options!
138
+ if conf.key?(:default)
139
+ args[-1] = "#{args[-1]}. #{I18n.t('i18n_tasks.cmd.args.default_text', value: conf[:default])}"
140
+ end
141
+ args
142
+ end
143
+
144
+ def parse_options!(options, command, argv)
145
+ commands[command][:args].each do |flag|
146
+ name = option_name flag
147
+ options[name] = parse_option flag, options[name], argv, self.context
148
+ end
149
+ end
150
+
151
+ def parse_option(flag, val, argv, context)
152
+ conf = flag.last.is_a?(Hash) ? flag.last : {}
153
+ if conf[:consume_positional]
154
+ val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(',') } : argv)
155
+ end
156
+ val = conf[:default] if val.nil? && conf.key?(:default)
157
+ val = conf[:parser].call(val, context) if conf.key?(:parser)
158
+ val
159
+ end
160
+
161
+ def option_name(flag)
162
+ flag.detect { |f| f.start_with?('--') }.sub(/\A--/, '').sub(/[^\-\w].*\z/, '').to_sym
163
+ end
164
+
165
+ def try_call(v)
166
+ if v.respond_to? :call
167
+ v.call
168
+ else
169
+ v
170
+ end
171
+ end
172
+
173
+ def error(message, exit_code)
174
+ log_error message
175
+ fail ExecutionError.new(message, exit_code)
176
+ end
177
+
178
+ class ExecutionError < Exception
179
+ attr_reader :exit_code
180
+
181
+ def initialize(message, exit_code)
182
+ super(message)
183
+ @exit_code = exit_code
184
+ end
185
+ end
186
+
187
+ def auto_output_coloring(coloring = ENV['I18N_TASKS_COLOR'] || STDOUT.isatty)
188
+ coloring_was = Term::ANSIColor.coloring?
189
+ Term::ANSIColor.coloring = coloring
190
+ yield
191
+ ensure
192
+ Term::ANSIColor.coloring = coloring_was
193
+ end
194
+
195
+ end