i18n-tasks 0.4.5 → 0.5.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -4
  3. data/CHANGES.md +7 -0
  4. data/README.md +10 -14
  5. data/i18n-tasks.gemspec +1 -1
  6. data/lib/i18n/tasks.rb +0 -2
  7. data/lib/i18n/tasks/base_task.rb +4 -2
  8. data/lib/i18n/tasks/commands.rb +14 -14
  9. data/lib/i18n/tasks/configuration.rb +10 -2
  10. data/lib/i18n/tasks/console_context.rb +73 -0
  11. data/lib/i18n/tasks/data.rb +0 -47
  12. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +6 -1
  13. data/lib/i18n/tasks/data/file_system_base.rb +1 -1
  14. data/lib/i18n/tasks/data/router/conservative_router.rb +5 -5
  15. data/lib/i18n/tasks/data/router/pattern_router.rb +2 -2
  16. data/lib/i18n/tasks/data/tree/node.rb +47 -36
  17. data/lib/i18n/tasks/data/tree/nodes.rb +0 -4
  18. data/lib/i18n/tasks/data/tree/siblings.rb +54 -9
  19. data/lib/i18n/tasks/data/tree/traversal.rb +62 -23
  20. data/lib/i18n/tasks/fill_tasks.rb +29 -21
  21. data/lib/i18n/tasks/ignore_keys.rb +1 -1
  22. data/lib/i18n/tasks/key_pattern_matching.rb +17 -0
  23. data/lib/i18n/tasks/missing_keys.rb +39 -44
  24. data/lib/i18n/tasks/plural_keys.rb +14 -1
  25. data/lib/i18n/tasks/reports/base.rb +28 -8
  26. data/lib/i18n/tasks/reports/spreadsheet.rb +9 -8
  27. data/lib/i18n/tasks/reports/terminal.rb +33 -29
  28. data/lib/i18n/tasks/scanners/base_scanner.rb +22 -14
  29. data/lib/i18n/tasks/scanners/pattern_scanner.rb +2 -1
  30. data/lib/i18n/tasks/unused_keys.rb +13 -13
  31. data/lib/i18n/tasks/used_keys.rb +39 -38
  32. data/lib/i18n/tasks/version.rb +1 -1
  33. data/spec/i18n_tasks_spec.rb +41 -40
  34. data/spec/locale_tree/siblings_spec.rb +26 -1
  35. data/spec/support/i18n_tasks_output_matcher.rb +4 -1
  36. data/spec/support/trees.rb +6 -1
  37. data/spec/used_keys_spec.rb +23 -15
  38. metadata +4 -11
  39. data/lib/i18n/tasks/file_structure.rb +0 -19
  40. data/lib/i18n/tasks/key.rb +0 -48
  41. data/lib/i18n/tasks/key/key_group.rb +0 -45
  42. data/lib/i18n/tasks/key/match_pattern.rb +0 -24
  43. data/lib/i18n/tasks/key/usages.rb +0 -12
  44. data/lib/i18n/tasks/key_group.rb +0 -68
  45. data/spec/key_group_spec.rb +0 -49
@@ -20,17 +20,28 @@ module I18n::Tasks::Data::Tree
20
20
  end
21
21
 
22
22
  def derive(new_attr = {})
23
- self.class.new(attributes.merge(new_attr))
23
+ node = self.class.new(attributes.merge(new_attr).except(:children))
24
+ node.children = new_attr[:children] || @children.try(:derive, parent: node)
25
+ node
24
26
  end
25
27
 
26
28
  def key=(value)
27
- dirty!
28
- @key = value.try(:to_s)
29
+ value = value.try(:to_s)
30
+ if @key != value
31
+ dirty!
32
+ parent.try(:children).try(:key_renamed, value, @key)
33
+ @key = value
34
+ end
29
35
  end
30
36
 
31
- def children=(nodes_or_siblings)
37
+ def children=(children)
32
38
  dirty!
33
- @children = Siblings.new(nodes: nodes_or_siblings, parent: self)
39
+ if Siblings === children || children.nil?
40
+ @children = children
41
+ @children.parent = self if @children
42
+ else
43
+ @children = Siblings.new(nodes: children, parent: self)
44
+ end
34
45
  end
