i18n-tasks 0.8.7 → 0.9.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -5
  3. data/CHANGES.md +3 -3
  4. data/Gemfile +1 -1
  5. data/README.md +4 -4
  6. data/bin/i18n-tasks +0 -1
  7. data/config/locales/en.yml +103 -102
  8. data/config/locales/ru.yml +1 -1
  9. data/i18n-tasks.gemspec +1 -2
  10. data/lib/i18n/tasks.rb +0 -1
  11. data/lib/i18n/tasks/base_task.rb +0 -1
  12. data/lib/i18n/tasks/cli.rb +1 -1
  13. data/lib/i18n/tasks/command/commander.rb +0 -1
  14. data/lib/i18n/tasks/command/commands/missing.rb +3 -15
  15. data/lib/i18n/tasks/command/commands/usages.rb +5 -6
  16. data/lib/i18n/tasks/command/option_parsers/locale.rb +1 -8
  17. data/lib/i18n/tasks/command_error.rb +5 -1
  18. data/lib/i18n/tasks/commands.rb +0 -1
  19. data/lib/i18n/tasks/configuration.rb +1 -9
  20. data/lib/i18n/tasks/console_context.rb +2 -2
  21. data/lib/i18n/tasks/data.rb +0 -1
  22. data/lib/i18n/tasks/data/adapter/json_adapter.rb +0 -1
  23. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +0 -1
  24. data/lib/i18n/tasks/data/file_formats.rb +0 -1
  25. data/lib/i18n/tasks/data/file_system.rb +0 -1
  26. data/lib/i18n/tasks/data/file_system_base.rb +0 -1
  27. data/lib/i18n/tasks/data/router/conservative_router.rb +0 -1
  28. data/lib/i18n/tasks/data/router/pattern_router.rb +0 -1
  29. data/lib/i18n/tasks/data/tree/node.rb +6 -7
  30. data/lib/i18n/tasks/data/tree/nodes.rb +0 -1
  31. data/lib/i18n/tasks/data/tree/siblings.rb +29 -10
  32. data/lib/i18n/tasks/data/tree/traversal.rb +0 -3
  33. data/lib/i18n/tasks/google_translation.rb +0 -1
  34. data/lib/i18n/tasks/ignore_keys.rb +0 -1
  35. data/lib/i18n/tasks/key_pattern_matching.rb +0 -1
  36. data/lib/i18n/tasks/logging.rb +0 -1
  37. data/lib/i18n/tasks/missing_keys.rb +0 -1
  38. data/lib/i18n/tasks/plural_keys.rb +0 -1
  39. data/lib/i18n/tasks/reports/base.rb +1 -2
  40. data/lib/i18n/tasks/reports/spreadsheet.rb +0 -1
  41. data/lib/i18n/tasks/reports/terminal.rb +41 -17
  42. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +32 -0
  43. data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +24 -0
  44. data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +27 -0
  45. data/lib/i18n/tasks/scanners/files/file_finder.rb +61 -0
  46. data/lib/i18n/tasks/scanners/files/file_reader.rb +18 -0
  47. data/lib/i18n/tasks/scanners/key_occurrences.rb +35 -0
  48. data/lib/i18n/tasks/scanners/occurence.rb +50 -0
  49. data/lib/i18n/tasks/scanners/pattern_scanner.rb +97 -38
  50. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +2 -3
  51. data/lib/i18n/tasks/scanners/relative_keys.rb +3 -4
  52. data/lib/i18n/tasks/scanners/scanner.rb +15 -0
  53. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +43 -0
  54. data/lib/i18n/tasks/unused_keys.rb +4 -5
  55. data/lib/i18n/tasks/used_keys.rb +76 -23
  56. data/lib/i18n/tasks/version.rb +1 -2
  57. data/spec/conservative_router_spec.rb +0 -1
  58. data/spec/file_system_data_spec.rb +0 -1
  59. data/spec/fixtures/app/controllers/events_controller.rb +1 -2
  60. data/spec/google_translate_spec.rb +0 -1
  61. data/spec/i18n_tasks_spec.rb +4 -15
  62. data/spec/key_pattern_matching_spec.rb +0 -1
  63. data/spec/locale_tree/siblings_spec.rb +0 -1
  64. data/spec/pattern_scanner_spec.rb +34 -36
  65. data/spec/plural_keys_spec.rb +0 -1
  66. data/spec/readme_spec.rb +0 -1
  67. data/spec/relative_keys_spec.rb +15 -10
  68. data/spec/scanners/files/caching_file_finder_provider_spec.rb +18 -0
  69. data/spec/scanners/files/caching_file_finder_spec.rb +39 -0
  70. data/spec/scanners/files/caching_file_reader_spec.rb +18 -0
  71. data/spec/scanners/files/file_finder_spec.rb +52 -0
  72. data/spec/scanners/files/file_reader_spec.rb +15 -0
  73. data/spec/scanners/scanner_multiplexer_spec.rb +26 -0
  74. data/spec/spec_helper.rb +1 -1
  75. data/spec/support/capture_std.rb +0 -1
  76. data/spec/support/fixtures.rb +0 -1
  77. data/spec/support/i18n_tasks_output_matcher.rb +0 -1
  78. data/spec/support/key_pattern_matcher.rb +0 -1
  79. data/spec/support/keys_and_occurrences.rb +27 -0
  80. data/spec/support/test_codebase.rb +0 -1
  81. data/spec/support/trees.rb +1 -7
  82. data/spec/used_keys_spec.rb +15 -16
  83. data/templates/config/i18n-tasks.yml +9 -2
  84. metadata +29 -9
  85. data/lib/i18n/tasks/scanners/base_scanner.rb +0 -149
  86. data/spec/commands/missing_commands_spec.rb +0 -23
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/command/dsl'
3
2
  require 'i18n/tasks/command/collection'
