i18n-tools 0.0.2

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