35
46
 
36
47
  def each(&block)
@@ -47,12 +58,12 @@ module I18n::Tasks::Data::Tree
47
58
  key.nil?
48
59
  end
49
60
 
50
- def hash_or_value
61
+ def value_or_children_hash
51
62
  leaf? ? value : children.try(:to_hash)
52
63
  end
53
64
 
54
65
  def leaf?
55
- !children?
66
+ !children
56
67
  end
57
68
 
58
69
  attr_writer :leaf
@@ -81,11 +92,9 @@ module I18n::Tasks::Data::Tree
81
92
  # do not use directly. use parent.append(node) instead
82
93
  def parent=(value)
83
94
  return if @parent == value
84
- if value
85
- @parent.children.remove!(self) if @parent && @parent.children
86
- @parent = value
87
- dirty!
88
- end
95
+ @parent.children.remove!(self) if @parent.try(:children) && @parent.children != value.children
96
+ @parent = value
97
+ dirty!
89
98
  @parent
90
99
  end
91
100
 
@@ -101,12 +110,10 @@ module I18n::Tasks::Data::Tree
101
110
 
102
111
  # append and reparent nodes
103
112
  def append!(nodes)
104
- if nodes.any?
105
- if @children
106
- @children.merge!(nodes)
107
- else
108
- @children = Siblings.new(nodes: nodes, parent: self)
109
- end
113
+ if @children
114
+ @children.merge!(nodes)
115
+ else
116
+ @children = Siblings.new(nodes: nodes, parent: self)
110
117
  end
111
118
  self
112
119
  end
@@ -116,7 +123,7 @@ module I18n::Tasks::Data::Tree
116
123
  end
117
124
 
118
125
  def full_key(opts = {})
119
- root = opts.key?(:root) ? opts[:root] : true
126
+ root = opts.key?(:root) ? opts[:root] : true
120
127
  @full_key ||= {}
121
128
  @full_key[root] ||= "#{"#{parent.full_key(root: root)}." if parent? && (root || parent.parent?)}#{key}"
122
129
  end
@@ -127,6 +134,12 @@ module I18n::Tasks::Data::Tree
127
134
  parent.walk_to_root &visitor if parent?
128
135
  end
129
136
 
137
+ def root
138
+ p = nil
139
+ walk_to_root { |node| p = node }
140
+ p
141
+ end
142
+
130
143
  def walk_from_root(&visitor)
131
144
  return to_enum(:walk_from_root) unless visitor
132
145
  walk_to_root.reverse_each do |node|
@@ -138,6 +151,14 @@ module I18n::Tasks::Data::Tree
138
151
  Nodes.new([self])
139
152
  end
140
153
 
154
+ def to_siblings
155
+ if parent
156
+ parent.children
157
+ else
158
+ Siblings.new(nodes: [self])
159
+ end
160
+ end
161
+
141
162
  def to_hash
142
163
  @hash ||= begin
143
164
  children_hash = (children || {}).map(&:to_hash).reduce(:deep_merge) || {}
@@ -155,24 +176,14 @@ module I18n::Tasks::Data::Tree
155
176
  delegate :to_yaml, to: :to_hash
156
177
 
157
178
  def inspect(level = 0)
158
- label =
159
- if null?
160
- Term::ANSIColor.dark '∅'
161
- else
162
- key = Term::ANSIColor.color(1 + level % 15, self.key)
163
- if leaf?
164
- value = Term::ANSIColor.cyan(self.value.to_s)
165
- "#{key}: #{value}"
166
- else
167
- "#{key}"
168
- end + (self.data? ? " #{self.data}" : '')
169
- end
170
-
171
- r = "#{' ' * level}#{label}"
172
- if children?
173
- r += "\n" + children.map { |c| c.inspect(level + 1) }.join("\n") if children?
179
+ if null?
180
+ label = Term::ANSIColor.dark '∅'
181
+ else
182
+ label = [Term::ANSIColor.color(1 + level % 15, self.key),
183
+ (": #{Term::ANSIColor.cyan(self.value.to_s)}" if leaf?),
184
+ (" #{self.data}" if data?)].compact.join
174
185
  end
