i18n-tasks 0.7.13 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,53 @@
1
+ module I18n::Tasks
2
+ module Command
3
+ module OptionParsers
4
+ module Enum
5
+ class Parser
6
+ DEFAULT_ERROR = proc { |invalid, valid|
7
+ I18n.t('i18n_tasks.cmd.enum_opt.invalid', invalid: invalid, valid: valid * ', ')
8
+ }
9
+
10
+ def initialize(valid, error_message = DEFAULT_ERROR)
11
+ @valid = valid.map(&:to_s)
12
+ @error_message = error_message
13
+ end
14
+
15
+ def call(value, *)
16
+ return @valid.first unless value.present?
17
+ if @valid.include?(value)
18
+ value
19
+ else
20
+ raise CommandError.new @error_message.call(value, @valid)
21
+ end
22
+ end
23
+ end
24
+
25
+ class ListParser
26
+ DEFAULT_ERROR = proc { |invalid, valid|
27
+ I18n.t('i18n_tasks.cmd.enum_list_opt.invalid', invalid: invalid * ', ', valid: valid * ', ')
28
+ }
29
+
30
+ def initialize(valid, error_message = DEFAULT_ERROR)
31
+ @valid = valid.map(&:to_s)
32
+ @error_message = error_message
33
+ end
34
+
35
+ def call(values, *)
36
+ values = Array(values)
37
+ return @valid if values == %w(all)
38
+ invalid = values - @valid
39
+ if invalid.empty?
40
+ if values.empty?
41
+ @valid
42
+ else
43
+ values
44
+ end
45
+ else
46
+ raise CommandError.new @error_message.call(invalid, @valid)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ module I18n::Tasks
2
+ module Command
3
+ module OptionParsers
4
+ module Locale
5
+ module Validator
6
+ VALID_LOCALE_RE = /\A\w[\w\-\.]*\z/i
7
+
8
+ def validate!(locale)
9
+ if VALID_LOCALE_RE !~ locale
10
+ raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.invalid_locale', invalid: locale))
11
+ end
12
+ locale
13
+ end
14
+ end
15
+
16
+ module Parser
17
+ module_function
18
+ extend Validator
19
+
20
+ # @param [#base_locale, #locales] context
21
+ def call(val, context)
22
+ if val.blank? || val == 'base'
23
+ context.base_locale
24
+ else
25
+ validate! val
26
+ end
27
+ end
28
+ end
29
+
30
+ module ListParser
31
+ module_function
32
+ extend Validator
33
+
34
+ # @param [#base_locale,#locales] context
35
+ def call(vals, context)
36
+ if vals == %w(all) || vals.blank?
37
+ context.locales
38
+ else
39
+ vals.map { |v| v == 'base' ? context.base_locale : v }
40
+ end.tap do |locales|
41
+ locales.each { |locale| validate! locale }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,46 +1,36 @@
1
- require 'i18n/tasks/command/options/enum_opt'
2
- require 'i18n/tasks/command/options/list_opt'
3
1
 
4
2
  module I18n::Tasks
5
3
  module Command
6
4
  module Options
7
5
  module Common
8
6
  include Command::DSL
9
- include Options::EnumOpt
10
- include Options::ListOpt
11
7
 
12
- VALID_LOCALE_RE = /\A\w[\w\-\.]*\z/i
8
+ arg :nostdin,
9
+ '-S',
10
+ '--nostdin',
11
+ t('i18n_tasks.cmd.args.desc.nostdin')
13
12
 
14
- cmd_opt :nostdin, {
15
- short: :S,
16
- long: :nostdin,
17
- desc: t('i18n_tasks.cmd.args.desc.nostdin'),
18
- conf: {default: false}
19
- }
13
+ arg :confirm,
14
+ '-y',
15
+ '--confirm',
16
+ desc: t('i18n_tasks.cmd.args.desc.confirm')
20
17
 
21
- cmd_opt :confirm, {
22
- short: :y,
23
- long: :confirm,
24
- desc: t('i18n_tasks.cmd.args.desc.confirm'),
25
- conf: {default: false}
26
- }
18
+ arg :pattern,
19
+ '-p',
20
+ '--pattern PATTERN',
21
+ t('i18n_tasks.cmd.args.desc.key_pattern')
27
22
 
