diff --git a/.rubocop b/.rubocop new file mode 100644 index 00000000..eb11be00 --- /dev/null +++ b/.rubocop @@ -0,0 +1 @@ +-D diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..d9f0911c --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,54 @@ +AllCops: + TargetRubyVersion: 2.3 + Exclude: + - 'tmp/**/*' + - 'spec/fixtures/**/*' + +Metrics/AbcSize: + Max: 28 + +Metrics/ClassLength: + Max: 125 + Exclude: + - 'lib/i18n/tasks/cli.rb' + +Metrics/CyclomaticComplexity: + Max: 10 + +Metrics/BlockLength: + Max: 30 + +Metrics/LineLength: + Max: 120 + +Metrics/MethodLength: + Max: 25 + +Metrics/PerceivedComplexity: + Max: 9 + +Style/Documentation: + Enabled: false + +Style/ClassAndModuleChildren: + Enabled: false + +Style/GuardClause: + Enabled: false + +Style/MultilineBlockChain: + Enabled: false + +Style/SafeNavigation: + # TODO(glebm): Remove this when we update the minimum Ruby version to 2.3+. + Enabled: false + +Style/SpecialGlobalVars: + Enabled: false + +Style/SignalException: + EnforcedStyle: semantic + + +Style/SingleLineBlockParams: + Enabled: false diff --git a/Gemfile b/Gemfile index 12ba587a..8d96f329 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ +# frozen_string_literal: true source 'https://rubygems.org' # Specify your gem's dependencies in i18n-tasks.gemspec diff --git a/Rakefile b/Rakefile index 12067436..fdd9c291 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,8 @@ +# frozen_string_literal: true require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:rspec) -task :default => :rspec +task default: :rspec task :irb do # $: << File.expand_path('lib', __FILE__) diff --git a/bin/i18n-tasks b/bin/i18n-tasks index b1c960e4..0a141b90 100755 --- a/bin/i18n-tasks +++ b/bin/i18n-tasks @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true if ENV['I18N_TASKS_BIN_SIMPLECOV_COVERAGE'] require_relative '../spec/bin_simplecov_helper' diff --git a/i18n-tasks.gemspec b/i18n-tasks.gemspec index 13c29c4a..676d705f 100644 --- a/i18n-tasks.gemspec +++ b/i18n-tasks.gemspec @@ -1,14 +1,15 @@ +# frozen_string_literal: true lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'i18n/tasks/version' -Gem::Specification.new do |s| +Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength s.name = 'i18n-tasks' s.version = I18n::Tasks::VERSION s.authors = ['glebm'] s.email = ['glex.spb@gmail.com'] s.license = 'MIT' - s.summary = %q{Manage localization and translation with the awesome power of static analysis} + s.summary = 'Manage localization and translation with the awesome power of static analysis' s.description = <<-TEXT i18n-tasks helps you find and manage missing and unused translations. @@ -21,16 +22,14 @@ cp $(i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/ # Add an RSpec for missing and unused keys: cp $(i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/ TEXT - s.homepage = 'https://github.com/glebm/i18n-tasks' + s.homepage = 'https://github.com/glebm/i18n-tasks' if s.respond_to?(:metadata=) s.metadata = { 'issue_tracker' => 'https://github.com/glebm/i18n-tasks' } end - if s.respond_to?(:required_ruby_version=) - s.required_ruby_version = '~> 2.1' - end + s.required_ruby_version = '~> 2.1' if s.respond_to?(:required_ruby_version=) - s.files = `git ls-files`.split($/) - s.files -= s.files.grep(%r{^(doc/|\.|spec/)}) + %w(CHANGES.md config/i18n-tasks.yml Gemfile) + s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) + s.files -= s.files.grep(%r{^(doc/|\.|spec/)}) + %w(CHANGES.md config/i18n-tasks.yml Gemfile) s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } - %w(i18n-tasks.cmd) s.test_files = s.files.grep(%r{^(test|spec|features)/}) s.require_paths = ['lib'] @@ -48,5 +47,6 @@ TEXT s.add_development_dependency 'bundler', '~> 1.3' s.add_development_dependency 'rake' s.add_development_dependency 'rspec', '~> 3.3' + s.add_development_dependency 'rubocop' s.add_development_dependency 'yard' end diff --git a/lib/i18n/tasks.rb b/lib/i18n/tasks.rb index 5bc80b5b..2ab4ba4b 100644 --- a/lib/i18n/tasks.rb +++ b/lib/i18n/tasks.rb @@ -11,9 +11,7 @@ def verbose? @verbose end - def verbose=(value) - @verbose = value - end + attr_writer :verbose # Add a scanner to the default configuration. # @@ -37,14 +35,13 @@ def add_commands(commands_module) end end - @verbose = !!ENV['VERBOSE'] + @verbose = !ENV['VERBOSE'].nil? module Data end end end - require 'active_support/inflector' require 'active_support/core_ext/hash' require 'active_support/core_ext/array/access' diff --git a/lib/i18n/tasks/cli.rb b/lib/i18n/tasks/cli.rb index dbb3aad7..6ed7b986 100644 --- a/lib/i18n/tasks/cli.rb +++ b/lib/i18n/tasks/cli.rb @@ -10,16 +10,13 @@ def self.start(argv) new.start(argv) end - def initialize - end + def initialize; end def start(argv) I18n.with_locale(base_task.internal_locale) do auto_output_coloring do begin - if run(argv) == :exit_1 - exit 1 - end + exit 1 if run(argv) == :exit_1 rescue OptionParser::ParseError => e error e.message, 64 rescue I18n::Tasks::CommandError => e @@ -51,7 +48,7 @@ def context def commands # load base task to initialize plugins base_task - @commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr('_'.freeze, '-'.freeze) } + @commands ||= ::I18n::Tasks::Commands.cmds.transform_keys { |k| k.to_s.tr('_', '-') } end private @@ -64,7 +61,7 @@ def parse!(argv) command = parse_command! argv options = optparse! command, argv parse_options! options, command, argv - [command.tr('-'.freeze, '_'.freeze), options.update(arguments: argv)] + [command.tr('-', '_'), options.update(arguments: argv)] end def optparse!(command, argv) @@ -103,14 +100,14 @@ def optparse_no_command!(argv) def allow_help_arg_first!(argv) # allow `i18n-tasks --help command` in addition to `i18n-tasks command --help` - if %w(-h --help).include?(argv[0]) && argv[1] && !argv[1].start_with?('-'.freeze) + if %w(-h --help).include?(argv[0]) && argv[1] && !argv[1].start_with?('-') argv[0], argv[1] = argv[1], argv[0] end end def parse_command!(argv) allow_help_arg_first! argv - if argv[0] && !argv[0].start_with?('-'.freeze) + if argv[0] && !argv[0].start_with?('-') if commands.keys.include?(argv[0]) argv.shift else @@ -120,9 +117,9 @@ def parse_command!(argv) end def verbose_option(op) - op.on('--verbose', 'Verbose output') { + op.on('--verbose', 'Verbose output') do ::I18n::Tasks.verbose = true - } + end end def help_option(op) @@ -157,14 +154,14 @@ def optparse_args(flag) def parse_options!(options, command, argv) commands[command][:args].each do |flag| name = option_name flag - options[name] = parse_option flag, options[name], argv, self.context + options[name] = parse_option flag, options[name], argv, context end end def parse_option(flag, val, argv, context) conf = flag.last.is_a?(Hash) ? flag.last : {} if conf[:consume_positional] - val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(','.freeze) } : argv) + val = Array(val) + Array(flag.include?(Array) ? argv.flat_map { |x| x.split(',') } : argv) end val = conf[:default] if val.nil? && conf.key?(:default) val = conf[:parser].call(val, context) if conf.key?(:parser) @@ -172,9 +169,9 @@ def parse_option(flag, val, argv, context) end def option_name(flag) - flag.detect { |f| - f.start_with?('--'.freeze) - }.sub(/\A--(\[no-\])?/, ''.freeze).sub(/[^\-\w].*\z/, ''.freeze).to_sym + flag.detect do |f| + f.start_with?('--') + end.sub(/\A--(\[no-\])?/, '').sub(/[^\-\w].*\z/, '').to_sym end def try_call(v) @@ -190,7 +187,7 @@ def error(message, exit_code) fail ExecutionError.new(message, exit_code) end - class ExecutionError < Exception + class ExecutionError < RuntimeError attr_reader :exit_code def initialize(message, exit_code) @@ -206,5 +203,4 @@ def auto_output_coloring(coloring = ENV['I18N_TASKS_COLOR'] || STDOUT.isatty) ensure Term::ANSIColor.coloring = coloring_was end - end diff --git a/lib/i18n/tasks/command/commander.rb b/lib/i18n/tasks/command/commander.rb index 5b2a8dc5..e0fa4f98 100644 --- a/lib/i18n/tasks/command/commander.rb +++ b/lib/i18n/tasks/command/commander.rb @@ -10,7 +10,6 @@ class Commander attr_reader :i18n - # @param [I18n::Tasks::BaseTask] i18n def initialize(i18n) @i18n = i18n @@ -18,8 +17,8 @@ def initialize(i18n) def run(name, opts = {}) name = name.to_sym - public_name = name.to_s.tr '_'.freeze, '-'.freeze - log_verbose "task: #{public_name}(#{opts.map { |k, v| "#{k}: #{v.inspect}" } * ', '.freeze})" + public_name = name.to_s.tr '_', '-' + log_verbose "task: #{public_name}(#{opts.map { |k, v| "#{k}: #{v.inspect}" } * ', '})" if opts.empty? || method(name).arity.zero? send name else diff --git a/lib/i18n/tasks/command/commands/health.rb b/lib/i18n/tasks/command/commands/health.rb index 6a775bda..78c90f5a 100644 --- a/lib/i18n/tasks/command/commands/health.rb +++ b/lib/i18n/tasks/command/commands/health.rb @@ -14,7 +14,7 @@ def health(opt = {}) forest = i18n.data_forest(opt[:locales]) stats = i18n.forest_stats(forest) if stats[:key_count].zero? - raise CommandError.new t('i18n_tasks.health.no_keys_detected') + fail CommandError, t('i18n_tasks.health.no_keys_detected') end terminal_report.forest_stats forest, stats [missing(opt), unused(opt)].detect { |result| result == :exit_1 } diff --git a/lib/i18n/tasks/command/commands/meta.rb b/lib/i18n/tasks/command/commands/meta.rb index b2d5cf1e..3db2d711 100644 --- a/lib/i18n/tasks/command/commands/meta.rb +++ b/lib/i18n/tasks/command/commands/meta.rb @@ -13,8 +13,8 @@ def config(opts = {}) cfg = i18n.config_for_inspect cfg = cfg.slice(*opts[:arguments]) if opts[:arguments].present? cfg = cfg.to_yaml - cfg.sub! /\A---\n/, '' - cfg.gsub! /^([^\s-].+?:)/, Term::ANSIColor.cyan(Term::ANSIColor.bold('\1')) + cfg.sub!(/\A---\n/, '') + cfg.gsub!(/^([^\s-].+?:)/, Term::ANSIColor.cyan(Term::ANSIColor.bold('\1'))) puts cfg end diff --git a/lib/i18n/tasks/command/commands/missing.rb b/lib/i18n/tasks/command/commands/missing.rb index 81d774f2..473e9db8 100644 --- a/lib/i18n/tasks/command/commands/missing.rb +++ b/lib/i18n/tasks/command/commands/missing.rb @@ -14,9 +14,12 @@ module Missing Array, t('i18n_tasks.cmd.args.desc.missing_types', valid: missing_types * ', '), parser: OptionParsers::Enum::ListParser.new( - missing_types, - proc { |invalid, valid| I18n.t('i18n_tasks.cmd.errors.invalid_missing_type', - invalid: invalid * ', ', valid: valid * ', ', count: invalid.length) }) + missing_types, + proc do |invalid, valid| + I18n.t('i18n_tasks.cmd.errors.invalid_missing_type', + invalid: invalid * ', ', valid: valid * ', ', count: invalid.length) + end + ) cmd :missing, pos: '[locale ...]', @@ -45,23 +48,23 @@ def translate_missing(opt = {}) cmd :add_missing, pos: '[locale ...]', desc: t('i18n_tasks.cmd.desc.add_missing'), - args: [:locales, :out_format, arg(:value) + [{default: '%{value_or_default_or_human_key}'}], + args: [:locales, :out_format, arg(:value) + [{ default: '%{value_or_default_or_human_key}' }], ['--nil-value', 'Set value to nil. Takes precedence over the value argument.']] - def add_missing(opt = {}) + def add_missing(opt = {}) # rubocop:disable Metrics/AbcSize added = i18n.empty_forest locales = (opt[:locales] || i18n.locales) value = opt[:'nil-value'] ? nil : opt[:value] if locales[0] == i18n.base_locale # Merge base locale first, as this may affect the value for the other locales - forest = i18n.missing_keys({locales: [locales[0]]}.update(opt.slice(:types, :base_locale))). - set_each_value!(value) + forest = i18n.missing_keys({ locales: [locales[0]] }.update(opt.slice(:types, :base_locale))) + .set_each_value!(value) i18n.data.merge! forest added.merge! forest locales = locales[1..-1] end - forest = i18n.missing_keys({locales: locales}.update(opt.slice(:types, :base_locale))). - set_each_value!(value) + forest = i18n.missing_keys({ locales: locales }.update(opt.slice(:types, :base_locale))) + .set_each_value!(value) i18n.data.merge! forest added.merge! forest log_stderr t('i18n_tasks.add_missing.added', count: added.leaves.count) diff --git a/lib/i18n/tasks/command/commands/tree.rb b/lib/i18n/tasks/command/commands/tree.rb index 6117c49d..ab196d39 100644 --- a/lib/i18n/tasks/command/commands/tree.rb +++ b/lib/i18n/tasks/command/commands/tree.rb @@ -50,8 +50,8 @@ def tree_rename_key(opt = {}) key = arg_or_pos! :key, opt name = arg_or_pos! :name, opt forest = forest_pos_or_stdin! opt - raise CommandError.new('pass full key to rename (-k, --key)') if key.blank? - raise CommandError.new('pass new name (-n, --name)') if name.blank? + fail CommandError, 'pass full key to rename (-k, --key)' if key.blank? + fail CommandError, 'pass new name (-n, --name)' if name.blank? forest.rename_each_key!(key, name) print_forest forest, opt end @@ -76,7 +76,7 @@ def tree_set_value(opt = {}) value = arg_or_pos! :value, opt forest = forest_pos_or_stdin!(opt) key_pattern = opt[:pattern] - raise CommandError.new('pass value (-v, --value)') if value.blank? + fail CommandError, 'pass value (-v, --value)' if value.blank? forest.set_each_value!(value, key_pattern) print_forest forest, opt end diff --git a/lib/i18n/tasks/command/commands/usages.rb b/lib/i18n/tasks/command/commands/usages.rb index 4a79195b..21d03562 100644 --- a/lib/i18n/tasks/command/commands/usages.rb +++ b/lib/i18n/tasks/command/commands/usages.rb @@ -16,7 +16,8 @@ module Usages def find(opt = {}) opt[:filter] ||= opt.delete(:pattern) || opt[:arguments].try(:first) - print_forest i18n.used_tree(strict: opt[:strict], key_filter: opt[:filter].presence, include_raw_references: true), opt, :used_keys + result = i18n.used_tree(strict: opt[:strict], key_filter: opt[:filter].presence, include_raw_references: true) + print_forest result, opt, :used_keys end cmd :unused, @@ -54,10 +55,10 @@ def confirm_remove_unused!(unused_keys, opt) return if ENV['CONFIRM'] || opt[:confirm] locales = bold(unused_keys.flat_map { |root| root.key.split('+') }.sort.uniq * ', ') msg = [ - red(t('i18n_tasks.remove_unused.confirm', count: unused_keys.leaves.count, locales: locales)), - yellow(t('i18n_tasks.common.continue_q')), - yellow('(yes/no)') - ] * ' ' + red(t('i18n_tasks.remove_unused.confirm', count: unused_keys.leaves.count, locales: locales)), + yellow(t('i18n_tasks.common.continue_q')), + yellow('(yes/no)') + ].join(' ') exit 1 unless agree msg end end diff --git a/lib/i18n/tasks/command/commands/xlsx.rb b/lib/i18n/tasks/command/commands/xlsx.rb index 80403c81..1b313c1c 100644 --- a/lib/i18n/tasks/command/commands/xlsx.rb +++ b/lib/i18n/tasks/command/commands/xlsx.rb @@ -15,7 +15,7 @@ def xlsx_report(opt = {}) begin require 'axlsx' rescue LoadError - message = %Q(For spreadsheet report please add axlsx gem to Gemfile:\ngem 'axlsx', '~> 2.0') + message = %(For spreadsheet report please add axlsx gem to Gemfile:\ngem 'axlsx', '~> 2.0') log_stderr Term::ANSIColor.red Term::ANSIColor.bold message exit 1 end diff --git a/lib/i18n/tasks/command/dsl.rb b/lib/i18n/tasks/command/dsl.rb index e68fecbf..44fcf16d 100644 --- a/lib/i18n/tasks/command/dsl.rb +++ b/lib/i18n/tasks/command/dsl.rb @@ -17,8 +17,7 @@ module ClassMethods def cmd(name, conf = nil) if conf conf = conf.dup - conf[:args] = (args = conf[:args]) ? args.map { |arg| Symbol === arg ? arg(arg) : arg } : [] - + conf[:args] = (conf[:args] || []).map { |arg| arg.is_a?(Symbol) ? arg(arg) : arg } dsl(:cmds)[name] = conf else dsl(:cmds)[name] diff --git a/lib/i18n/tasks/command/option_parsers/enum.rb b/lib/i18n/tasks/command/option_parsers/enum.rb index c8ca7d7f..21bd4e6d 100644 --- a/lib/i18n/tasks/command/option_parsers/enum.rb +++ b/lib/i18n/tasks/command/option_parsers/enum.rb @@ -4,9 +4,9 @@ module Command module OptionParsers module Enum class Parser - DEFAULT_ERROR = proc { |invalid, valid| + DEFAULT_ERROR = proc do |invalid, valid| I18n.t('i18n_tasks.cmd.enum_opt.invalid', invalid: invalid, valid: valid * ', ') - } + end def initialize(valid, error_message = DEFAULT_ERROR) @valid = valid.map(&:to_s) @@ -18,15 +18,15 @@ def call(value, *) if @valid.include?(value) value else - raise CommandError.new @error_message.call(value, @valid) + fail CommandError, @error_message.call(value, @valid) end end end class ListParser - DEFAULT_ERROR = proc { |invalid, valid| + DEFAULT_ERROR = proc do |invalid, valid| I18n.t('i18n_tasks.cmd.enum_list_opt.invalid', invalid: invalid * ', ', valid: valid * ', ') - } + end def initialize(valid, error_message = DEFAULT_ERROR) @valid = valid.map(&:to_s) @@ -44,7 +44,7 @@ def call(values, *) values end else - raise CommandError.new @error_message.call(invalid, @valid) + fail CommandError, @error_message.call(invalid, @valid) end end end diff --git a/lib/i18n/tasks/command/option_parsers/locale.rb b/lib/i18n/tasks/command/option_parsers/locale.rb index a81e32a1..fe1ae206 100644 --- a/lib/i18n/tasks/command/option_parsers/locale.rb +++ b/lib/i18n/tasks/command/option_parsers/locale.rb @@ -8,7 +8,7 @@ module Validator def validate!(locale) if VALID_LOCALE_RE !~ locale - raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.invalid_locale', invalid: locale)) + fail CommandError, I18n.t('i18n_tasks.cmd.errors.invalid_locale', invalid: locale) end locale end @@ -16,6 +16,7 @@ def validate!(locale) module Parser module_function + extend Validator # @param [#base_locale, #locales] context @@ -30,6 +31,7 @@ def call(val, context) module ListParser module_function + extend Validator # @param [#base_locale,#locales] context @@ -44,7 +46,7 @@ def call(vals, context) end def move_base_locale_to_front!(locales, base_locale) - if (pos = locales.index(base_locale)) && pos > 0 + if (pos = locales.index(base_locale)) && pos.positive? locales[pos], locales[0] = locales[0], locales[pos] end locales diff --git a/lib/i18n/tasks/command/options/data.rb b/lib/i18n/tasks/command/options/data.rb index c8dfd11e..983ae3ef 100644 --- a/lib/i18n/tasks/command/options/data.rb +++ b/lib/i18n/tasks/command/options/data.rb @@ -7,22 +7,23 @@ module Options module Data include Command::DSL - DATA_FORMATS = %w(yaml json keys) - OUT_FORMATS = ['terminal-table', *DATA_FORMATS, 'inspect'] + DATA_FORMATS = %w(yaml json keys).freeze + OUT_FORMATS = ['terminal-table', *DATA_FORMATS, 'inspect'].freeze - format_arg = proc { |type, values| + format_arg = proc do |type, values| default = values.first arg type, '-f', '--format FORMAT', values, - {'yml' => 'yaml'}, + { 'yml' => 'yaml' }, t("i18n_tasks.cmd.args.desc.#{type}", valid_text: values * ', ', default_text: default), parser: OptionParsers::Enum::Parser.new(values, - proc { |value, valid| - I18n.t('i18n_tasks.cmd.errors.invalid_format', - invalid: value, valid: valid * ', ') }) - } + proc do |value, valid| + I18n.t('i18n_tasks.cmd.errors.invalid_format', + invalid: value, valid: valid * ', ') + end) + end # i18n-tasks-use t('i18n_tasks.cmd.args.desc.data_format') format_arg.call(:data_format, DATA_FORMATS) @@ -41,7 +42,7 @@ def forests_stdin_and_pos!(opt, num = false, format = opt[:format]) sources = [] else sources = [$stdin.read] - num -= 1 if num + num -= 1 if num end if num num.times { sources << args.shift } @@ -53,14 +54,14 @@ def forests_stdin_and_pos!(opt, num = false, format = opt[:format]) end def merge_forests_stdin_and_pos!(opt) - forests_stdin_and_pos!(opt).inject(i18n.empty_forest) { |result, forest| + forests_stdin_and_pos!(opt).inject(i18n.empty_forest) do |result, forest| result.merge! forest - } + end end def parse_forest(src, format) - if !src - raise CommandError.new(I18n.t('i18n_tasks.cmd.errors.pass_forest')) + unless src + fail CommandError, I18n.t('i18n_tasks.cmd.errors.pass_forest') end if format == 'keys' ::I18n::Tasks::Data::Tree::Siblings.from_key_names parse_keys(src) @@ -76,14 +77,14 @@ def parse_keys(src) def print_forest(forest, opts, version = :show_tree) format = opts[:format].to_s case format - when 'terminal-table' - terminal_report.send(version, forest) - when 'inspect' - puts forest.inspect - when 'keys' - puts forest.key_names(root: true) - when *DATA_FORMATS - puts i18n.data.adapter_dump forest.to_hash(true), format + when 'terminal-table' + terminal_report.send(version, forest) + when 'inspect' + puts forest.inspect + when 'keys' + puts forest.key_names(root: true) + when *DATA_FORMATS + puts i18n.data.adapter_dump forest.to_hash(true), format end end end diff --git a/lib/i18n/tasks/command_error.rb b/lib/i18n/tasks/command_error.rb index 295bb1aa..cc1e30e8 100644 --- a/lib/i18n/tasks/command_error.rb +++ b/lib/i18n/tasks/command_error.rb @@ -5,12 +5,10 @@ module Tasks # 1. show error message of the backtrace # 2. exit with non-zero exit code class CommandError < StandardError - - def initialize(error = nil, message) + def initialize(error = nil, message) # rubocop:disable Style/OptionalArguments super(message) set_backtrace error.backtrace if error end end end end - diff --git a/lib/i18n/tasks/configuration.rb b/lib/i18n/tasks/configuration.rb index 55dbcc4d..feec3f50 100644 --- a/lib/i18n/tasks/configuration.rb +++ b/lib/i18n/tasks/configuration.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module I18n::Tasks::Configuration DEFAULTS = { - base_locale: 'en'.freeze, - internal_locale: 'en'.freeze, - search: ::I18n::Tasks::UsedKeys::SEARCH_DEFAULTS, - data: ::I18n::Tasks::Data::DATA_DEFAULTS - } + base_locale: 'en', + internal_locale: 'en', + search: ::I18n::Tasks::UsedKeys::SEARCH_DEFAULTS, + data: ::I18n::Tasks::Data::DATA_DEFAULTS + }.freeze # i18n-tasks config (defaults + config/i18n-tasks.yml) # @return [Hash{String => String,Hash,Array}] @@ -14,9 +14,9 @@ def config end CONFIG_FILES = %w( - config/i18n-tasks.yml config/i18n-tasks.yml.erb - i18n-tasks.yml i18n-tasks.yml.erb - ) + config/i18n-tasks.yml config/i18n-tasks.yml.erb + i18n-tasks.yml i18n-tasks.yml.erb + ).freeze def file_config file = CONFIG_FILES.detect { |f| File.exist?(f) } @@ -24,7 +24,7 @@ def file_config if config.present? config.with_indifferent_access.tap do |c| if c[:relative_roots] - warn_deprecated 'config/i18n-tasks.yml has relative_roots on top level. Please move relative_roots under search.' + warn_deprecated 'Please move relative_roots under search in config/i18n-tasks.yml.' c[:search][:relative_roots] = c.delete(:relative_roots) end end @@ -44,8 +44,8 @@ def config=(conf) def data_config @config_sections[:data] ||= begin { - adapter: data.class.name, - config: data.config + adapter: data.class.name, + config: data.config } end end @@ -54,7 +54,7 @@ def data_config # @return [Hash{String => String,Hash,Array}] def translation_config @config_sections[:translation] ||= begin - conf = (config[:translation] || {}).with_indifferent_access + conf = (config[:translation] || {}).with_indifferent_access conf[:api_key] ||= ENV['GOOGLE_TRANSLATE_API_KEY'] if ENV.key?('GOOGLE_TRANSLATE_API_KEY') conf end @@ -75,7 +75,7 @@ def internal_locale end def ignore_config(type = nil) - key = type ? "ignore_#{type}" : 'ignore' + key = type ? "ignore_#{type}" : 'ignore' @config_sections[key] ||= config[key] end @@ -96,25 +96,25 @@ def config_sections end def config_for_inspect - to_hash_from_indifferent(config_sections.reject { |k, v| v.blank? }).tap do |sections| + to_hash_from_indifferent(config_sections.reject { |_k, v| v.blank? }).tap do |sections| sections.each do |_k, section| - section.merge! section.delete('config') if Hash === section && section.key?('config') + section.merge! section.delete('config') if section.is_a?(Hash) && section.key?('config') end end end private - def to_hash_from_indifferent(v) - case v - when Hash - v.stringify_keys.to_hash.tap do |h| - h.each { |k, v| h[k] = to_hash_from_indifferent(v) if Hash === v || Array === v } - end - when Array - v.map { |e| to_hash_from_indifferent e } - else - v + def to_hash_from_indifferent(value) + case value + when Hash + value.stringify_keys.to_hash.tap do |h| + h.each { |k, v| h[k] = to_hash_from_indifferent(v) if v.is_a?(Hash) || v.is_a?(Array) } + end + when Array + value.map { |e| to_hash_from_indifferent e } + else + value end end end diff --git a/lib/i18n/tasks/console_context.rb b/lib/i18n/tasks/console_context.rb index 644b8302..31e7b227 100644 --- a/lib/i18n/tasks/console_context.rb +++ b/lib/i18n/tasks/console_context.rb @@ -27,14 +27,15 @@ def start module Messages include Term::ANSIColor - extend self + + module_function def banner bold("i18n-tasks v#{I18n::Tasks::VERSION} IRB") + "\nType #{green 'guide'} to learn more" end def guide - green(bold "i18n-tasks IRB Quick Start guide") + "\n" + <<-TEXT + green(bold('i18n-tasks IRB Quick Start guide')) + "\n" + <<-TEXT #{yellow 'Data as trees'} tree(locale) used_tree(key_filter: nil, strict: nil) diff --git a/lib/i18n/tasks/data.rb b/lib/i18n/tasks/data.rb index 6d767643..0704aa93 100644 --- a/lib/i18n/tasks/data.rb +++ b/lib/i18n/tasks/data.rb @@ -4,15 +4,16 @@ module I18n::Tasks module Data DATA_DEFAULTS = { - adapter: 'I18n::Tasks::Data::FileSystem' - } + adapter: 'I18n::Tasks::Data::FileSystem' + }.freeze # I18n data provider # @see I18n::Tasks::Data::FileSystem def data @data ||= begin data_config = (config[:data] || {}).deep_symbolize_keys - data_config.merge!(base_locale: base_locale, locales: config[:locales]) + data_config[:base_locale] = base_locale + data_config[:locales] = config[:locales] adapter_class = data_config[:adapter].presence || data_config[:class].presence || DATA_DEFAULTS[:adapter] adapter_class = adapter_class.to_s adapter_class = 'I18n::Tasks::Data::FileSystem' if adapter_class == 'file_system' @@ -59,7 +60,7 @@ def key_value?(key, locale = base_locale) # write to store, normalizing all data def normalize_store!(from = nil, pattern_router = false) - from = self.locales unless from + from = locales unless from router = pattern_router ? ::I18n::Tasks::Data::Router::PatternRouter.new(data, data.config) : data.router data.with_router(router) do Array(from).each do |target_locale| diff --git a/lib/i18n/tasks/data/adapter/json_adapter.rb b/lib/i18n/tasks/data/adapter/json_adapter.rb index 70924069..53ed7377 100644 --- a/lib/i18n/tasks/data/adapter/json_adapter.rb +++ b/lib/i18n/tasks/data/adapter/json_adapter.rb @@ -5,21 +5,22 @@ module I18n::Tasks module Data module Adapter module JsonAdapter - extend self + class << self + # @return [Hash] locale tree + def parse(str, opts) + JSON.parse(str, parse_opts(opts)) + end - # @return [Hash] locale tree - def parse(str, opts) - JSON.parse(str, parse_opts(opts)) - end + # @return [String] + def dump(tree, opts) + JSON.generate(tree, parse_opts(opts)) + end - # @return [String] - def dump(tree, opts) - JSON.generate(tree, parse_opts(opts)) - end + private - private - def parse_opts(opts) - opts.try(:symbolize_keys) || {} + def parse_opts(opts) + opts.try(:symbolize_keys) || {} + end end end end diff --git a/lib/i18n/tasks/data/adapter/yaml_adapter.rb b/lib/i18n/tasks/data/adapter/yaml_adapter.rb index cda8720f..c8251def 100644 --- a/lib/i18n/tasks/data/adapter/yaml_adapter.rb +++ b/lib/i18n/tasks/data/adapter/yaml_adapter.rb @@ -4,23 +4,22 @@ module I18n::Tasks module Data module Adapter module YamlAdapter - extend self - - # @return [Hash] locale tree - def parse(str, options) - if YAML.method(:load).arity.abs == 2 - YAML.load(str, options || {}) - else - # older jruby and rbx 2.2.7 do not accept options - YAML.load(str) + class << self + # @return [Hash] locale tree + def parse(str, options) + if YAML.method(:load).arity.abs == 2 + YAML.load(str, options || {}) + else + # older jruby and rbx 2.2.7 do not accept options + YAML.load(str) + end end - end - # @return [String] - def dump(tree, options) - tree.to_yaml(options || {}) + # @return [String] + def dump(tree, options) + tree.to_yaml(options || {}) + end end - end end end diff --git a/lib/i18n/tasks/data/file_formats.rb b/lib/i18n/tasks/data/file_formats.rb index ef0ff5eb..ad3dc73f 100644 --- a/lib/i18n/tasks/data/file_formats.rb +++ b/lib/i18n/tasks/data/file_formats.rb @@ -19,8 +19,8 @@ def adapter_parse(tree, format) def adapter_op(op, format, tree, config) self.class.adapter_by_name(format).send(op, tree, config) - rescue Exception => e - raise CommandError.new("#{format} #{op} error: #{e.message}") + rescue Exception => e # rubocop:disable Lint/RescueException + raise CommandError, "#{format} #{op} error: #{e.message}" end protected @@ -43,10 +43,10 @@ def write_tree(path, tree) content = adapter_dump(hash, adapter) # Ignore unchanged data return if File.file?(path) && - # Comparing hashes for equality directly would ignore key order. - # Round-trip through the adapter and compare the strings instead: - content == adapter_dump(load_file(path), adapter) - ::FileUtils.mkpath(File.dirname path) + # Comparing hashes for equality directly would ignore key order. + # Round-trip through the adapter and compare the strings instead: + content == adapter_dump(load_file(path), adapter) + ::FileUtils.mkpath(File.dirname(path)) ::File.open(path, 'w') { |f| f.write content } end @@ -58,9 +58,11 @@ def register_adapter(name, pattern, adapter) end def adapter_name_for_path(path) - @fn_patterns.detect { |(_name, pattern, _adapter)| + @fn_patterns.detect do |(_name, pattern, _adapter)| ::File.fnmatch(pattern, path) - }.try(:first) or raise CommandError.new("Adapter not found for #{path}. Registered adapters: #{@fn_patterns.inspect}") + end.try(:first) || fail( + CommandError, "Adapter not found for #{path}. Registered adapters: #{@fn_patterns.inspect}" + ) end def adapter_names @@ -69,9 +71,12 @@ def adapter_names def adapter_by_name(name) name = name.to_s - @fn_patterns.detect { |(adapter_name, _pattern, _adapter)| + @fn_patterns.detect do |(adapter_name, _pattern, _adapter)| adapter_name.to_s == name - }.try(:last) or raise CommandError.new("Adapter with name #{name.inspect} not found. Registered adapters: #{@fn_patterns.inspect}") + end.try(:last) || fail( + CommandError, + "Adapter with name #{name.inspect} not found. Registered adapters: #{@fn_patterns.inspect}" + ) end end end diff --git a/lib/i18n/tasks/data/file_system_base.rb b/lib/i18n/tasks/data/file_system_base.rb index 0346b910..4a2a0e14 100644 --- a/lib/i18n/tasks/data/file_system_base.rb +++ b/lib/i18n/tasks/data/file_system_base.rb @@ -15,9 +15,9 @@ class FileSystemBase attr_writer :locales DEFAULTS = { - read: ['config/locales/%{locale}.yml'], - write: ['config/locales/%{locale}.yml'] - } + read: ['config/locales/%{locale}.yml'], + write: ['config/locales/%{locale}.yml'] + }.freeze def initialize(config = {}) self.config = config.except(:base_locale, :locales) @@ -33,10 +33,10 @@ def initialize(config = {}) # get locale tree def get(locale) - locale = locale.to_s + locale = locale.to_s @trees ||= {} @trees[locale] ||= Tree::Siblings[locale => {}].merge!( - read_locale locale + read_locale(locale) ) end @@ -48,7 +48,7 @@ def set(locale, tree) router.route locale, tree do |path, tree_slice| write_tree path, tree_slice end - @trees.delete(locale) if @trees + @trees&.delete(locale) @available_locales = nil end @@ -57,12 +57,12 @@ def write(forest) end def merge!(forest) - forest.inject(Tree::Siblings.new) { |result, root| + forest.inject(Tree::Siblings.new) do |result, root| locale = root.key merged = get(locale).merge(root) set locale, merged result.merge! merged - } + end end def remove_by_key!(forest) @@ -88,17 +88,13 @@ def available_locales @available_locales ||= begin locales = Set.new Array(config[:read]).map do |pattern| - [pattern, Dir.glob(pattern % {locale: '*'})] if pattern.include?('%{locale}'.freeze) + [pattern, Dir.glob(pattern % { locale: '*' })] if pattern.include?('%{locale}') end.compact.each do |pattern, paths| - p = pattern.gsub('\\'.freeze, '\\\\'.freeze).gsub('/'.freeze, '\/'.freeze).gsub('.'.freeze, '\.'.freeze) - p = p.gsub(/(\*+)/) { - $1 == '**'.freeze ? '.*'.freeze : '[^/]*?'.freeze - }.gsub('%{locale}'.freeze, '([^/.]+)'.freeze) + p = pattern.gsub('\\', '\\\\').gsub('/', '\/').gsub('.', '\.') + p = p.gsub(/(\*+)/) { Regexp.last_match(1) == '**' ? '.*' : '[^/]*?' }.gsub('%{locale}', '([^/.]+)') re = /\A#{p}\z/ paths.each do |path| - if re =~ path - locales << $1 - end + locales << Regexp.last_match(1) if re =~ path end end locales @@ -112,7 +108,7 @@ def t(key, locale) end def config=(config) - @config = DEFAULTS.deep_merge((config || {}).reject { |k, v| v.nil? }) + @config = DEFAULTS.deep_merge((config || {}).reject { |_k, v| v.nil? }) reload end @@ -124,16 +120,16 @@ def with_router(router) self.router = router_was end - ROUTER_NAME_ALIASES = { - 'conservative_router' => 'I18n::Tasks::Data::Router::ConservativeRouter', - 'pattern_router' => 'I18n::Tasks::Data::Router::PatternRouter' - } + 'conservative_router' => 'I18n::Tasks::Data::Router::ConservativeRouter', + 'pattern_router' => 'I18n::Tasks::Data::Router::PatternRouter' + }.freeze def router @router ||= begin name = @config[:router].presence || 'conservative_router' name = ROUTER_NAME_ALIASES[name] || name - ActiveSupport::Inflector.constantize(name).new(self, @config.merge(base_locale: base_locale, locales: locales)) + router_class = ActiveSupport::Inflector.constantize(name) + router_class.new(self, @config.merge(base_locale: base_locale, locales: locales)) end end attr_writer :router @@ -142,7 +138,7 @@ def router def read_locale(locale) Array(config[:read]).map do |path| - Dir.glob path % {locale: locale} + Dir.glob path % { locale: locale } end.reduce(:+).map do |path| [path.freeze, load_file(path) || {}] end.map do |path, data| diff --git a/lib/i18n/tasks/data/router/conservative_router.rb b/lib/i18n/tasks/data/router/conservative_router.rb index 7c3c3df5..e881770f 100644 --- a/lib/i18n/tasks/data/router/conservative_router.rb +++ b/lib/i18n/tasks/data/router/conservative_router.rb @@ -20,9 +20,7 @@ def route(locale, forest, &block) path = key_path(locale, key) # infer from another locale unless path - inferred_from = (locales - [locale]).detect { |loc| - path = key_path(loc, key) - } + inferred_from = (locales - [locale]).detect { |loc| path = key_path(loc, key) } path = LocalePathname.replace_locale(path, inferred_from, locale) if inferred_from end key_with_locale = "#{locale}.#{key}" @@ -36,9 +34,9 @@ def route(locale, forest, &block) if not_found.present? # fall back to pattern router not_found_tree = forest.select_keys(root: true) { |key, _| not_found.include?(key) } - super(locale, not_found_tree) { |path, tree| + super(locale, not_found_tree) do |path, tree| out[path] += tree.key_names(root: true) - } + end end out.each do |dest, keys| @@ -60,4 +58,3 @@ def key_path(locale, key) end end end - diff --git a/lib/i18n/tasks/data/router/pattern_router.rb b/lib/i18n/tasks/data/router/pattern_router.rb index 2e70bf00..31d7d010 100644 --- a/lib/i18n/tasks/data/router/pattern_router.rb +++ b/lib/i18n/tasks/data/router/pattern_router.rb @@ -32,11 +32,11 @@ def route(locale, forest, &block) pattern, path = routes.detect { |route| route[0] =~ key } if pattern key_match = $~ - path = path % {locale: locale} + path = path % { locale: locale } path.gsub!(/\\\d+/) { |m| key_match[m[1..-1].to_i] } (out[path] ||= Set.new) << "#{locale}.#{key}" else - raise CommandError.new("Cannot route key #{key}. Routes are #{@routes_config.inspect}") + fail CommandError, "Cannot route key #{key}. Routes are #{@routes_config.inspect}" end end out.each do |dest, keys| @@ -48,11 +48,10 @@ def route(locale, forest, &block) private def compile_routes(routes) - routes.map { |x| x.is_a?(String) ? ['*', x] : x }.map { |x| + routes.map { |x| x.is_a?(String) ? ['*', x] : x }.map do |x| [compile_key_pattern(x[0]), x[1]] - } + end end end end end - diff --git a/lib/i18n/tasks/data/tree/node.rb b/lib/i18n/tasks/data/tree/node.rb index 302e17cb..a53cb5f0 100644 --- a/lib/i18n/tasks/data/tree/node.rb +++ b/lib/i18n/tasks/data/tree/node.rb @@ -3,7 +3,7 @@ require 'i18n/tasks/data/tree/traversal' require 'i18n/tasks/data/tree/siblings' module I18n::Tasks::Data::Tree - class Node + class Node # rubocop:disable Metrics/ClassLength include Enumerable include Traversal @@ -11,16 +11,16 @@ class Node attr_reader :key, :children, :parent def initialize(key:, value: nil, data: nil, parent: nil, children: nil) - @key = key + @key = key @key = @key.to_s.freeze if @key - @value = value - @data = data - @parent = parent + @value = value + @data = data + @parent = parent self.children = (children if children) end def attributes - {key: @key, value: @value, data: @data.try(:clone), parent: @parent, children: @children} + { key: @key, value: @value, data: @data.try(:clone), parent: @parent, children: @children } end def derive(new_attr = {}) @@ -29,12 +29,12 @@ def derive(new_attr = {}) def children=(children) @children = case children - when Siblings - children.parent == self ? children : children.derive(parent: self) - when NilClass - nil - else - Siblings.new(nodes: children, parent: self) + when Siblings + children.parent == self ? children : children.derive(parent: self) + when NilClass + nil + else + Siblings.new(nodes: children, parent: self) end dirty! end @@ -59,7 +59,7 @@ def root? end def parent? - !!parent + !parent.nil? end def children? @@ -99,8 +99,8 @@ def append(nodes) end def full_key(opts = {}) - root = opts.key?(:root) ? opts[:root] : true - @full_key ||= {} + root = opts.key?(:root) ? opts[:root] : true + @full_key ||= {} @full_key[root] ||= "#{"#{parent.full_key(root: root)}." if parent? && (root || parent.parent?)}#{key}" end @@ -128,6 +128,7 @@ def set(full_key, node) dirty! node end + alias []= set def to_nodes @@ -144,9 +145,9 @@ def to_hash(sort = false) if key.nil? children_hash elsif leaf? - {key => value} + { key => value } else - {key => children_hash} + { key => children_hash } end end end @@ -167,7 +168,7 @@ def inspect(level = 0) def format_value_for_inspect(value) if value.is_a?(Symbol) - "#{Term::ANSIColor.bold(Term::ANSIColor.yellow '⮕ ')}#{Term::ANSIColor.yellow value.to_s}" + "#{Term::ANSIColor.bold(Term::ANSIColor.yellow('⮕ '))}#{Term::ANSIColor.yellow value.to_s}" else Term::ANSIColor.cyan(value.to_s) end @@ -176,7 +177,7 @@ def format_value_for_inspect(value) protected def dirty! - @hash = nil + @hash = nil @full_key = nil end diff --git a/lib/i18n/tasks/data/tree/nodes.rb b/lib/i18n/tasks/data/tree/nodes.rb index d6ef2479..ac4b0ddc 100644 --- a/lib/i18n/tasks/data/tree/nodes.rb +++ b/lib/i18n/tasks/data/tree/nodes.rb @@ -20,7 +20,7 @@ def to_nodes end def attributes - {nodes: @list} + { nodes: @list } end def derive(new_attr = {}) @@ -33,7 +33,7 @@ def derive(new_attr = {}) def to_hash(sort = false) (@hash ||= {})[sort] ||= begin if sort - self.sort { |a, b| a.key <=> b.key } + sort_by(&:key) else self end.map { |node| node.to_hash(sort) }.reduce({}, :deep_merge!) @@ -54,7 +54,7 @@ def inspect # methods below change state def remove!(node) - @list.delete(node) or raise "#{node.full_key} not found in #{self.inspect}" + @list.delete(node) || fail("#{node.full_key} not found in #{inspect}") dirty! self end @@ -88,6 +88,7 @@ def children(&block) alias children? any? protected + def dirty! @hash = nil end diff --git a/lib/i18n/tasks/data/tree/siblings.rb b/lib/i18n/tasks/data/tree/siblings.rb index 6e6b9070..6a226f39 100644 --- a/lib/i18n/tasks/data/tree/siblings.rb +++ b/lib/i18n/tasks/data/tree/siblings.rb @@ -8,7 +8,7 @@ module I18n::Tasks::Data::Tree # Siblings represents a subtree sharing a common parent # in case of an empty parent (nil) it represents a forest # siblings' keys are unique - class Siblings < Nodes + class Siblings < Nodes # rubocop:disable Metrics/ClassLength include ::I18n::Tasks::SplitKey attr_reader :parent, :key_to_node @@ -17,7 +17,7 @@ def initialize(opts = {}) super(nodes: opts[:nodes]) @parent = opts[:parent] || first.try(:parent) @list.map! { |node| node.parent == @parent ? node : node.derive(parent: @parent) } - @key_to_node = @list.inject({}) { |h, node| h[node.key] = node; h } + @key_to_node = @list.each_with_object({}) { |node, h| h[node.key] = node } end def attributes @@ -53,9 +53,7 @@ def replace_node!(node, new_node) def get(full_key) first_key, rest = split_key(full_key.to_s, 2) node = key_to_node[first_key] - if rest && node - node = node.children.try(:get, rest) - end + node = node.children.try(:get, rest) if rest && node node end @@ -63,7 +61,7 @@ def get(full_key) # add or replace node by full key def set(full_key, node) - raise 'value should be a I18n::Tasks::Data::Tree::Node' unless node.is_a?(Node) + fail 'value should be a I18n::Tasks::Data::Tree::Node' unless node.is_a?(Node) key_part, rest = split_key(full_key, 2) child = key_to_node[key_part] @@ -87,7 +85,6 @@ def set(full_key, node) alias []= set - # methods below change state def remove!(node) @@ -98,7 +95,7 @@ def remove!(node) def append!(nodes) nodes = nodes.map do |node| - raise "already has a child with key '#{node.key}'" if key_to_node.key?(node.key) + fail "already has a child with key '#{node.key}'" if key_to_node.key?(node.key) key_to_node[node.key] = (node.parent == parent ? node : node.derive(parent: parent)) end super(nodes) @@ -123,11 +120,11 @@ def merge(nodes) end def subtract_keys(keys) - remove_nodes_and_emptied_ancestors(keys.inject(Set.new) { |set, key| (node = get(key)) ? set << node : set }) + remove_nodes_and_emptied_ancestors(find_nodes(keys)) end def subtract_keys!(keys) - remove_nodes_and_emptied_ancestors!(keys.inject(Set.new) { |set, key| (node = get(key)) ? set << node : set }) + remove_nodes_and_emptied_ancestors!(find_nodes(keys)) end def subtract_by_key(other) @@ -146,7 +143,7 @@ def set_root_key!(new_key, data = nil) end # @param on_leaves_merge [Proc] invoked when a leaf is merged with another leaf - def merge_node!(node, on_leaves_merge: nil) + def merge_node!(node, on_leaves_merge: nil) # rubocop:disable Metrics/AbcSize,Metrics/PerceivedComplexity if key_to_node.key?(node.key) our = key_to_node[node.key] return if our == node @@ -182,6 +179,13 @@ def remove_nodes_and_emptied_ancestors!(nodes) private + def find_nodes(keys) + keys.each_with_object(Set.new) do |key, set| + node = get(key) + set << node if node + end + end + # Adds all the ancestors that only contain the given nodes as descendants to the given nodes. # @param nodes [Set] Modified in-place. def add_ancestors_that_only_contain_nodes!(nodes) @@ -205,7 +209,7 @@ def build_forest(opts = {}, &block) opts[:nodes] ||= [] parse_parent_opt!(opts) forest = Siblings.new(opts) - block.call(forest) if block + yield(forest) if block # forest.parent.children = forest forest end @@ -216,38 +220,39 @@ def from_key_occurrences(key_occurrences) build_forest do |forest| key_occurrences.each do |key_occurrence| forest[key_occurrence.key] = ::I18n::Tasks::Data::Tree::Node.new( - key: split_key(key_occurrence.key).last, - data: {occurrences: key_occurrence.occurrences}) + key: split_key(key_occurrence.key).last, + data: { occurrences: key_occurrence.occurrences } + ) end end end def from_key_attr(key_attrs, opts = {}, &block) - build_forest(opts) { |forest| - key_attrs.each { |(full_key, attr)| - raise "Invalid key #{full_key.inspect}" if full_key.end_with?('.') + build_forest(opts) do |forest| + key_attrs.each do |(full_key, attr)| + fail "Invalid key #{full_key.inspect}" if full_key.end_with?('.') node = ::I18n::Tasks::Data::Tree::Node.new(attr.merge(key: split_key(full_key).last)) - block.call(full_key, node) if block + yield(full_key, node) if block forest[full_key] = node - } - } + end + end end def from_key_names(keys, opts = {}, &block) - build_forest(opts) { |forest| - keys.each { |full_key| + build_forest(opts) do |forest| + keys.each do |full_key| node = ::I18n::Tasks::Data::Tree::Node.new(key: split_key(full_key).last) - block.call(full_key, node) if block + yield(full_key, node) if block forest[full_key] = node - } - } + end + end end # build forest from nested hash, e.g. {'es' => { 'common' => { name => 'Nombre', 'age' => 'Edad' } } } # this is the native i18n gem format def from_nested_hash(hash, opts = {}) parse_parent_opt!(opts) - raise ::I18n::Tasks::CommandError.new("invalid tree #{hash.inspect}") unless hash.respond_to?(:map) + fail I18n::Tasks::CommandError, "invalid tree #{hash.inspect}" unless hash.respond_to?(:map) opts[:nodes] = hash.map { |key, value| Node.from_key_value key, value } Siblings.new(opts) end @@ -257,13 +262,14 @@ def from_nested_hash(hash, opts = {}) # build forest from [[Full Key, Value]] def from_flat_pairs(pairs) Siblings.new.tap do |siblings| - pairs.each { |full_key, value| + pairs.each do |full_key, value| siblings[full_key] = ::I18n::Tasks::Data::Tree::Node.new(key: split_key(full_key).last, value: value) - } + end end end private + def parse_parent_opt!(opts) if opts[:parent_key] opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new(key: opts[:parent_key]) @@ -273,7 +279,8 @@ def parse_parent_opt!(opts) end if opts[:parent_locale] opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new( - key: opts[:parent_locale], data: {locale: opts[:parent_locale]}) + key: opts[:parent_locale], data: { locale: opts[:parent_locale] } + ) end end end diff --git a/lib/i18n/tasks/data/tree/traversal.rb b/lib/i18n/tasks/data/tree/traversal.rb index 33c25501..530386bc 100644 --- a/lib/i18n/tasks/data/tree/traversal.rb +++ b/lib/i18n/tasks/data/tree/traversal.rb @@ -2,8 +2,7 @@ module I18n::Tasks module Data::Tree # Any Enumerable that yields nodes can mix in this module - module Traversal - + module Traversal # rubocop:disable Metrics/ModuleLength def nodes(&block) depth_first(&block) end @@ -41,12 +40,13 @@ def breadth_first(&visitor) def depth_first(&visitor) return to_enum(:depth_first) unless visitor - each { |node| + each do |node| visitor.yield node + next unless node.children? node.children.each do |child| child.depth_first(&visitor) - end if node.children? - } + end + end self end @@ -58,7 +58,6 @@ def keys(key_opts = {}, &visitor) self end - def key_names(opts = {}) opts[:root] = false unless opts.key?(:root) keys(opts).map { |key, _node| key } @@ -88,12 +87,11 @@ def root_key_value_data(sort = false) def select_nodes(&block) tree = Siblings.new each do |node| - if block.yield(node) - tree.append! node.derive( - parent: tree.parent, - children: (node.children.select_nodes(&block).to_a if node.children) - ) - end + next unless block.yield(node) + tree.append! node.derive( + parent: tree.parent, + children: (node.children.select_nodes(&block).to_a if node.children) + ) end tree end @@ -104,7 +102,7 @@ def select_nodes!(&block) to_remove = [] each do |node| if block.yield(node) - node.children.select_nodes!(&block) if node.children + node.children&.select_nodes!(&block) else # removing during each is unsafe to_remove << node @@ -117,56 +115,54 @@ def select_nodes!(&block) # @return Siblings def select_keys(opts = {}, &block) root = opts.key?(:root) ? opts[:root] : false - ok = {} + ok = {} keys(root: root) do |full_key, node| if block.yield(full_key, node) - node.walk_to_root { |p| + node.walk_to_root do |p| break if ok[p] ok[p] = true - } + end end end - select_nodes { |node| + select_nodes do |node| ok[node] - } + end end # @return Siblings def intersect_keys(other_tree, key_opts = {}, &block) if block - select_keys(key_opts) { |key, node| + select_keys(key_opts) do |key, node| other_node = other_tree[key] - other_node && block.call(key, node, other_node) - } + other_node && yield(key, node, other_node) + end else - select_keys(key_opts) { |key, node| other_tree[key] } + select_keys(key_opts) { |key, _node| other_tree[key] } end end def grep_keys(match, opts = {}) select_keys(opts) do |full_key, _node| - match === full_key + match === full_key # rubocop:disable Style/CaseEquality end end def set_each_value!(val_pattern, key_pattern = nil, &value_proc) - value_proc ||= proc { |node| + value_proc ||= proc do |node| node_value = node.value next node_value if node.reference? - human_key = ActiveSupport::Inflector.humanize(node.key.to_s) - full_key = node.full_key + human_key = ActiveSupport::Inflector.humanize(node.key.to_s) + full_key = node.full_key + default = (node.data[:occurrences] || []).detect { |o| o.default_arg.presence }.try(:default_arg) StringInterpolation.interpolate_soft( - val_pattern, - value: node_value, - human_key: human_key, - key: full_key, - value_or_human_key: node_value.presence || human_key, - value_or_default_or_human_key: node_value.presence || - (node.data[:occurrences] || []).detect { |o| - o.default_arg.presence }.try(:default_arg) || - human_key + val_pattern, + value: node_value, + human_key: human_key, + key: full_key, + value_or_human_key: node_value.presence || human_key, + value_or_default_or_human_key: node_value.presence || default || human_key ) - } + end if key_pattern.present? pattern_re = I18n::Tasks::KeyPatternMatching.compile_key_pattern(key_pattern) end diff --git a/lib/i18n/tasks/google_translation.rb b/lib/i18n/tasks/google_translation.rb index 4ecaaeb9..6e829c99 100644 --- a/lib/i18n/tasks/google_translation.rb +++ b/lib/i18n/tasks/google_translation.rb @@ -4,7 +4,6 @@ module I18n::Tasks module GoogleTranslation - # @param [I18n::Tasks::Tree::Siblings] forest to translate to the locales of its root nodes # @param [String] from locale # @return [I18n::Tasks::Tree::Siblings] translated forest @@ -17,18 +16,18 @@ def google_translate_forest(forest, from) # @param [Array<[String, Object]>] list of key-value pairs # @return [Array<[String, Object]>] translated list - def google_translate_list(list, opts) + def google_translate_list(list, opts) # rubocop:disable Metrics/AbcSize return [] if list.empty? - opts = opts.dup + opts = opts.dup opts[:key] ||= translation_config[:api_key] validate_google_translate_api_key! opts[:key] - key_pos = list.each_with_index.inject({}) { |idx, ((k, _v), i)| idx[k] = i; idx } + key_pos = list.each_with_index.inject({}) { |idx, ((k, _v), i)| idx.update(k => i) } # copy reference keys as is, instead of translating reference_key_vals = list.select { |_k, v| v.is_a? Symbol } || [] list -= reference_key_vals - result = list.group_by { |k_v| html_key? k_v[0], opts[:from] }.map { |is_html, list_slice| - fetch_google_translations list_slice, opts.merge(is_html ? {html: true} : {format: 'text'}) - }.reduce(:+) || [] + result = list.group_by { |k_v| html_key? k_v[0], opts[:from] }.map do |is_html, list_slice| + fetch_google_translations list_slice, opts.merge(is_html ? { html: true } : { format: 'text' }) + end.reduce(:+) || [] result.concat(reference_key_vals) result.sort! { |a, b| key_pos[a[0]] <=> key_pos[b[0]] } result @@ -39,7 +38,7 @@ def google_translate_list(list, opts) def fetch_google_translations(list, opts) from_values(list, EasyTranslate.translate(to_values(list), opts)).tap do |result| if result.blank? - raise CommandError.new(I18n.t('i18n_tasks.google_translate.errors.no_results')) + fail CommandError, I18n.t('i18n_tasks.google_translate.errors.no_results') end end end @@ -48,7 +47,7 @@ def fetch_google_translations(list, opts) def validate_google_translate_api_key!(key) if key.blank? - raise CommandError.new(I18n.t('i18n_tasks.google_translate.errors.no_api_key')) + fail CommandError, I18n.t('i18n_tasks.google_translate.errors.no_api_key') end end @@ -71,13 +70,11 @@ def from_values(list, translated_values) # @return [String, Array, nil] value for Google Translate or nil for non-string values def dump_value(value) case value - when Array - # dump recursively - value.map { |v| dump_value v } - when String - replace_interpolations value - else - nil + when Array + # dump recursively + value.map { |v| dump_value v } + when String + replace_interpolations value end end @@ -87,18 +84,18 @@ def dump_value(value) # @return [Object] final translated value def parse_value(untranslated, each_translated) case untranslated - when Array - # implode array - untranslated.map { |from| parse_value(from, each_translated) } - when String - restore_interpolations untranslated, each_translated.next - else - untranslated + when Array + # implode array + untranslated.map { |from| parse_value(from, each_translated) } + when String + restore_interpolations untranslated, each_translated.next + else + untranslated end end - INTERPOLATION_KEY_RE = /%\{[^}]+\}/.freeze - UNTRANSLATABLE_STRING = 'zxzxzx'.freeze + INTERPOLATION_KEY_RE = /%\{[^}]+\}/ + UNTRANSLATABLE_STRING = 'zxzxzx' # @param [String] value # @return [String] 'hello, %{name}' => 'hello, ' diff --git a/lib/i18n/tasks/html_keys.rb b/lib/i18n/tasks/html_keys.rb index 83f52f27..5fbe1d97 100644 --- a/lib/i18n/tasks/html_keys.rb +++ b/lib/i18n/tasks/html_keys.rb @@ -5,9 +5,11 @@ module HtmlKeys MAYBE_PLURAL_HTML_KEY_PATTERN = /[.\-_]html\.[^.]+\z/ def html_key?(full_key, locale) + # rubocop:disable Style/DoubleNegation !!(full_key =~ HTML_KEY_PATTERN || full_key =~ MAYBE_PLURAL_HTML_KEY_PATTERN && depluralize_key(split_key(full_key, 2)[1], locale) =~ HTML_KEY_PATTERN) + # rubocop:enable Style/DoubleNegation end end end diff --git a/lib/i18n/tasks/ignore_keys.rb b/lib/i18n/tasks/ignore_keys.rb index 46413908..cc0f0f3e 100644 --- a/lib/i18n/tasks/ignore_keys.rb +++ b/lib/i18n/tasks/ignore_keys.rb @@ -14,13 +14,14 @@ def ignore_pattern(type, locale = nil) @ignore_patterns ||= HashWithIndifferentAccess.new @ignore_patterns[type] ||= {} @ignore_patterns[type][locale] ||= begin - global, type_ignore = ignore_config.presence || [], ignore_config(type).presence || [] + global = ignore_config.presence || [] + type_ignore = ignore_config(type).presence || [] patterns = if type_ignore.is_a?(Array) global + type_ignore elsif type_ignore.is_a?(Hash) # ignore per locale global + (type_ignore['all'] || []) + - type_ignore.select { |k, v| k.to_s =~ /\b#{locale}\b/ }.values.flatten(1).compact + type_ignore.select { |k, _v| k.to_s =~ /\b#{locale}\b/ }.values.flatten(1).compact end compile_patterns_re patterns end diff --git a/lib/i18n/tasks/key_pattern_matching.rb b/lib/i18n/tasks/key_pattern_matching.rb index 619b7ed4..90e0612d 100644 --- a/lib/i18n/tasks/key_pattern_matching.rb +++ b/lib/i18n/tasks/key_pattern_matching.rb @@ -2,8 +2,9 @@ require 'strscan' module I18n::Tasks::KeyPatternMatching - extend self - MATCH_NOTHING = /\z\A/.freeze + extend self # rubocop:disable Style/ModuleFunction + + MATCH_NOTHING = /\z\A/ # one regex to match any def compile_patterns_re(key_patterns) @@ -11,7 +12,7 @@ def compile_patterns_re(key_patterns) # match nothing MATCH_NOTHING else - /(?:#{ key_patterns.map { |p| compile_key_pattern p } * '|'.freeze })/m + /(?:#{ key_patterns.map { |p| compile_key_pattern p } * '|' })/m end end @@ -26,10 +27,10 @@ def compile_key_pattern(key_pattern) end def key_pattern_re_body(key_pattern) - key_pattern. - gsub(/\./, '\.'.freeze). - gsub(/\*/, '.*'.freeze). - gsub(/:/, '(?<=^|\.)[^.]+?(?=\.|$)'.freeze). - gsub(/\{(.*?)}/) { "(#{$1.strip.gsub /\s*,\s*/, '|'.freeze})" } + key_pattern + .gsub(/\./, '\.') + .gsub(/\*/, '.*') + .gsub(/:/, '(?<=^|\.)[^.]+?(?=\.|$)') + .gsub(/\{(.*?)}/) { "(#{Regexp.last_match(1).strip.gsub(/\s*,\s*/, '|')})" } end end diff --git a/lib/i18n/tasks/locale_list.rb b/lib/i18n/tasks/locale_list.rb index 56f4bd1c..f7ef6699 100644 --- a/lib/i18n/tasks/locale_list.rb +++ b/lib/i18n/tasks/locale_list.rb @@ -1,19 +1,17 @@ # frozen_string_literal: true module I18n::Tasks module LocaleList - extend self + module_function # @return locales converted to strings, with base locale first, the rest sorted alphabetically def normalize_locale_list(locales, base_locale, include_base = false) locales = Array(locales).map(&:to_s).sort if locales.include?(base_locale) [base_locale] + (locales - [base_locale]) + elsif include_base + [base_locale] + locales else - if include_base - [base_locale] + locales - else - locales - end + locales end end end diff --git a/lib/i18n/tasks/locale_pathname.rb b/lib/i18n/tasks/locale_pathname.rb index fe5278bf..9c04fb26 100644 --- a/lib/i18n/tasks/locale_pathname.rb +++ b/lib/i18n/tasks/locale_pathname.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true module I18n::Tasks module LocalePathname - extend self + class << self + def replace_locale(path, from, to) + path && path.gsub(path_locale_re(from), to) + end - def replace_locale(path, from, to) - path && path.gsub(path_locale_re(from), to) - end - - private + private - def path_locale_re(locale) - (@path_locale_res ||= {})[locale] ||= /(?<=^|[\/.])#{locale}(?=[\/.])/.freeze + def path_locale_re(locale) + (@path_locale_res ||= {})[locale] ||= %r{(?<=^|[/.])#{locale}(?=[/.])} + end end end end diff --git a/lib/i18n/tasks/logging.rb b/lib/i18n/tasks/logging.rb index 7379ede6..fbc393f1 100644 --- a/lib/i18n/tasks/logging.rb +++ b/lib/i18n/tasks/logging.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true module I18n::Tasks::Logging - extend self + module_function def warn_deprecated(message) log_stderr Term::ANSIColor.yellow Term::ANSIColor.bold "#{program_name}: [DEPRECATED] #{message}" end - def log_verbose(message = nil, &block) + def log_verbose(message = nil) if ::I18n::Tasks.verbose? - log_stderr Term::ANSIColor.bright_blue(message || block.call) + log_stderr Term::ANSIColor.bright_blue(message || yield) end end diff --git a/lib/i18n/tasks/missing_keys.rb b/lib/i18n/tasks/missing_keys.rb index 703aaffd..a1aaf272 100644 --- a/lib/i18n/tasks/missing_keys.rb +++ b/lib/i18n/tasks/missing_keys.rb @@ -2,11 +2,10 @@ require 'set' module I18n::Tasks module MissingKeys - MISSING_TYPES = { - used: {glyph: '✗', summary: 'used in code but missing from base locale'}, - diff: {glyph: '∅', summary: 'translated in one locale but not in the other'} - } + used: { glyph: '✗', summary: 'used in code but missing from base locale' }, + diff: { glyph: '∅', summary: 'translated in one locale but not in the other' } + }.freeze def self.missing_keys_types @missing_keys_types ||= MISSING_TYPES.keys @@ -29,59 +28,59 @@ def missing_keys(locales: nil, types: nil, base_locale: nil) def eq_base_keys(opts = {}) locales = Array(opts[:locales]).presence || self.locales - (locales - [base_locale]).inject(empty_forest) { |tree, locale| + (locales - [base_locale]).inject(empty_forest) do |tree, locale| tree.merge! equal_values_tree(locale, base_locale) - } + end end def missing_diff_forest(locales, base = base_locale) tree = empty_forest # present in base but not locale - (locales - [base]).each { |locale| + (locales - [base]).each do |locale| tree.merge! missing_diff_tree(locale, base) - } + end if locales.include?(base) # present in locale but not base - (self.locales - [base]).each { |locale| + (self.locales - [base]).each do |locale| tree.merge! missing_diff_tree(base, locale) - } + end end tree end def missing_used_forest(locales, _base = base_locale) - locales.inject(empty_forest) { |forest, locale| + locales.inject(empty_forest) do |forest, locale| forest.merge! missing_used_tree(locale) - } + end end # keys present in compared_to, but not in locale def missing_diff_tree(locale, compared_to = base_locale) - data[compared_to].select_keys { |key, _node| + data[compared_to].select_keys do |key, _node| locale_key_missing? locale, depluralize_key(key, compared_to) - }.set_root_key!(locale, type: :missing_diff).keys { |_key, node| + end.set_root_key!(locale, type: :missing_diff).keys do |_key, node| # change path and locale to base - data = {locale: locale, missing_diff_locale: node.data[:locale]} + data = { locale: locale, missing_diff_locale: node.data[:locale] } if node.data.key?(:path) data[:path] = LocalePathname.replace_locale(node.data[:path], node.data[:locale], locale) end node.data.update data - } + end end # keys used in the code missing translations in locale def missing_used_tree(locale) - used_tree(strict: true).select_keys { |key, _node| + used_tree(strict: true).select_keys do |key, _node| locale_key_missing?(locale, key) - }.set_root_key!(locale, type: :missing_used) + end.set_root_key!(locale, type: :missing_used) end def equal_values_tree(locale, compare_to = base_locale) base = data[compare_to].first.children - data[locale].select_keys(root: false) { |key, node| + data[locale].select_keys(root: false) do |key, node| other_node = base[key] other_node && !node.reference? && node.value == other_node.value && !ignore_key?(key, :eq_base, locale) - }.set_root_key!(locale, type: :eq_base) + end.set_root_key!(locale, type: :eq_base) end def locale_key_missing?(locale, key) @@ -96,7 +95,7 @@ def collapse_same_key_in_locales!(forest) to_remove = [] forest.each do |root| locale = root.key - root.keys { |key, node| + root.keys do |key, node| next unless yield node if locales_and_node_by_key.key?(key) locales_and_node_by_key[key][0] << locale @@ -104,17 +103,16 @@ def collapse_same_key_in_locales!(forest) locales_and_node_by_key[key] = [[locale], node] end to_remove << node - } + end end forest.remove_nodes_and_emptied_ancestors! to_remove - locales_and_node_by_key.inject({}) { |inv, (key, (locales, node))| + locales_and_node_by_key.each_with_object({}) do |(key, (locales, node)), inv| (inv[locales.sort.join('+')] ||= []) << [key, node] - inv - }.map { |locales, keys_nodes| - keys_nodes.each { |(key, node)| + end.map do |locales, keys_nodes| + keys_nodes.each do |(key, node)| forest["#{locales}.#{key}"] = node - } - } + end + end forest end end diff --git a/lib/i18n/tasks/plural_keys.rb b/lib/i18n/tasks/plural_keys.rb index 3c8927a3..6112bf56 100644 --- a/lib/i18n/tasks/plural_keys.rb +++ b/lib/i18n/tasks/plural_keys.rb @@ -7,22 +7,21 @@ module I18n::Tasks::PluralKeys def collapse_plural_nodes!(tree) tree.leaves.map(&:parent).compact.uniq.each do |node| children = node.children - if plural_forms?(children) - node.value = children.to_hash - node.children = nil - node.data.merge! children.first.data - end + next unless plural_forms?(children) + node.value = children.to_hash + node.children = nil + node.data.merge! children.first.data end tree end # @param [String] key i18n key # @param [String] locale to pull key data from - # @return the base form if the key is a specific plural form (e.g. apple for apple.many), and the key as passed otherwise + # @return [String] the base form if the key is a specific plural form (e.g. apple for apple.many), the key otherwise. def depluralize_key(key, locale = base_locale) return key if key !~ PLURAL_KEY_RE key_name = last_key_part(key) - parent_key = key[0 .. - (key_name.length + 2)] + parent_key = key[0..- (key_name.length + 2)] nodes = tree("#{locale}.#{parent_key}").presence || (locale != base_locale && tree("#{base_locale}.#{parent_key}")) if nodes && plural_forms?(nodes) parent_key diff --git a/lib/i18n/tasks/references.rb b/lib/i18n/tasks/references.rb index 968a1bc8..57be42d5 100644 --- a/lib/i18n/tasks/references.rb +++ b/lib/i18n/tasks/references.rb @@ -5,64 +5,95 @@ module References # 1. Raw references -- a subset of the usages tree with keys that are reference key usages. # 2. Resolved references -- all the used references in their fully resolved form. # 3. Reference keys -- all the used reference keys. - def process_references(usages, data_references = merge_reference_trees(data_forest.select_keys { |_, node| node.reference? })) - fail ArgumentError.new('usages must be a Data::Tree::Instance') unless usages.is_a?(Data::Tree::Siblings) - fail ArgumentError.new('all_references must be a Data::Tree::Instance') unless data_references.is_a?(Data::Tree::Siblings) - raw_refs = empty_forest + def process_references(usages, + data_refs = merge_reference_trees(data_forest.select_keys { |_, node| node.reference? })) + fail ArgumentError, 'usages must be a Data::Tree::Instance' unless usages.is_a?(Data::Tree::Siblings) + fail ArgumentError, 'all_references must be a Data::Tree::Instance' unless data_refs.is_a?(Data::Tree::Siblings) + raw_refs = empty_forest resolved_refs = empty_forest - refs = empty_forest - data_references.key_to_node.each do |ref_key_part, ref_node| + refs = empty_forest + data_refs.key_to_node.each do |ref_key_part, ref_node| usages.each do |usage_node| next unless usage_node.key == ref_key_part if ref_node.leaf? - unless refs.key_to_node.key?(ref_node.key) - refs.merge_node!(Data::Tree::Node.new(key: ref_node.key, data: usage_node.data)) - end - resolved_refs.merge!( - Data::Tree::Siblings.from_key_names([ref_node.value.to_s]) { |_, resolved_node| - raw_refs.merge_node!(usage_node) - if usage_node.leaf? - resolved_node.data.merge!(usage_node.data) - else - resolved_node.children = usage_node.children - end - resolved_node.leaves { |node| node.data[:ref_info] = [ref_node.full_key, ref_node.value.to_s] } - }.tap { |new_resolved_refs| - refs.key_to_node[ref_node.key].data.tap { |ref_data| - ref_data[:occurrences] ||= [] - new_resolved_refs.leaves { |leaf| ref_data[:occurrences].concat(leaf.data[:occurrences] || []) } - ref_data[:occurrences].sort_by!(&:path) - ref_data[:occurrences].uniq! - } - }) + process_leaf!(ref_node, usage_node, raw_refs, resolved_refs, refs) else - child_raw_refs, child_resolved_refs, child_refs = process_references(usage_node.children, ref_node.children) - raw_refs.merge_node! Data::Tree::Node.new(key: ref_node.key, children: child_raw_refs) unless child_raw_refs.empty? - resolved_refs.merge! child_resolved_refs - refs.merge_node! Data::Tree::Node.new(key: ref_node.key, children: child_refs) unless child_refs.empty? + process_non_leaf!(ref_node, usage_node, raw_refs, resolved_refs, refs) end end end [raw_refs, resolved_refs, refs] end + private + + # @param [I18n::Tasks::Data::Tree::Node] ref + # @param [I18n::Tasks::Data::Tree::Node] usage + # @param [I18n::Tasks::Data::Tree::Siblings] raw_refs + # @param [I18n::Tasks::Data::Tree::Siblings] resolved_refs + # @param [I18n::Tasks::Data::Tree::Siblings] refs + def process_leaf!(ref, usage, raw_refs, resolved_refs, refs) + refs.merge_node!(Data::Tree::Node.new(key: ref.key, data: usage.data)) unless refs.key_to_node.key?(ref.key) + new_resolved_refs = Data::Tree::Siblings.from_key_names([ref.value.to_s]) do |_, resolved_node| + raw_refs.merge_node!(usage) + if usage.leaf? + resolved_node.data.merge!(usage.data) + else + resolved_node.children = usage.children + end + resolved_node.leaves { |node| node.data[:ref_info] = [ref.full_key, ref.value.to_s] } + end + add_occurences! refs.key_to_node[ref.key].data, new_resolved_refs + resolved_refs.merge! new_resolved_refs + end + + # @param [Hash] ref_data + # @param [I18n::Tasks::Data::Tree::Siblings] new_resolved_refs + def add_occurences!(ref_data, new_resolved_refs) + ref_data[:occurrences] ||= [] + new_resolved_refs.leaves do |leaf| + ref_data[:occurrences].concat(leaf.data[:occurrences] || []) + end + ref_data[:occurrences].sort_by!(&:path) + ref_data[:occurrences].uniq! + end + + # @param [I18n::Tasks::Data::Tree::Node] ref + # @param [I18n::Tasks::Data::Tree::Node] usage + # @param [I18n::Tasks::Data::Tree::Siblings] raw_refs + # @param [I18n::Tasks::Data::Tree::Siblings] resolved_refs + # @param [I18n::Tasks::Data::Tree::Siblings] refs + def process_non_leaf!(ref, usage, raw_refs, resolved_refs, refs) + child_raw_refs, child_resolved_refs, child_refs = process_references(usage.children, ref.children) + raw_refs.merge_node! Data::Tree::Node.new(key: ref.key, children: child_raw_refs) unless child_raw_refs.empty? + resolved_refs.merge! child_resolved_refs + refs.merge_node! Data::Tree::Node.new(key: ref.key, children: child_refs) unless child_refs.empty? + end + # Given a forest of references, merge trees into one tree, ensuring there are no conflicting references. - # @param roots [Data::Tree::Siblings] - # @return [Data::Tree::Siblings] + # @param roots [I18n::Tasks::Data::Tree::Siblings] + # @return [I18n::Tasks::Data::Tree::Siblings] def merge_reference_trees(roots) roots.inject(empty_forest) do |forest, root| - root.keys { |full_key, node| - log_warn( + root.keys do |full_key, node| + if full_key == node.value.to_s + log_warn( "Self-referencing key #{node.full_key(root: false).inspect} in #{node.data[:locale].inspect}" - ) if full_key == node.value.to_s - } + ) + end + end forest.merge!( - root.children, - on_leaves_merge: -> (node, other) { + root.children, + on_leaves_merge: lambda do |node, other| + if node.value != other.value log_warn( - "Conflicting references: #{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]}, but ⮕ #{other.value} in #{other.data[:locale]}" - ) if node.value != other.value - }) + 'Conflicting references: '\ + "#{node.full_key(root: false)} ⮕ #{node.value} in #{node.data[:locale]},"\ + " but ⮕ #{other.value} in #{other.data[:locale]}" + ) + end + end + ) end end end diff --git a/lib/i18n/tasks/reports/base.rb b/lib/i18n/tasks/reports/base.rb index 65a7e085..5f900c20 100644 --- a/lib/i18n/tasks/reports/base.rb +++ b/lib/i18n/tasks/reports/base.rb @@ -30,24 +30,25 @@ def eq_base_title(key_values, locale = base_locale) def used_title(keys_nodes, filter) used_n = keys_nodes.map { |_k, node| node.data[:occurrences].size }.reduce(:+).to_i - "#{keys_nodes.size} key#{'s' if keys_nodes.size != 1}#{" matching '#{filter}'" if filter}#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n > 0}" + "#{keys_nodes.size} key#{'s' if keys_nodes.size != 1}#{" matching '#{filter}'" if filter}"\ + "#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n.positive?}" end # Sort keys by their attributes in order # @param [Hash] order e.g. {locale: :asc, type: :desc, key: :asc} - def sort_by_attr!(objects, order = {locale: :asc, key: :asc}) + def sort_by_attr!(objects, order = { locale: :asc, key: :asc }) order_keys = order.keys - objects.sort! { |a, b| + objects.sort! do |a, b| by = order_keys.detect { |k| a[k] != b[k] } order[by] == :desc ? b[by] <=> a[by] : a[by] <=> b[by] - } + end objects end def forest_to_attr(forest) - forest.keys(root: false).map { |key, node| - {key: key, value: node.value, type: node.data[:type], locale: node.root.key, data: node.data} - } + forest.keys(root: false).map do |key, node| + { key: key, value: node.value, type: node.data[:type], locale: node.root.key, data: node.data } + end end def format_locale(locale) diff --git a/lib/i18n/tasks/reports/spreadsheet.rb b/lib/i18n/tasks/reports/spreadsheet.rb index 15e56b00..bd014327 100644 --- a/lib/i18n/tasks/reports/spreadsheet.rb +++ b/lib/i18n/tasks/reports/spreadsheet.rb @@ -4,8 +4,7 @@ module I18n::Tasks::Reports class Spreadsheet < Base - - def save_report(path, opts) + def save_report(path, _opts) path = path.presence || 'tmp/i18n-report.xlsx' p = Axlsx::Package.new p.use_shared_strings = true # see #159 @@ -19,22 +18,24 @@ def save_report(path, opts) private - def add_missing_sheet(wb) + def add_missing_sheet(wb) # rubocop:disable Metrics/AbcSize forest = collapse_missing_tree! task.missing_keys wb.styles do |s| - type_cell = s.add_style :alignment => {:horizontal => :center} - locale_cell = s.add_style :alignment => {:horizontal => :center} + type_cell = s.add_style alignment: { horizontal: :center } + locale_cell = s.add_style alignment: { horizontal: :center } regular_style = s.add_style - wb.add_worksheet(name: missing_title(forest)) { |sheet| - sheet.page_setup.fit_to :width => 1 - sheet.add_row [I18n.t('i18n_tasks.common.type'), I18n.t('i18n_tasks.common.locale'), I18n.t('i18n_tasks.common.key'), I18n.t('i18n_tasks.common.base_value')] + wb.add_worksheet(name: missing_title(forest)) do |sheet| + sheet.page_setup.fit_to width: 1 + sheet.add_row [I18n.t('i18n_tasks.common.type'), I18n.t('i18n_tasks.common.locale'), + I18n.t('i18n_tasks.common.key'), I18n.t('i18n_tasks.common.base_value')] style_header sheet forest.keys do |key, node| - locale, type = format_locale(node.root.data[:locale]), node.data[:type] + locale = format_locale(node.root.data[:locale]) + type = node.data[:type] sheet.add_row [missing_type_info(type)[:summary], locale, key, task.t(key)], - styles: [type_cell, locale_cell, regular_style, regular_style] + styles: [type_cell, locale_cell, regular_style, regular_style] end - } + end end end @@ -48,11 +49,10 @@ def add_unused_sheet(wb) add_locale_key_value_table wb, keys, name: unused_title(keys) end - private - def add_locale_key_value_table(wb, keys, worksheet_opts = {}) wb.add_worksheet worksheet_opts do |sheet| - sheet.add_row [I18n.t('i18n_tasks.common.locale'), I18n.t('i18n_tasks.common.key'), I18n.t('i18n_tasks.common.value')] + sheet.add_row [I18n.t('i18n_tasks.common.locale'), I18n.t('i18n_tasks.common.key'), + I18n.t('i18n_tasks.common.value')] style_header sheet keys.each do |locale_k_v| sheet.add_row locale_k_v @@ -60,9 +60,8 @@ def add_locale_key_value_table(wb, keys, worksheet_opts = {}) end end - def style_header(sheet) - border_bottom = sheet.workbook.styles.add_style(border: {style: :thin, color: '000000', edges: [:bottom]}) + border_bottom = sheet.workbook.styles.add_style(border: { style: :thin, color: '000000', edges: [:bottom] }) sheet.rows.first.style = border_bottom end end diff --git a/lib/i18n/tasks/reports/terminal.rb b/lib/i18n/tasks/reports/terminal.rb index 4d8532c2..97bdf793 100644 --- a/lib/i18n/tasks/reports/terminal.rb +++ b/lib/i18n/tasks/reports/terminal.rb @@ -4,7 +4,7 @@ module I18n module Tasks module Reports - class Terminal < Base + class Terminal < Base # rubocop:disable Metrics/ClassLength include Term::ANSIColor def missing_keys(forest = task.missing_keys) @@ -12,10 +12,10 @@ def missing_keys(forest = task.missing_keys) if forest.present? print_title missing_title(forest) print_table headings: [cyan(bold(I18n.t('i18n_tasks.common.locale'))), - cyan(bold I18n.t('i18n_tasks.common.key')), + cyan(bold(I18n.t('i18n_tasks.common.key'))), I18n.t('i18n_tasks.missing.details_title')] do |t| t.rows = sort_by_attr!(forest_to_attr(forest)).map do |a| - [{value: cyan(format_locale(a[:locale])), alignment: :center}, + [{ value: cyan(format_locale(a[:locale])), alignment: :center }, format_key(a[:key], a[:data]), missing_key_info(a)] end @@ -27,14 +27,14 @@ def missing_keys(forest = task.missing_keys) def icon(type) glyph = missing_type_info(type)[:glyph] - {missing_used: red(glyph), missing_diff: yellow(glyph)}[type] + { missing_used: red(glyph), missing_diff: yellow(glyph) }[type] end def used_keys(used_tree = task.used_tree) # For the used tree we may have usage nodes that are not leaves as references. - keys_nodes = used_tree.nodes.select { |node| !!node.data[:occurrences] }.map { |node| + keys_nodes = used_tree.nodes.select { |node| node.data[:occurrences].present? }.map do |node| [node.full_key(root: false), node] - } + end print_title used_title(keys_nodes, used_tree.first.root.data[:key_filter]) # Group multiple nodes if keys_nodes.present? @@ -86,7 +86,8 @@ def missing_key_info(leaf) if leaf[:type] == :missing_used first_occurrence leaf else - "#{cyan leaf[:data][:missing_diff_locale]} #{format_value(leaf[:value].is_a?(String) ? leaf[:value].strip : leaf[:value])}" + "#{cyan leaf[:data][:missing_diff_locale]} "\ + "#{format_value(leaf[:value].is_a?(String) ? leaf[:value].strip : leaf[:value])}" end end @@ -106,23 +107,22 @@ def format_value(val) end def format_reference_desc(node_data) - return nil unless node_data - case node_data[:ref_type] - when :reference_usage - bold(yellow('(ref)')) - when :reference_usage_resolved - bold(yellow('(resolved ref)')) - when :reference_usage_key - bold(yellow('(ref key)')) - end + return nil unless node_data + case node_data[:ref_type] + when :reference_usage + bold(yellow('(ref)')) + when :reference_usage_resolved + bold(yellow('(resolved ref)')) + when :reference_usage_key + bold(yellow('(ref key)')) + end end def print_occurrences(node, full_key = node.full_key) occurrences = node.data[:occurrences] - puts [bold("#{full_key}"), + puts [bold(full_key.to_s), format_reference_desc(node.data), - (green(occurrences.size.to_s) if occurrences.size > 1) - ].compact.join ' ' + (green(occurrences.size.to_s) if occurrences.size > 1)].compact.join ' ' occurrences.each do |occurrence| puts " #{key_occurrence full_key, occurrence}" end @@ -134,7 +134,7 @@ def print_locale_key_value_data_table(locale_key_value_datas) bold(cyan(I18n.t('i18n_tasks.common.key'))), I18n.t('i18n_tasks.common.value')] do |t| t.rows = locale_key_value_datas.map { |(locale, k, v, data)| - [{value: cyan(locale), alignment: :center}, format_key(k, data), format_value(v)] + [{ value: cyan(locale), alignment: :center }, format_key(k, data), format_value(v)] } end else @@ -143,15 +143,15 @@ def print_locale_key_value_data_table(locale_key_value_datas) end def print_title(title) - log_stderr "#{bold title.strip} #{dark "|"} #{"i18n-tasks v#{I18n::Tasks::VERSION}"}" + log_stderr "#{bold title.strip} #{dark '|'} #{"i18n-tasks v#{I18n::Tasks::VERSION}"}" end def print_success(message) - log_stderr bold(green "✓ #{I18n.t('i18n_tasks.cmd.encourage').sample} #{message}") + log_stderr bold(green("✓ #{I18n.t('i18n_tasks.cmd.encourage').sample} #{message}")) end def print_error(message) - log_stderr(bold red message) + log_stderr(bold(red(message))) end def print_info(message) @@ -159,8 +159,7 @@ def print_info(message) end def indent(txt, n = 2) - spaces = ' ' * n - txt.gsub /^/, spaces + txt.gsub(/^/, ' ' * n) end def print_table(opts, &block) @@ -177,18 +176,19 @@ def first_occurrence(leaf) # @type [I18n::Tasks::Scanners::KeyOccurrences] occurrences = leaf[:data][:occurrences] # @type [I18n::Tasks::Scanners::Occurrence] - first = occurrences.first - [green("#{first.path}:#{first.line_num}"), - ("(#{I18n.t 'i18n_tasks.common.n_more', count: occurrences.length - 1})" if occurrences.length > 1) + first = occurrences.first + [ + green("#{first.path}:#{first.line_num}"), + ("(#{I18n.t 'i18n_tasks.common.n_more', count: occurrences.length - 1})" if occurrences.length > 1) ].compact.join(' ') end def highlight_key(full_key, line, range = (0..-1)) - line.dup.tap { |s| - s[range] = s[range].sub(full_key) { |m| + line.dup.tap do |s| + s[range] = s[range].sub(full_key) do |m| highlight_string m - } - } + end + end end module HighlightUnderline diff --git a/lib/i18n/tasks/scanners/file_scanner.rb b/lib/i18n/tasks/scanners/file_scanner.rb index ffd8f6bb..6c49e757 100644 --- a/lib/i18n/tasks/scanners/file_scanner.rb +++ b/lib/i18n/tasks/scanners/file_scanner.rb @@ -12,7 +12,8 @@ class FileScanner < Scanner def initialize( config: {}, file_finder_provider: Files::CachingFileFinderProvider.new, - file_reader: Files::CachingFileReader.new) + file_reader: Files::CachingFileReader.new + ) @config = config @file_reader = file_reader @file_finder = file_finder_provider.get(**config.slice(:paths, :only, :exclude)) @@ -20,11 +21,11 @@ def initialize( # @return (see Scanner#keys) def keys - (traverse_files { |path| + (traverse_files do |path| scan_file(path) - }.reduce(:+) || []).group_by(&:first).map { |key, keys_occurrences| + end.reduce(:+) || []).group_by(&:first).map do |key, keys_occurrences| Results::KeyOccurrences.new(key: key, occurrences: keys_occurrences.map(&:second)) - } + end end protected @@ -32,7 +33,7 @@ def keys # Extract all occurrences of translate calls from the file at the given path. # # @return [Array<[key, Results::KeyOccurrence]>] each occurrence found in the file - def scan_file(path) + def scan_file(_path) fail 'Unimplemented' end diff --git a/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb b/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb index f2666885..2e764c5f 100644 --- a/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +++ b/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb @@ -7,12 +7,11 @@ module I18n::Tasks::Scanners::Files # @note This class is thread-safe. All methods are cached. # @since 0.9.0 class CachingFileFinderProvider - # @param exclude [Array] def initialize(exclude: []) @cache = {} @mutex = Mutex.new - @defaults = {exclude: exclude} + @defaults = { exclude: exclude } end # Initialize a {CachingFileFinder} or get one from cache based on the constructor arguments. diff --git a/lib/i18n/tasks/scanners/files/caching_file_reader.rb b/lib/i18n/tasks/scanners/files/caching_file_reader.rb index d7f20273..c6948479 100644 --- a/lib/i18n/tasks/scanners/files/caching_file_reader.rb +++ b/lib/i18n/tasks/scanners/files/caching_file_reader.rb @@ -7,7 +7,6 @@ module I18n::Tasks::Scanners::Files # @note This class is thread-safe. All methods are cached. # @since 0.9.0 class CachingFileReader < FileReader - def initialize super @mutex = Mutex.new diff --git a/lib/i18n/tasks/scanners/files/file_finder.rb b/lib/i18n/tasks/scanners/files/file_finder.rb index 06bfc5be..010afd0a 100644 --- a/lib/i18n/tasks/scanners/files/file_finder.rb +++ b/lib/i18n/tasks/scanners/files/file_finder.rb @@ -13,7 +13,7 @@ class FileFinder # @param exclude [Arry] {File.fnmatch}-compatible patterns of files to exclude. # Files matching any of the exclusion patterns will be excluded even if they match an inclusion pattern. def initialize(paths: ['.'], only: nil, exclude: []) - raise 'paths argument is required' if paths.nil? + fail 'paths argument is required' if paths.nil? @paths = paths @include = only @exclude = exclude || [] @@ -29,22 +29,19 @@ def traverse_files end # @return [Array] found files - def find_files + def find_files # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity results = [] paths = @paths.select { |p| File.exist?(p) } - if paths.empty? - log_warn "None of the search.paths exist #{@paths.inspect}" - else - Find.find(*paths) do |path| - is_dir = File.directory?(path) - hidden = File.basename(path).start_with?('.') && !%w(. ./).include?(path) - not_incl = @include && !path_fnmatch_any?(path, @include) - excl = path_fnmatch_any?(path, @exclude) - if is_dir || hidden || not_incl || excl - Find.prune if is_dir && (hidden || excl) - else - results << path - end + log_warn "None of the search.paths exist #{@paths.inspect}" if paths.empty? + Find.find(*paths) do |path| + is_dir = File.directory?(path) + hidden = File.basename(path).start_with?('.') && !%w(. ./).include?(path) + not_incl = @include && !path_fnmatch_any?(path, @include) + excl = path_fnmatch_any?(path, @exclude) + if is_dir || hidden || not_incl || excl + Find.prune if is_dir && (hidden || excl) + else + results << path end end results diff --git a/lib/i18n/tasks/scanners/files/file_reader.rb b/lib/i18n/tasks/scanners/files/file_reader.rb index 4ffb1015..adc8bf45 100644 --- a/lib/i18n/tasks/scanners/files/file_reader.rb +++ b/lib/i18n/tasks/scanners/files/file_reader.rb @@ -4,7 +4,6 @@ module I18n::Tasks::Scanners::Files # # @since 0.9.0 class FileReader - # Return the contents of the file at the given path. # The file is read in the 'rb' mode and UTF-8 encoding. # diff --git a/lib/i18n/tasks/scanners/occurrence_from_position.rb b/lib/i18n/tasks/scanners/occurrence_from_position.rb index c80bf2ad..830ae9ef 100644 --- a/lib/i18n/tasks/scanners/occurrence_from_position.rb +++ b/lib/i18n/tasks/scanners/occurrence_from_position.rb @@ -14,12 +14,13 @@ def occurrence_from_position(path, contents, position, raw_key: nil) line_begin = contents.rindex(/^/, position - 1) line_end = contents.index(/.(?=\r?\n|$)/, position) Results::Occurrence.new( - path: path, - pos: position, - line_num: contents[0..position].count("\n".freeze) + 1, - line_pos: position - line_begin + 1, - line: contents[line_begin..line_end], - raw_key: raw_key) + path: path, + pos: position, + line_num: contents[0..position].count("\n") + 1, + line_pos: position - line_begin + 1, + line: contents[line_begin..line_end], + raw_key: raw_key + ) end end end diff --git a/lib/i18n/tasks/scanners/pattern_mapper.rb b/lib/i18n/tasks/scanners/pattern_mapper.rb index e295c783..115228d6 100644 --- a/lib/i18n/tasks/scanners/pattern_mapper.rb +++ b/lib/i18n/tasks/scanners/pattern_mapper.rb @@ -40,7 +40,7 @@ def scan_file(path) next unless valid_key?(matches[:key]) end result << [absolute_key(key % matches, path), - occurrence_from_position(path, text, match.offset(0).first)] + occurrence_from_position(path, text, match.offset(0).first)] end result end @@ -52,7 +52,7 @@ def scan_file(path) def configure_patterns(patterns) patterns.map do |(pattern, key)| - [pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern % {key: KEY_GROUP}), key] + [pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern % { key: KEY_GROUP }), key] end end end diff --git a/lib/i18n/tasks/scanners/pattern_scanner.rb b/lib/i18n/tasks/scanners/pattern_scanner.rb index 1f0f021f..d2fbdf3f 100644 --- a/lib/i18n/tasks/scanners/pattern_scanner.rb +++ b/lib/i18n/tasks/scanners/pattern_scanner.rb @@ -13,8 +13,10 @@ class PatternScanner < FileScanner def initialize(**args) super - @pattern = config[:pattern].present? ? Regexp.new(config[:pattern]) : default_pattern - @ignore_lines_res = (config[:ignore_lines] || []).inject({}) { |h, (ext, re)| h.update(ext.to_s => Regexp.new(re)) } + @pattern = config[:pattern].present? ? Regexp.new(config[:pattern]) : default_pattern + @ignore_lines_res = (config[:ignore_lines] || []).each_with_object({}) do |(ext, re), h| + h[ext.to_s] = Regexp.new(re) + end end protected @@ -25,17 +27,17 @@ def scan_file(path) keys = [] text = read_file(path) text.scan(@pattern) do |match| - src_pos = Regexp.last_match.offset(0).first + src_pos = Regexp.last_match.offset(0).first location = occurrence_from_position(path, text, src_pos, raw_key: strip_literal(match[0])) next if exclude_line?(location.line, path) key = match_to_key(match, path, location) next unless key - key = key + ':'.freeze if key.end_with?('.'.freeze) + key += ':' if key.end_with?('.') next unless valid_key?(key) keys << [key, location] end keys - rescue Exception => e + rescue Exception => e # rubocop:disable Lint/RescueException raise ::I18n::Tasks::CommandError.new(e, "Error scanning #{path}: #{e.message}") end @@ -67,8 +69,8 @@ def key_relative_to_method?(path) end def closest_method(occurrence) - method = File.readlines(occurrence.path, encoding: 'UTF-8'.freeze). - first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ } + method = File.readlines(occurrence.path, encoding: 'UTF-8') + .first(occurrence.line_num - 1).reverse_each.find { |x| x =~ /\bdef\b/ } method && method.strip.sub(/^def\s*/, '').sub(/[\(\s;].*$/, '') end diff --git a/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb b/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb index 5c20d4a8..770ca17f 100644 --- a/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +++ b/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb @@ -6,7 +6,6 @@ module I18n::Tasks::Scanners # both scope: "literal", and scope: [:array, :of, 'literals'] forms are supported # Caveat: scope is only detected when it is the first argument class PatternWithScopeScanner < PatternScanner - protected def default_pattern @@ -24,7 +23,7 @@ def match_to_key(match, path, location) key = super scope = match[1] if scope - scope_ns = scope.gsub(/[\[\]\s]+/, ''.freeze).split(','.freeze).map { |arg| strip_literal(arg) } * '.'.freeze + scope_ns = scope.gsub(/[\[\]\s]+/, '').split(',').map { |arg| strip_literal(arg) } * '.' "#{scope_ns}.#{key}" else key unless match[0] =~ /\A\w/ diff --git a/lib/i18n/tasks/scanners/relative_keys.rb b/lib/i18n/tasks/scanners/relative_keys.rb index 093561e1..80538d47 100644 --- a/lib/i18n/tasks/scanners/relative_keys.rb +++ b/lib/i18n/tasks/scanners/relative_keys.rb @@ -12,40 +12,40 @@ def absolute_key(key, path, roots: config[:relative_roots], calling_method: nil) return key unless key.start_with?(DOT) fail 'roots argument is required' unless roots.present? normalized_path = File.expand_path(path) - root = path_root(normalized_path, roots) or - fail CommandError.new("Cannot resolve relative key \"#{key}\".\n" + - "Set search.relative_roots in config/i18n-tasks.yml (currently #{roots.inspect})") - normalized_path.sub!(root, ''.freeze) + (root = path_root(normalized_path, roots)) || + fail(CommandError, "Cannot resolve relative key \"#{key}\".\n" \ + "Set search.relative_roots in config/i18n-tasks.yml (currently #{roots.inspect})") + normalized_path.sub!(root, '') "#{prefix(normalized_path, calling_method: calling_method)}#{key}" end private - DOT = '.'.freeze + DOT = '.' # Detect the appropriate relative path root # @param [String] path /full/path # @param [Array] roots array of full paths # @return [String] the closest ancestor root for path, with a trailing {File::SEPARATOR}. def path_root(path, roots) - roots.map { |p| + roots.map do |p| File.expand_path(p) + File::SEPARATOR - }.sort.reverse_each.detect { |root| + end.sort.reverse_each.detect do |root| path.start_with?(root) - } + end end # @param normalized_path [String] path/relative/to/a/root # @param calling_method [#call, Symbol, String, false, nil] def prefix(normalized_path, calling_method: nil) - file_key = normalized_path.gsub(%r((\.[^/]+)*$), ''.freeze).tr(File::SEPARATOR, DOT) + file_key = normalized_path.gsub(%r{(\.[^/]+)*$}, '').tr(File::SEPARATOR, DOT) calling_method = calling_method.call if calling_method.respond_to?(:call) if calling_method && calling_method.present? # Relative keys in mailers have a `_mailer` infix, but relative keys in controllers do not have one: - "#{file_key.sub(/_controller$/, ''.freeze)}.#{calling_method}" + "#{file_key.sub(/_controller$/, '')}.#{calling_method}" else # Remove _ prefix from partials - file_key.gsub(%r(\._), DOT) + file_key.gsub(/\._/, DOT) end end end diff --git a/lib/i18n/tasks/scanners/results/key_occurrences.rb b/lib/i18n/tasks/scanners/results/key_occurrences.rb index 290fc568..0703b934 100644 --- a/lib/i18n/tasks/scanners/results/key_occurrences.rb +++ b/lib/i18n/tasks/scanners/results/key_occurrences.rb @@ -40,15 +40,14 @@ def inspect # @param keys_occurrences [Enumerable] # @return [Array] a new array. def self.merge_keys(keys_occurrences) - keys_occurrences.inject({}) { |results_by_key, key_occurrences| + keys_occurrences.each_with_object({}) do |key_occurrences, results_by_key| (results_by_key[key_occurrences.key] ||= []) << key_occurrences.occurrences - results_by_key - }.map { |key, all_occurrences| + end.map do |key, all_occurrences| occurrences = all_occurrences.flatten(1) occurrences.sort_by!(&:path) occurrences.uniq! new(key: key, occurrences: occurrences) - } + end end end end diff --git a/lib/i18n/tasks/scanners/results/occurrence.rb b/lib/i18n/tasks/scanners/results/occurrence.rb index 8df02c19..8fc2e79a 100644 --- a/lib/i18n/tasks/scanners/results/occurrence.rb +++ b/lib/i18n/tasks/scanners/results/occurrence.rb @@ -34,6 +34,7 @@ class Occurrence # @param line [String] # @param raw_key [String, nil] # @param default_arg [String, nil] + # rubocop:disable Metrics/ParameterLists def initialize(path:, pos:, line_num:, line_pos:, line:, raw_key: nil, default_arg: nil) @path = path @pos = pos @@ -50,7 +51,7 @@ def inspect def ==(other) other.path == @path && other.pos == @pos && other.line_num == @line_num && other.line == @line && - other.raw_key == @raw_key && other.default_arg == @default_arg + other.raw_key == @raw_key && other.default_arg == @default_arg end def eql?(other) diff --git a/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb b/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb index b61887ac..9545d06c 100644 --- a/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +++ b/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb @@ -46,8 +46,8 @@ def on_send(send_node) receiver = send_node.children[0] message = send_node.children[1] if @messages.include?(message) && - # use `any?` because `include?` checks type equality, but the receiver is a Parser::AST::Node != AST::Node. - @receivers.any? { |r| r == receiver } + # use `any?` because `include?` checks type equality, but the receiver is a Parser::AST::Node != AST::Node. + @receivers.any? { |r| r == receiver } @callback.call(send_node, @method_name) else handler_missing send_node diff --git a/lib/i18n/tasks/scanners/ruby_ast_scanner.rb b/lib/i18n/tasks/scanners/ruby_ast_scanner.rb index 5716281d..ddfe4c46 100644 --- a/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +++ b/lib/i18n/tasks/scanners/ruby_ast_scanner.rb @@ -4,13 +4,16 @@ require 'i18n/tasks/scanners/ruby_ast_call_finder' require 'parser/current' +# rubocop:disable Metrics/AbcSize,Metrics/BlockNesting,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity +# TODO: make this class more readable. + module I18n::Tasks::Scanners # Scan for I18n.translate calls using whitequark/parser - class RubyAstScanner < FileScanner + class RubyAstScanner < FileScanner # rubocop:disable Metrics/ClassLength include RelativeKeys include AST::Sexp - MAGIC_COMMENT_PREFIX = /\A.\s*i18n-tasks-use\s+/.freeze + MAGIC_COMMENT_PREFIX = /\A.\s*i18n-tasks-use\s+/ def initialize(messages: %i(t translate), receivers: [nil, s(:const, nil, :I18n)], **args) super(args) @@ -41,13 +44,13 @@ def scan_file(path) @parser.reset associated_node = comment_to_node[comment] @call_finder.collect_calls( - @parser.parse(make_buffer(path, comment.text.sub(MAGIC_COMMENT_PREFIX, '').split(/\s+(?=t)/).join('; '))) + @parser.parse(make_buffer(path, comment.text.sub(MAGIC_COMMENT_PREFIX, '').split(/\s+(?=t)/).join('; '))) ) do |send_node, _method_name| # method_name is not available at this stage send_node_to_key_occurrence(send_node, nil, location: associated_node || comment.location) end end - rescue Exception => e + rescue Exception => e # rubocop:disable Lint/RescueException raise ::I18n::Tasks::CommandError.new(e, "Error scanning #{path}: #{e.message}") end @@ -57,16 +60,16 @@ def scan_file(path) # @return [nil, [key, Occurrence]] full absolute key name and the occurrence. def send_node_to_key_occurrence(send_node, method_name, location: send_node.loc) if (first_arg_node = send_node.children[2]) && - (key = extract_string(first_arg_node)) + (key = extract_string(first_arg_node)) if (second_arg_node = send_node.children[3]) && - second_arg_node.type == :hash - if (scope_node = extract_hash_pair(second_arg_node, 'scope'.freeze)) + second_arg_node.type == :hash + if (scope_node = extract_hash_pair(second_arg_node, 'scope')) scope = extract_string(scope_node.children[1], - array_join_with: '.'.freeze, array_flatten: true, array_reject_blank: true) + array_join_with: '.', array_flatten: true, array_reject_blank: true) return nil if scope.nil? && scope_node.type != :nil - key = [scope, key].join('.') unless scope == ''.freeze + key = [scope, key].join('.') unless scope == '' end - default_arg = if (default_arg_node = extract_hash_pair(second_arg_node, 'default'.freeze)) + default_arg = if (default_arg_node = extract_hash_pair(second_arg_node, 'default')) extract_string(default_arg_node.children[1]) end end @@ -86,11 +89,11 @@ def send_node_to_key_occurrence(send_node, method_name, location: send_node.loc) # @param key [String] node key as a string (indifferent symbol-string matching). # @return [AST::Node, nil] a node of type `:pair` or nil. def extract_hash_pair(node, key) - node.children.detect { |child| + node.children.detect do |child| next unless child.type == :pair key_node = child.children[0] %i(sym str).include?(key_node.type) && key_node.children[0].to_s == key - } + end end # If the node type is of `%i(sym str int false true)`, return the value as a string. @@ -111,16 +114,17 @@ def extract_string(node, array_join_with: nil, array_flatten: false, array_rejec elsif %i(true false).include?(node.type) node.type.to_s elsif :nil == node.type - ''.freeze + '' elsif :array == node.type && array_join_with extract_array_as_string( - node, - array_join_with: array_join_with, - array_flatten: array_flatten, - array_reject_blank: array_reject_blank).tap { |str| + node, + array_join_with: array_join_with, + array_flatten: array_flatten, + array_reject_blank: array_reject_blank + ).tap do |str| # `nil` is returned when a dynamic value is encountered in strict mode. Propagate: return nil if str.nil? - } + end elsif !config[:strict] && %i(dsym dstr).include?(node.type) node.children.map do |child| if %i(sym str).include?(child.type) @@ -153,10 +157,12 @@ def extract_array_as_string(node, array_join_with:, array_flatten: false, array_ end end end - children_strings.reject! { |x| - # empty strings and nils in the scope argument are ignored by i18n - x == ''.freeze - } if array_reject_blank + if array_reject_blank + children_strings.reject! do |x| + # empty strings and nils in the scope argument are ignored by i18n + x == '' + end + end children_strings.join(array_join_with) end @@ -170,13 +176,14 @@ def keys_relative_to_calling_method?(path) # @return [Results::Occurrence] def range_to_occurrence(raw_key, range, default_arg: nil) Results::Occurrence.new( - path: range.source_buffer.name, - pos: range.begin_pos, - line_num: range.line, - line_pos: range.column, - line: range.source_line, - raw_key: raw_key, - default_arg: default_arg) + path: range.source_buffer.name, + pos: range.begin_pos, + line_num: range.line, + line_pos: range.column, + line: range.source_line, + raw_key: raw_key, + default_arg: default_arg + ) end # Create an {Parser::Source::Buffer} with the given contents. @@ -186,9 +193,10 @@ def range_to_occurrence(raw_key, range, default_arg: nil) # @param contents [String] # @return [Parser::Source::Buffer] file contents def make_buffer(path, contents = read_file(path)) - Parser::Source::Buffer.new(path).tap { |buffer| + Parser::Source::Buffer.new(path).tap do |buffer| buffer.raw_source = contents - } + end end end end +# rubocop:enable Metrics/AbcSize,Metrics/BlockNesting,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity diff --git a/lib/i18n/tasks/scanners/scanner_multiplexer.rb b/lib/i18n/tasks/scanners/scanner_multiplexer.rb index 65578268..8468f34c 100644 --- a/lib/i18n/tasks/scanners/scanner_multiplexer.rb +++ b/lib/i18n/tasks/scanners/scanner_multiplexer.rb @@ -25,9 +25,9 @@ def keys def collect_results return [@scanners[0].keys] if @scanners.length == 1 Array.new(@scanners.length).tap do |results| - @scanners.map.with_index { |scanner, i| + @scanners.map.with_index do |scanner, i| Thread.start { results[i] = scanner.keys } - }.each(&:join) + end.each(&:join) end end end diff --git a/lib/i18n/tasks/split_key.rb b/lib/i18n/tasks/split_key.rb index 03582556..d26ddc53 100644 --- a/lib/i18n/tasks/split_key.rb +++ b/lib/i18n/tasks/split_key.rb @@ -2,7 +2,7 @@ module I18n module Tasks module SplitKey - extend self + module_function # split a key by dots (.) # dots inside braces or parenthesis are not split on @@ -37,7 +37,7 @@ def key_parts(key, &block) return enum_for(:key_parts, key) unless block nesting = PARENS counts = PARENS_ZEROS # dup'd later if key contains parenthesis - delim = '.'.freeze + delim = '.' from = to = 0 key.each_char do |char| if char == delim && PARENS_ZEROS == counts @@ -46,7 +46,7 @@ def key_parts(key, &block) else nest_i, nest_inc = nesting[char] if nest_i - counts = counts.dup if counts.frozen? + counts = counts.dup if counts.frozen? counts[nest_i] += nest_inc end to += 1 @@ -56,12 +56,11 @@ def key_parts(key, &block) true end - PARENS = %w({} [] ()).inject({}) { |h, s| + PARENS = %w({} [] ()).each_with_object({}) do |s, h| i = h.size / 2 h[s[0].freeze] = [i, 1].freeze h[s[1].freeze] = [i, -1].freeze - h - }.freeze + end.freeze PARENS_ZEROS = Array.new(PARENS.size, 0).freeze private_constant :PARENS private_constant :PARENS_ZEROS diff --git a/lib/i18n/tasks/stats.rb b/lib/i18n/tasks/stats.rb index 7eca655c..0fd77b77 100644 --- a/lib/i18n/tasks/stats.rb +++ b/lib/i18n/tasks/stats.rb @@ -2,18 +2,20 @@ module I18n::Tasks module Stats def forest_stats(forest) - key_count = forest.leaves.count + key_count = forest.leaves.count locale_count = forest.count if key_count.zero? - {key_count: 0} + { key_count: 0 } else { - locales: forest.map(&:key).join(', '), - key_count: key_count, - locale_count: locale_count, - per_locale_avg: forest.inject(0) { |sum, f| sum + f.leaves.count } / locale_count, - key_segments_avg: '%.1f' % (forest.leaves.inject(0) { |sum, node| sum + node.walk_to_root.count - 1 } / key_count.to_f), - value_chars_avg: forest.leaves.inject(0) { |sum, node| sum + node.value.to_s.length } / key_count + locales: forest.map(&:key).join(', '), + key_count: key_count, + locale_count: locale_count, + per_locale_avg: forest.inject(0) { |sum, f| sum + f.leaves.count } / locale_count, + key_segments_avg: format( + '%.1f', forest.leaves.inject(0) { |sum, node| sum + node.walk_to_root.count - 1 } / key_count.to_f + ), + value_chars_avg: forest.leaves.inject(0) { |sum, node| sum + node.value.to_s.length } / key_count } end end diff --git a/lib/i18n/tasks/string_interpolation.rb b/lib/i18n/tasks/string_interpolation.rb index 967961f8..4dbb5d69 100644 --- a/lib/i18n/tasks/string_interpolation.rb +++ b/lib/i18n/tasks/string_interpolation.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module I18n::Tasks module StringInterpolation - extend self + module_function def interpolate_soft(s, t = {}) return s unless s diff --git a/lib/i18n/tasks/unused_keys.rb b/lib/i18n/tasks/unused_keys.rb index 524d59ee..629ac6de 100644 --- a/lib/i18n/tasks/unused_keys.rb +++ b/lib/i18n/tasks/unused_keys.rb @@ -15,8 +15,8 @@ def unused_tree(locale: base_locale, strict: nil) used_key_names = used_tree(strict: true).key_names collapse_plural_nodes! data[locale].select_keys { |key, _node| !ignore_key?(key, :unused) && - (strict || !used_in_expr?(key)) && - !used_key_names.include?(depluralize_key(key, locale)) + (strict || !used_in_expr?(key)) && + !used_key_names.include?(depluralize_key(key, locale)) } end end diff --git a/lib/i18n/tasks/used_keys.rb b/lib/i18n/tasks/used_keys.rb index 30127c7e..d18565c2 100644 --- a/lib/i18n/tasks/used_keys.rb +++ b/lib/i18n/tasks/used_keys.rb @@ -7,24 +7,24 @@ require 'i18n/tasks/scanners/files/caching_file_reader' module I18n::Tasks - module UsedKeys + module UsedKeys # rubocop:disable Metrics/ModuleLength SEARCH_DEFAULTS = { - paths: %w(app/).freeze, - relative_roots: %w(app/controllers app/helpers app/mailers app/presenters app/views).freeze, - scanners: [['::I18n::Tasks::Scanners::RubyAstScanner', only: %w(*.rb)]], - strict: true, - }.tap { |defaults| + paths: %w(app/).freeze, + relative_roots: %w(app/controllers app/helpers app/mailers app/presenters app/views).freeze, + scanners: [['::I18n::Tasks::Scanners::RubyAstScanner', only: %w(*.rb)]], + strict: true + }.tap do |defaults| defaults[:scanners] << ['::I18n::Tasks::Scanners::PatternWithScopeScanner', exclude: defaults[:scanners].map { |(_, opts)| opts[:only] }.reduce(:+).freeze, - ignore_lines: {'opal' => %q(^\s*#(?!\si18n-tasks-use)), - 'haml' => %q(^\s*-\s*#(?!\si18n-tasks-use)), - 'slim' => %q(^\s*(?:-#|/)(?!\si18n-tasks-use)), - 'coffee' => %q(^\s*#(?!\si18n-tasks-use)), - 'erb' => %q(^\s*<%\s*#(?!\si18n-tasks-use))}.freeze] } - + ignore_lines: { 'opal' => %q(^\s*#(?!\si18n-tasks-use)), + 'haml' => %q(^\s*-\s*#(?!\si18n-tasks-use)), + 'slim' => %q(^\s*(?:-#|/)(?!\si18n-tasks-use)), + 'coffee' => %q(^\s*#(?!\si18n-tasks-use)), + 'erb' => %q(^\s*<%\s*#(?!\si18n-tasks-use)) }.freeze] + end ALWAYS_EXCLUDE = %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less - *.yml *.json *.zip *.tar.gz) + *.yml *.json *.zip *.tar.gz).freeze # Find all keys in the source and return a forest with the keys in absolute form and their occurrences. # @@ -41,28 +41,25 @@ def used_tree(key_filter: nil, strict: nil, include_raw_references: false) src_tree.tap do |result| tree = result['used'].children tree.subtract_by_key!(raw_refs) - if include_raw_references - tree.merge!(raw_refs) - end + tree.merge!(raw_refs) if include_raw_references tree.merge!(used_refs).merge!(resolved_refs) end end def used_in_source_tree(key_filter: nil, strict: nil) keys = ((@keys_used_in_source_tree ||= {})[strict?(strict)] ||= - scanner(strict: strict).keys.freeze) + scanner(strict: strict).keys.freeze) if key_filter key_filter_re = compile_key_pattern(key_filter) keys = keys.reject { |k| k.key !~ key_filter_re } end Data::Tree::Node.new( - key: 'used', - data: {key_filter: key_filter}, - children: Data::Tree::Siblings.from_key_occurrences(keys) + key: 'used', + data: { key_filter: key_filter }, + children: Data::Tree::Siblings.from_key_occurrences(keys) ).to_siblings end - def scanner(strict: nil) (@scanner ||= {})[strict?(strict)] ||= begin shared_options = search_config.dup @@ -70,16 +67,18 @@ def scanner(strict: nil) shared_options[:strict] = strict unless strict.nil? log_verbose 'Scanners: ' Scanners::ScannerMultiplexer.new( - scanners: search_config[:scanners].map { |(class_name, args)| - if args && args[:strict] - fail CommandError.new('the strict option is global and cannot be applied on the scanner level') - end - - ActiveSupport::Inflector.constantize(class_name).new( - config: merge_scanner_configs(shared_options, args || {}), - file_finder_provider: caching_file_finder_provider, - file_reader: caching_file_reader) - }.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } }) + scanners: search_config[:scanners].map do |(class_name, args)| + if args && args[:strict] + fail CommandError, 'the strict option is global and cannot be applied on the scanner level' + end + + ActiveSupport::Inflector.constantize(class_name).new( + config: merge_scanner_configs(shared_options, args || {}), + file_finder_provider: caching_file_finder_provider, + file_reader: caching_file_reader + ) + end.tap { |scanners| log_verbose { scanners.map { |s| " #{s.class.name} #{s.config.inspect}" } * "\n" } } + ) end end @@ -122,9 +121,9 @@ def caching_file_reader @caching_file_reader ||= Scanners::Files::CachingFileReader.new end - # @return whether the key is potentially used in a code expression such as `t("category.#{ category_key }")` + # @return [Boolean] whether the key is potentially used in a code expression such as `t("category.#{category_key}")` def used_in_expr?(key) - !!(key =~ expr_key_re) + !!(key =~ expr_key_re) # rubocop:disable Style/DoubleNegation end private @@ -137,17 +136,17 @@ def strict?(strict) # keys in the source that end with a ., e.g. t("category.#{ cat.i18n_key }") or t("category." + category.key) # @param [String] replacement for interpolated values. - def expr_key_re(replacement: ':'.freeze) + def expr_key_re(replacement: ':') @expr_key_re ||= begin # disallow patterns with no keys ignore_pattern_re = /\A[\.#{replacement}]*\z/ - patterns = used_in_source_tree(strict: false).key_names.select { |k| - k.end_with?('.'.freeze) || k =~ /\#{/.freeze - }.map { |k| - pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.'.freeze)}" + patterns = used_in_source_tree(strict: false).key_names.select do |k| + k.end_with?('.') || k =~ /\#{/ + end.map do |k| + pattern = "#{replace_key_exp(k, replacement)}#{replacement if k.end_with?('.')}" next if pattern =~ ignore_pattern_re pattern - }.compact + end.compact compile_key_pattern "{#{patterns * ','}}" end end @@ -160,15 +159,15 @@ def replace_key_exp(key, replacement) scanner = StringScanner.new(key) braces = [] result = [] - while (match_until = scanner.scan_until(/(?:#?\{|})/.freeze)) - if scanner.matched == '#{'.freeze + while (match_until = scanner.scan_until(/(?:#?\{|})/)) + if scanner.matched == '#{' braces << scanner.matched result << match_until[0..-3] if braces.length == 1 elsif scanner.matched == '}' prev_brace = braces.pop - result << replacement if braces.empty? && prev_brace == '#{'.freeze + result << replacement if braces.empty? && prev_brace == '#{' else - braces << '{'.freeze + braces << '{' end end result << key[scanner.pos..-1] unless scanner.eos? diff --git a/spec/bin_simplecov_helper.rb b/spec/bin_simplecov_helper.rb index 03417322..cae337c6 100644 --- a/spec/bin_simplecov_helper.rb +++ b/spec/bin_simplecov_helper.rb @@ -8,11 +8,9 @@ SimpleCov::Formatter::HTMLFormatter.send(:define_method, :puts) { |*| } if defined?(CodeClimate) class NullLoger < Logger - def initialize(*args) - end + def initialize(*args); end - def add(*args, &block) - end + def add(*args, &block); end end CodeClimate::TestReporter.configuration.logger = NullLoger.new end diff --git a/spec/commands/data_commands_spec.rb b/spec/commands/data_commands_spec.rb index 20766da5..21301c6d 100644 --- a/spec/commands/data_commands_spec.rb +++ b/spec/commands/data_commands_spec.rb @@ -4,14 +4,13 @@ RSpec.describe 'Data commands' do delegate :run_cmd, to: :TestCodebase def en_data - {'en' => {'a' => '1', 'common' => {'hello' => 'Hello'}}} + { 'en' => { 'a' => '1', 'common' => { 'hello' => 'Hello' } } } end def en_data_2 - {'en' => {'common' => {'hi' => 'Hi'}}} + { 'en' => { 'common' => { 'hi' => 'Hi' } } } end - before do TestCodebase.setup('config/locales/en.yml' => en_data.to_yaml) end @@ -21,19 +20,21 @@ def en_data_2 end it '#data' do - expect(JSON.parse(run_cmd 'data', '-fjson', '-len')).to eq(en_data) + expect(JSON.parse(run_cmd('data', '-fjson', '-len'))).to eq(en_data) end it '#data-merge' do - expect(JSON.parse(run_cmd 'data-merge', '-fjson', '-S', en_data_2.to_json)).to eq(en_data.deep_merge en_data_2) + expect(JSON.parse(run_cmd('data-merge', '-fjson', '-S', en_data_2.to_json))).to eq(en_data.deep_merge(en_data_2)) end it '#data-write' do - expect(JSON.parse(run_cmd 'data-write', '-fjson', '-S', en_data_2.to_json)).to eq(en_data_2) + expect(JSON.parse(run_cmd('data-write', '-fjson', '-S', en_data_2.to_json))).to eq(en_data_2) end it '#data-remove' do - to_remove = {'en' => {'common' => {'hello' => ''}}} - expect(JSON.parse(run_cmd 'data-remove', '-fjson', '-S', to_remove.to_json)).to eq('en' => {'common' => en_data['en']['common'] }) + to_remove = { 'en' => { 'common' => { 'hello' => '' } } } + expect(JSON.parse(run_cmd('data-remove', '-fjson', '-S', to_remove.to_json))).to( + eq('en' => { 'common' => en_data['en']['common'] }) + ) end end diff --git a/spec/commands/missing_commands_spec.rb b/spec/commands/missing_commands_spec.rb index 38e0308e..9094b1ce 100644 --- a/spec/commands/missing_commands_spec.rb +++ b/spec/commands/missing_commands_spec.rb @@ -8,16 +8,17 @@ describe 'adds the missing keys to base locale first, then to other locales' do around do |ex| TestCodebase.setup( - 'config/i18n-tasks.yml' => {base_locale: 'en', locales: %w(es fr)}.to_yaml, - 'config/locales/es.yml' => {'es' => {'a' => 'A', 'ref' => :ref}}.to_yaml) + 'config/i18n-tasks.yml' => { base_locale: 'en', locales: %w(es fr) }.to_yaml, + 'config/locales/es.yml' => { 'es' => { 'a' => 'A', 'ref' => :ref } }.to_yaml + ) TestCodebase.in_test_app_dir { ex.call } TestCodebase.teardown end it 'with -v argument' do run_cmd 'add-missing', '-vTRME' - expect(YAML.load_file('config/locales/en.yml')).to eq('en' => {'a' => 'TRME', 'ref' => :ref}) - expect(YAML.load_file('config/locales/fr.yml')).to eq('fr' => {'a' => 'TRME', 'ref' => :ref}) + expect(YAML.load_file('config/locales/en.yml')).to eq('en' => { 'a' => 'TRME', 'ref' => :ref }) + expect(YAML.load_file('config/locales/fr.yml')).to eq('fr' => { 'a' => 'TRME', 'ref' => :ref }) end end end diff --git a/spec/commands/tree_commands_spec.rb b/spec/commands/tree_commands_spec.rb index 52e0ca49..1f0ae62b 100644 --- a/spec/commands/tree_commands_spec.rb +++ b/spec/commands/tree_commands_spec.rb @@ -12,7 +12,7 @@ end context 'tree-merge' do - trees = [{'a' => '1', 'b' => '2'}, {'a' => '-1', 'c' => '3'}] + trees = [{ 'a' => '1', 'b' => '2' }, { 'a' => '-1', 'c' => '3' }] it trees.map(&:to_json).join(', ') do merged = JSON.parse run_cmd('tree-merge', '-fjson', '-S', *trees.map(&:to_json)) expect(merged).to eq trees.reduce(:merge) @@ -20,7 +20,7 @@ end context 'tree-filter' do - forest = {'a' => '1', 'b' => '2', 'c' => {'a' => '3'}} + forest = { 'a' => '1', 'b' => '2', 'c' => { 'a' => '3' } } pattern = '{a,c.*}' it "-p #{pattern.inspect} #{forest.to_json}" do selected = JSON.parse run_cmd('tree-filter', '-fjson', '-p', pattern, forest.to_json) @@ -29,17 +29,17 @@ end context 'tree-subtract' do - trees = [{'a' => '1', 'b' => '2'}, {'a' => '-1', 'c' => '3'}] + trees = [{ 'a' => '1', 'b' => '2' }, { 'a' => '-1', 'c' => '3' }] it trees.map(&:to_json).join(' - ') do subtracted = JSON.parse run_cmd('tree-subtract', '-fjson', '-S', *trees.map(&:to_json)) - expected = {'b' => '2'} + expected = { 'b' => '2' } expect(subtracted).to eq expected end end context 'tree-rename-key' do def forest - {'a' => {'b' => {'a' => '1'}}} + { 'a' => { 'b' => { 'a' => '1' } } } end def rename_key(from, to) @@ -59,7 +59,7 @@ def rename_key(from, to) context 'tree-convert' do def forest - {'x' => '1', 'a' => {'b' => {'a' => '2'}}} + { 'x' => '1', 'a' => { 'b' => { 'a' => '2' } } } end it 'converts to keys' do diff --git a/spec/conservative_router_spec.rb b/spec/conservative_router_spec.rb index 4d69c19a..3f269cfa 100644 --- a/spec/conservative_router_spec.rb +++ b/spec/conservative_router_spec.rb @@ -5,23 +5,23 @@ describe '#available_locales' do before do TestCodebase.setup( - 'config/locales/en.yml' => {en: {a: 1}}.to_yaml, - 'config/locales/other.en.yml' => {en: {b: 1}}.to_yaml, - 'config/locales/es.yml' => {es: {}}.to_yaml, - 'config/locales/other.es.yml' => {es: {c: 1}}.to_yaml + 'config/locales/en.yml' => { en: { a: 1 } }.to_yaml, + 'config/locales/other.en.yml' => { en: { b: 1 } }.to_yaml, + 'config/locales/es.yml' => { es: {} }.to_yaml, + 'config/locales/other.es.yml' => { es: { c: 1 } }.to_yaml ) end after do TestCodebase.teardown end - let(:data) { + let(:data) do I18n::Tasks::Data::FileSystem.new( - router: 'conservative_router', - base_locale: 'en', - read: 'config/locales/*%{locale}.yml', - write: ['config/locales/not_found.%{locale}.yml'] + router: 'conservative_router', + base_locale: 'en', + read: 'config/locales/*%{locale}.yml', + write: ['config/locales/not_found.%{locale}.yml'] ) - } + end it 'preserves existing keys' do TestCodebase.in_test_app_dir do @@ -33,7 +33,7 @@ it 'infers new keys from base locale' do TestCodebase.in_test_app_dir do - data['es'] = data['es'].merge!(build_tree(es: {a: 1, b: 2})) + data['es'] = data['es'].merge!(build_tree(es: { a: 1, b: 2 })) data.reload expect(data['es']['es.a'].data[:path]).to eq('config/locales/es.yml') expect(data['es']['es.b'].data[:path]).to eq('config/locales/other.es.yml') @@ -42,7 +42,7 @@ it 'falls back to pattern_router when the key is new' do TestCodebase.in_test_app_dir do - data['es'] = data['es'].merge!(build_tree(es: {z: 2})) + data['es'] = data['es'].merge!(build_tree(es: { z: 2 })) data.reload expect(data['es']['es.z'].data[:path]).to eq('config/locales/not_found.es.yml') end diff --git a/spec/file_system_data_spec.rb b/spec/file_system_data_spec.rb index 1c5b27c2..3c6d985a 100644 --- a/spec/file_system_data_spec.rb +++ b/spec/file_system_data_spec.rb @@ -5,9 +5,9 @@ describe '#available_locales' do before do TestCodebase.setup( - 'config/locales/en.yml' => {en: {}}, - 'config/locales/es.yml' => {es: {}}, - 'config/locales/other.fr.yml' => {fr: {}} + 'config/locales/en.yml' => { en: {} }, + 'config/locales/es.yml' => { es: {} }, + 'config/locales/other.fr.yml' => { fr: {} } ) end after do @@ -15,22 +15,22 @@ end let(:data) { I18n::Tasks::Data::FileSystem.new } it 'default pattern' do - data.config = {read: ['config/locales/%{locale}.yml']} - TestCodebase.in_test_app_dir { + data.config = { read: ['config/locales/%{locale}.yml'] } + TestCodebase.in_test_app_dir do expect(data.available_locales.sort).to eq(%w(en es).sort) - } + end end it 'more inclusive pattern' do - data.config = {read: ['config/locales/*%{locale}.yml']} - TestCodebase.in_test_app_dir { + data.config = { read: ['config/locales/*%{locale}.yml'] } + TestCodebase.in_test_app_dir do expect(data.available_locales.sort).to eq(%w(en es fr).sort) - } + end end it 'another pattern' do - data.config = {read: ['config/locales/*.%{locale}.yml']} - TestCodebase.in_test_app_dir { + data.config = { read: ['config/locales/*.%{locale}.yml'] } + TestCodebase.in_test_app_dir do expect(data.available_locales.sort).to eq(%w(fr).sort) - } + end end end @@ -39,64 +39,66 @@ after { TestCodebase.teardown } it '#get' do - data.config = {read: ['a.yml', '{b,c}.yml']} + data.config = { read: ['a.yml', '{b,c}.yml'] } TestCodebase.setup( - 'a.yml' => {en: {a: 1}}.stringify_keys.to_yaml, - 'b.yml' => {en: {b: 1}}.stringify_keys.to_yaml, - 'c.yml' => {en: {c: 1}}.stringify_keys.to_yaml + 'a.yml' => { en: { a: 1 } }.stringify_keys.to_yaml, + 'b.yml' => { en: { b: 1 } }.stringify_keys.to_yaml, + 'c.yml' => { en: { c: 1 } }.stringify_keys.to_yaml ) - TestCodebase.in_test_app_dir { + TestCodebase.in_test_app_dir do actual = data[:en].to_hash['en'].symbolize_keys expect(actual).to eq(a: 1, b: 1, c: 1) - } + end end it '#set' do - data.config = {read: 'a.yml', write: [['{:}.*', '\1.%{locale}.yml']]} - keys = {'a' => {'b' => 'c'}, 'x' => 'y'} - locale_data = {'pizza' => keys, 'sushi' => keys} + data.config = { read: 'a.yml', write: [['{:}.*', '\1.%{locale}.yml']] } + keys = { 'a' => { 'b' => 'c' }, 'x' => 'y' } + locale_data = { 'pizza' => keys, 'sushi' => keys } TestCodebase.setup - TestCodebase.in_test_app_dir { + TestCodebase.in_test_app_dir do data[:en] = data[:en].merge!('en' => locale_data) - files = %w(pizza.en.yml sushi.en.yml) + files = %w(pizza.en.yml sushi.en.yml) expect(Dir['*.yml'].sort).to eq(files.sort) - files.each { |f| expect(YAML.load_file(f)['en']).to eq({File.basename(f, '.en.yml') => keys}) } - } + files.each { |f| expect(YAML.load_file(f)['en']).to eq(File.basename(f, '.en.yml') => keys) } + end end end describe 'json' do - let!(:data) { + let!(:data) do I18n::Tasks::Data::FileSystem.new( - read: ['config/locales/%{locale}.json'], - write: ['config/locales/%{locale}.json'] + read: ['config/locales/%{locale}.json'], + write: ['config/locales/%{locale}.json'] ) - } + end after { TestCodebase.teardown } it 'reads' do - data.config = {read: ['a.json', '{b,c}.json']} + data.config = { read: ['a.json', '{b,c}.json'] } TestCodebase.setup( - 'a.json' => {en: {a: 1}}.stringify_keys.to_json, - 'b.json' => {en: {b: 1}}.stringify_keys.to_json, - 'c.json' => {en: {c: 1}}.stringify_keys.to_json + 'a.json' => { en: { a: 1 } }.stringify_keys.to_json, + 'b.json' => { en: { b: 1 } }.stringify_keys.to_json, + 'c.json' => { en: { c: 1 } }.stringify_keys.to_json ) - TestCodebase.in_test_app_dir { + TestCodebase.in_test_app_dir do expect(data[:en].to_hash['en'].symbolize_keys).to eq(a: 1, b: 1, c: 1) - } + end end it 'writes' do - data.config = {read: 'a.json', write: [['{:}.*', '\1.%{locale}.json']]} - keys = {'a' => {'b' => 'c'}, 'x' => 'y'} - locale_data = {'pizza' => keys, 'sushi' => keys} + data.config = { read: 'a.json', write: [['{:}.*', '\1.%{locale}.json']] } + keys = { 'a' => { 'b' => 'c' }, 'x' => 'y' } + locale_data = { 'pizza' => keys, 'sushi' => keys } TestCodebase.setup - TestCodebase.in_test_app_dir { + TestCodebase.in_test_app_dir do data[:en] = data[:en].merge!('en' => locale_data) - files = %w(pizza.en.json sushi.en.json) + files = %w(pizza.en.json sushi.en.json) expect(Dir['*.json'].sort).to eq(files.sort) - files.each { |f| expect(JSON.parse(File.read(f, encoding: 'UTF-8'))['en']).to eq({File.basename(f, '.en.json') => keys}) } - } + files.each do |f| + expect(JSON.parse(File.read(f, encoding: 'UTF-8'))['en']).to eq(File.basename(f, '.en.json') => keys) + end + end end end end diff --git a/spec/google_translate_spec.rb b/spec/google_translate_spec.rb index 3cd14a65..941a39df 100644 --- a/spec/google_translate_spec.rb +++ b/spec/google_translate_spec.rb @@ -3,7 +3,6 @@ require 'i18n/tasks/commands' RSpec.describe 'Google Translation' do - nil_value_test = ['nil-value-key', nil, nil] text_test = ['key', "Hello - %{user} O'Neill!", "Hola - %{user} O'Neill!"] html_test = ['html-key.html', "Hello - %{user} O'neill", "Hola - %{user} O'neill"] @@ -30,24 +29,24 @@ skip 'GOOGLE_TRANSLATE_API_KEY env var not set' unless ENV['GOOGLE_TRANSLATE_API_KEY'] in_test_app_dir do task.data[:en] = build_tree('en' => { - 'common' => { - 'a' => 'λ', - 'hello' => text_test[1], - 'hello_html' => html_test[1], - 'hello_plural_html' => { - 'one' => html_test_plrl[1] - }, - 'array_key' => array_test[1], - 'nil-value-key' => nil_value_test[1], - 'fixnum-key' => fixnum_test[1], - 'ref-key' => ref_key_test[1] - } - }) + 'common' => { + 'a' => 'λ', + 'hello' => text_test[1], + 'hello_html' => html_test[1], + 'hello_plural_html' => { + 'one' => html_test_plrl[1] + }, + 'array_key' => array_test[1], + 'nil-value-key' => nil_value_test[1], + 'fixnum-key' => fixnum_test[1], + 'ref-key' => ref_key_test[1] + } + }) task.data[:es] = build_tree('es' => { - 'common' => { - 'a' => 'λ', - } - }) + 'common' => { + 'a' => 'λ' + } + }) run_cmd 'translate-missing' expect(task.t('common.hello', 'es')).to eq(text_test[2]) diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index b9990f87..31d69eac 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -8,11 +8,11 @@ it 'does not have missing keys' do expect(missing_keys).to be_empty, - "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" + "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" end it 'does not have unused keys' do expect(unused_keys).to be_empty, - "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" + "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" end end diff --git a/spec/i18n_tasks_spec.rb b/spec/i18n_tasks_spec.rb index e15b2525..e93d2110 100644 --- a/spec/i18n_tasks_spec.rb +++ b/spec/i18n_tasks_spec.rb @@ -12,31 +12,33 @@ it 'shows help when invoked with no arguments, shows version on --version' do next if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' # These bin/i18n-tasks tests are executed in parallel for performance - env = {'I18N_TASKS_BIN_SIMPLECOV_COVERAGE' => '1'} + env = { 'I18N_TASKS_BIN_SIMPLECOV_COVERAGE' => '1' } in_test_app_dir do - clean_unrelated_warnings = -> s { - s.sub(%r(^warning: parser/cur.*?https://github.com/whitequark/parser#compatibility-with-ruby-mri\.\n)m, '') - .gsub(/^.*warning: constant ::(?:Fixnum|Bignum) is deprecated\n/, '') - } - clean_coverage_logging = -> s { s.sub /(?:\n^|\A)(?:Coverage = |.*Reporting coverage).*(?:$\n|\z)/i, '' } + clean_unrelated_warnings = lambda do |s| + s.sub(%r{^warning: parser/cur.*?https://github.com/whitequark/parser#compatibility-with-ruby-mri\.\n}m, '') + .gsub(/^.*warning: constant ::(?:Fixnum|Bignum) is deprecated\n/, '') + end + clean_coverage_logging = ->(s) { s.sub(/(?:\n^|\A)(?:Coverage = |.*Reporting coverage).*(?:$\n|\z)/i, '') } [ - proc { - out, err, status = Open3.capture3(env, 'bundle exec ../../bin/i18n-tasks') - out, err = clean_coverage_logging[out], clean_unrelated_warnings[clean_coverage_logging[err]] - expect(status).to be_success - expect(out).to be_empty - expect(err.lines.first.chomp).to eq('Usage: i18n-tasks [command] [options]') - expect(err).to include('Available commands', 'add-missing') - # a task from a plugin - expect(err).to include('greet') - }, - proc { - out, err, status = Open3.capture3(env, 'bundle exec ../../bin/i18n-tasks --version') - out, err = clean_coverage_logging[out], clean_unrelated_warnings[clean_coverage_logging[err]] - expect(status).to be_success - expect(err).to be_empty - expect(out.chomp).to eq I18n::Tasks::VERSION - } + proc do + out, err, status = Open3.capture3(env, 'bundle exec ../../bin/i18n-tasks') + out = clean_coverage_logging[out] + err = clean_unrelated_warnings[clean_coverage_logging[err]] + expect(status).to be_success + expect(out).to be_empty + expect(err.lines.first.chomp).to eq('Usage: i18n-tasks [command] [options]') + expect(err).to include('Available commands', 'add-missing') + # a task from a plugin + expect(err).to include('greet') + end, + proc do + out, err, status = Open3.capture3(env, 'bundle exec ../../bin/i18n-tasks --version') + out = clean_coverage_logging[out] + err = clean_unrelated_warnings[clean_coverage_logging[err]] + expect(status).to be_success + expect(err).to be_empty + expect(out.chomp).to eq I18n::Tasks::VERSION + end ].map { |test| Thread.start(&test) }.each(&:join) end end @@ -49,14 +51,14 @@ it 'outputs stats' do t = i18n_task out = run_cmd_capture_stderr('health') - in_test_app_dir { t.forest_stats(t.data_forest t.locales) }.values.each do |v| + in_test_app_dir { t.forest_stats(t.data_forest(t.locales)) }.values.each do |v| expect(out).to include(v.to_s) end end end describe 'missing' do - let (:expected_missing_keys_in_source) { + let :expected_missing_keys_in_source do %w( used_but_missing.key relative.index.missing @@ -76,8 +78,8 @@ .not_relative reference-missing-target.a ) + ['⮕ missing_target'] - } - let (:expected_missing_keys_diff) { + end + let :expected_missing_keys_diff do %w( es.missing_in_es.a en.present_in_es_but_not_en.a @@ -85,22 +87,22 @@ es.missing_in_es_plural_2.a en.only_in_es ) - } + end it 'detects missing' do es_keys = expected_missing_keys_diff.grep(/^es\./) + - expected_missing_keys_in_source.map { |k| k[0] != '⮕' ? "es.#{k}" : k } + expected_missing_keys_in_source.map { |k| k[0] != '⮕' ? "es.#{k}" : k } out, result = run_cmd_capture_stdout_and_result 'missing' expect(result).to eq :exit_1 expect(out).to be_i18n_keys(expected_missing_keys_diff + - expected_missing_keys_in_source.map { |k| k[0] != '⮕' ? "all.#{k}" : k } ) - expect(run_cmd 'missing', '-les').to be_i18n_keys es_keys - expect(run_cmd 'missing', 'es').to be_i18n_keys es_keys + expected_missing_keys_in_source.map { |k| k[0] != '⮕' ? "all.#{k}" : k }) + expect(run_cmd('missing', '-les')).to be_i18n_keys es_keys + expect(run_cmd('missing', 'es')).to be_i18n_keys es_keys end end describe 'eq_base' do it 'detects eq-base' do - expect(run_cmd 'eq-base').to be_i18n_keys %w(es.same_in_es.a) + expect(run_cmd('eq-base')).to be_i18n_keys %w(es.same_in_es.a) end end @@ -124,7 +126,7 @@ end it 'detects unused (--strict)' do - expect(run_cmd 'unused', '--strict').to be_i18n_keys expected_unused_keys_strict + expect(run_cmd('unused', '--strict')).to be_i18n_keys expected_unused_keys_strict end end @@ -152,110 +154,111 @@ it 'sorts the keys' do in_test_app_dir do run_cmd 'normalize' - en_yml_data = i18n_task.data.reload['en'].select_keys { |_k, node| + en_yml_data = i18n_task.data.reload['en'].select_keys do |_k, node| node.data[:path] == 'config/locales/en.yml' - } + end expect(en_yml_data).to be_present - en_yml_data.nodes { |nodes| + en_yml_data.nodes do |nodes| next unless nodes.children keys = nodes.children.map(&:key) expect(keys).to eq keys.sort - } + end end end it 'moves keys to the corresponding files as per data.write' do - in_test_app_dir { + in_test_app_dir do expect(File).to_not exist 'config/locales/devise.en.yml' run_cmd 'normalize', '--pattern_router' expect(YAML.load_file('config/locales/devise.en.yml')['en']['devise']['a']).to eq 'EN_TEXT' - } + end end end describe 'xlsx_report' do it 'saves' do - in_test_app_dir { + in_test_app_dir do run_cmd 'xlsx-report' expect(File).to exist 'tmp/i18n-report.xlsx' FileUtils.cp('tmp/i18n-report.xlsx', '..') - } + end end - end describe 'add_missing' do it 'default placeholder: default_or_value_or_human_key' do - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/en.yml')['en']['used_but_missing']).to be_nil expect(YAML.load_file('config/locales/en.yml')['en']['default_arg']).to be_nil - } + end run_cmd 'add-missing', 'base' - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/en.yml')['en']['used_but_missing']['key']).to eq 'Key' expect(YAML.load_file('config/locales/en.yml')['en']['present_in_es_but_not_en']['a']).to eq 'ES_TEXT' expect(YAML.load_file('config/locales/en.yml')['en']['default_arg']).to eq 'Default Text' - } + end end it 'default value: base_value for non-base locale' do - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']).to be_nil - } + end run_cmd 'add-missing', 'es' - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']['a']).to eq 'EN_TEXT' expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es_plural_1']['a']['one']).to eq 'EN_TEXT' - } + end end it '--value' do - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']).to be_nil - } + end run_cmd 'normalize', '--pattern_router' run_cmd 'add-missing', '-v', 'TRME' - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']['a']).to eq 'TRME' expect(YAML.load_file('config/locales/devise.es.yml')['es']['devise']['a']).to eq 'ES_TEXT' expect(YAML.load_file('config/locales/en.yml')['en']['present_in_es_but_not_en']['a']).to eq 'TRME' - } + end end it '--value with %{value}' do - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']).to be_nil - } + end run_cmd 'add-missing', '-v', 'TRME %{value}' - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']['a']).to eq 'TRME EN_TEXT' expect(YAML.load_file('config/locales/en.yml')['en']['present_in_es_but_not_en']['a']).to eq 'TRME ES_TEXT' - } + end end it '--value with %{key}' do - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']).to be_nil - } + end run_cmd 'add-missing', '-v', 'TRME %{key}' - in_test_app_dir { + in_test_app_dir do expect(YAML.load_file('config/locales/es.yml')['es']['missing_in_es']['a']).to eq 'TRME es.missing_in_es.a' - expect(YAML.load_file('config/locales/en.yml')['en']['present_in_es_but_not_en']['a']).to eq 'TRME en.present_in_es_but_not_en.a' - } + expect(YAML.load_file('config/locales/en.yml')['en']['present_in_es_but_not_en']['a']).to( + eq 'TRME en.present_in_es_but_not_en.a' + ) + end end end describe 'config' do it 'prints config' do - expect(YAML.load(Term::ANSIColor.uncolor(run_cmd 'config'))).to( - eq(in_test_app_dir { i18n_task.config_for_inspect }) + expect(YAML.load(Term::ANSIColor.uncolor(run_cmd('config')))).to( + eq(in_test_app_dir { i18n_task.config_for_inspect }) ) end end describe 'find' do it 'prints usages' do - result = Term::ANSIColor.uncolor(run_cmd 'find', 'used.*') + result = Term::ANSIColor.uncolor(run_cmd('find', 'used.*')) expect(result).to eq(<<-TXT) used.a 2 app/views/usages.html.slim:1 p = t 'used.a' @@ -264,7 +267,7 @@ end it 'finds references' do - result = Term::ANSIColor.uncolor(run_cmd 'find', 'reference*') + result = Term::ANSIColor.uncolor(run_cmd('find', 'reference*')) expect(result).to eq(<<-TXT) missing_target.a (resolved ref) app/views/index.html.slim:36 = t 'reference-missing-target.a' @@ -287,69 +290,69 @@ # --- setup --- BENCH_KEYS = ENV['BENCH_KEYS'].to_i before(:each) do - gen_data = ->(v) { + gen_data = lambda do |v| v_num = v.chars.map(&:ord).join('').to_i { - 'ca' => {'a' => v, 'b' => v, 'c' => v, 'd' => v, 'e' => "#{v}%{i}", 'f' => "#{v}%{i}"}, - 'cb' => {'a' => v, 'b' => "#{v}%{i}"}, - 'hash' => { - 'pattern' => {'a' => v}, - 'pattern2' => {'a' => v}, - 'pattern3' => {'x' => {'y' => {'z' => v}}}, - }, - 'unused' => {'a' => v, 'numeric' => v_num, 'plural' => {'one' => v, 'other' => v}}, - 'ignore_unused' => {'a' => v}, - 'missing_in_es' => {'a' => v}, - 'missing_in_es_plural_1' => {'a' => {'one' => v, 'other' => v}}, - 'missing_in_es_plural_2' => {'a' => {'one' => v, 'other' => v}}, - 'same_in_es' => {'a' => v}, - 'ignore_eq_base_all' => {'a' => v}, - 'ignore_eq_base_es' => {'a' => v}, - 'blank_in_es' => {'a' => v}, - 'relative' => { - 'index' => { - 'title' => v, - 'description' => v, - 'summary' => v, - } - }, - 'numeric' => {'a' => v_num}, - 'plural' => {'a' => {'one' => v, 'other' => "%{count} #{v}s"}}, - 'devise' => {'a' => v}, - 'scoped' => {'x' => v}, - 'very' => {'scoped' => {'x' => v}}, - 'used' => {'a' => v}, - 'latin_extra' => {'çüéö' => v}, - 'not_a_comment' => v, - 'reference-ok-plain' => :'resolved_reference_target.a', - 'reference-ok-nested' => :resolved_reference_target, - 'reference-unused' => :'resolved_reference_target.a', - 'reference-unused-target' => :'unused.a', - 'reference-missing-target' => :missing_target, - 'resolved_reference_target' => {'a' => v} - }.tap { |r| + 'ca' => { 'a' => v, 'b' => v, 'c' => v, 'd' => v, 'e' => "#{v}%{i}", 'f' => "#{v}%{i}" }, + 'cb' => { 'a' => v, 'b' => "#{v}%{i}" }, + 'hash' => { + 'pattern' => { 'a' => v }, + 'pattern2' => { 'a' => v }, + 'pattern3' => { 'x' => { 'y' => { 'z' => v } } } + }, + 'unused' => { 'a' => v, 'numeric' => v_num, 'plural' => { 'one' => v, 'other' => v } }, + 'ignore_unused' => { 'a' => v }, + 'missing_in_es' => { 'a' => v }, + 'missing_in_es_plural_1' => { 'a' => { 'one' => v, 'other' => v } }, + 'missing_in_es_plural_2' => { 'a' => { 'one' => v, 'other' => v } }, + 'same_in_es' => { 'a' => v }, + 'ignore_eq_base_all' => { 'a' => v }, + 'ignore_eq_base_es' => { 'a' => v }, + 'blank_in_es' => { 'a' => v }, + 'relative' => { + 'index' => { + 'title' => v, + 'description' => v, + 'summary' => v + } + }, + 'numeric' => { 'a' => v_num }, + 'plural' => { 'a' => { 'one' => v, 'other' => "%{count} #{v}s" } }, + 'devise' => { 'a' => v }, + 'scoped' => { 'x' => v }, + 'very' => { 'scoped' => { 'x' => v } }, + 'used' => { 'a' => v }, + 'latin_extra' => { 'çüéö' => v }, + 'not_a_comment' => v, + 'reference-ok-plain' => :'resolved_reference_target.a', + 'reference-ok-nested' => :resolved_reference_target, + 'reference-unused' => :'resolved_reference_target.a', + 'reference-unused-target' => :'unused.a', + 'reference-missing-target' => :missing_target, + 'resolved_reference_target' => { 'a' => v } + }.tap do |r| if BENCH_KEYS > 0 gen = r['bench'] = {} BENCH_KEYS.times { |i| gen["key#{i}"] = v } end - } - } + end + end - en_data = gen_data.('EN_TEXT') - es_data = gen_data.('ES_TEXT').except('missing_in_es', 'missing_in_es_plural_1', 'missing_in_es_plural_2') + en_data = gen_data.call('EN_TEXT') + es_data = gen_data.call('ES_TEXT').except('missing_in_es', 'missing_in_es_plural_1', 'missing_in_es_plural_2') es_data['same_in_es']['a'] = 'EN_TEXT' es_data['blank_in_es']['a'] = '' es_data['ignore_eq_base_all']['a'] = 'EN_TEXT' es_data['ignore_eq_base_es']['a'] = 'EN_TEXT' es_data['only_in_es'] = 1 - es_data['present_in_es_but_not_en'] = {'a' => 'ES_TEXT'} + es_data['present_in_es_but_not_en'] = { 'a' => 'ES_TEXT' } fs = fixtures_contents.merge( - 'config/locales/en.yml' => {'en' => en_data}.to_yaml, - 'config/locales/es.yml' => {'es' => es_data}.to_yaml, - # test that our algorithms can scale to the order of {BENCH_KEYS} keys. - 'vendor/heavy.file' => BENCH_KEYS.times.map { |i| "t('bench.key#{i}') " }.join + 'config/locales/en.yml' => { 'en' => en_data }.to_yaml, + 'config/locales/es.yml' => { 'es' => es_data }.to_yaml, + # test that our algorithms can scale to the order of {BENCH_KEYS} keys. + 'vendor/heavy.file' => Array.new(BENCH_KEYS) { |i| "t('bench.key#{i}') " }.join ) TestCodebase.setup fs diff --git a/spec/key_pattern_matching_spec.rb b/spec/key_pattern_matching_spec.rb index 3d691224..487ec0a7 100644 --- a/spec/key_pattern_matching_spec.rb +++ b/spec/key_pattern_matching_spec.rb @@ -57,7 +57,7 @@ it 'captures' do p = 'a.{x,y}.{:}' compile_key_pattern(p) =~ 'a.x.c' - expect([$1, $2]).to eq(['x', 'c']) + expect([Regexp.last_match(1), Regexp.last_match(2)]).to eq(%w(x c)) end end end diff --git a/spec/locale_pathname_spec.rb b/spec/locale_pathname_spec.rb index 941301b1..70d0d871 100644 --- a/spec/locale_pathname_spec.rb +++ b/spec/locale_pathname_spec.rb @@ -2,24 +2,25 @@ require 'spec_helper' RSpec.describe 'LocalePathname' do - include ::I18n::Tasks::LocalePathname context '#replace_locale' do + def replace_locale(path, from, to) + ::I18n::Tasks::LocalePathname.replace_locale(path, from, to) + end it 'es.yml' do - expect(replace_locale 'es.yml', 'es', 'fr').to eq 'fr.yml' + expect(replace_locale('es.yml', 'es', 'fr')).to eq 'fr.yml' end it 'scope.es.yml' do - expect(replace_locale 'scope.es.yml', 'es', 'fr').to eq 'scope.fr.yml' + expect(replace_locale('scope.es.yml', 'es', 'fr')).to eq 'scope.fr.yml' end it 'path/es.yml' do - expect(replace_locale 'path/es.yml', 'es', 'fr').to eq 'path/fr.yml' + expect(replace_locale('path/es.yml', 'es', 'fr')).to eq 'path/fr.yml' end it 'path/scope.es.yml' do - expect(replace_locale 'path/scope.es.yml', 'es', 'fr').to eq 'path/scope.fr.yml' + expect(replace_locale('path/scope.es.yml', 'es', 'fr')).to eq 'path/scope.fr.yml' end - end end diff --git a/spec/locale_tree/siblings_spec.rb b/spec/locale_tree/siblings_spec.rb index 3fa4d63f..82ad7597 100644 --- a/spec/locale_tree/siblings_spec.rb +++ b/spec/locale_tree/siblings_spec.rb @@ -2,40 +2,43 @@ require 'spec_helper' RSpec.describe 'Tree siblings / forest' do - context 'Node' do it '::new with children' do children = I18n::Tasks::Data::Tree::Siblings.from_key_attr([['a', value: 1]]) node = new_node( - key: 'fr', - children: children + key: 'fr', + children: children ) expect(node.to_siblings.first.children.parent.key).to eq 'fr' end it '== (false by value)' do - expect(build_node({'a' => {'b' => {'c' => 1}}})).to_not( - eq(build_node({'a' => {'b' => {'c' => 2}}}))) + expect(build_node('a' => { 'b' => { 'c' => 1 } })).to_not( + eq(build_node('a' => { 'b' => { 'c' => 2 } })) + ) end it '== (false by key)' do - expect(build_node({'a' => {'b' => {'c' => 1}}})).to_not( - eq(build_node({'a' => {'b' => {'d' => 1}}}))) + expect(build_node('a' => { 'b' => { 'c' => 1 } })).to_not( + eq(build_node('a' => { 'b' => { 'd' => 1 } })) + ) end it '== (false by children)' do - expect(build_node({'a' => {'b' => {'c' => 1}}})).to_not( - eq(build_node({'a' => {'b' => {'c' => 1}, 'x' => 2}}))) + expect(build_node('a' => { 'b' => { 'c' => 1 } })).to_not( + eq(build_node('a' => { 'b' => { 'c' => 1 }, 'x' => 2 })) + ) end it '== (true)' do - expect(build_node({'a' => {'b' => {'c' => 1}, 'x' => 2}})).to_not( - eq(build_node({'a' => {'b' => {'d' => 1}, 'x' => 2}}))) + expect(build_node('a' => { 'b' => { 'c' => 1 }, 'x' => 2 })).to_not( + eq(build_node('a' => { 'b' => { 'd' => 1 }, 'x' => 2 })) + ) end end context 'a tree' do - let(:a_hash) { {'a' => 1, 'b' => {'ba' => 1, 'bb' => 2}} } + let(:a_hash) { { 'a' => 1, 'b' => { 'ba' => 1, 'bb' => 2 } } } it '::from_nested_hash' do a = build_tree(a_hash) @@ -54,7 +57,7 @@ it '#merge' do a = build_tree(a_hash) - b_hash = {'b' => {'bc' => 1}, 'c' => 1} + b_hash = { 'b' => { 'bc' => 1 }, 'c' => 1 } expect(a.merge(build_tree(b_hash)).to_hash).to eq(a_hash.deep_merge(b_hash)) end @@ -69,7 +72,7 @@ it '#merge conflict value <- scope' do a = build_tree(a: 1) - b = build_tree(a: {b: 1}) + b = build_tree(a: { b: 1 }) expect { silence_stderr { a.merge(b) } }.to_not raise_error end @@ -79,30 +82,30 @@ end it '#intersect' do - x = {a: 1, b: {ba: 1, bb: 2}} - y = {b: {ba: 1, bc: 3}, c: 1} - intersection = {'b' => {'ba' => 1}} + x = { a: 1, b: { ba: 1, bb: 2 } } + y = { b: { ba: 1, bc: 3 }, c: 1 } + intersection = { 'b' => { 'ba' => 1 } } a = build_tree(x) b = build_tree(y) expect(a.intersect_keys(b, root: true).to_hash).to eq(intersection) end it '#select_keys' do - expect(build_tree(a: 1, b: 1).select_keys {|k, node| k == 'b'}.to_hash).to eq({'b' => 1}) + expect(build_tree(a: 1, b: 1).select_keys { |k, _node| k == 'b' }.to_hash).to eq('b' => 1) end it '#append!' do - expect(build_tree({'a' => 1}).append!(new_node(key: 'b', value: 2)).to_hash).to eq('a' => 1, 'b' => 2) + expect(build_tree('a' => 1).append!(new_node(key: 'b', value: 2)).to_hash).to eq('a' => 1, 'b' => 2) end it '#set replace value' do - expect(build_tree(a: {b: 1}).tap {|t| t['a.b'] = new_node(key: 'b', value: 2) }.to_hash).to( - eq('a' => {'b' => 2}) + expect(build_tree(a: { b: 1 }).tap { |t| t['a.b'] = new_node(key: 'b', value: 2) }.to_hash).to( + eq('a' => { 'b' => 2 }) ) end it '#set get' do - t = build_tree(a: {x: 1}) + t = build_tree(a: { x: 1 }) node = new_node(key: 'd', value: 'e') t['a.b.c.' + node.key] = node expect(t['a.b.c.d'].value).to eq('e') diff --git a/spec/pattern_scanner_spec.rb b/spec/pattern_scanner_spec.rb index 5aa7ff0b..be1e044f 100644 --- a/spec/pattern_scanner_spec.rb +++ b/spec/pattern_scanner_spec.rb @@ -3,26 +3,28 @@ RSpec.describe 'PatternScanner' do describe '#keys' do - let(:expected_key) { + let(:expected_key) do 'events.show.success' - } + end - let(:expected_occurrence) { - {path: 'spec/fixtures/app/controllers/events_controller.rb', - pos: 836, - line_num: 32, - line_pos: 5, - line: ' t(".success")', - raw_key: '.success'} - } + let(:expected_occurrence) do + { path: 'spec/fixtures/app/controllers/events_controller.rb', + pos: 836, + line_num: 32, + line_pos: 5, + line: ' t(".success")', + raw_key: '.success' } + end it 'returns absolute keys from controllers' do file_path = 'spec/fixtures/app/controllers/events_controller.rb' scanner = I18n::Tasks::Scanners::PatternScanner.new( - config: {paths: ['spec/fixtures/'], only: [file_path], relative_roots: ['spec/fixtures/app/controllers']}) + config: { paths: ['spec/fixtures/'], only: [file_path], relative_roots: ['spec/fixtures/app/controllers'] } + ) allow(scanner).to receive(:relative_roots).and_return(['spec/fixtures/app/controllers']) expect(scanner.keys.detect { |key_occurrences| key_occurrences.key =~ /success/ }).to( - eq make_key_occurrences(expected_key, [expected_occurrence])) + eq make_key_occurrences(expected_key, [expected_occurrence]) + ) end end @@ -36,21 +38,20 @@ "t('a.b')", "t('a.b', :arg => val)", "t('a.b', arg: val)", - "t :a_b", + 't :a_b', "t :'a.b'", 't :"a.b"', - "t(:ab)", + 't(:ab)', "t(:'a.b')", 't(:"a.b")', 'I18n.t("a.b")', - 'I18n.translate("a.b")' - ].each do |string| + 'I18n.translate("a.b")'].each do |string| it "matches #{string}" do expect(pattern).to match string end end - ["t \"a.b'", "t a.b"].each do |string| + ["t \"a.b'", 't a.b'].each do |string| it "does not match #{string}" do expect(pattern).to_not match string end diff --git a/spec/plural_keys_spec.rb b/spec/plural_keys_spec.rb index 112599f2..66eaef67 100644 --- a/spec/plural_keys_spec.rb +++ b/spec/plural_keys_spec.rb @@ -7,15 +7,15 @@ TestCodebase.setup('config/locales/en.yml' => '') TestCodebase.in_test_app_dir do tree = ::I18n::Tasks::Data::Tree::Siblings.from_nested_hash('en' => { - 'regular_key' => 'a', - 'plural_key' => { - 'one' => 'one', 'other' => '%{count}' - }, - 'not_really_plural' => { - 'one' => 'a', - 'green' => 'b' - } - }) + 'regular_key' => 'a', + 'plural_key' => { + 'one' => 'one', 'other' => '%{count}' + }, + 'not_really_plural' => { + 'one' => 'a', + 'green' => 'b' + } + }) task.data['en'] = tree task.data['en'] end diff --git a/spec/readme_spec.rb b/spec/readme_spec.rb index ecb2b1fa..b0246b49 100644 --- a/spec/readme_spec.rb +++ b/spec/readme_spec.rb @@ -3,7 +3,7 @@ RSpec.describe 'README.md' do let(:readme) { File.read('README.md', encoding: 'UTF-8') } it 'has valid YAML in ```yaml blocks' do - readme.scan /```yaml\n(.*)(?=^)\n```/ do |m| + readme.scan(/```yaml\n(.*)(?=^)\n```/) do |m| expect { YAML.load(m[0]) }.to_not raise_errors end end diff --git a/spec/references_spec.rb b/spec/references_spec.rb index d9ea7f6e..25c1e1f3 100644 --- a/spec/references_spec.rb +++ b/spec/references_spec.rb @@ -7,68 +7,77 @@ describe '#process_references' do it 'resolves plain references' do result = task.process_references( - build_tree( - 'reference' => nil, - 'not-a-reference' => nil - ), - build_tree( - 'reference' => :resolved - )) + build_tree( + 'reference' => nil, + 'not-a-reference' => nil + ), + build_tree( + 'reference' => :resolved + ) + ) expect(result.map(&:to_hash)).to( - eq [{'reference' => nil}, - {'resolved' => nil}, - {'reference' => nil}]) + eq [{ 'reference' => nil }, + { 'resolved' => nil }, + { 'reference' => nil }] + ) end it 'resolves nested references' do result = task.process_references( - build_tree( - 'reference' => {'a' => nil, 'b' => {'c' => nil}}, - 'not-a-reference' => nil - ), - build_tree( - 'reference' => :resolved - )) + build_tree( + 'reference' => { 'a' => nil, 'b' => { 'c' => nil } }, + 'not-a-reference' => nil + ), + build_tree( + 'reference' => :resolved + ) + ) expect(result.map(&:to_hash)).to( - eq [{'reference' => {'a' => nil, 'b' => {'c' => nil}}}, - {'resolved' => {'a' => nil, 'b' => {'c' => nil}}}, - {'reference' => nil}]) + eq [{ 'reference' => { 'a' => nil, 'b' => { 'c' => nil } } }, + { 'resolved' => { 'a' => nil, 'b' => { 'c' => nil } } }, + { 'reference' => nil }] + ) end it 'resolves nested references with nested keys' do result = task.process_references( - build_tree( - 'nested' => {'reference' => {'a' => nil, 'b' => {'c' => nil}}}, - 'not-a-reference' => nil - ), - build_tree( - 'nested' => {'reference' => :resolved} - )) + build_tree( + 'nested' => { 'reference' => { 'a' => nil, 'b' => { 'c' => nil } } }, + 'not-a-reference' => nil + ), + build_tree( + 'nested' => { 'reference' => :resolved } + ) + ) expect(result.map(&:to_hash)).to( - eq [{'nested' => {'reference' => {'a' => nil, 'b' => {'c' => nil}}}}, - {'resolved' => {'a' => nil, 'b' => {'c' => nil}}}, - {'nested' => {'reference' => nil}}]) + eq [{ 'nested' => { 'reference' => { 'a' => nil, 'b' => { 'c' => nil } } } }, + { 'resolved' => { 'a' => nil, 'b' => { 'c' => nil } } }, + { 'nested' => { 'reference' => nil } }] + ) end it 'resolves nested references with nested keys and nested reference targets' do result = task.process_references( - build_tree( - 'nested' => {'reference' => {'a' => nil, 'b' => {'c' => nil}}}, - 'not-a-reference' => nil - ), - build_tree( - 'nested' => {'reference' => :'resolved.nested'} - )) + build_tree( + 'nested' => { 'reference' => { 'a' => nil, 'b' => { 'c' => nil } } }, + 'not-a-reference' => nil + ), + build_tree( + 'nested' => { 'reference' => :'resolved.nested' } + ) + ) expect(result.map(&:to_hash)).to( - eq [{'nested' => {'reference' => {'a' => nil, 'b' => {'c' => nil}}}}, - {'resolved' => {'nested' => {'a' => nil, 'b' => {'c' => nil}}}}, - {'nested' => {'reference' => nil}}]) + eq [{ 'nested' => { 'reference' => { 'a' => nil, 'b' => { 'c' => nil } } } }, + { 'resolved' => { 'nested' => { 'a' => nil, 'b' => { 'c' => nil } } } }, + { 'nested' => { 'reference' => nil } }] + ) end it 'returns empty array when nothing to resolve' do result = task.process_references( - build_tree('not-a-reference' => nil), - build_tree('reference' => :resolved)) + build_tree('not-a-reference' => nil), + build_tree('reference' => :resolved) + ) expect(result.map(&:to_hash)).to(eq [{}, {}, {}]) end end diff --git a/spec/relative_keys_spec.rb b/spec/relative_keys_spec.rb index a8c6092f..bd566aa3 100644 --- a/spec/relative_keys_spec.rb +++ b/spec/relative_keys_spec.rb @@ -10,7 +10,6 @@ class RelativeKeysUser let(:relative_keys) { RelativeKeysUser.new } describe 'absolute_key' do - context 'default settings' do it 'works' do expect(relative_keys.absolute_key('.title', 'app/views/movies/show.html.slim', @@ -28,10 +27,11 @@ class RelativeKeysUser context 'relative key in controller' do it 'works' do key = relative_keys.absolute_key( - '.success', - 'app/controllers/users_controller.rb', - roots: %w(app/controllers), - calling_method: 'create') + '.success', + 'app/controllers/users_controller.rb', + roots: %w(app/controllers), + calling_method: 'create' + ) expect(key).to eq('users.create.success') end @@ -39,10 +39,11 @@ class RelativeKeysUser context 'multiple words in controller name' do it 'works' do key = relative_keys.absolute_key( - '.success', - 'app/controllers/admin_users_controller.rb', - roots: %w(app/controllers), - calling_method: 'create') + '.success', + 'app/controllers/admin_users_controller.rb', + roots: %w(app/controllers), + calling_method: 'create' + ) expect(key).to eq('admin_users.create.success') end @@ -51,10 +52,11 @@ class RelativeKeysUser context 'nested in module' do it 'works' do key = relative_keys.absolute_key( - '.success', - 'app/controllers/nested/users_controller.rb', - roots: %w(app/controllers), - calling_method: 'create') + '.success', + 'app/controllers/nested/users_controller.rb', + roots: %w(app/controllers), + calling_method: 'create' + ) expect(key).to eq('nested.users.create.success') end @@ -64,10 +66,11 @@ class RelativeKeysUser context 'relative key in mailer' do it 'works' do key = relative_keys.absolute_key( - '.subject', - 'app/mailers/user_mailer.rb', - roots: %w(app/mailers), - calling_method: 'welcome') + '.subject', + 'app/mailers/user_mailer.rb', + roots: %w(app/mailers), + calling_method: 'welcome' + ) expect(key).to eq('user_mailer.welcome.subject') end @@ -75,10 +78,11 @@ class RelativeKeysUser context 'multiple words in mailer name' do it 'works' do key = relative_keys.absolute_key( - '.subject', - 'app/mailers/admin_user_mailer.rb', - roots: %w(app/mailers), - calling_method: 'welcome') + '.subject', + 'app/mailers/admin_user_mailer.rb', + roots: %w(app/mailers), + calling_method: 'welcome' + ) expect(key).to eq('admin_user_mailer.welcome.subject') end @@ -87,10 +91,11 @@ class RelativeKeysUser context 'nested in module' do it 'works' do key = relative_keys.absolute_key( - '.subject', - 'app/mailers/nested/user_mailer.rb', - roots: %w(app/mailers), - calling_method: 'welcome') + '.subject', + 'app/mailers/nested/user_mailer.rb', + roots: %w(app/mailers), + calling_method: 'welcome' + ) expect(key).to eq('nested.user_mailer.welcome.subject') end diff --git a/spec/scanners/files/caching_file_finder_provider_spec.rb b/spec/scanners/files/caching_file_finder_provider_spec.rb index f4acb2f3..6f85ef37 100644 --- a/spec/scanners/files/caching_file_finder_provider_spec.rb +++ b/spec/scanners/files/caching_file_finder_provider_spec.rb @@ -7,13 +7,15 @@ it 'provides the same instance for the same arguments' do provider = I18n::Tasks::Scanners::Files::CachingFileFinderProvider.new expect(provider.get(only: ['./a'])).to( - be(provider.get(only: ['./a']))) + be(provider.get(only: ['./a'])) + ) end it 'provides different instances for different arguments' do provider = I18n::Tasks::Scanners::Files::CachingFileFinderProvider.new expect(provider.get(only: ['./a'])).to_not( - be(provider.get(only: ['./b']))) + be(provider.get(only: ['./b'])) + ) end end end diff --git a/spec/scanners/files/caching_file_finder_spec.rb b/spec/scanners/files/caching_file_finder_spec.rb index 9851347e..bb1347af 100644 --- a/spec/scanners/files/caching_file_finder_spec.rb +++ b/spec/scanners/files/caching_file_finder_spec.rb @@ -3,19 +3,19 @@ require 'i18n/tasks/scanners/files/caching_file_finder' RSpec.describe 'CachingFileFinder' do - let(:test_files) { + let(:test_files) do %w(a/a/a/a.txt a/a/a.txt a/a/b.txt a/b/a.txt a/b/b.txt a.txt) - } + end describe '#find_files' do it 'accesses the filesystem only once' do begin - TestCodebase.setup(test_files.inject({}) { |h, f| h[f] = ''; h }) - TestCodebase.in_test_app_dir { + TestCodebase.setup(test_files.each_with_object({}) { |f, h| h[f] = '' }) + TestCodebase.in_test_app_dir do finder = I18n::Tasks::Scanners::Files::CachingFileFinder.new expect(finder.find_files).to eq test_files.map { |f| File.join('.', f) } TestCodebase.teardown expect(finder.find_files).to eq test_files.map { |f| File.join('.', f) } - } + end ensure TestCodebase.teardown end @@ -25,13 +25,13 @@ describe '#traverse_files' do it 'accesses the filesystem only once' do begin - TestCodebase.setup(test_files.inject({}) { |h, f| h[f] = ''; h }) - TestCodebase.in_test_app_dir { + TestCodebase.setup(test_files.each_with_object({}) { |f, h| h[f] = '' }) + TestCodebase.in_test_app_dir do finder = I18n::Tasks::Scanners::Files::CachingFileFinder.new expect(finder.traverse_files { |f| f }).to eq test_files.map { |f| File.join('.', f) } TestCodebase.teardown expect(finder.traverse_files { |f| f }).to eq test_files.map { |f| File.join('.', f) } - } + end ensure TestCodebase.teardown end diff --git a/spec/scanners/files/file_finder_spec.rb b/spec/scanners/files/file_finder_spec.rb index 0199d4b9..cd4ea6ea 100644 --- a/spec/scanners/files/file_finder_spec.rb +++ b/spec/scanners/files/file_finder_spec.rb @@ -3,11 +3,11 @@ require 'i18n/tasks/scanners/files/file_finder' RSpec.describe 'FileFinder' do - let(:test_files) { + let(:test_files) do %w(a/a/a/a.txt a/a/a.txt a/a/b.txt a/b/a.txt a/b/b.txt a.txt) - } + end around do |ex| - TestCodebase.setup(test_files.inject({}) { |h, f| h[f] = ''; h }) + TestCodebase.setup(test_files.each_with_object({}) { |f, h| h[f] = '' }) TestCodebase.in_test_app_dir { ex.call } TestCodebase.teardown end @@ -25,13 +25,15 @@ it 'find only the files specified by the inclusion patterns' do finder = I18n::Tasks::Scanners::Files::FileFinder.new( - paths: %w(a), only: %w(a/a/**)) + paths: %w(a), only: %w(a/a/**) + ) expect(finder.find_files).to eq test_files.select { |f| f.start_with?('a/a/') } end it 'finds only the files not specified by the exclusion patterns' do finder = I18n::Tasks::Scanners::Files::FileFinder.new( - exclude: %w(./a/a/**)) + exclude: %w(./a/a/**) + ) expect(finder.find_files).to eq test_files.select { |f| !f.start_with?('a/a/') }.map { |f| File.join('.', f) } end end diff --git a/spec/scanners/pattern_mapper_spec.rb b/spec/scanners/pattern_mapper_spec.rb index 307dc4c2..afbe304b 100644 --- a/spec/scanners/pattern_mapper_spec.rb +++ b/spec/scanners/pattern_mapper_spec.rb @@ -4,27 +4,27 @@ RSpec.describe 'PatternMapper' do describe '#scan' do - let(:mock_file_path) { + let(:mock_file_path) do 'app/views/main/index.html' - } + end let(:mock_file_contents) { <<-ERB } <%= title %> <%= Spree.t 'x' %> <%= Spree.t 'invalid.' %> ERB - let(:expected_key_occurrences) { - [make_key_occurrences('main.index.title', [{path: mock_file_path, line_num: 1, line_pos: 7, pos: 6, - line: mock_file_contents.lines[0].chomp}]), - make_key_occurrences('spree.x', [{path: mock_file_path, line_num: 2, line_pos: 11, pos: 29, - line: mock_file_contents.lines[1].chomp}])] - } + let(:expected_key_occurrences) do + [make_key_occurrences('main.index.title', [{ path: mock_file_path, line_num: 1, line_pos: 7, pos: 6, + line: mock_file_contents.lines[0].chomp }]), + make_key_occurrences('spree.x', [{ path: mock_file_path, line_num: 2, line_pos: 11, pos: 29, + line: mock_file_contents.lines[1].chomp }])] + end it 'maps patterns to keys' do mapper = I18n::Tasks::Scanners::PatternMapper.new(config: { - relative_roots: ['app/views'], - patterns: [[/<%\s*=\s*title/, '.title'], - ['Spree\.t[( ]\s*%{key}', 'spree.%{key}']] - }) + relative_roots: ['app/views'], + patterns: [[/<%\s*=\s*title/, '.title'], + ['Spree\.t[( ]\s*%{key}', 'spree.%{key}']] + }) expect(mapper).to receive(:traverse_files) { [mapper.send(:scan_file, mock_file_path)] } expect(mapper).to receive(:read_file).with(mock_file_path).and_return(mock_file_contents) expect(mapper.keys).to eq expected_key_occurrences diff --git a/spec/scanners/scanner_multiplexer_spec.rb b/spec/scanners/scanner_multiplexer_spec.rb index 5021ea73..7205c2a1 100644 --- a/spec/scanners/scanner_multiplexer_spec.rb +++ b/spec/scanners/scanner_multiplexer_spec.rb @@ -4,21 +4,22 @@ RSpec.describe 'ScannerMultiplexer' do describe '#keys' do - let(:key_a) { make_key_occurrences 'key.a', [{path: 'a'}] } - let(:key_b_1) { make_key_occurrences 'key.b', [{path: 'b1'}] } - let(:key_b_2) { make_key_occurrences 'key.b', [{path: 'b2'}] } - let(:key_c) { make_key_occurrences 'key.c', [{path: 'c'}] } + let(:key_a) { make_key_occurrences 'key.a', [{ path: 'a' }] } + let(:key_b_1) { make_key_occurrences 'key.b', [{ path: 'b1' }] } + let(:key_b_2) { make_key_occurrences 'key.b', [{ path: 'b2' }] } + let(:key_c) { make_key_occurrences 'key.c', [{ path: 'c' }] } scanner_mock = Struct.new(:keys) let(:scanner_one) { scanner_mock.new([key_a, key_b_1]) } let(:scanner_two) { scanner_mock.new([key_b_2, key_c]) } - let(:expected_key_occurrences) { + let(:expected_key_occurrences) do [key_a, I18n::Tasks::Scanners::Results::KeyOccurrences.new( - key: 'key.b', occurrences: key_b_1.occurrences + key_b_2.occurrences), + key: 'key.b', occurrences: key_b_1.occurrences + key_b_2.occurrences + ), key_c] - } + end it 'returns the merged results' do scanner_multiplexer = I18n::Tasks::Scanners::ScannerMultiplexer.new(scanners: [scanner_one, scanner_two]) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cc33cc75..f29b5abc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,13 +5,13 @@ require 'simplecov' end -$: << File.expand_path('../lib', __FILE__) +$LOAD_PATH << File.expand_path('../lib', __FILE__) require 'i18n/tasks' require 'rake' require 'term/ansicolor' -Term::ANSIColor::coloring = false +Term::ANSIColor.coloring = false Dir['spec/support/**/*.rb'].each { |f| require "./#{f}" } diff --git a/spec/split_key_spec.rb b/spec/split_key_spec.rb index 56619367..d07b5aa8 100644 --- a/spec/split_key_spec.rb +++ b/spec/split_key_spec.rb @@ -12,23 +12,22 @@ ['a.#{b.c}', %w(a #{b.c})], ['a.#{b.c}.', %w(a #{b.c})], ['a.#{b.c}.d', %w(a #{b.c} d)], - ['a.#{b.c}.d.[e.f]', %w(a #{b.c} d [e.f])], - ].each do |(arg, ret)| + ['a.#{b.c}.d.[e.f]', %w(a #{b.c} d [e.f])]].each do |(arg, ret)| it "#{arg} is split into #{ret.inspect}" do - expect(split_key arg).to eq(ret) + expect(split_key(arg)).to eq(ret) end end it 'limits results to second argument' do - expect(split_key 'a.b.c', 1).to eq(['a.b.c']) - expect(split_key 'a.b.c', 2).to eq(['a', 'b.c']) - expect(split_key 'a.b.c.', 2).to eq(['a', 'b.c.']) - expect(split_key 'a.b.c.d.e.f', 4).to eq(['a', 'b', 'c', 'd.e.f']) + expect(split_key('a.b.c', 1)).to eq(['a.b.c']) + expect(split_key('a.b.c', 2)).to eq(['a', 'b.c']) + expect(split_key('a.b.c.', 2)).to eq(['a', 'b.c.']) + expect(split_key('a.b.c.d.e.f', 4)).to eq(['a', 'b', 'c', 'd.e.f']) end it 'last part' do - expect(last_key_part 'a.b.c').to eq('c') - expect(last_key_part 'a').to eq('a') - expect(last_key_part 'a.b.c.d').to eq('d') + expect(last_key_part('a.b.c')).to eq('c') + expect(last_key_part('a')).to eq('a') + expect(last_key_part('a.b.c.d')).to eq('d') end end diff --git a/spec/support/capture_std.rb b/spec/support/capture_std.rb index 987c22af..dfba9703 100644 --- a/spec/support/capture_std.rb +++ b/spec/support/capture_std.rb @@ -18,7 +18,8 @@ module CaptureStd include ActiveSupport::Testing::Stream if defined?(ActiveSupport::Testing::Stream) def capture_stderr - err, $stderr = $stderr, StringIO.new + err = $stderr + $stderr = StringIO.new yield $stderr.string ensure @@ -26,7 +27,8 @@ def capture_stderr end def capture_stdout - out, $stdout = $stdout, StringIO.new + out = $stdout + $stdout = StringIO.new yield $stdout.string ensure diff --git a/spec/support/fixtures.rb b/spec/support/fixtures.rb index ea77c980..0a911b4e 100644 --- a/spec/support/fixtures.rb +++ b/spec/support/fixtures.rb @@ -4,12 +4,10 @@ module FixturesSupport def fixtures_contents @fixtures_contents ||= begin fixtures_path = 'spec/fixtures' - Dir.glob("#{fixtures_path}/**/*").inject({}) { |h, path| + Dir.glob("#{fixtures_path}/**/*").inject({}) do |h, path| next h if File.directory?(path) - h[path[fixtures_path.length + 1..-1]] = Pathname.new(path).read - h - } + h.update(path[fixtures_path.length + 1..-1] => Pathname.new(path).read) + end end end end - diff --git a/spec/support/i18n_tasks_output_matcher.rb b/spec/support/i18n_tasks_output_matcher.rb index 696f0cd5..12a32552 100644 --- a/spec/support/i18n_tasks_output_matcher.rb +++ b/spec/support/i18n_tasks_output_matcher.rb @@ -7,18 +7,18 @@ def locale_re def extract_keys(actual) actual = Term::ANSIColor.uncolor(actual).split("\n").map(&:presence).compact actual = actual[3..-2] - actual = actual.map { |row| + actual = actual.map do |row| row[1..-1].gsub(/(?:\s+|^)\|(?:\s+|$)/, '|').gsub(/\s+/, ' ').strip.split(/\s*\|\s*/) - }.compact + end.compact return [] if actual.empty? locale_col = 0 key_col = 1 - actual.map { |row| + actual.map do |row| key = [row[locale_col], row[key_col]].map(&:presence).compact.join('.') key = key[0..-2] if key.end_with?('.:') key = key.sub(/\((?:ref|resolved ref|ref key)\) /, '') key - }.compact + end.compact end match do |actual| @@ -32,8 +32,8 @@ def extract_keys(actual) <<-MSG.strip Expected #{e}, but had #{a}. Diff: -missing: #{e-a} -extra: #{a-e} +missing: #{e - a} +extra: #{a - e} MSG end end diff --git a/spec/support/keys_and_occurrences.rb b/spec/support/keys_and_occurrences.rb index 94402915..4526bb49 100644 --- a/spec/support/keys_and_occurrences.rb +++ b/spec/support/keys_and_occurrences.rb @@ -1,29 +1,31 @@ -# frozen_string_literal: true -module KeysAndOccurrences - def make_occurrence(path: '', line: '', pos: 1, line_pos: 1, line_num: 1, raw_key: nil) - ::I18n::Tasks::Scanners::Results::Occurrence.new( - path: path, line: line, pos: pos, line_pos: line_pos, line_num: line_num, raw_key: raw_key) - end - - def make_occurrences(occurrences) - occurrences.map { |attr| make_occurrence(attr) } - end - - def make_key_occurrences(key, occurrences) - ::I18n::Tasks::Scanners::Results::KeyOccurrences.new(key: key, occurrences: make_occurrences(occurrences)) - end - - # adjust position to account for \r on Windows - def adjust_occurrences(data) - if Gem.win_platform? - data = data.dup - data[:occurrences].map! { |occ| adjust_occurrence occ } - end - data - end - - # adjust position to account for \r on Windows - def adjust_occurrence(occurrence) - occurrence.dup.tap { |o| o.instance_variable_set(:@pos, o.pos + o.line_num - 1) } - end -end +# frozen_string_literal: true +module KeysAndOccurrences + # rubocop:disable Metrics/ParameterLists + def make_occurrence(path: '', line: '', pos: 1, line_pos: 1, line_num: 1, raw_key: nil) + ::I18n::Tasks::Scanners::Results::Occurrence.new( + path: path, line: line, pos: pos, line_pos: line_pos, line_num: line_num, raw_key: raw_key + ) + end + + def make_occurrences(occurrences) + occurrences.map { |attr| make_occurrence(attr) } + end + + def make_key_occurrences(key, occurrences) + ::I18n::Tasks::Scanners::Results::KeyOccurrences.new(key: key, occurrences: make_occurrences(occurrences)) + end + + # adjust position to account for \r on Windows + def adjust_occurrences(data) + if Gem.win_platform? + data = data.dup + data[:occurrences].map! { |occ| adjust_occurrence occ } + end + data + end + + # adjust position to account for \r on Windows + def adjust_occurrence(occurrence) + occurrence.dup.tap { |o| o.instance_variable_set(:@pos, o.pos + o.line_num - 1) } + end +end diff --git a/spec/support/test_codebase.rb b/spec/support/test_codebase.rb index c5b74898..2e6a2d40 100644 --- a/spec/support/test_codebase.rb +++ b/spec/support/test_codebase.rb @@ -6,78 +6,90 @@ require 'i18n/tasks/cli' module TestCodebase - include CaptureStd - extend self AT = 'tmp/test_codebase' - def i18n_task(*args) - in_test_app_dir do - ::I18n::Tasks::BaseTask.new(*args) + class << self + include CaptureStd + + def i18n_task(*args) + in_test_app_dir do + ::I18n::Tasks::BaseTask.new(*args) + end end - end - def run_cmd(name, *args) - capture_stdout { capture_stderr { in_test_app_dir { - run_cli(name, *args) - } } } - end + def run_cmd(name, *args) + capture_stdout do + capture_stderr do + in_test_app_dir do + run_cli(name, *args) + end + end + end + end - def run_cmd_capture_stdout_and_result(name, *args) - result = nil - out = capture_stdout { capture_stderr { in_test_app_dir { - result = run_cli(name, *args) - } } } - [out, result] - end + def run_cmd_capture_stdout_and_result(name, *args) + result = nil + out = capture_stdout do + capture_stderr do + in_test_app_dir do + result = run_cli(name, *args) + end + end + end + [out, result] + end - def run_cmd_capture_stderr(name, *args) - capture_stderr { capture_stdout { in_test_app_dir { - run_cli(name, *args) - } } } - end + def run_cmd_capture_stderr(name, *args) + capture_stderr do + capture_stdout do + in_test_app_dir do + run_cli(name, *args) + end + end + end + end - def run_cli(name, *args) - i18n_cli.run([name, *args]) - end + def run_cli(name, *args) + i18n_cli.run([name, *args]) + end - def i18n_cli - in_test_app_dir { ::I18n::Tasks::CLI.new } - end + def i18n_cli + in_test_app_dir { ::I18n::Tasks::CLI.new } + end - def setup(files = {}) - FileUtils.mkdir_p AT - in_test_app_dir do - files.each do |path, content| - FileUtils.mkdir_p File.dirname(path) - File.open(path, 'w') { |f| f.write(content) } + def setup(files = {}) + FileUtils.mkdir_p AT + in_test_app_dir do + files.each do |path, content| + FileUtils.mkdir_p File.dirname(path) + File.open(path, 'w') { |f| f.write(content) } + end end end - end - def teardown - FileUtils.rm_rf AT - end + def teardown + FileUtils.rm_rf AT + end - def rake_result(task, *args) - in_test_app_dir { - rake_task = Rake::Task[task] - rake_task.reenable - capture_stdout { rake_task.invoke(*args) } - } - end + def rake_result(task, *args) + in_test_app_dir do + rake_task = Rake::Task[task] + rake_task.reenable + capture_stdout { rake_task.invoke(*args) } + end + end - def in_test_app_dir - return yield if @in_dir - begin - pwd = Dir.pwd - Dir.chdir AT - @in_dir = true - yield - ensure - Dir.chdir pwd - @in_dir = false + def in_test_app_dir + return yield if @in_dir + begin + pwd = Dir.pwd + Dir.chdir AT + @in_dir = true + yield + ensure + Dir.chdir pwd + @in_dir = false + end end end end - - diff --git a/spec/support/trees.rb b/spec/support/trees.rb index 2fb006ac..385d3a8a 100644 --- a/spec/support/trees.rb +++ b/spec/support/trees.rb @@ -10,7 +10,7 @@ def build_tree(hash) end def build_node(attr = {}) - raise 'invalid node (more than 1 root)' if attr.size > 1 + fail 'invalid node (more than 1 root)' if attr.size > 1 key, value = attr.first I18n::Tasks::Data::Tree::Node.from_key_value(key, value) end diff --git a/spec/used_keys_spec.rb b/spec/used_keys_spec.rb index 1881e254..5854557e 100644 --- a/spec/used_keys_spec.rb +++ b/spec/used_keys_spec.rb @@ -13,7 +13,7 @@ end around do |ex| - task.config[:search] = {paths: [file_name]} + task.config[:search] = { paths: [file_name] } TestCodebase.setup(file_name => file_content) TestCodebase.in_test_app_dir { ex.run } TestCodebase.teardown @@ -24,18 +24,20 @@ leaves = used.leaves.to_a expect(leaves.size).to eq 2 expect_node_key_data( - leaves[0], - 'a', - occurrences: make_occurrences( - [{path: 'a.html.slim', pos: 6, line_num: 1, line_pos: 7, line: "div = t 'a'", raw_key: 'a'}, - {path: 'a.html.slim', pos: 18, line_num: 2, line_pos: 7, line: " p = t 'a'", raw_key: 'a'}]) + leaves[0], + 'a', + occurrences: make_occurrences( + [{ path: 'a.html.slim', pos: 6, line_num: 1, line_pos: 7, line: "div = t 'a'", raw_key: 'a' }, + { path: 'a.html.slim', pos: 18, line_num: 2, line_pos: 7, line: " p = t 'a'", raw_key: 'a' }] + ) ) expect_node_key_data( - leaves[1], - 'b', - occurrences: make_occurrences( - [{path: 'a.html.slim', pos: 29, line_num: 3, line_pos: 6, line: "h1 = t 'b'", raw_key: 'b'}]) + leaves[1], + 'b', + occurrences: make_occurrences( + [{ path: 'a.html.slim', pos: 29, line_num: 3, line_pos: 6, line: "h1 = t 'b'", raw_key: 'b' }] + ) ) end @@ -43,10 +45,11 @@ used_keys = task.used_tree(key_filter: 'b*') expect(used_keys.size).to eq 1 expect_node_key_data( - used_keys.leaves.first, - 'b', - occurrences: make_occurrences( - [{path: 'a.html.slim', pos: 29, line_num: 3, line_pos: 6, line: "h1 = t 'b'", raw_key: 'b'}]) + used_keys.leaves.first, + 'b', + occurrences: make_occurrences( + [{ path: 'a.html.slim', pos: 29, line_num: 3, line_pos: 6, line: "h1 = t 'b'", raw_key: 'b' }] + ) ) end @@ -64,13 +67,14 @@ used_keys = task.used_tree expect(used_keys.size).to eq 1 expect_node_key_data( - used_keys.leaves.first, - 'a', - occurrences: make_occurrences( - [{path: 'a.html.haml', pos: 15, line_num: 1, line_pos: 16, - line: "#first{ title: t('a') }", raw_key: 'a'}, - {path: 'a.html.haml', pos: 40, line_num: 2, line_pos: 17, - line: ".second{ title: t('a') }", raw_key: 'a'}]) + used_keys.leaves.first, + 'a', + occurrences: make_occurrences( + [{ path: 'a.html.haml', pos: 15, line_num: 1, line_pos: 16, + line: "#first{ title: t('a') }", raw_key: 'a' }, + { path: 'a.html.haml', pos: 40, line_num: 2, line_pos: 17, + line: ".second{ title: t('a') }", raw_key: 'a' }] + ) ) end end diff --git a/templates/rspec/i18n_spec.rb b/templates/rspec/i18n_spec.rb index e7126127..b2b2e617 100644 --- a/templates/rspec/i18n_spec.rb +++ b/templates/rspec/i18n_spec.rb @@ -8,11 +8,11 @@ it 'does not have missing keys' do expect(missing_keys).to be_empty, - "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" + "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them" end it 'does not have unused keys' do expect(unused_keys).to be_empty, - "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" + "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them" end end