175
- r
186
+ [' ' * level, label, ("\n" + children.map { |c| c.inspect(level + 1) }.join("\n") if children?)].compact.join
176
187
  end
177
188
 
178
189
  protected
@@ -32,10 +32,6 @@ module I18n::Tasks::Data::Tree
32
32
  @hash ||= map(&:to_hash).reduce(:deep_merge!) || {}
33
33
  end
34
34
 
35
- def key_names
36
- keys(root: false).map { |key, _node| key }
37
- end
38
-
39
35
  delegate :to_json, to: :to_hash
40
36
  delegate :to_yaml, to: :to_hash
41
37
 
@@ -12,8 +12,8 @@ module I18n::Tasks::Data::Tree
12
12
  def initialize(opts = {})
13
13
  super(nodes: opts[:nodes])
14
14
  @key_to_node = siblings.inject({}) { |h, node| h[node.key] = node; h }
15
- @parent = first.try(:parent) || Node.null
16
- self.parent = opts[:parent] if opts[:parent]
15
+ @parent = first.try(:parent)
16
+ self.parent = opts[:parent] || @parent || Node.null
17
17
  end
18
18
 
19
19
  def attributes
@@ -22,16 +22,19 @@ module I18n::Tasks::Data::Tree
22
22
 
23
23
  def parent=(node)
24
24
  return if @parent == node
25
- each { |n| n.parent = node }
25
+ each { |root| root.parent = node }
26
26
  @parent = node
27
27
  end
28
28
 
29
- alias siblings each
29
+ def siblings(&block)
30
+ each(&block)
31
+ self
32
+ end
30
33
 
31
34
  # @return [Node] by full key
32
35
  def get(full_key)
33
36
  first_key, rest = full_key.to_s.split('.', 2)
34
- node = key_to_node[first_key]
37
+ node = key_to_node[first_key]
35
38
  if rest && node
36
39
  node = node.children.try(:get, rest)
37
40
  end
@@ -43,7 +46,7 @@ module I18n::Tasks::Data::Tree
43
46
  # add or replace node by full key
44
47
  def set(full_key, node)
45
48
  key_part, rest = full_key.split('.', 2)
46
- child = key_to_node[key_part]
49
+ child = key_to_node[key_part]
47
50
  if rest
48
51
  unless child
49
52
  child = Node.new(key: key_part)
@@ -106,17 +109,51 @@ module I18n::Tasks::Data::Tree
106
109
  derive.merge!(nodes)
107
110
  end
108
111
 
112
+ def key_renamed(new_name, old_name)
113
+ node = key_to_node.delete old_name
114
+ key_to_node[new_name] = node
115
+ end
116
+
109
117
  class << self
110
118
  def null
111
119
  new
112
120
  end
113
121
 
122
+ def build_forest(opts = {}, &block)
123
+ opts[:nodes] ||= []
124
+ parse_parent_opt!(opts)
125
+ forest = Siblings.new(opts)
126
+ block.call(forest) if block
127
+ forest.parent.children = forest
128
+ end
129
+
130
+ def from_key_attr(key_attrs, opts = {}, &block)
131
+ build_forest(opts) { |forest|
132
+ key_attrs.each { |(full_key, attr)|
133
+ raise "Invalid key #{full_key.inspect}" if full_key.end_with?('.')
134
+ node = Node.new(attr.merge(key: full_key.split('.').last))
135
+ block.call(full_key, node) if block
136
+ forest[full_key] = node
137
+ }
138
+ }
139
+ end
140
+
141
+ def from_key_names(keys, opts = {}, &block)
142
+ build_forest(opts) { |forest|
143
+ keys.each { |full_key|
144
+ node = Node.new(key: full_key.split('.').last)
145
+ block.call(full_key, node) if block
146
+ forest[full_key] = node
147
+ }
148
+ }
149
+ end
150
+
114
151
  # build forest from nested hash, e.g. {'es' => { 'common' => { name => 'Nombre', 'age' => 'Edad' } } }
115
152
  # this is the native i18n gem format
116
153
  def from_nested_hash(hash, opts = {})
