i18n-tasks 0.2.20 → 0.2.21

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab1b5fe079fcb795dc047f1e0e4f3d777b83c2ef
4
- data.tar.gz: 23b4a9f9e87b2f0ed8ac2c8c8a2b1992edc7b90d
3
+ metadata.gz: 3d02f77ecd3ceb644a2a5a86d1b5dc622880e170
4
+ data.tar.gz: e859f73a4891034d5e1e95b7b5938aa7d3dace73
5
5
  SHA512:
6
- metadata.gz: 4100f55d87da5b5131c3adf1794ae10d2fb4b1de691966ff6b24129bf660fca3035a857d841707ceb8946214926646b28aad8f0b2154b77b294a98422b533cd3
7
- data.tar.gz: 335f197c93d93fc41c27600815a74a964851778050d1d699de15be38998f17a3f5e3354dddcb52708ec50151a5a058cab41fd232de814c6b47e18ddfb6768595
6
+ metadata.gz: 4d821eab3b6e9f54870ccaa4c76869cea3ca79dc7eebdd62566c2464997bcd08485826b98a2d3d83f27d4ee8c609f502db9f1f5c528f1f1db133894e48f7835d
7
+ data.tar.gz: 44be3b83606e217455633dbb806e972bac6071a238c5f9f74b499a3fc883199d2edb22804dc160e8c5f3708e0f22c122680a6ea2bbe007d15f15f72873f1986f
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v0.2.21
2
+
3
+ * `rake i18n:usages[pattern]`
4
+ * performance regression fixes
5
+
1
6
  ## v0.2.20
2
7
 
3
8
  * `rake i18n:usages` report
data/README.md CHANGED
@@ -10,7 +10,7 @@ Tasks to manage translations in ruby applications using I18n.
10
10
  Simply add to Gemfile:
11
11
 
12
12
  ```ruby
13
- gem 'i18n-tasks', '~> 0.2.20'
13
+ gem 'i18n-tasks', '~> 0.2.21'
14
14
  ```
15
15
 
16
16
  If not using Rails, require the tasks in Rakefile:
@@ -144,6 +144,10 @@ Inspect all the usages with:
144
144
 
145
145
  ```bash
146
146
  rake i18n:usages
147
+ # Filter by a key pattern
148
+ rake i18n:usages[auth.*]
149
+ # Because commas are not allowed inside rake arguments, + is used here instead
150
+ rake i18n:usages['{number+currency}.format.*']
147
151
  ```
148
152
 