28
- cmd_opt :pattern, {
29
- short: :p,
30
- long: :pattern=,
31
- desc: t('i18n_tasks.cmd.args.desc.key_pattern'),
32
- conf: {argument: true, optional: false}
33
- }
23
+ arg :value,
24
+ '-v',
25
+ '--value VALUE',
26
+ t('i18n_tasks.cmd.args.desc.value')
34
27
 
35
- cmd_opt :value, {
36
- short: :v,
37
- long: :value=,
38
- desc: t('i18n_tasks.cmd.args.desc.value'),
39
- conf: {argument: true, optional: false}
40
- }
28
+ def arg_or_pos!(key, opts)
29
+ opts[key] ||= opts[:arguments].try(:shift)
30
+ end
41
31
 
42
- def opt_or_arg!(key, opt)
43
- opt[key] ||= opt[:arguments].try(:shift)
32
+ def pos_or_stdin!(opts)
33
+ opts[:arguments].try(:shift) || $stdin.read
44
34
  end
45
35
  end
46
36
  end
@@ -0,0 +1,91 @@
1
+ require 'i18n/tasks/command/option_parsers/enum'
2
+
3
+ module I18n::Tasks
4
+ module Command
5
+ module Options
6
+ module Data
7
+ include Command::DSL
8
+
9
+ DATA_FORMATS = %w(yaml json keys)
10
+ OUT_FORMATS = ['terminal-table', *DATA_FORMATS, 'inspect']
11
+
12
+ format_arg = proc { |type, values|
13
+ default = values.first
14
+ arg type,
15
+ '-f',
16
+ '--format FORMAT',
17
+ values,
18
+ {'yml' => 'yaml'},
19
+ t("i18n_tasks.cmd.args.desc.#{type}", valid_text: values * ', ', default_text: default),
20
+ parser: OptionParsers::Enum::Parser.new(values,
21
+ proc { |value, valid|
22
+ I18n.t('i18n_tasks.cmd.errors.invalid_format',
23
+ invalid: value, valid: valid * ', ') })
24
+ }
25
+
26
+ # i18n-tasks-use t('i18n_tasks.cmd.args.desc.data_format')
27
+ format_arg.call(:data_format, DATA_FORMATS)
28
+
29
+ # i18n-tasks-use t('i18n_tasks.cmd.args.desc.out_format')
30
+ format_arg.call(:out_format, OUT_FORMATS)
31
+
32
+ def forest_pos_or_stdin!(opt, format = opt[:format])
33
+ parse_forest(pos_or_stdin!(opt), format)
34
+ end
35
+
36
+ # @return [Array<I18n::Tasks::Data::Tree::Siblings>] trees read from stdin and positional arguments
37
+ def forests_stdin_and_pos!(opt, num = false, format = opt[:format])
38
+ args = opt[:arguments] || []
39
+ if opt[:nostdin]
40
+ sources = []
41
+ else
42
+ sources = [$stdin.read]
43
+ num -= 1 if num
44
+ end
45
+ if num
46
+ num.times { sources << args.shift }
47
+ else
48
+ sources += args
49
+ args.clear
50
+ end
51
+ sources.map { |src| parse_forest(src, format) }
52
+ end
53
+
54
+ def merge_forests_stdin_and_pos!(opt)
55
+ forests_stdin_and_pos!(opt).inject(i18n.empty_forest) { |result, forest|
56
+ result.merge! forest
57
+ }
58
+ end
59
+
60
+ def parse_forest(src, format)
61
+ if !src
62
+ raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.pass_forest'))
63
+ end
64
+ if format == 'keys'
65
+ ::I18n::Tasks::Data::Tree::Siblings.from_key_names parse_keys(src)
66
+ else
67
+ ::I18n::Tasks::Data::Tree::Siblings.from_nested_hash i18n.data.adapter_parse(src, format)
68
+ end
69
+ end
70
+
71
+ def parse_keys(src)
72
+ Array(src).compact.flat_map { |v| v.strip.split(/\s*[,\s\n]\s*/) }.map(&:presence).compact
73
+ end
74
+
75
+ def print_forest(forest, opts, version = :show_tree)
76
+ format = opts[:format].to_s
77
+ case format
78
+ when 'terminal-table'
79
+ terminal_report.send(version, forest)
80
+ when 'inspect'
81
+ puts forest.inspect
82
+ when 'keys'
83
+ puts forest.key_names(root: true)
84
+ when *DATA_FORMATS
85
+ puts i18n.data.adapter_dump forest.to_hash(true), format
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,53 +1,33 @@
1
+ require 'i18n/tasks/command/option_parsers/locale'
2
+
1
3
  module I18n::Tasks
