i18n-tools 0.0.2

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.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sven Fuchs <svenfuchs@artweb-design.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1 @@
1
+ to turn off filename generation in zsh: unsetopt GLOB
data/bin/i18n-keys ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'commander'
5
+ require File.dirname(__FILE__) + '/../lib/i18n/keys'
6
+ require File.dirname(__FILE__) + '/../lib/i18n/keys/commands'
data/lib/ansi.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Ansi
2
+ COLORS = { :red =>";31", :yellow => ";33", :green => ";32" }
3
+ STYLES = { :bold => ";1", :underline => ";4" }
4
+
5
+ def ansi_format(text, formats)
6
+ res = "\e[0"
7
+ if formats.is_a?(Array) || formats.is_a?(Hash)
8
+ COLORS.each { |k,v| res += v if formats.include?(k) }
9
+ STYLES.each { |k,v| res += v if formats.include?(k) }
10
+ elsif formats.is_a?(Symbol)
11
+ COLORS.each { |k,v| res += v if formats == k }
12
+ STYLES.each { |k,v| res += v if formats == k }
13
+ elsif formats.respond_to?(:to_sym)
14
+ COLORS.each { |k,v| res += v if formats.to_sym == k }
15
+ STYLES.each { |k,v| res += v if formats.to_sym == k }
16
+ end
17
+ res += "m" + text.to_s + "\e[0m"
18
+ end
19
+ end
data/lib/i18n/keys.rb ADDED
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + '/parser/ruby_parser'
2
+ require File.dirname(__FILE__) + '/parser/erb_parser'
3
+ require File.dirname(__FILE__) + '/keys/index'
4
+
5
+ module I18n
6
+ module Keys
7
+ VERSION = '0.0.1'
8
+
9
+ @@root = '.'
10
+ @@verbose = true
11
+
12
+ class << self
13
+ def verbose?
14
+ @@verbose
15
+ end
16
+
17
+ def verbose=(verbose)
18
+ @@verbose = !!verbose
19
+ end
20
+
21
+ def root
22
+ @@root
23
+ end
24
+
25
+ def root=(dir)
26
+ @@root = dir
27
+ end
28
+
29
+ def meta_dir
30
+ dir = root + '/.i18n'
31
+ FileUtils.mkdir(dir) unless File.exists?(dir)
32
+ dir
33
+ end
34
+
35
+ def config
36
+ @config ||= YAML.load_file(meta_dir + '/config.yml') rescue { 'indices' => {} }
37
+ end
38
+
39
+ def config=(config)
40
+ @config = config
41
+ end
42
+
43
+ def index(*args)
44
+ options = args.last.is_a?(Hash) ? args.pop : {}
45
+ name = args.first || options.delete(:index)
46
+ index = Index.load_or_create_or_init(name, options)
47
+ index
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ program :version, I18n::Keys::VERSION
2
+ program :description, 'ruby i18n tools'
3
+
4
+ command :find do |c|
5
+ c.syntax = 'i18n find [key] --index --verbose'
6
+ c.summary = 'Find keys passed to I18n.t()'
7
+ c.example 'i18n find', 'i18n find foo bar --index'
8
+ c.option '--index', 'Use an index'
9
+ c.option '--verbose', 'Output information about index build'
10
+ c.when_called do |args, options|
11
+ I18n::Keys.verbose = options.verbose
12
+ index = I18n::Keys.index(:index => options.index)
13
+ index.each(*args.map { |arg| arg.dup }) { |occurence| puts occurence.to_s }
14
+ end
15
+ end
16
+
17
+ command :replace do |c|
18
+ c.syntax = 'i18n replace [key] [replacement] --index --verbose'
19
+ c.summary = 'Replace keys passed to I18n.t() by something else'
20
+ c.example 'i18n replace', 'i18n replace foo bar --index'
21
+ c.option '--index', 'Use an index'
22
+ c.option '--verbose', 'Output information about index build'
23
+ c.when_called do |args, options|
24
+ search, replacement = args.shift, args.shift
25
+ raise Commander::Runner::InvalidCommandError.new('Wrong number of arguments') unless search && replacement
26
+ I18n::Keys.verbose = options.verbose
27
+ index = I18n::Keys.index(:index => options.index)
28
+ index.each(search.dup, replacement.dup) do |occurence|
29
+ index.replace(occurence, replacement) if I18n::Commands.replace?(occurence, replacement)
30
+ end
31
+ end
32
+ end
33
+
34
+ module I18n
35
+ module Commands
36
+ class << self
37
+ def replace?(occurence, replacement)
38
+ return true if @all
39
+ answer = I18n::Commands.confirm_replacement(occurence, replacement)[0, 1]
40
+ answer == 'a' ? @all = true : answer == 'y'
41
+ end
42
+
43
+ def confirm_replacement(occurence, replacement)
44
+ puts occurence.to_s, occurence.context
45
+ msg = "Replace this occurence of the key \"#{occurence.key}\" with \"#{replacement}\"? [Y]es [N]o [A]ll"
46
+ answer = ask(msg, %w(y yes n no a all)) do |q|
47
+ q.case = :downcase
48
+ q.readline = true
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ module I18n
2
+ module Keys
3
+ class Index
4
+ module Formatter
5
+ module Setup
6
+ def setup(target)
7
+ (class << target; self; end).send(:include, self)
8
+ end
9
+ end
10
+
11
+ module Stdin
12
+ extend Setup
13
+
14
+ def build(*args)
15
+ puts "building index \"#{name}\"" if I18n::Keys.verbose?
16
+ super
17
+ puts "\nfound #{occurences.size} occurences of #{keys.size} keys in #{files.size} files" if I18n::Keys.verbose?
18
+ end
19
+
20
+ def save
21
+ puts "saving index \"#{name}\"" if I18n::Keys.verbose?
22
+ super
23
+ end
24
+
25
+ def parse(file)
26
+ # puts " parsing #{file}" if I18n::Keys.verbose?
27
+ putc '.' if I18n::Keys.verbose?
28
+ super
29
+ end
30
+
31
+ # def each(*keys)
32
+ # puts "iterating occurences of: #{keys.map { |key| key.to_sym.inspect }.join(', ')}" if I18n::Keys.verbose?
33
+ # super
34
+ # end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,209 @@
1
+ require 'yaml'
2
+ require File.dirname(__FILE__) + '/formatter'
3
+ require File.dirname(__FILE__) + '/occurence'
4
+
5
+ # Keys.index(:default, :pattern => '/**/*.{rb,erb}').each(:foo, :bar) { |occurence| ... }
6
+ #
7
+ # when the :index name is not given it creates the index on the fly but won't save it
8
+ # when the :index name is true it uses :default as an index name
9
+ #
10
+ # when an index with the given name does not exist it builds and saves an index
11
+ # when an index with the given name exists and the given pattern does not match
12
+ # the index's pattern it issues a warning and rebuilds the index
13
+ #
14
+ # when no keys are given, occurences of all keys will be iterated
15
+ # when keys are given, only occurences of the given keys will be iterated
16
+
17
+ module I18n
18
+ module Keys
19
+ class Index
20
+ include Enumerable
21
+
22
+ @@formatter = Formatter::Stdin
23
+ @@default_pattern = '/**/*.{rb,erb}'
24
+
25
+ class << self
26
+ def load_or_create_or_init(*args)
27
+ options = args.last.is_a?(Hash) ? args.pop : {}
28
+ name = TrueClass === args.first ? :default : args.first
29
+ index = name ? load_or_create(name, options) : new(name, options)
30
+ index
31
+ end
32
+
33
+ def load_or_create(*args)
34
+ options = args.last.is_a?(Hash) ? args.pop : {}
35
+ name = args.first || :default
36
+ exists?(name) ? load(name) : create(name, options)
37
+ end
38
+
39
+ def create(*args)
40
+ index = new(*args)
41
+ index.update
42
+ index
43
+ end
44
+
45
+ def load(name)
46
+ File.open(filename(name), 'r') { |f| Marshal.load(f) } if exists?
47
+ end
48
+
49
+ def mk_dir
50
+ FileUtils.mkdir_p(store_dir) unless exists?
51
+ end
52
+
53
+ def exists?(name = nil)
54
+ name ? File.exists?(filename(name)) : File.exists?(store_dir)
55
+ end
56
+
57
+ def delete(name)
58
+ new(name).delete
59
+ end
60
+
61
+ def delete_all
62
+ FileUtils.rm_r(store_dir) if exists? rescue Errno::ENOENT
63
+ end
64
+
65
+ def filename(name)
66
+ store_dir + "/#{name.to_s}.marshal"
67
+ end
68
+
69
+ def store_dir
70
+ File.expand_path(Keys.meta_dir + '/indizes')
71
+ end
72
+ end
73
+
74
+ attr_reader :name
75
+
76
+ [:keys, :occurences, :by_key].each do |name|
77
+ class_eval <<-code
78
+ def #{name} # def key
79
+ build unless built? # build unless built?
80
+ @#{name} # @key
81
+ end # end
82
+ code
83
+ end
84
+
85
+ def initialize(*args)
86
+ options = args.last.is_a?(Hash) ? args.pop : {}
87
+ @name = args.first || :default
88
+ @pattern = options[:pattern] || @@default_pattern
89
+ reset!
90
+ @@formatter.setup(self) if @@formatter
91
+ end
92
+
93
+ def reset!
94
+ @built = false
95
+ @keys = []
96
+ @occurences = []
97
+ @by_key = {}
98
+ end
99
+
100
+ def built?
101
+ @built
102
+ end
103
+
104
+ def build(options = {})
105
+ @occurences = find_occurences(options)
106
+ @occurences.each do |occurence|
107
+ @keys << occurence.key
108
+ (@by_key[occurence.key] ||= []) << occurence
109
+ end
110
+ @built = true
111
+ end
112
+
113
+ def exists?
114
+ File.exists?(filename)
115
+ end
116
+
117
+ def save
118
+ self.class.mk_dir
119
+ File.open(filename, 'w+') { |f| Marshal.dump(self, f) }
120
+ end
121
+
122
+ def update(options = {})
123
+ reset!
124
+ build(options)
125
+ save
126
+ end
127
+
128
+ def delete
129
+ FileUtils.rm(filename) if exists? rescue Errno::ENOENT
130
+ end
131
+
132
+ def filename
133
+ self.class.filename(name)
134
+ end
135
+
136
+ def files
137
+ Dir[Keys.root + pattern]
138
+ end
139
+
140
+ def pattern
141
+ @pattern ||= config['pattern'] || @@default_pattern
142
+ end
143
+
144
+ def config
145
+ @config ||= Keys.config['indices'][name] || { }
146
+ end
147
+
148
+ def each(*keys)
149
+ patterns = key_patterns(keys)
150
+ occurences.each { |occurence| yield(occurence) if key_matches?(occurence.key, patterns) }
151
+ end
152
+
153
+ def inject(memo, *keys)
154
+ each(*keys) { |occurence| yield(memo, occurence) }
155
+ memo
156
+ end
157
+
158
+ def replace!(occurence, replacement)
159
+ occurence.replace!(replacement)
160
+ save if exists?
161
+ end
162
+
163
+ def marshal_dump
164
+ keys = :name, :pattern, :keys, :occurences, :by_key
165
+ keys.inject({ :built => built? }) { |result, key| result[key] = send(key); result }
166
+ end
167
+
168
+ def marshal_load(data)
169
+ keys = :name, :pattern, :keys, :occurences, :by_key, :built
170
+ keys.each { |key| instance_variable_set(:"@#{key}", data[key]) }
171
+ end
172
+
173
+ protected
174
+
175
+ def find_occurences(options)
176
+ files.inject([]) do |result, file|
177
+ calls = parse(file).find_by_type(:call).select { |call| call[2] == :t }
178
+ calls.inject(result) do |result, node|
179
+ node.each_key_node { |key| result << Occurence.from_sexp(key, file) }
180
+ result
181
+ end
182
+ end
183
+ end
184
+
185
+ def key_matches?(subject, key_patterns)
186
+ key_patterns.empty? || key_patterns.any? do |key, pattern|
187
+ subject.to_sym == key || subject.to_s =~ pattern
188
+ end
189
+ end
190
+
191
+ def key_patterns(keys)
192
+ keys.inject({}) { |result, key| result[key] = key_pattern(key); result }
193
+ end
194
+
195
+ def key_pattern(key)
196
+ key = key.to_s.dup
197
+ match_end = key.gsub!(/\*$/, '') ? '' : '$'
198
+ pattern = Regexp.escape("#{key}")
199
+ /^#{pattern}#{match_end}/
200
+ end
201
+
202
+ def parse(file)
203
+ source = File.read(file)
204
+ source = ErbParser.new.to_ruby(source) if File.extname(file) == '.erb'
205
+ RubyParser.new.parse(source)
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,120 @@
1
+ require File.dirname(__FILE__) + '/../../ansi'
2
+
3
+ module I18n
4
+ module Keys
5
+ class Occurence
6
+ include Ansi
7
+
8
+ @@context_lines = 2
9
+
10
+ class << self
11
+ def context_lines
12
+ @@context_lines
13
+ end
14
+
15
+ def context_lines=(num)
16
+ @@context_lines = num
17
+ end
18
+
19
+ def from_sexp(sexp, file = nil)
20
+ new(sexp[1], file || sexp.file, sexp.source_start_pos, sexp.source_end_pos)
21
+ end
22
+ end
23
+
24
+ attr_reader :key, :file, :start_pos, :end_pos
25
+
26
+ def initialize(key, file, start_pos, end_pos)
27
+ @key = key.to_sym
28
+ @file = file
29
+ @start_pos = start_pos
30
+ @end_pos = end_pos
31
+ end
32
+
33
+ def replace!(replacement)
34
+ replacement = replacement.to_sym.inspect
35
+ content = content_head + replacement + content_tail
36
+ @key = replacement.to_sym
37
+ self.length = replacement.to_s.length
38
+ File.open(file, 'w+') { |f| f.write(content) }
39
+ @position, @lines, @context = nil, nil, nil
40
+ end
41
+
42
+ def content
43
+ lines.join
44
+ end
45
+
46
+ def lines
47
+ @lines ||= File.open(file, 'r') { |f| f.readlines }
48
+ end
49
+
50
+ def line(highlight = false)
51
+ line = lines[line_num - 1].dup
52
+ highlight ? line_head + ansi_format(original_key, [:red, :bold]) + line_tail : line
53
+ end
54
+
55
+ def line_num
56
+ position[0]
57
+ end
58
+
59
+ def column
60
+ position[1]
61
+ end
62
+
63
+ def length
64
+ end_pos - start_pos + 1
65
+ end
66
+
67
+ def length=(length)
68
+ @length = length
69
+ @end_pos = start_pos + length - 1
70
+ end
71
+
72
+ def original_key
73
+ line[column - 1, length]
74
+ end
75
+
76
+ def content_head
77
+ content[0..(start_pos - 1)].to_s
78
+ end
79
+
80
+ def content_tail
81
+ content[(end_pos + 1)..-1].to_s
82
+ end
83
+
84
+ def line_head
85
+ line[0..(column - 2)].to_s
86
+ end
87
+
88
+ def line_tail
89
+ line[(column + length - 1)..-1].to_s
90
+ end
91
+
92
+ def context
93
+ @context ||=
94
+ lines[line_num - self.class.context_lines - 1, self.class.context_lines].join +
95
+ line(true) + lines[line_num, self.class.context_lines].join
96
+ end
97
+
98
+ def to_s
99
+ "#{key}: #{file} [#{line_num}/#{column}]"
100
+ end
101
+
102
+ def ==(other)
103
+ key == other.key and start_pos == other.start_pos and end_pos == other.end_pos
104
+ end
105
+
106
+ def position
107
+ @position ||= begin
108
+ line_num, col = 1, 1
109
+ lines.inject(0) do |sum, line|
110
+ break if sum + line.length > start_pos
111
+ line_num += 1
112
+ col = start_pos - sum - line.length + 1
113
+ sum += line.length
114
+ end
115
+ [line_num, col]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,54 @@
1
+ # replaces html and erb tags with whitespace so that we can parse the result
2
+ # as pure ruby preserving the exact positions of tokens in the original erb
3
+ # source code
4
+
5
+ require 'erb'
6
+ $KCODE = 'u'
7
+
8
+ class String
9
+ def to_whitespace
10
+ gsub(/\S/, ' ')
11
+ end
12
+ end
13
+
14
+ module I18n
15
+ class ErbParser
16
+ class Scanner < ERB::Compiler::Scanner
17
+ def scan
18
+ stag_reg = /(.*?)(^[ \t]*<%-|<%%|<%=|<%#|<%-|<%|\z)/m
19
+ etag_reg = /(.*?)(%%>|-%>|%>|\z)/m
20
+ scanner = StringScanner.new(@src)
21
+ while !scanner.eos?
22
+ scanner.scan(@stag ? etag_reg : stag_reg)
23
+ yield(scanner[1]) unless scanner[1].nil?
24
+ yield(scanner[2]) unless scanner[2].nil?
25
+ end
26
+ end
27
+ end
28
+ ERB::Compiler::Scanner.regist_scanner(Scanner, nil, false)
29
+
30
+ def to_ruby(source)
31
+ result = ''
32
+ comment = false
33
+ scanner = ERB::Compiler.new(nil).make_scanner(source)
34
+ scanner.scan do |token|
35
+ comment = true if token == '<%#'
36
+ if scanner.stag.nil?
37
+ result << token.to_whitespace
38
+ scanner.stag = token if ['<%', '<%-', '<%=', '<%#', "\n"].include?(token)
39
+ elsif ['%>', '-%>'].include?(token)
40
+ result << token.to_whitespace
41
+ scanner.stag = nil
42
+ else
43
+ result << (comment ? token.to_whitespace : token) # so, this is the ruby code, then
44
+ comment = false
45
+ end
46
+ end
47
+ result
48
+ end
49
+
50
+ def content_dump(string)
51
+ string.split("\n").map { |s| s.to_whitespace }.join("\n")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,93 @@
1
+ require 'rubygems'
2
+ require 'sexp_processor'
3
+ require 'ruby_parser'
4
+
5
+ # monkey patch galore, adds the ability to inspect the original source code
6
+ # that a :lit or :str sexp was parsed from
7
+
8
+ Sexp.class_eval do
9
+ attr_accessor :full_source, :source_start_pos, :source_end_pos
10
+
11
+ def value
12
+ self[1]
13
+ end
14
+
15
+ def source_range
16
+ source_start_pos..source_end_pos if source_start_pos && source_end_pos
17
+ end
18
+
19
+ def source
20
+ full_source[source_range] if full_source && source_range
21
+ end
22
+
23
+ def find_by_type(type)
24
+ nodes = []
25
+ nodes << self if self.first == type
26
+ each_of_type(type) { |node| nodes << node }
27
+ nodes
28
+ end
29
+
30
+ def each_key_node(&block)
31
+ each do |element|
32
+ next unless Sexp === element
33
+ element.each_key_node(&block)
34
+ block.call(element) if element.is_key_node?
35
+ end
36
+ end
37
+
38
+ def is_key_node?
39
+ first == :str || first == :lit && self[1].is_a?(Symbol)
40
+ end
41
+ end
42
+
43
+ RubyLexer.class_eval do
44
+ attr_accessor :source_start_pos, :source_end_pos
45
+
46
+ def reset_source_positions!
47
+ self.source_start_pos, self.source_end_pos = nil, nil, nil
48
+ end
49
+ end
50
+
51
+ module I18n
52
+ class RubyParser < RubyParser
53
+ def s(*args)
54
+ result = super
55
+
56
+ result.full_source = lexer.src.string.dup
57
+ result.source_start_pos = lexer.source_start_pos
58
+ result.source_end_pos = lexer.source_end_pos || lexer.src.pos
59
+ lexer.reset_source_positions!
60
+
61
+ result
62
+ end
63
+
64
+ # :tSYMBOL
65
+ def _reduce_380(val, _values, result)
66
+ lexer.source_start_pos = lexer.src.pos - lexer.yacc_value.size - 1
67
+ lexer.source_end_pos = lexer.src.pos - 1
68
+ super
69
+ end
70
+
71
+ # :tSTRING
72
+ def _reduce_386(val, _values, result)
73
+ lexer.source_start_pos = lexer.src.pos - lexer.yacc_value.size - 2
74
+ lexer.source_end_pos = lexer.src.pos - 1
75
+ super
76
+ end
77
+
78
+ # :tSYMBEG
79
+ def _reduce_403(val, _values, result)
80
+ lexer.source_start_pos = lexer.src.pos - lexer.yacc_value.size - 2
81
+ super
82
+ end
83
+
84
+ def _reduce_418(val, _values, result)
85
+ result = super
86
+ result.source_start_pos = val[1].source_start_pos
87
+ result.source_end_pos = val[1].source_end_pos
88
+ result.full_source = val[1].full_source
89
+ result
90
+ end
91
+ end
92
+ end
93
+
data/test/all.rb ADDED
@@ -0,0 +1 @@
1
+ Dir[File.dirname(__FILE__) + '/*_test.rb'].each { |file| require file }
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'erb'
3
+
4
+ class ErbParserTest < Test::Unit::TestCase
5
+ def test_sexp_filename
6
+ erb = File.read("#{File.dirname(__FILE__)}/fixtures/template.html.erb")
7
+ ruby = I18n::ErbParser.new.to_ruby(erb)
8
+ assert_equal erb.length, ruby.length
9
+ %w(erb_1 erb_2 foo.erb_3).each do |key|
10
+ assert ruby.index(key)
11
+ assert_equal erb.index(key), ruby.index(key)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ class Foo
2
+ def foo
3
+ t(:bar)
4
+ t(:"baaar")
5
+ t(:'baar')
6
+ t(:'foo.bar')
7
+ t("bar")
8
+ t('bar_1')
9
+ 1 + 1
10
+ t(1)
11
+ t(1.1)
12
+ end
13
+ foo(:outside_)
14
+ end
@@ -0,0 +1 @@
1
+ t(:bar_2)
@@ -0,0 +1,19 @@
1
+ <html>
2
+ <ul>
3
+ <% [:foo].each do |foo| %>
4
+ <li>
5
+
6
+ <span><%= t(:erb_1) %></span>
7
+ <%#= comment
8
+ comment
9
+ comment %>
10
+ </li>
11
+ <% end %>
12
+ <br>
13
+
14
+ <%- t(:erb_2) -%>
15
+
16
+ <%- t(:'foo.erb_3') -%>
17
+
18
+ </ul>
19
+ </html>
@@ -0,0 +1,135 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class IndexTest < Test::Unit::TestCase
4
+ include I18n
5
+
6
+ def setup
7
+ I18n::Keys.verbose = false
8
+ @filenames = %W( #{File.dirname(__FILE__)}/fixtures/source_1.rb
9
+ #{File.dirname(__FILE__)}/fixtures/source_2.rb )
10
+ @default_keys_root, Keys.root = Keys.root, File.expand_path(File.dirname(__FILE__)) + '/fixtures'
11
+ FileUtils.cp(@filenames[0], "#{@filenames[0]}.backup")
12
+ end
13
+
14
+ def teardown
15
+ Keys::Index.delete_all
16
+ Keys.root = @default_keys_root
17
+ FileUtils.mv("#{@filenames[0]}.backup", @filenames[0])
18
+ end
19
+
20
+ def test_index_default_pattern
21
+ Keys.config = nil
22
+ assert_equal '/**/*.{rb,erb}', Keys::Index.new.pattern
23
+ end
24
+
25
+ def test_index_finds_files
26
+ expected = %w(test/fixtures/source_1.rb test/fixtures/source_2.rb).map { |p| File.expand_path(p) }
27
+ assert_equal expected, Keys::Index.new.files & expected
28
+ end
29
+
30
+ def test_occurences_built_lazily_on_a_fresh_index
31
+ index = Keys::Index.new(:default)
32
+ expected = [:bar, :baaar, :baar, :bar, :bar_1, :bar_2]
33
+ assert_equal expected, index.occurences.select { |key| expected.include?(key.key) }.map(&:key)
34
+ end
35
+
36
+ def test_occurences_from_marshalled_index
37
+ index = Keys::Index.new(:marshalled)
38
+ index.update
39
+ index = Keys::Index.load(:marshalled)
40
+ expected = [:bar, :baaar, :baar, :bar, :bar_1, :bar_2]
41
+ assert_equal expected, index.occurences.select { |key| expected.include?(key.key) }.map(&:key)
42
+ end
43
+
44
+ def test_index_create_builds_and_saves_the_index
45
+ index = Keys::Index.new(:create)
46
+ assert !index.exists?
47
+ index = Keys::Index.create(:create, :pattern => '/**/*.{rb}')
48
+ assert index.built?
49
+ assert index.exists?
50
+ assert_equal '/**/*.{rb}', index.pattern
51
+ end
52
+
53
+ def test_index_load_or_create_creates_an_index
54
+ index = Keys::Index.load_or_create(:load_or_create, :pattern => '/**/*.{rb}')
55
+ assert index.exists?
56
+ assert_equal '/**/*.{rb}', index.pattern
57
+ end
58
+
59
+ def test_index_load_or_create_or_init_with_no_name_given_does_not_save_the_index
60
+ index = Keys::Index.load_or_create_or_init(:pattern => '/**/*.{rb}')
61
+ assert !index.exists?
62
+ assert_equal '/**/*.{rb}', index.pattern
63
+ end
64
+
65
+ def test_index_load_or_create_or_init_with_true_given_saves_the_default_index
66
+ index = Keys::Index.load_or_create_or_init(true, :pattern => '/**/*.{rb}')
67
+ assert index.exists?
68
+ assert_equal :default, index.name
69
+ assert_equal '/**/*.{rb}', index.pattern
70
+ end
71
+
72
+ def test_index_load_or_create_or_init_with_name_given_saves_the_named_index
73
+ index = Keys::Index.load_or_create_or_init(:foo, :pattern => '/**/*.{rb}')
74
+ assert index.exists?
75
+ assert_equal :foo, index.name
76
+ assert_equal '/**/*.{rb}', index.pattern
77
+ end
78
+
79
+ def test_index_exists_is_true_when_index_directory_exists
80
+ index = Keys::Index.create('foo')
81
+ assert index.exists?
82
+ FileUtils.rm_r(index.filename)
83
+ assert !index.exists?
84
+ end
85
+
86
+ def test_index_inject_with_no_keys_given_iterates_over_all_occurences
87
+ index = Keys::Index.new(:pattern => '/**/*.{rb}')
88
+ expected = [:bar, :baaar, :baar, :'foo.bar', :bar, :bar_1, :bar_2]
89
+ result = index.inject([]) { |result, occurence| result << occurence.key }
90
+ assert_equal expected, result
91
+ end
92
+
93
+ def test_index_inject_with_keys_given_iterates_over_occurences_of_given_keys
94
+ index = Keys::Index.new(:pattern => '/**/*.{rb}')
95
+ expected = [:bar, :baaar, :baar, :bar]
96
+ result = index.inject([], :bar, :baaar, :baar) { |result, occurence| result << occurence.key }
97
+ assert_equal expected, result
98
+ end
99
+
100
+ def test_key_included_with_no_keys_given_returns_true
101
+ index = Keys::Index.new
102
+ assert index.send(:key_matches?, :foo, [])
103
+ end
104
+
105
+ def test_key_included_with_the_key_included_returns_true
106
+ index = Keys::Index.new
107
+ assert index.send(:key_matches?, :foo, { :foo => /^foo$/, :bar => /^bar$/ })
108
+ end
109
+
110
+ def test_key_pattern_with_no_wild_cards_returns_a_pattern_matching_only_the_key
111
+ index = Keys::Index.new
112
+ assert_equal /^foo\.bar$/, index.send(:key_pattern, :'foo.bar')
113
+ end
114
+
115
+ def test_key_pattern_with_a_dot_separated_wild_card_at_the_end_returns_a_pattern_matching_all_keys_starting_with_key
116
+ index = Keys::Index.new
117
+ assert_equal /^foo\.bar\./, index.send(:key_pattern, :'foo.bar.*')
118
+ end
119
+
120
+ def test_replace_replaces_key_without_wildcard_in_source_file
121
+ index = Keys::Index.create(:replace)
122
+ bar = index.by_key[:bar].first
123
+ index.replace!(bar, 'foo')
124
+ assert_equal " t(:foo)\n", bar.line
125
+
126
+ index = Keys::Index.load(:replace)
127
+ foo = index.by_key[:bar].first
128
+ assert foo == bar
129
+ end
130
+
131
+ # TODO
132
+ # - after replace make sure the index is updated
133
+ # - somehow also update the yaml/rb files
134
+
135
+ end
data/test/keys_test.rb ADDED
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class KeysTest < Test::Unit::TestCase
4
+ include I18n
5
+
6
+ def setup
7
+ @filenames = %W( #{File.dirname(__FILE__)}/fixtures/source_1.rb
8
+ #{File.dirname(__FILE__)}/fixtures/source_2.rb )
9
+ @default_keys_root, Keys.root = Keys.root, File.expand_path(File.dirname(__FILE__))
10
+ end
11
+
12
+ def teardown
13
+ Keys::Index.delete_all
14
+ Keys.root = @default_keys_root
15
+ Keys.config = nil
16
+ end
17
+
18
+ def test_root_defaults_to_dot
19
+ assert_equal '.', @default_keys_root
20
+ end
21
+
22
+ def test_can_set_root
23
+ old_root = I18n::Keys.root
24
+ assert_nothing_raised { I18n::Keys.root = 'path/to/root' }
25
+ assert_equal 'path/to/root', I18n::Keys.root
26
+ I18n::Keys.root = old_root
27
+ end
28
+
29
+ def test_index_given_no_name_returns_an_unbuilt_and_unsaved_index
30
+ index = Keys.index
31
+ assert_equal Keys::Index, index.class
32
+ assert !index.built?
33
+ assert !index.exists?
34
+ end
35
+
36
+ def test_index_given_true_as_a_name_returns_the_built_default_index
37
+ index = Keys.index(true)
38
+ assert_equal Keys::Index, index.class
39
+ assert_equal :default, index.name
40
+ assert index.built?
41
+ assert index.exists?
42
+ end
43
+
44
+ def test_index_given_a_regular_name_returns_the_built_named_index
45
+ index = Keys.index(:foo)
46
+ assert_equal Keys::Index, index.class
47
+ assert_equal :foo, index.name
48
+ assert index.built?
49
+ assert index.exists?
50
+ end
51
+
52
+ # def test_find_all_in_file
53
+ # expected = [:bar, :baaar, :baar, "bar", "bar_1"]
54
+ # keys = I18n::Keys.find(:files => @filenames[0])
55
+ # assert_equal expected, keys.select { |key| expected.include?(key.key) }.map(&:key)
56
+ # end
57
+ #
58
+ # def test_find_all_in_files
59
+ # expected = ['bar_1', :bar_2]
60
+ # keys = I18n::Keys.find(:files => @filenames)
61
+ # assert_equal expected, keys.select { |key| expected.include?(key.key) }.map(&:key)
62
+ # end
63
+ #
64
+ # def test_find_key_in_file
65
+ # expected = [:bar]
66
+ # keys = I18n::Keys.find(:keys => :bar, :files => @filenames[0])
67
+ # assert_equal expected, keys.select { |key| expected.include?(key.key) }.map(&:key)
68
+ # end
69
+ #
70
+ # def test_find_key_in_files
71
+ # expected = [:bar]
72
+ # keys = I18n::Keys.find(:keys => :bar, :files => @filenames)
73
+ # assert_equal expected, keys.select { |key| expected.include?(key.key) }.map(&:key)
74
+ # end
75
+ end
@@ -0,0 +1,130 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class OccurenceTest < Test::Unit::TestCase
4
+ def setup
5
+ I18n::Keys.verbose = false
6
+ end
7
+
8
+ def setup
9
+ I18n::Keys.verbose = false
10
+ @filename = "#{File.dirname(__FILE__)}/fixtures/source_1.rb"
11
+ FileUtils.cp(@filename, "#{@filename}.backup")
12
+ end
13
+
14
+ def teardown
15
+ FileUtils.mv("#{@filename}.backup", @filename)
16
+ end
17
+
18
+ def occurence(key = :bar)
19
+ index = I18n::Keys::Index.new
20
+ index.update
21
+ index.by_key[key.to_sym].first
22
+ end
23
+
24
+ def test_line_without_highlighting
25
+ assert_equal " t(:'baar')\n", occurence(:baar).line
26
+ end
27
+
28
+ def test_line_with_highlighting
29
+ assert_equal " t(\e[0;31;1m:'baar'\e[0m)\n", occurence(:baar).line(true)
30
+ end
31
+
32
+ def test_occurence_line_num
33
+ assert_equal 3, occurence.line_num
34
+ end
35
+
36
+ def test_occurence_column
37
+ assert_equal 7, occurence.column
38
+ end
39
+
40
+ def test_occurence_length
41
+ assert_equal 4, occurence.length
42
+ end
43
+
44
+ def test_original_key
45
+ assert ":bar", occurence.original_key
46
+ end
47
+
48
+ def test_content_head
49
+ assert_equal "def foo\n t(", occurence.content_head[-14, 999]
50
+ end
51
+
52
+ def test_content_tail
53
+ assert_equal ")\n t(:\"baaar\")\n", occurence.content_tail[0, 18]
54
+ end
55
+
56
+ def test_line_head
57
+ assert_equal " t(", occurence.line_head
58
+ end
59
+
60
+ def test_line_tail
61
+ assert_equal ")\n", occurence.line_tail
62
+ end
63
+
64
+ def test_context_returns_the_context_of_the_occurence_with_2_lines_setting
65
+ context = occurence(:baar).context.split("\n")
66
+ assert_equal 5, context.length
67
+ assert_equal " t(\e[0;31;1m:'baar'\e[0m)", context[2]
68
+ end
69
+
70
+ def test_context_returns_the_context_of_the_occurence_with_3_lines_setting
71
+ I18n::Keys::Occurence.context_lines = 3
72
+ context = occurence(:baar).context.split("\n")
73
+ assert_equal 7, context.length
74
+ assert_equal " t(\e[0;31;1m:'baar'\e[0m)", context[3]
75
+ end
76
+
77
+ def test_replace_simple_symbol_with_simple_symbol
78
+ bar = occurence(:bar)
79
+ bar.replace!(:oooooooo)
80
+ assert_equal " t(\e[0;31;1m:oooooooo\e[0m)\n", bar.line(true)
81
+ end
82
+
83
+ def test_replace_simple_symbol_with_quoted_symbol
84
+ bar = occurence(:bar)
85
+ bar.replace!(:'oooo.oooo')
86
+ assert_equal " t(\e[0;31;1m:\"oooo.oooo\"\e[0m)\n", bar.line(true)
87
+ end
88
+
89
+ def test_replace_simple_symbol_with_string
90
+ bar = occurence(:bar)
91
+ bar.replace!('oooooooo')
92
+ assert_equal " t(\e[0;31;1m:oooooooo\e[0m)\n", bar.line(true)
93
+ end
94
+
95
+ def test_replace_quoted_symbol_with_simple_symbol
96
+ bar = occurence(:'foo.bar')
97
+ bar.replace!(:oooooooo)
98
+ assert_equal " t(\e[0;31;1m:oooooooo\e[0m)\n", bar.line(true)
99
+ end
100
+
101
+ def test_replace_quoted_symbol_with_quoted_symbol
102
+ bar = occurence(:'foo.bar')
103
+ bar.replace!(:'oooo.oooo')
104
+ assert_equal " t(\e[0;31;1m:\"oooo.oooo\"\e[0m)\n", bar.line(true)
105
+ end
106
+
107
+ def test_replace_quoted_symbol_with_string
108
+ bar = occurence(:'foo.bar')
109
+ bar.replace!('oooooooo')
110
+ assert_equal " t(\e[0;31;1m:oooooooo\e[0m)\n", bar.line(true)
111
+ end
112
+
113
+ def test_replace_string_with_simple_symbol
114
+ bar = occurence('bar_1')
115
+ bar.replace!(:oooooooo)
116
+ assert_equal " t(\e[0;31;1m:oooooooo\e[0m)\n", bar.line(true)
117
+ end
118
+
119
+ def test_replace_string_with_quoted_symbol
120
+ bar = occurence('bar_1')
121
+ bar.replace!(:'oooo.oooo')
122
+ assert_equal " t(\e[0;31;1m:\"oooo.oooo\"\e[0m)\n", bar.line(true)
123
+ end
124
+
125
+ def test_replace_string_with_string
126
+ bar = occurence('bar_1')
127
+ bar.replace!('oooooooo')
128
+ assert_equal " t(\e[0;31;1m:oooooooo\e[0m)\n", bar.line(true)
129
+ end
130
+ end
@@ -0,0 +1,54 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RubyParserTest < Test::Unit::TestCase
4
+ def test_sexp_filename
5
+ filename = File.dirname(__FILE__) + '/fixtures/source_1.rb'
6
+ sexp = I18n::RubyParser.new.parse(File.read(filename), filename)
7
+ assert_equal filename, sexp.file
8
+ end
9
+
10
+ def test_sexp_source_positions
11
+ assert_equal 2, 't(:foo)'.to_sexp.find_node(:lit).source_start_pos
12
+ assert_equal 5, 't(:foo)'.to_sexp.find_node(:lit).source_end_pos
13
+ end
14
+
15
+ def test_sexp_source_symbol
16
+ assert_equal ':foo', 't(:foo)'.to_sexp.find_node(:lit).source
17
+ end
18
+
19
+ def test_sexp_source_symbol_and_options
20
+ assert_equal ':foo', 't(:foo, :bar => :baz)'.to_sexp.find_node(:lit).source
21
+ end
22
+
23
+ def test_sexp_source_symbol_with_double_quotes
24
+ assert_equal ':"foo"', 't(:"foo")'.to_sexp.find_node(:lit).source
25
+ end
26
+
27
+ def test_sexp_source_symbol_with_double_quotes_and_options
28
+ assert_equal ':"foo"', 't(:"foo", :bar => :baz)'.to_sexp.find_node(:lit).source
29
+ end
30
+
31
+ def test_sexp_source_symbol_with_single_quotes
32
+ assert_equal ":'foo'", "t(:'foo')".to_sexp.find_node(:lit).source
33
+ end
34
+
35
+ def test_sexp_source_symbol_with_single_quotes_and_options
36
+ assert_equal ":'foo'", "t(:'foo', :bar => :baz)".to_sexp.find_node(:lit).source
37
+ end
38
+
39
+ def test_sexp_source_string_with_double_quotes
40
+ assert_equal '"foo"', 't("foo")'.to_sexp.find_node(:str).source
41
+ end
42
+
43
+ def test_sexp_source_string_with_double_quotes_and_options
44
+ assert_equal '"foo"', 't("foo", :bar => :baz)'.to_sexp.find_node(:str).source
45
+ end
46
+
47
+ def test_sexp_source_string_with_single_quotes
48
+ assert_equal "'foo'", "t('foo')".to_sexp.find_node(:str).source
49
+ end
50
+
51
+ def test_sexp_source_string_with_single_quotes_and_options
52
+ assert_equal "'foo'", "t('foo', :bar => :baz)".to_sexp.find_node(:str).source
53
+ end
54
+ end
@@ -0,0 +1,14 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/i18n/keys'
3
+
4
+ class String
5
+ def to_sexp
6
+ I18n::RubyParser.new.parse(self)
7
+ end
8
+ end
9
+
10
+ class Sexp
11
+ def find_node(type)
12
+ each_of_type(type) { |node| return node }
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: i18n-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Sven Fuchs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-26 09:00:00 +02:00
13
+ default_executable: i18n-keys
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: visionmedia-commander
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.5.7
24
+ version:
25
+ description: Tools for Ruby/Rails I18n
26
+ email: rails-i18n@googlegroups.com
27
+ executables:
28
+ - i18n-keys
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - bin/i18n-keys
35
+ - lib/ansi.rb
36
+ - lib/i18n/keys/commands.rb
37
+ - lib/i18n/keys/formatter.rb
38
+ - lib/i18n/keys/index.rb
39
+ - lib/i18n/keys/occurence.rb
40
+ - lib/i18n/keys.rb
41
+ - lib/i18n/parser/erb_parser.rb
42
+ - lib/i18n/parser/ruby_parser.rb
43
+ - MIT-LICENSE
44
+ - README.textile
45
+ has_rdoc: false
46
+ homepage: http://rails-i18n.org
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: Tools for Ruby/Rails I18n
73
+ test_files:
74
+ - test/all.rb
75
+ - test/commands_test.rb
76
+ - test/erb_parser_test.rb
77
+ - test/fixtures/source_1.rb
78
+ - test/fixtures/source_2.rb
79
+ - test/fixtures/template.html.erb
80
+ - test/index_test.rb
81
+ - test/keys_test.rb
82
+ - test/occurence_test.rb
83
+ - test/ruby_parser_test.rb
84
+ - test/test_helper.rb