149
153
  ![i18n-screenshot](https://raw.github.com/glebm/i18n-tasks/master/doc/img/i18n-usages.png "rake i18n:usages output screenshot")
Binary file
@@ -9,7 +9,7 @@ module I18n
9
9
  elsif key_or_attr.is_a?(Hash)
10
10
  key_or_attr.merge(own_attr)
11
11
  else
12
- own_attr.merge(key: key_or_attr)
12
+ (own_attr || {}).merge(key: key_or_attr)
13
13
  end
14
14
 
15
15
  @own_attr[:key] = @own_attr[:key].to_s
@@ -4,7 +4,7 @@ module I18n
4
4
  class KeyGroup
5
5
  attr_reader :keys, :attr, :key_names
6
6
 
7
- delegate :size, :length, :each, :[], to: :keys
7
+ delegate :size, :length, :each, :[], :blank?, to: :keys
8
8
  include Enumerable
9
9
 
10
10
  def initialize(keys, attr = {})
@@ -27,7 +27,9 @@ module I18n::Tasks::Reports
27
27
  end
28
28
 
29
29
  def used_title(keys)
30
- "#{keys.length} keys used #{keys.map { |k| k[:usages].size }.reduce(:+)} times"
30
+ filter = keys.attr[:key_filter]
31
+ used_n = keys.map { |k| k[:usages].size }.reduce(:+).to_i
32
+ "#{keys.length} key#{'s' if keys.size != 1}#{" ~ '#{filter}'" if filter}#{" (#{used_n} usage#{'s' if used_n != 1})" if used_n > 0}"
31
33
  end
32
34
  end
33
35
  end
@@ -12,7 +12,7 @@ module I18n
12
12
  print_title missing_title(keys)
13
13
  if keys.present?
14
14
 
15
- $stderr.puts "#{bold 'Types:'} #{missing_types.values.map { |t| "#{t[:glyph]} #{t[:summary]}" } * ', '}"
15
+ print_info "#{bold 'Types:'} #{missing_types.values.map { |t| "#{t[:glyph]} #{t[:summary]}" } * ', '}"
16
16
 
17
17
  print_table headings: [magenta(bold('Locale')), bold('Type'), magenta('i18n Key'), bold(cyan "Base value (#{base_locale})")] do |t|
18
18
  t.rows = keys.map { |key|
@@ -36,22 +36,22 @@ module I18n
36
36
  end
37
37
  end
38
38
 
39
- def used_keys(keys = task.used_keys)
39
+ def used_keys(keys = task.used_keys(true))
40
40
  print_title used_title(keys)
41
41
  keys.sort_by_attr!(key: :asc)
42
42
  if keys.present?
43
43
  keys.each do |k|
44
- puts "#{bold k.key} (#{k[:usages].size}):"
44
+ puts "#{bold "#{k.key}"} #{k[:usages].size if k[:usages].size > 1}"
45
45
  k[:usages].each do |u|
46
- line = faint u[:line].dup.tap { |line|
47
- line.sub!(k[:key], underline(k[:key]))
46
+ line = u[:line].dup.tap { |line|
48
47
  line.strip!
48
+ line.sub!(/(.*?)(#{k[:key]})(.*)$/) { dark($1) + underline($2) + dark($3)}
49
49
  }
50
- puts " #{u[:path]}:#{u[:line_num]}: #{faint line}"
50
+ puts " #{green "#{u[:path]}:#{u[:line_num]}"} #{line}"
51
51
  end
52
52
  end
53
53
  else
54
- print_error 'No key usages found. Please check configuration in config/i18n-tasks.yml'
54
+ print_error 'No key usages found'
55
55
  end
56
56
  end
57
57
 
@@ -69,15 +69,19 @@ module I18n
69
69
  private
70
70
 
71
71
  def print_title(title)
72
- $stderr.puts "#{bold cyan title.strip} #{dark "|"} #{bold "i18n-tasks v#{I18n::Tasks::VERSION}"}"
72
+ print_info "#{bold cyan title.strip} #{dark "|"} #{bold "i18n-tasks v#{I18n::Tasks::VERSION}"}"
73
73
  end
74
74
 
75
75
  def print_success(message)
76
- $stderr.puts(bold green message)
76
+ print_info(bold green message)
77
77
  end
78
78
 
79
79
  def print_error(message)
80
- $stderr.puts(bold red message)
80
+ print_info(bold red message)
81
+ end
82
+
83
+ def print_info(*args)
84
+ $stderr.puts(*args)
81
85
  end
82
86
 
83
87
  def indent(txt, n = 2)
@@ -2,38 +2,40 @@ require 'i18n/tasks/relative_keys'
2
2
  module I18n::Tasks::Scanners
3
3
  class BaseScanner
4
4
  include ::I18n::Tasks::RelativeKeys
5
- attr_reader :config
5
+ include ::I18n::Tasks::KeyPatternMatching
6
+ attr_reader :config, :key_filter, :record_usages
6
7
 
7
8
  def initialize(config)
8
- @config = config.dup.with_indifferent_access.tap do |conf|
9
+ @config = config.dup.with_indifferent_access.tap do |conf|
9
10
  conf[:paths] = %w(app/) if conf[:paths].blank?
10
11
  conf[:include] = Array(conf[:include]) if conf[:include].present?
11
12
  conf[:exclude] = Array(conf[:exclude])
12
13
  end
14
+ @record_usages = false
15
+ end
16
+
17
+ def key_filter=(value)
18
+ @key_filter = value
19
+ @key_filter_pattern = compile_key_pattern(value) if @key_filter
13
20
  end
14
21
 
15
22
  # @return [Array] found key usages, absolutized and unique
16
23
  def keys
17
- @keys ||= begin
18
- @keys_by_name = {}
19
- keys = []
20
- usages = traverse_files { |path|
21
- ::I18n::Tasks::KeyGroup.new(scan_file(path), src_path: path) }.map(&:keys).flatten
22
- usages.group_by(&:key).each do |key, instances|
23
- key = {
24
- key: key,
25
- usages: instances.map { |inst| inst[:src].merge(path: inst[:src_path]) }
26
- }
27
- @keys_by_name[key.to_s] = key
28
- keys << key
29
- end
30
- keys
24
+ if @record_usages
25
+ keys_with_usages
26
+ else
27
+ @keys ||= traverse_files { |path| scan_file(path, read_file(path)).map(&:key) }.reduce(:+).uniq
31
28
  end
32
29
  end
33
30
 
34
- def key_usages(key)
35
- keys
36
- @keys_by_name[key.to_s][:usages]
31
+ def keys_with_usages
32
+ with_usages do
33
+ traverse_files { |path|
34
+ ::I18n::Tasks::KeyGroup.new(scan_file(path, read_file(path)), src_path: path)
35
+ }.map(&:keys).reduce(:+).group_by(&:key).map { |key, key_usages|
36
+ {key: key, usages: key_usages.map { |usage| usage[:src].merge(path: usage[:src_path]) }}
37
+ }
38
+ end
37
39
  end
38
40
 
39
41
  def read_file(path)
@@ -52,23 +54,47 @@ module I18n::Tasks::Scanners
52
54
  def traverse_files
53
55
  result = []
54
56
  Find.find(*config[:paths]) do |path|
55
- next if File.directory?(path)
56
- next if config[:include] and !config[:include].any? { |glob| File.fnmatch(glob, path) }
57
- next if config[:exclude].any? { |glob| File.fnmatch(glob, path) }
57
+ next if File.directory?(path) ||
58
+ config[:include] && !path_fnmatch_any?(path, config[:include]) ||
59
+ path_fnmatch_any?(path, config[:exclude])
58
60
  result << yield(path)
59
61
  end
60
62
  result
61
63
  end
62
64
 
65
+ def path_fnmatch_any?(path, globs)
66
+ globs.any? { |glob| File.fnmatch(glob, path) }
67
+ end
68
+ protected :path_fnmatch_any?
69
+
70
+ def with_key_filter(key_filter = nil)
71
+ filter_was = @key_filter
72
+ self.key_filter = key_filter
73
+ result = yield
74
+ self.key_filter = filter_was
75
+ result
76
+ end
77
+
78
+ def with_usages
79
+ was = @record_usages
80
+ @record_usages = true
81
+ result = yield
82
+ @record_usages = was
83
+ result
84
+ end
85
+
63
86
  protected
64
87
 
65
88
  def usage_context(text, src_pos)
89
+ return nil unless @record_usages
66
90
  line_begin = text.rindex(/^/, src_pos - 1)
67
91
  line_end = text.index(/.(?=\n|$)/, src_pos)
68
- {pos: src_pos,
69
- line_num: text[0..src_pos].count("\n") + 1,
70
- line_pos: src_pos - line_begin + 1,
71
- line: text[line_begin..line_end]}
92
+ {src: {
93
+ pos: src_pos,
94
+ line_num: text[0..src_pos].count("\n") + 1,
95
+ line_pos: src_pos - line_begin + 1,
96
+ line: text[line_begin..line_end]
97
+ }}
72
98
  end
73
99
 
74
100
  def extract_key_from_match(match, path)
@@ -88,7 +114,7 @@ module I18n::Tasks::Scanners
88
114
  VALID_KEY_RE = /^[\w.\#{}]+$/
89
115
 
90
116
  def valid_key?(key)
91
- key =~ VALID_KEY_RE
117
+ key =~ VALID_KEY_RE && !(@key_filter && @key_filter_pattern !~ key)
92
118
  end
93
119
 
94
120
  def relative_roots
@@ -1,11 +1,13 @@
1
- # Scans for I18n.t usages
2
1
  require 'i18n/tasks/scanners/base_scanner'
2
+
3
3
  module I18n::Tasks::Scanners
4
+ # Scans for I18n.t usages
5
+ #
4
6
  class PatternScanner < BaseScanner
5
7
  LITERAL_RE = /:?".+?"|:?'.+?'|:\w+/
6
8
  DEFAULT_PATTERN = /\bt(?:ranslate)?[( ]\s*(#{LITERAL_RE})/
7
9
 
8
- # Extract i18n keys from file based on the pattern. The pattern must capture key literal.
10
+ # Extract i18n keys from file based on the pattern which must capture the key literal.
9
11
  # @return [String] keys found in file
10
12
  def scan_file(path, text = read_file(path))
11
13
  keys = []
@@ -13,14 +15,13 @@ module I18n::Tasks::Scanners
13
15
  src_pos = Regexp.last_match.offset(0).first
14
16
  key = extract_key_from_match(match, path)
15
17
  next unless valid_key?(key)
16
- keys << ::I18n::Tasks::Key.new(key, src: usage_context(text, src_pos))
18
+ keys << ::I18n::Tasks::Key.new(key, usage_context(text, src_pos))
17
19
  end
18
20
  keys
19
21
  end
20
22
 
21
23
  protected
22
24
 
23
-
24
25
  def pattern
25
26
  @pattern ||= config[:pattern].present? ? Regexp.new(config[:pattern]) : DEFAULT_PATTERN
26
27
  end
@@ -4,8 +4,12 @@ require 'i18n/tasks/scanners/pattern_scanner'
4
4
  module I18n::Tasks::UsedKeys
5
5
  # find all keys in the source (relative keys are absolutized)
6
6
  # @return [Array<String>]
7
- def used_keys
8
- @used_keys ||= I18n::Tasks::KeyGroup.new(scanner.keys, type: :used)
7
+ def used_keys(with_usages = false)
8
+ if with_usages
9
+ I18n::Tasks::KeyGroup.new(scanner.keys_with_usages, type: :used, key_filter: scanner.key_filter)
10
+ else
11
+ @used_keys ||= I18n::Tasks::KeyGroup.new(scanner.keys, type: :used, key_filter: scanner.key_filter)
12
+ end
9
13
  end
10
14
 
11
15
  def scanner
@@ -1,5 +1,5 @@
1
1
  module I18n
2
2
  module Tasks
3
- VERSION = '0.2.20'
3
+ VERSION = '0.2.21'
4
4
  end
5
5
  end
@@ -57,9 +57,14 @@ namespace :i18n do
57
57
  end
58
58
  end
59
59
 
60
- desc 'show usages'
61
- task :usages => 'i18n:setup' do
62
- i18n_report.used_keys
60
+ desc 'show usages of the keys in the codebase'
61
+ task :usages, [:filter] => 'i18n:setup' do |t, args|
62
+ filter = args[:filter] ? args[:filter].tr('+', ',') : nil
63
+ i18n_report.used_keys(
64
+ i18n_task.scanner.with_key_filter(filter) {
65
+ i18n_task.used_keys(true)
66
+ }
67
+ )
63
68
  end
64
69
 
65
70
  desc 'normalize translation data: sort and move to the right files'
@@ -85,7 +85,7 @@ describe 'rake i18n' do
85
85
  end
86
86
 
87
87
  # --- setup ---
88
- BENCH_KEYS = 30
88
+ BENCH_KEYS = 100
89
89
  before do
90
90
  gen_data = ->(v) {
91
91
  v_num = v.chars.map(&:ord).join('').to_i
@@ -1,22 +1,40 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'UsedKeys' do
4
- let(:task) { I18n::Tasks::BaseTask.new }
4
+ let!(:task) { I18n::Tasks::BaseTask.new }
5
5
 
6
- it 'shows usages' do
6
+ around do |ex|
7
7
  task.config[:search] = {paths: ['a.html.slim']}
8
8
  TestCodebase.setup('a.html.slim' => <<-SLIM)
9
9
  div = t 'a'
10
10
  p = t 'a'
11
+ h1 = t 'b'
11
12
  SLIM
12
- TestCodebase.in_test_app_dir {
13
- used_keys = task.used_keys
14
- expect(used_keys.size).to eq 1
15
- usages_expected = [
16
- {pos: 6, line_num: 1, line_pos: 7, line: "div = t 'a'", path: 'a.html.slim'},
17
- {pos: 18, line_num: 2, line_pos: 7, line: " p = t 'a'", path: 'a.html.slim'}
18
- ]
19
- expect(used_keys[0].attr).to eq(type: :used, key: 'a', usages: usages_expected)
13
+ TestCodebase.in_test_app_dir { ex.run }
14
+ end
15
+
16
+ it '#used_keys(true) finds usages' do
17
+ used_keys = task.used_keys(true)
18
+ expect(used_keys.size).to eq 2
19
+ expect(used_keys[0].own_attr).to(
20
+ eq(key: 'a',
21
+ usages: [{pos: 6, line_num: 1, line_pos: 7, line: "div = t 'a'", path: 'a.html.slim'},
22
+ {pos: 18, line_num: 2, line_pos: 7, line: " p = t 'a'", path: 'a.html.slim'}])
23
+ )
24
+ expect(used_keys[1].own_attr).to(
25
+ eq(key: 'b',
26
+ usages: [{pos: 29, line_num: 3, line_pos: 6, line: "h1 = t 'b'", path: 'a.html.slim'}])
27
+ )
28
+ end
29
+
30
+ it '#used_keys(true) finds usages with filter' do
31
+ used_keys = task.scanner.with_key_filter('b*') {
32
+ task.used_keys(true)
20
33
  }
34
+ expect(used_keys.size).to eq 1
35
+ expect(used_keys[0].own_attr).to(
36
+ eq(key: 'b',
37
+ usages: [{pos: 29, line_num: 3, line_pos: 6, line: "h1 = t 'b'", path: 'a.html.slim'}])
38
+ )
21
39
  end
22
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.20
4
+ version: 0.2.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - glebm