4
3
  require 'i18n/tasks/command/commands/health'
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  module I18n::Tasks::Configuration
3
2
  # i18n-tasks config (defaults + config/i18n-tasks.yml)
4
3
  # @return [Hash{String => String,Hash,Array}]
@@ -53,13 +52,6 @@ module I18n::Tasks::Configuration
53
52
  end
54
53
  end
55
54
 
56
- def search_config
57
- @config_sections[:search] ||= {
58
- scanner: scanner.class.name,
59
- config: scanner.config
60
- }
61
- end
62
-
63
55
  # @return [Array<String>] all available locales, base_locale is always first
64
56
  def locales
65
57
  @config_sections[:locales] ||= data.locales
@@ -87,7 +79,7 @@ module I18n::Tasks::Configuration
87
79
  internal_locale
88
80
  locales
89
81
  data_config
90
- search_config
82
+ @config_sections[:search] ||= search_config
91
83
  translation_config
92
84
  IGNORE_TYPES.each do |ignore_type|
93
85
  ignore_config ignore_type
@@ -36,8 +36,8 @@ module I18n::Tasks
36
36
  green(bold "i18n-tasks IRB Quick Start guide") + "\n" + <<-TEXT
37
37
  #{yellow 'Data as trees'}
38
38
  tree(locale)
39
- used_tree(source_occurrences: false, key_filter: nil)
40
- unused_tree(locale)
39
+ used_tree(key_filter: nil, strict: nil)
40
+ unused_tree(locale: base_locale, strict: nil)
41
41
  build_tree('es' => {'hello' => 'Hola'})
42
42
 
43
43
  #{yellow 'Traversal'}
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/data/file_system'
3
2
 
4
3
  module I18n::Tasks
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'json'
3
2
 
4
3
  module I18n::Tasks
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'yaml'
3
2
  module I18n::Tasks
4
3
  module Data
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'fileutils'
3
2
 
4
3
  module I18n
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/data/file_system_base'
3
2
  require 'i18n/tasks/data/adapter/json_adapter'
4
3
  require 'i18n/tasks/data/adapter/yaml_adapter'
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/data/tree/node'
3
2
  require 'i18n/tasks/data/router/pattern_router'
4
3
  require 'i18n/tasks/data/router/conservative_router'
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/data/router/pattern_router'
3
2
 
4
3
  module I18n::Tasks
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/key_pattern_matching'
3
2
  require 'i18n/tasks/data/tree/node'
4
3
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
 
3
2
  require 'i18n/tasks/data/tree/traversal'
4
3
  require 'i18n/tasks/data/tree/siblings'
@@ -10,13 +9,13 @@ module I18n::Tasks::Data::Tree
10
9
  attr_accessor :value
11
10
  attr_reader :key, :children, :parent
12
11
 
13
- def initialize(opts = {})
14
- @key = opts[:key]
12
+ def initialize(key:, value: nil, data: nil, parent: nil, children: nil)
13
+ @key = key
15
14
  @key = @key.to_s.freeze if @key
