i18n-tasks 0.4.5 → 0.5.0

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