2
4
  module Command
3
5
  module Options
4
6
  module Locales
5
7
  include Command::DSL
6
8
 
7
- cmd_opt :locales, {
8
- short: :l,
9
- long: :locales=,
10
- desc: t('i18n_tasks.cmd.args.desc.locales_filter'),
11
- conf: {as: Array, delimiter: /\s*[+:,]\s*/, default: 'all', argument: true, optional: false},
12
- parse: :parse_locales
13
- }
14
-
15
- cmd_opt :locale, {
16
- short: :l,
17
- long: :locale=,
18
- desc: t('i18n_tasks.cmd.args.desc.locale'),
19
- conf: {default: 'base', argument: true, optional: false},
20
- parse: :parse_locale
21
- }
22
-
23
- cmd_opt :locale_to_translate_from, cmd_opt(:locale).merge(
24
- short: :f,
25
- long: :from=,
26
- desc: t('i18n_tasks.cmd.args.desc.locale_to_translate_from'))
27
-
28
- def parse_locales(opt, key = :locales)
29
- argv = Array(opt[:arguments]) + Array(opt[key])
30
- locales = if argv == ['all'] || argv == 'all' || argv.blank?
31
- i18n.locales
32
- else
33
- explode_list_opt(argv).map { |v| v == 'base' ? base_locale : v }
34
- end
35
- locales.each { |locale| validate_locale!(locale) }
36
- log_verbose "locales for the command are #{locales.inspect}"
37
- opt[key] = locales
38
- end
9
+ arg :locales,
10
+ '-l',
11
+ '--locales en,es,ru',
12
+ Array,
13
+ t('i18n_tasks.cmd.args.desc.locales_filter'),
14
+ parser: OptionParsers::Locale::ListParser,
15
+ default: 'all',
16
+ consume_positional: true
39
17
 
40
- def parse_locale(opt, key = :locale)
41
- val = opt[key]
42
- opt[key] = base_locale if val.blank? || val == 'base'
43
- opt[key]
44
- end
18
+ arg :locale,
19
+ '-l',
20
+ '--locale en',
21
+ t('i18n_tasks.cmd.args.desc.locale'),
22
+ parser: OptionParsers::Locale::Parser,
23
+ default: 'base'
45
24
 
46
- def validate_locale!(locale)
47
- if Common::VALID_LOCALE_RE !~ locale
48
- raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.invalid_locale', invalid: locale))
49
- end
50
- end
25
+ arg :locale_to_translate_from,
26
+ '-f',
27
+ '--from en',
28
+ t('i18n_tasks.cmd.args.desc.locale_to_translate_from'),
29
+ parser: OptionParsers::Locale::Parser,
30
+ default: 'base'
51
31
  end
52
32
  end
53
33
  end
@@ -25,9 +25,9 @@ module I18n::Tasks
25
25
  locales = config[:locales].presence
26
26
  @locales = LocaleList.normalize_locale_list(locales || available_locales, base_locale, true)
27
27
  if locales.present?
28
- log_verbose "data locales: #{@locales}"
28
+ log_verbose "locales read from config #{@locales * ', '}"
29
29
  else
30
- log_verbose "data locales (inferred): #{@locales}"
30
+ log_verbose "locales inferred from data: #{@locales * ', '}"
31
31
  end
32
32
  end
33
33
 
@@ -4,6 +4,9 @@ require 'i18n/tasks/data/tree/traversal'
4
4
  require 'i18n/tasks/data/tree/siblings'
5
5
  module I18n::Tasks::Data::Tree
6
6
  class Node
7
+ include Enumerable
8
+ include Traversal
9
+
7
10
  attr_accessor :value
8
11
  attr_reader :key, :children, :parent
9
12
 
@@ -42,9 +45,6 @@ module I18n::Tasks::Data::Tree
42
45
  self
43
46
  end
44
47
 
45
- include Enumerable
46
- include Traversal
47
-
48
48
  def value_or_children_hash
49
49
  leaf? ? value : children.try(:to_hash)
50
50
  end
@@ -4,6 +4,9 @@ require 'i18n/tasks/data/tree/traversal'
4
4
  module I18n::Tasks::Data::Tree
5
5
  # A list of nodes
6
6
  class Nodes
7
+ include Enumerable
8
+ include Traversal
9
+
7
10
  attr_reader :list
8
11
 
9
12
  def initialize(opts = {})