117
- opts[:parent] ||= Node.null
118
- Siblings.new nodes: hash.map { |key, value| Node.from_key_value key, value },
119
- parent: opts[:parent]
154
+ parse_parent_opt!(opts)
155
+ opts[:nodes] = hash.map { |key, value| Node.from_key_value key, value }
156
+ Siblings.new(opts)
120
157
  end
121
158
 
122
159
  alias [] from_nested_hash
@@ -129,6 +166,14 @@ module I18n::Tasks::Data::Tree
129
166
  }
130
167
  end
131
168
  end
169
+
170
+ private
171
+ def parse_parent_opt!(opts)
172
+ opts[:parent] = Node.new(key: opts[:parent_key]) if opts[:parent_key]
173
+ opts[:parent] = Node.new(opts[:parent_attr]) if opts[:parent_attr]
174
+ opts[:parent] = Node.new(key: opts[:parent_locale], data: {locale: opts[:parent_locale]}) if opts[:parent_locale]
175
+ opts[:parent] ||= Node.null
176
+ end
132
177
  end
133
178
  end
134
179
  end
@@ -4,7 +4,7 @@ module I18n::Tasks::Data::Tree
4
4
  module Traversal
5
5
 
6
6
  def nodes(&block)
7
- walk_depth_first(&block)
7
+ depth_first(&block)
8
8
  end
9
9
 
10
10
  def leaves(&visitor)
@@ -12,13 +12,7 @@ module I18n::Tasks::Data::Tree
12
12
  nodes do |node|
13
13
  visitor.yield(node) if node.leaf?
14
14
  end
15
- end
16
-
17
- # @param root include root in full key
18
- def keys(opts = {}, &visitor)
19
- root = opts.key?(:root) ? opts[:root] : true
20
- return to_enum(:keys, root: root) unless visitor
21
- leaves { |node| visitor.yield(node.full_key(root: root), node) }
15
+ self
22
16
  end
23
17
 
24
18
  def levels(&block)
@@ -29,47 +23,71 @@ module I18n::Tasks::Data::Tree
29
23
  block.yield non_null
30
24
  Nodes.new(nodes.children).levels(&block)
31
25
  end
26
+ self
32
27
  end
33
28
 
34
- def walk_breadth_first(&visitor)
35
- return to_enum(:walk_breadth_first) unless visitor
29
+ def breadth_first(&visitor)
30
+ return to_enum(:breadth_first) unless visitor
36
31
  levels do |nodes|
37
32
  nodes.each { |node| visitor.yield(node) unless node.null? }
38
33
  end
34
+ self
39
35
  end
40
36
 
41
- def walk_depth_first(&visitor)
42
- return to_enum(:walk_depth_first) unless visitor
37
+ def depth_first(&visitor)
38
+ return to_enum(:depth_first) unless visitor
43
39
  each { |node|
44
40
  visitor.yield node unless node.null?
45
41
  node.children.each do |child|
46
- child.walk_depth_first(&visitor)
42
+ child.depth_first(&visitor)
47
43
  end if node.children?
48
44
  }
45
+ self
46
+ end
47
+
48
+ # @option root include root in full key
49
+ def keys(key_opts = {}, &visitor)
50
+ key_opts[:root] = false unless key_opts.key?(:root)
51
+ return to_enum(:keys, key_opts) unless visitor
52
+ leaves { |node| visitor.yield(node.full_key(key_opts), node) }
53
+ self
54
+ end
55
+
56
+
57
+ def key_names(opts = {})
58
+ opts[:root] = false unless opts.key?(:root)
59
+ keys(opts).map { |key, _node| key }
60
+ end
61
+
62
+ def key_values(opts = {})
63
+ opts[:root] = false unless opts.key?(:root)
64
+ keys(opts).map { |key, node| [key, node.value] }
65
+ end
66
+
67
+ def root_key_values
68
+ keys(root: false).map { |key, node| [node.root.key, key, node.value]}
49
69
  end
50
70
 
51
71
  #-- modify / derive
52
72
 
53
73
  # @return Siblings
54
74
  def select_nodes(&block)
55
- result = Node.new(children: [])
75
+ tree = Siblings.new
56
76
  each do |node|
57
77
  if block.yield(node)
