i18n-tasks 0.8.7 → 0.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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