@@ -11,8 +14,6 @@ module I18n::Tasks::Data::Tree
11
14
  end
12
15
 
13
16
  delegate :each, :present?, :empty?, :blank?, :size, :to_a, to: :@list
14
- include Enumerable
15
- include Traversal
16
17
 
17
18
  def to_nodes
18
19
  self
@@ -1,6 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'i18n/tasks/data/tree/traversal'
4
3
  require 'i18n/tasks/data/tree/nodes'
5
4
  module I18n::Tasks::Data::Tree
6
5
  # Siblings represents a subtree sharing a common parent
@@ -3,24 +3,28 @@ module I18n::Tasks::Logging
3
3
  extend self
4
4
 
5
5
  def warn_deprecated(message)
6
- log_stderr Term::ANSIColor.yellow Term::ANSIColor.bold "i18n-tasks: [DEPRECATED] #{message}"
6
+ log_stderr Term::ANSIColor.yellow Term::ANSIColor.bold "#{program_name}: [DEPRECATED] #{message}"
7
7
  end
8
8
 
9
9
  def log_verbose(message)
10
- if ENV['VERBOSE']
11
- log_stderr Term::ANSIColor.green "i18n-tasks: #{message}"
10
+ if ::I18n::Tasks.verbose?
11
+ log_stderr Term::ANSIColor.bright_blue(message)
12
12
  end
13
13
  end
14
14
 
15
15
  def log_warn(message)
16
- log_stderr Term::ANSIColor.yellow "i18n-tasks: [WARN] #{message}"
16
+ log_stderr Term::ANSIColor.yellow "#{program_name}: [WARN] #{message}"
17
17
  end
18
18
 
19
19
  def log_error(message)
20
- log_stderr Term::ANSIColor.red Term::ANSIColor.bold "i18n-tasks: #{message}"
20
+ log_stderr Term::ANSIColor.red Term::ANSIColor.bold "#{program_name}: #{message}"
21
21
  end
22
22
 
23
23
  def log_stderr(*args)
24
24
  $stderr.puts(*args)
25
25
  end
26
+
27
+ def program_name
28
+ @program_name ||= File.basename($PROGRAM_NAME)
29
+ end
26
30
  end
@@ -10,17 +10,15 @@ module I18n::Tasks::Scanners
10
10
 
11
11
  attr_reader :config, :key_filter, :ignore_lines_res
12
12
 
13
+ ALWAYS_EXCLUDE = %w(*.jpg *.png *.gif *.svg *.ico *.eot *.ttf *.woff *.woff2 *.pdf
14
+ *.css *.sass *.scss *.less *.yml *.json)
15
+
13
16
  def initialize(config = {})
14
17
  @config = config.dup.with_indifferent_access.tap do |conf|
15
- conf[:relative_roots] = %w(app/views) if conf[:relative_roots].blank?
18
+ conf[:relative_roots] = %w(app/views app/controllers app/helpers app/presenters) if conf[:relative_roots].blank?
16
19
  conf[:paths] = %w(app/) if conf[:paths].blank?
17
20
  conf[:include] = Array(conf[:include]) if conf[:include].present?
18
- if conf.key?(:exclude)
19
- conf[:exclude] = Array(conf[:exclude])
20
- else
21
- # exclude common binary extensions by default (images and fonts)
22
- conf[:exclude] = %w(*.jpg *.png *.gif *.svg *.ico *.eot *.ttf *.woff *.woff2 *.pdf)
23
- end
21
+ conf[:exclude] = Array(conf[:exclude]) + ALWAYS_EXCLUDE
24
22
  # Regexps for lines to ignore per extension
25
23
  if conf[:ignore_lines] && !conf[:ignore_lines].is_a?(Hash)
26
24
  warn_deprecated "search.ignore_lines must be a Hash, found #{conf[:ignore_lines].class.name}"
@@ -28,6 +26,7 @@ module I18n::Tasks::Scanners
28
26
  end
29
27
  conf[:ignore_lines] ||= {
30
28
  'rb' => %q(^\s*#(?!\si18n-tasks-use)),
29
+ 'opal' => %q(^\s*#(?!\si18n-tasks-use)),
31
30
  'haml' => %q(^\s*-\s*#(?!\si18n-tasks-use)),
32
31
  'slim' => %q(^\s*(?:-#|/)(?!\si18n-tasks-use)),
33
32
  'coffee' => %q(^\s*#(?!\si18n-tasks-use)),