i18n-tasks 0.2.20 → 0.2.21

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