58
- result.append!(
59
- node.derive(
60
- parent: result ,
61
- children: (node.children.select_nodes(&block) if node.children)
62
- )
78
+ tree.append! node.derive(
79
+ parent: tree.parent,
80
+ children: (node.children.select_nodes(&block).to_a if node.children)
63
81
  )
64
82
  end
65
83
  end
66
- result.children
84
+ tree
67
85
  end
68
86
 
69
87
  # @return Siblings
70
88
  def select_keys(opts = {}, &block)
71
- root = opts.key?(:root) ? opts[:root] : true
72
- ok = {}
89
+ root = opts.key?(:root) ? opts[:root] : false
90
+ ok = {}
73
91
  keys(root: root) do |full_key, node|
74
92
  if block.yield(full_key, node)
75
93
  node.walk_to_root { |p|
@@ -78,7 +96,28 @@ module I18n::Tasks::Data::Tree
78
96
  }
79
97
  end
80
98
  end
81
- select_nodes { |node| ok[node] }
99
+ select_nodes { |node|
100
+ ok[node]
101
+ }
102
+ end
103
+
104
+
105
+ # @return Siblings
106
+ def intersect_keys(other_tree, key_opts = {}, &block)
107
+ if block
108
+ select_keys(key_opts) { |key, node|
109
+ other_node = other_tree[key]
110
+ other_node && block.call(key, node, other_node)
111
+ }
112
+ else
113
+ select_keys(key_opts) { |key, node| other_tree[key] }
114
+ end
115
+ end
116
+
117
+ def grep_keys(match, opts = {})
118
+ select_keys(opts) do |full_key, _node|
119
+ match === full_key
120
+ end
82
121
  end
83
122
  end
84
123
  end
@@ -1,26 +1,34 @@
1
1
  # coding: utf-8
2
- module I18n::Tasks::FillTasks
3
- def fill_missing_value(opts = {})
4
- opts = opts.merge(
5
- keys: proc { |locale| keys_to_fill(locale) }
6
- )
7
- opts[:value] ||= '' unless opts[:values].present?
8
- update_data opts
9
- end
10
-
11
- def fill_missing_google_translate(opts = {})
12
- from = opts[:from] || base_locale
13
- opts = opts.merge(
14
- locales: Array(opts[:locales]) - Array(from),
15
- keys: proc { |locale| missing_tree(locale, from).key_names.map(&:to_s) },
16
- values: proc { |keys, locale|
17
- google_translate(keys.zip(keys.map(&t_proc(from))), to: locale, from: from).map(&:last)
2
+ module I18n::Tasks
3
+ module FillTasks
4
+ def fill_missing_value(opts = {})
5
+ value = opts[:value] || ''
6
+ locales_for_update(opts).each do |locale|
7
+ data[locale] = data[locale].merge! missing_tree(locale).keys { |key, node|
8
+ node.value = value.respond_to?(:call) ? value.call(key, locale, node) : value
18
9
  }
19
- )
20
- update_data opts
21
- end
10
+ end
11
+ end
12
+
13
+ def fill_missing_google_translate(opts = {})
14
+ from = opts[:from] || base_locale
15
+ locales = (Array(opts[:locales]).presence || self.locales) - [from]
16
+ locales.each do |locale|
17
+ keys = missing_tree(locale, from).key_names.map(&:to_s)
18
+ values = google_translate(keys.zip(keys.map(&t_proc(from))), to: locale, from: from).map(&:last)
19
+
20
+ data[locale] = Data::Tree::Node.new(
21
+ key: locale,
22
+ children: Data::Tree::Siblings.from_flat_pairs(keys.zip(values))
23
+ ).to_siblings
24
+ end
25
+ end
22
26
 
23
- def keys_to_fill(locale)
24
- keys_missing_from_locale(locale).key_names
27
+ def locales_for_update(opts)
28
+ locales = (Array(opts[:locales] || opts[:locale]).presence || self.locales).map(&:to_s)
29
+ # make sure base_locale always comes first if present
30
+ locales = [base_locale] + (locales - [base_locale]) if locales.include?(base_locale)
31
+ locales
32
+ end
25
33
  end
26
34
  end