16
- @value = opts[:value]
17
- @data = opts[:data]
18
- @parent = opts[:parent]
19
- self.children = (opts[:children] if opts[:children])
15
+ @value = value
16
+ @data = data
17
+ @parent = parent
18
+ self.children = (children if children)
20
19
  end
21
20
 
22
21
  def attributes
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
 
3
2
  require 'i18n/tasks/data/tree/traversal'
4
3
  module I18n::Tasks::Data::Tree
@@ -1,8 +1,8 @@
1
- # coding: utf-8
2
-
3
1
  require 'set'
4
2
  require 'i18n/tasks/split_key'
5
3
  require 'i18n/tasks/data/tree/nodes'
4
+ require 'i18n/tasks/data/tree/node'
5
+
6
6
  module I18n::Tasks::Data::Tree
7
7
  # Siblings represents a subtree sharing a common parent
8
8
  # in case of an empty parent (nil) it represents a forest
@@ -44,7 +44,7 @@ module I18n::Tasks::Data::Tree
44
44
  end
45
45
 
46
46
  def replace_node!(node, new_node)
47
- @list[@list.index(node)] = new_node
47
+ @list[@list.index(node)] = new_node
48
48
  key_to_node[new_node.key] = new_node
49
49
  end
50
50
 
@@ -64,7 +64,7 @@ module I18n::Tasks::Data::Tree
64
64
  def set(full_key, node)
65
65
  raise 'value should be a I18n::Tasks::Data::Tree::Node' unless node.is_a?(Node)
66
66
  key_part, rest = split_key(full_key, 2)
67
- child = key_to_node[key_part]
67
+ child = key_to_node[key_part]
68
68
 
69
69
  if rest
70
70
  unless child
@@ -202,11 +202,23 @@ module I18n::Tasks::Data::Tree
202
202
  forest
203
203
  end
204
204
 
205
+ # @param key_occurrences [I18n::Tasks::Scanners::KeyOccurrences]
206
+ # @return [Siblings]
207
+ def from_key_occurrences(key_occurrences)
208
+ build_forest do |forest|
209
+ key_occurrences.each do |key_occurrence|
210
+ forest[key_occurrence.key] = ::I18n::Tasks::Data::Tree::Node.new(
211
+ key: split_key(key_occurrence.key).last,
212
+ data: {occurrences: key_occurrence.occurrences})
213
+ end
214
+ end
215
+ end
216
+
205
217
  def from_key_attr(key_attrs, opts = {}, &block)
