svenfuchs-i18n-tools 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/core_ext/hash/iterate_nested.rb +35 -0
- data/lib/core_ext/hash/slice.rb +20 -0
- data/lib/core_ext/hash/sorted_yaml_style.rb +17 -0
- data/lib/core_ext/hash/symbolize_keys.rb +14 -0
- data/lib/core_ext/module/attribute_accessors.rb +48 -0
- data/lib/core_ext/object/deep_clone.rb +5 -0
- data/lib/core_ext/object/instance_variables.rb +9 -0
- data/lib/core_ext/object/meta_class.rb +5 -0
- data/lib/core_ext/object/tap.rb +6 -0
- data/lib/i18n/backend/simple_storage.rb +119 -0
- data/lib/i18n/commands/keys.rb +84 -0
- data/lib/i18n/exceptions/key_exists.rb +9 -0
- data/lib/i18n/index.rb +33 -0
- data/lib/i18n/index/base.rb +38 -0
- data/lib/i18n/index/file.rb +55 -0
- data/lib/i18n/index/format.rb +49 -0
- data/lib/i18n/index/key.rb +45 -0
- data/lib/i18n/index/occurence.rb +18 -0
- data/lib/i18n/index/simple.rb +69 -0
- data/lib/i18n/index/simple/data.rb +34 -0
- data/lib/i18n/index/simple/storage.rb +79 -0
- data/lib/i18n/ripper2ruby.rb +7 -0
- data/lib/i18n/ripper2ruby/translate_args_list.rb +89 -0
- data/lib/i18n/ripper2ruby/translate_call.rb +66 -0
- data/lib/i18n/translation_properties.rb +38 -0
- data/test/all.rb +1 -1
- data/test/core_ext/hash_iterate_nested.rb +31 -0
- data/test/fixtures/all.rb.src +106 -0
- data/test/fixtures/config.yml +3 -0
- data/test/fixtures/locale/de.yml +4 -0
- data/test/fixtures/locale/en.yml +4 -0
- data/test/fixtures/source_1.rb +2 -1
- data/test/fixtures/translate/double_key.rb +32 -0
- data/test/fixtures/translate/double_scope.rb +32 -0
- data/test/fixtures/translate/single_key.rb +10 -0
- data/test/fixtures/translate/single_scope.rb +32 -0
- data/test/i18n/backend/simple_storage_test.rb +81 -0
- data/test/i18n/backend/translation_properties_test.rb +33 -0
- data/test/i18n/index/all.rb +1 -0
- data/test/i18n/index/args_replace_test.rb +218 -0
- data/test/i18n/index/calls_replace_test.rb +67 -0
- data/test/i18n/index/commands_test.rb +75 -0
- data/test/i18n/index/key_test.rb +32 -0
- data/test/i18n/index/simple_test.rb +67 -0
- data/test/i18n/ripper2ruby/translate_call_test.rb +98 -0
- data/test/test_helper.rb +66 -9
- metadata +49 -32
- data/MIT-LICENSE +0 -20
- data/README.textile +0 -1
- data/bin/i18n-keys +0 -6
- data/lib/ansi.rb +0 -19
- data/lib/i18n/keys.rb +0 -51
- data/lib/i18n/keys/commands.rb +0 -53
- data/lib/i18n/keys/formatter.rb +0 -39
- data/lib/i18n/keys/index.rb +0 -209
- data/lib/i18n/keys/occurence.rb +0 -120
- data/lib/i18n/parser/erb_parser.rb +0 -54
- data/lib/i18n/parser/ruby_parser.rb +0 -93
- data/test/commands_test.rb +0 -1
- data/test/erb_parser_test.rb +0 -31
- data/test/index_test.rb +0 -135
- data/test/keys_test.rb +0 -75
- data/test/occurence_test.rb +0 -130
- data/test/ruby_parser_test.rb +0 -54
@@ -0,0 +1,55 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Files < Hash
|
4
|
+
def [](filename)
|
5
|
+
fetch(filename)
|
6
|
+
rescue
|
7
|
+
store(filename, File.new(filename))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class File
|
12
|
+
attr_accessor :filename
|
13
|
+
|
14
|
+
def initialize(filename)
|
15
|
+
self.filename = filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def source
|
19
|
+
@source ||= begin
|
20
|
+
source = ::File.read(filename) # TODO srsly ... how am i supposed to open a file in ruby 1.9?
|
21
|
+
source = ::File.open(filename, 'r:iso-8859-1:utf-8') { |f| f.read } unless source.valid_encoding?
|
22
|
+
source
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def ruby
|
27
|
+
@ruby ||= begin
|
28
|
+
parser.parse
|
29
|
+
rescue Ripper::RubyBuilder::ParseError => e
|
30
|
+
puts "\nWARNING Ruby 1.9 incompatible syntax in: " + e.message.gsub(Dir.pwd, '') + ". Can not index file."
|
31
|
+
Ruby::Program.new(source, filename)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def calls
|
36
|
+
@calls ||= ruby.select_translate_calls
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(source)
|
40
|
+
@source = source
|
41
|
+
save
|
42
|
+
end
|
43
|
+
|
44
|
+
def save
|
45
|
+
::File.open(filename, 'w+') { |f| f.write(source) } # TODO need to modify/write unfiltered source for ERB
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def parser
|
51
|
+
@parser ||= Index.parser.new(source, filename)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# wicked. but i can't see a better pattern right now. i want a module to include
|
2
|
+
# into the meta_class. and i don't want to set the io object globally (or on
|
3
|
+
# any class or module var) so i need to instantiate something, too.
|
4
|
+
|
5
|
+
module I18n
|
6
|
+
module Index
|
7
|
+
module Format
|
8
|
+
class Base
|
9
|
+
attr_accessor :io
|
10
|
+
|
11
|
+
def initialize(io = nil)
|
12
|
+
self.io = io || $stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
def out(str)
|
16
|
+
io.print(str)
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup(target)
|
20
|
+
$stdout.sync = true
|
21
|
+
(class << target; self; end).send(:include, self.class::Hooks)
|
22
|
+
target.format = self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Stdout < Base
|
27
|
+
module Hooks
|
28
|
+
attr_accessor :format
|
29
|
+
|
30
|
+
def build(*args)
|
31
|
+
format.out "indexing files ... "
|
32
|
+
super
|
33
|
+
format.out "found #{occurences.size} occurences of #{keys.size} keys in #{filenames.size} files in total\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
def save
|
37
|
+
format.out "saving index\n"
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(file)
|
42
|
+
format.out '.'
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Key
|
4
|
+
class << self
|
5
|
+
def patterns(*keys)
|
6
|
+
keys.inject({}) { |result, key| result[key] = pattern(key); result }
|
7
|
+
end
|
8
|
+
|
9
|
+
def pattern(key)
|
10
|
+
key = key.to_s.dup
|
11
|
+
match_start = key.gsub!(/^\*/, '') ? '' : '^'
|
12
|
+
match_end = key.gsub!(/\*$/, '') ? '' : '$'
|
13
|
+
pattern = Regexp.escape("#{key}")
|
14
|
+
/#{match_start}#{pattern}#{match_end}/
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :key
|
19
|
+
|
20
|
+
def initialize(key)
|
21
|
+
self.key = key
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches?(*keys)
|
25
|
+
patterns = Key.patterns(*keys)
|
26
|
+
patterns.empty? || patterns.any? do |key, pattern|
|
27
|
+
self.key == key.to_sym || self.key.to_s =~ pattern
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def <=>(other)
|
32
|
+
key <=> other.key
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
key == (other.respond_to?(:key) ? other.key : other)
|
37
|
+
end
|
38
|
+
alias eql? ==
|
39
|
+
|
40
|
+
def hash
|
41
|
+
key.hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Occurence
|
4
|
+
attr_reader :key, :filename, :position
|
5
|
+
|
6
|
+
def initialize(key, filename, position)
|
7
|
+
@key = key
|
8
|
+
@filename = filename
|
9
|
+
@position = position
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
key == other.key && filename == other.filename && position == other.position
|
14
|
+
end
|
15
|
+
alias eql? ==
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'i18n/index/base'
|
2
|
+
require 'i18n/index/key'
|
3
|
+
require 'i18n/index/occurence'
|
4
|
+
require 'i18n/index/format'
|
5
|
+
require 'i18n/index/simple/storage'
|
6
|
+
require 'i18n/index/simple/data'
|
7
|
+
require 'erb/stripper'
|
8
|
+
|
9
|
+
module I18n
|
10
|
+
module Index
|
11
|
+
class Simple < Base
|
12
|
+
include Storage
|
13
|
+
|
14
|
+
def find_call(*keys)
|
15
|
+
return unless key = data.keys.detect { |key, data| key.matches?(*keys) }
|
16
|
+
occurence = data.occurences(key).first
|
17
|
+
files[occurence.filename].ruby.select_translate_calls(:position => occurence.position).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_calls(*keys)
|
21
|
+
keys = data.keys.select { |key, data| key.matches?(*keys) }
|
22
|
+
occurences = keys.map { |key| data.occurences(key) }.flatten
|
23
|
+
occurences.map do |occurence|
|
24
|
+
files[occurence.filename].ruby.select_translate_calls(:position => occurence.position)
|
25
|
+
end.flatten
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace_key(call, search, replacement)
|
29
|
+
data.remove(call)
|
30
|
+
call.replace_key(search.to_s.gsub(/[^\w\.]/, ''), replacement.to_sym)
|
31
|
+
files[call.filename].update(call.root.src)
|
32
|
+
data.add(call)
|
33
|
+
save if built?
|
34
|
+
end
|
35
|
+
|
36
|
+
def data
|
37
|
+
@data ||= Data.new
|
38
|
+
build unless built?
|
39
|
+
@data
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys
|
43
|
+
data.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def occurences
|
47
|
+
data.values.map { |value| value[:occurences] }.flatten
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def reset!
|
53
|
+
@built = false
|
54
|
+
@data = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def built?
|
58
|
+
@built
|
59
|
+
end
|
60
|
+
|
61
|
+
def build
|
62
|
+
reset!
|
63
|
+
@built = true
|
64
|
+
calls = filenames.map { |filename| files[filename].calls }.flatten
|
65
|
+
calls.each { |call| data.add(call) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Simple < Base
|
4
|
+
class Data < Hash
|
5
|
+
def add(call)
|
6
|
+
key = Key.new(call.full_key(true))
|
7
|
+
occurences(key) << Occurence.new(key, call.filename, call.position)
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove(call)
|
11
|
+
key = Key.new(call.full_key(true))
|
12
|
+
occurences = occurences(key)
|
13
|
+
occurences.delete(Occurence.new(key, call.filename, call.position))
|
14
|
+
self.delete(key) if occurences.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def occurences(key)
|
18
|
+
self[key] ||= { :occurences => [] }
|
19
|
+
self[key][:occurences]
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
key = Key.new(key) unless key.is_a?(Key)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(key, value)
|
28
|
+
key = Key.new(key) unless key.is_a?(Key)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module I18n
|
2
|
+
module Index
|
3
|
+
class Simple < Base
|
4
|
+
module Storage
|
5
|
+
class << self
|
6
|
+
def included(target)
|
7
|
+
target.send(:extend, ClassMethods)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def load_or_create(options = {})
|
13
|
+
exists?(options) ? load(options) : create(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def create(options = {})
|
19
|
+
index = Simple.new(options)
|
20
|
+
index.update
|
21
|
+
index
|
22
|
+
end
|
23
|
+
|
24
|
+
def load(options)
|
25
|
+
::File.open(filename(options), 'r') { |f| ::Marshal.load(f) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def exists?(options)
|
29
|
+
::File.exists?(filename(options))
|
30
|
+
end
|
31
|
+
|
32
|
+
def filename(options)
|
33
|
+
store_dir(options) + "/index.marshal"
|
34
|
+
end
|
35
|
+
|
36
|
+
def store_dir(options)
|
37
|
+
root_dir = options[:root_dir] || Dir.pwd
|
38
|
+
::File.expand_path(root_dir + '/.i18n')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def exists?
|
44
|
+
::File.exists?(filename)
|
45
|
+
end
|
46
|
+
|
47
|
+
def update
|
48
|
+
reset!
|
49
|
+
build
|
50
|
+
save
|
51
|
+
end
|
52
|
+
|
53
|
+
def save
|
54
|
+
mkdir
|
55
|
+
::File.open(filename, 'w+') { |f| ::Marshal.dump(self, f) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete
|
59
|
+
FileUtils.rm(filename) if exists? rescue Errno::ENOENT
|
60
|
+
end
|
61
|
+
|
62
|
+
def filename
|
63
|
+
self.class.send(:filename, :root_dir => root_dir)
|
64
|
+
end
|
65
|
+
|
66
|
+
def store_dir
|
67
|
+
self.class.send(:store_dir, :root_dir => root_dir)
|
68
|
+
end
|
69
|
+
|
70
|
+
def mkdir
|
71
|
+
FileUtils.mkdir_p(store_dir) unless ::File.exists?(store_dir)
|
72
|
+
end
|
73
|
+
|
74
|
+
def marshalled_vars
|
75
|
+
super + [:built, :data]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Ruby
|
2
|
+
class ArgsList
|
3
|
+
def to_translate_args_list
|
4
|
+
meta_class.send(:include, TranslateArgsList)
|
5
|
+
self
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module TranslateArgsList
|
10
|
+
def full_key(joined = false)
|
11
|
+
full_key = normalize_keys(scope, key)
|
12
|
+
joined ? join_key(full_key) : full_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
first.arg.value if first
|
17
|
+
end
|
18
|
+
|
19
|
+
def scope
|
20
|
+
last.arg.is_a?(Ruby::Hash) ? last.arg.value[:scope] : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def key_matches?(keys)
|
24
|
+
keys = normalize_keys(keys)
|
25
|
+
keys == full_key[0, keys.length]
|
26
|
+
end
|
27
|
+
|
28
|
+
def replace_key(search, replace)
|
29
|
+
original_length = length
|
30
|
+
self.key, self.scope = compute_replace_keys(search, replace)
|
31
|
+
root.replace_src(row, column, original_length, to_ruby)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def key=(key)
|
37
|
+
self[0] = key
|
38
|
+
end
|
39
|
+
|
40
|
+
def scope=(scope)
|
41
|
+
if scope
|
42
|
+
set_option(:scope, scope)
|
43
|
+
elsif options
|
44
|
+
options.delete(:scope)
|
45
|
+
pop if options.arg.empty?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def compute_replace_keys(search, replace)
|
50
|
+
search = normalize_keys(search)
|
51
|
+
replace = normalize_keys(replace)
|
52
|
+
key = normalize_keys(self.key)
|
53
|
+
scope = normalize_keys(self.scope)
|
54
|
+
all = scope + key
|
55
|
+
|
56
|
+
all[key_index(search), search.length] = replace
|
57
|
+
|
58
|
+
if scope.empty?
|
59
|
+
key = all
|
60
|
+
else
|
61
|
+
key = all.slice!(-[key.length, all.size].min..-1) # i.e. we preserve the key length, this is debatable
|
62
|
+
scope = all.empty? ? nil : all
|
63
|
+
# scope = all.slice!(0, scope.length) # this would preserve the scope length
|
64
|
+
# key = all # comment this in, previous lines out and observe the tests
|
65
|
+
end
|
66
|
+
|
67
|
+
key = [scope.pop] if key.empty?
|
68
|
+
scope = scope.first if scope && scope.size == 1
|
69
|
+
scope = nil if scope && scope.empty?
|
70
|
+
|
71
|
+
[join_key(key), scope]
|
72
|
+
end
|
73
|
+
|
74
|
+
def key_index(search)
|
75
|
+
(all = full_key).each_index { |ix| return ix if all[ix, search.length] == search } and nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def join_key(key)
|
79
|
+
key.map { |k| k.to_s }.join('.').to_sym
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalize_keys(*args)
|
83
|
+
args.flatten.
|
84
|
+
map { |k| k.to_s.split(/\./) }.flatten.
|
85
|
+
reject { |k| k.empty? }.
|
86
|
+
map { |k| k.to_sym }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|