206
218
  build_forest(opts) { |forest|
207
219
  key_attrs.each { |(full_key, attr)|
208
220
  raise "Invalid key #{full_key.inspect}" if full_key.end_with?('.')
209
- node = Node.new(attr.merge(key: split_key(full_key).last))
221
+ node = ::I18n::Tasks::Data::Tree::Node.new(attr.merge(key: split_key(full_key).last))
210
222
  block.call(full_key, node) if block
211
223
  forest[full_key] = node
212
224
  }
@@ -216,7 +228,7 @@ module I18n::Tasks::Data::Tree
216
228
  def from_key_names(keys, opts = {}, &block)
217
229
  build_forest(opts) { |forest|
218
230
  keys.each { |full_key|
219
- node = Node.new(key: split_key(full_key).last)
231
+ node = ::I18n::Tasks::Data::Tree::Node.new(key: split_key(full_key).last)
220
232
  block.call(full_key, node) if block
221
233
  forest[full_key] = node
222
234
  }
@@ -238,16 +250,23 @@ module I18n::Tasks::Data::Tree
238
250
  def from_flat_pairs(pairs)
239
251
  Siblings.new.tap do |siblings|
240
252
  pairs.each { |full_key, value|
241
- siblings[full_key] = Node.new(key: split_key(full_key).last, value: value)
253
+ siblings[full_key] = ::I18n::Tasks::Data::Tree::Node.new(key: split_key(full_key).last, value: value)
242
254
  }
243
255
  end
244
256
  end
245
257
 
246
258
  private
247
259
  def parse_parent_opt!(opts)
248
- opts[:parent] = Node.new(key: opts[:parent_key]) if opts[:parent_key]
249
- opts[:parent] = Node.new(opts[:parent_attr]) if opts[:parent_attr]
250
- opts[:parent] = Node.new(key: opts[:parent_locale], data: {locale: opts[:parent_locale]}) if opts[:parent_locale]
260
+ if opts[:parent_key]
261
+ opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new(key: opts[:parent_key])
262
+ end
263
+ if opts[:parent_attr]
264
+ opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new(opts[:parent_attr])
265
+ end
266
+ if opts[:parent_locale]
267
+ opts[:parent] = ::I18n::Tasks::Data::Tree::Node.new(
268
+ key: opts[:parent_locale], data: {locale: opts[:parent_locale]})
269
+ end
251
270
  end
252
271
  end
253
272
  end
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  module I18n::Tasks
3
2
  module Data::Tree
4
3
  # Any Enumerable that yields nodes can mix in this module
@@ -147,12 +146,10 @@ module I18n::Tasks
147
146
  value_proc ||= proc { |node|
148
147
  node_value = node.value
149
148
  human_key = ActiveSupport::Inflector.humanize(node.key.to_s)
150
- full_key = node.full_key
151
149
  StringInterpolation.interpolate_soft(
152
150
  val_pattern,
153
151
  value: node_value,
154
152
  human_key: human_key,
155
- key: full_key,
156
153
  value_or_human_key: node_value.presence || human_key
157
154
  )
158
155
  }
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'easy_translate'
3
2
  require 'i18n/tasks/html_keys'
4
3
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  module I18n::Tasks::IgnoreKeys
3
2
  # whether to ignore the key
4
3
  # will also apply global ignore rules
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  module I18n::Tasks::KeyPatternMatching
3
2
  extend self
4
3
  MATCH_NOTHING = /\z\A/
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  module I18n::Tasks::Logging
3
2
  extend self
4
3
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'set'
3
2
  module I18n::Tasks
4
3
  module MissingKeys
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'set'
3
2
  module I18n::Tasks::PluralKeys
4
3
  PLURAL_KEY_SUFFIXES = Set.new %w(zero one two few many other)
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  module I18n::Tasks::Reports
3
2
  class Base
4
3
  include I18n::Tasks::Logging
@@ -31,7 +30,7 @@ module I18n::Tasks::Reports
31
30
  def used_title(used_tree)
32
31
  leaves = used_tree.leaves.to_a
33
32
  filter = used_tree.first.root.data[:key_filter]
34
- used_n = leaves.map { |node| node.data[:source_occurrences].size }.reduce(:+).to_i
33
+ used_n = leaves.map { |node| node.data[:occurrences].size }.reduce(:+).to_i
35
34
  "#{leaves.length} key#{'s' if leaves.size != 1}#{" matching '#{filter}'" if filter}#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n > 0}"
36
35
  end
37
36
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/reports/base'
3
2
  require 'fileutils'
4
3
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require 'i18n/tasks/reports/base'
3
2
  require 'terminal-table'
4
3
  module I18n
@@ -29,7 +28,7 @@ module I18n
29
28
  {missing_used: red(glyph), missing_diff: yellow(glyph)}[type]
30
29
  end
31
30
 
32
- def used_keys(used_tree = task.used_tree(source_occurrences: true))
31
+ def used_keys(used_tree = task.used_tree)
33
32
  print_title used_title(used_tree)
34
33
  keys_nodes = used_tree.keys.to_a
35
34
  if keys_nodes.present?
@@ -66,11 +65,11 @@ module I18n
66
65
  end
67
66
 
68
67
  def forest_stats(forest, stats = task.forest_stats(forest))
69
- text = if stats[:locale_count] == 1
70
- I18n.t('i18n_tasks.data_stats.text_single_locale', stats)
71
- else
72
- I18n.t('i18n_tasks.data_stats.text', stats)
73
- end
68
+ text = if stats[:locale_count] == 1
69
+ I18n.t('i18n_tasks.data_stats.text_single_locale', stats)
70
+ else
71
+ I18n.t('i18n_tasks.data_stats.text', stats)
72
+ end
74
73
  title = bold(I18n.t('i18n_tasks.data_stats.title', stats.slice(:locales)))
75
74
  print_info "#{cyan title} #{cyan text}"
76
75
  end
@@ -86,7 +85,7 @@ module I18n
86
85
  end
87
86
 
88
87
  def print_occurrences(node, full_key = node.full_key)
89
- occurrences = node.data[:source_occurrences]
88
+ occurrences = node.data[:occurrences]
90
89
  puts "#{bold "#{full_key}"} #{green(occurrences.size.to_s) if occurrences.size > 1}"
91
90
  occurrences.each do |occurrence|
92
91
  puts " #{key_occurrence full_key, occurrence}"
@@ -132,21 +131,46 @@ module I18n
132
131
  puts ::Terminal::Table.new(opts, &block)
133
132
  end
134
133
 
135
- def key_occurrence(full_key, info)
136
- location = green "#{info[:src_path]}:#{info[:line_num]}"
137
- source = highlight_key(full_key, info[:line], info[:line_pos]..-1).strip
134
+ def key_occurrence(full_key, occurrence)
135
+ location = green "#{occurrence.path}:#{occurrence.line_num}"
136
+ source = highlight_key(full_key, occurrence.line, occurrence.line_pos..-1).strip
138
137
  "#{location} #{source}"
139
138
  end
140
139
 
140
+ def first_occurrence(leaf)
141
+ # @type [I18n::Tasks::Scanners::KeyOccurrences]
142
+ occurrences = leaf[:data][:occurrences]
143
+ # @type [I18n::Tasks::Scanners::Occurrence]
144
+ first = occurrences.first
145
+ [green("#{first.path}:#{first.line_num}"),
146
+ ("(#{I18n.t 'i18n_tasks.common.n_more', count: occurrences.length - 1})" if occurrences.length > 1)
147
+ ].compact.join(' ')
148
+ end
149
+
141
150
  def highlight_key(full_key, line, range = (0..-1))
142
- line.dup.tap { |s| s[range] = s[range].sub(full_key) { |m| underline m } }
151
+ line.dup.tap { |s|
152
+ s[range] = s[range].sub(full_key) { |m|
153
+ highlight_string m
154
+ }
155
+ }
143
156
  end
144
157
 
145
- def first_occurrence(leaf)
146
- usages = leaf[:data][:source_occurrences]
147
- first = usages.first
148
- [green("#{first[:src_path]}:#{first[:line_num]}"),
149
- ("(#{I18n.t 'i18n_tasks.common.n_more', count: usages.length - 1})" if usages.length > 1)].compact.join(' ')
158
+ module HighlightUnderline
159
+ def highlight_string(s)
160
+ underline s
161
+ end
162
+ end
163
+
164
+ module HighlightOther
165
+ def highlight_string(s)
166
+ yellow s
167
+ end
168
+ end
169
+
170
+ if Gem.win_platform?
171
+ include HighlightOther
172
+ else
173
+ include HighlightUnderline
150
174
  end
151
175
  end
152
176
  end
@@ -0,0 +1,32 @@
1
+ require 'i18n/tasks/scanners/files/file_finder'
2
+ module I18n::Tasks::Scanners::Files
3
+ # Finds the files in the specified search paths with support for exclusion / inclusion patterns.
4
+ # Wraps a {FileFinder} and caches the results.
5
+ #
6
+ # @note This class is thread-safe. All methods are cached.
7
+ # @since 0.9.0
8
+ class CachingFileFinder < FileFinder
9
+ # @param (see FileFinder#initialize)
10
+ def initialize(**args)
11
+ super
12
+ @mutex = Mutex.new
13
+ @cached_paths = nil
14
+ end
15
+
16
+ # Traverse the paths and yield the matching ones.
17
+ #
18
+ # @note This method is cached, it will only access the filesystem on the first invocation.
19
+ # @param (see FileFinder#traverse_files)
20
+ # @yieldparam (see FileFinder#traverse_files)
21
+ # @return (see FileFinder#traverse_files)
22
+ def traverse_files
23
+ super
24
+ end
25
+
26
+ # @note This method is cached, it will only access the filesystem on the first invocation.
27
+ # @return (see FileFinder#find_files)
28
+ def find_files
29
+ @cached_paths || @mutex.synchronize { @cached_paths ||= super }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ require 'i18n/tasks/scanners/files/caching_file_finder'
2
+
3
+ module I18n::Tasks::Scanners::Files
4
+ # Finds the files and provides their contents.
5
+ #
6
+ # @note This class is thread-safe. All methods are cached.
7
+ # @since 0.9.0
8
+ class CachingFileFinderProvider
9
+ def initialize
10
+ @cache = {}
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ # Initialize a {CachingFileFinder} or get one from cache based on the constructor arguments.
15
+ #
16
+ # @param (see FileFinder#initialize)
17
+ # @return [CachingFileFinder]
18
+ def get(**file_finder_args)
19
+ @cache[file_finder_args] || @mutex.synchronize do
20
+ @cache[file_finder_args] ||= CachingFileFinder.new(**file_finder_args)
21
+ end
22
+ end
23
+ end
24
+ end