grosser-get_pomo 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/README.markdown ADDED
@@ -0,0 +1,47 @@
1
+ A simple and extendable .mo and .po file parser/generator.
2
+
3
+ Advanteges over original [mo](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/mofile.rb) / [po](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/poparser.rb)-parser:
4
+
5
+ - simple architecture + easy to extend/modify
6
+ - emtpy msgstr translations are read
7
+ - comments are included
8
+ - fuzzy can be set/unset
9
+ - multiple translations can be combined in a new po file(with comments and fuzzy and ...)
10
+ - po files can be written from any kind of input
11
+ - easy mo-file handling/merging
12
+ - po/mo file handling is identical, if you know one, you know both
13
+
14
+ Setup
15
+ =====
16
+ sudo gem install get_pomo
17
+
18
+ ###Static interface
19
+ #parse po files
20
+ translations = GetPomo::PoFile.parse(File.read('xxx.po')) + GetPomo::PoFile.parse(File.read('yyy.po'))
21
+
22
+ #and use the data...
23
+ msgids = translations.reject{|t|t.plural? or t.fuzzy?}.map(&:msgid)
24
+
25
+ #or write a new po file (unique by msgid)...
26
+ File.open('xxx.po','w){|f|f.print(GetPomo::PoFile.to_text(translations))}
27
+
28
+
29
+ ###Instance interface
30
+ p = GetPomo::PoFile.new
31
+ p.add_translations_from_text(File.read('...'))
32
+ ...
33
+ p.translations
34
+ p.to_text
35
+
36
+ `GetPomo::MoFile` behaves identical.
37
+
38
+ TODO
39
+ ====
40
+ - extracting of version/pluralisation_rule/plurals/translator... (from msgid "")
41
+ - the vendor/mofile is really complex, maybe it can be refactored (also some parts are not needed)
42
+
43
+ Author
44
+ ======
45
+ [Michael Grosser](http://pragmatig.wordpress.com)
46
+ grosser.michael@gmail.com
47
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ task :default => :spec
2
+ require 'spec/rake/spectask'
3
+ Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
4
+
5
+ begin
6
+ project_name = 'get_pomo'
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = project_name
10
+ gem.summary = "Ruby/Gettext: A .po and .mo file parser/generator"
11
+ gem.email = "grosser.michael@gmail.com"
12
+ gem.homepage = "http://github.com/grosser/#{project_name}"
13
+ gem.authors = ["Michael Grosser"]
14
+ end
15
+
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.0
data/get_pomo.gemspec ADDED
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{grosser-get_pomo}
8
+ s.version = "0.6.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2009-11-20}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "README.markdown"
16
+ ]
17
+ s.files = [
18
+ ".gitignore",
19
+ "README.markdown",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "get_pomo.gemspec",
23
+ "lib/get_pomo.rb",
24
+ "lib/get_pomo/mo_file.rb",
25
+ "lib/get_pomo/po_file.rb",
26
+ "lib/get_pomo/translation.rb",
27
+ "prototype_treetop/po.treetop",
28
+ "prototype_treetop/test.rb",
29
+ "spec/files/complex.mo",
30
+ "spec/files/empty.mo",
31
+ "spec/files/plural.mo",
32
+ "spec/files/singular.mo",
33
+ "spec/files/singular_2.mo",
34
+ "spec/pomo/mo_file_spec.rb",
35
+ "spec/pomo/po_file_spec.rb",
36
+ "spec/pomo/translation_spec.rb",
37
+ "spec/pomo_spec.rb",
38
+ "spec/spec_helper.rb",
39
+ "vendor/README.rdoc",
40
+ "vendor/iconv.rb",
41
+ "vendor/mofile.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/grosser/get_pomo}
44
+ s.rdoc_options = ["--charset=UTF-8"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.3.5}
47
+ s.summary = %q{Ruby/Gettext: A .po and .mo file parser/generator}
48
+ s.test_files = [
49
+ "spec/spec_helper.rb",
50
+ "spec/pomo_spec.rb",
51
+ "spec/pomo/translation_spec.rb",
52
+ "spec/pomo/mo_file_spec.rb",
53
+ "spec/pomo/po_file_spec.rb"
54
+ ]
55
+
56
+ if s.respond_to? :specification_version then
57
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
58
+ s.specification_version = 3
59
+
60
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
61
+ else
62
+ end
63
+ else
64
+ end
65
+ end
66
+
data/lib/get_pomo.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'get_pomo/po_file'
2
+ module GetPomo
3
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
4
+
5
+ extend self
6
+
7
+ def self.unique_translations(translations)
8
+ last_seen_at_index = {}
9
+ translations.each_with_index {|translation,index|last_seen_at_index[translation.msgid]=index}
10
+ last_seen_at_index.values.sort.map{|index| translations[index]}
11
+ end
12
+ end
@@ -0,0 +1,62 @@
1
+ require 'get_pomo/translation'
2
+ require File.join(File.dirname(__FILE__),'..','..','vendor','mofile')
3
+
4
+ module GetPomo
5
+ class MoFile
6
+ PLURAL_SEPERATOR = "\000"
7
+
8
+ def self.parse(text)
9
+ MoFile.new.add_translations_from_text(text)
10
+ end
11
+
12
+ def self.to_text(translations)
13
+ m = MoFile.new(:translations=>translations)
14
+ m.to_text
15
+ end
16
+
17
+ attr_reader :translations
18
+
19
+ def initialize(options = {})
20
+ @translations = options[:translations] || []
21
+ end
22
+
23
+ def add_translations_from_text(text)
24
+ text = StringIO.new(text)
25
+ @translations += GetPomo::GetText::MOFile.open(text, "UTF-8").map do |msgid,msgstr|
26
+ translation = Translation.new
27
+ if plural? msgid or plural? msgstr
28
+ translation.msgid = split_plural(msgid)
29
+ translation.msgstr = split_plural(msgstr)
30
+ else
31
+ translation.msgid = msgid
32
+ translation.msgstr = msgstr
33
+ end
34
+ translation
35
+ end
36
+ end
37
+
38
+ def to_text
39
+ m = GetPomo::GetText::MOFile.new
40
+ GetPomo.unique_translations(translations).each {|t| m[plural_to_string(t.msgid)] = plural_to_string(t.msgstr)}
41
+
42
+ io = StringIO.new
43
+ m.save_to_stream io
44
+ io.rewind
45
+ io.read
46
+ end
47
+
48
+ private
49
+
50
+ def plural_to_string(plural_or_singular)
51
+ [*plural_or_singular] * PLURAL_SEPERATOR
52
+ end
53
+
54
+ def plural? string
55
+ string.include? PLURAL_SEPERATOR
56
+ end
57
+
58
+ def split_plural string
59
+ string.split PLURAL_SEPERATOR
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,111 @@
1
+ require 'get_pomo/translation'
2
+
3
+ module GetPomo
4
+ class PoFile
5
+ def self.parse(text)
6
+ PoFile.new.add_translations_from_text(text)
7
+ end
8
+
9
+ def self.to_text(translations)
10
+ p = PoFile.new(:translations=>translations)
11
+ p.to_text
12
+ end
13
+
14
+ attr_reader :translations
15
+
16
+ def initialize(options = {})
17
+ @translations = options[:translations] || []
18
+ end
19
+
20
+ #the text is split into lines and then converted into logical translations
21
+ #each translation consists of comments(that come before a translation)
22
+ #and a msgid / msgstr
23
+ def add_translations_from_text(text)
24
+ start_new_translation
25
+ text.split(/$/).each_with_index do |line,index|
26
+ @line_number = index + 1
27
+ next if line.empty?
28
+ if method_call? line
29
+ parse_method_call line
30
+ elsif comment? line
31
+ add_comment line
32
+ else
33
+ add_string line
34
+ end
35
+ end
36
+ start_new_translation #instance_variable has to be overwritten or errors can occur on next add
37
+ translations
38
+ end
39
+
40
+ def to_text
41
+ GetPomo.unique_translations(translations).map {|translation|
42
+ comment = translation.comment.to_s.split(/\n|\r\n/).map{|line|"##{line}\n"}*''
43
+ msgid_and_msgstr = if translation.plural?
44
+ msgids =
45
+ %Q(msgid "#{translation.msgid[0]}"\n)+
46
+ %Q(msgid_plural "#{translation.msgid[1]}"\n)
47
+
48
+ msgstrs = []
49
+ translation.msgstr.each_with_index do |msgstr,index|
50
+ msgstrs << %Q(msgstr[#{index}] "#{msgstr}")
51
+ end
52
+
53
+ msgids + (msgstrs*"\n")
54
+ else
55
+ %Q(msgid "#{translation.msgid}"\n)+
56
+ %Q(msgstr "#{translation.msgstr}")
57
+ end
58
+
59
+ comment + msgid_and_msgstr
60
+ } * "\n\n"
61
+ end
62
+
63
+ private
64
+
65
+ #e.g. # fuzzy
66
+ def comment?(line)
67
+ line =~ /^\s*#/
68
+ end
69
+
70
+ def add_comment(line)
71
+ start_new_translation if translation_complete?
72
+ @current_translation.add_text(line.strip.sub('#','')+"\n",:to=>:comment)
73
+ end
74
+
75
+ #msgid "hello"
76
+ def method_call?(line)
77
+ line =~ /^\s*[a-z]/
78
+ end
79
+
80
+ #msgid "hello" -> method call msgid + add string "hello"
81
+ def parse_method_call(line)
82
+ method, string = line.match(/^\s*([a-z0-9_\[\]]+)(.*)/)[1..2]
83
+ raise "no method found" unless method
84
+
85
+ start_new_translation if method == 'msgid' and translation_complete?
86
+ @last_method = method.to_sym
87
+ add_string(string)
88
+ end
89
+
90
+ #"hello" -> hello
91
+ def add_string(string)
92
+ return if string.strip.empty?
93
+ raise "not string format: #{string.inspect} on line #{@line_number}" unless string.strip =~ /^['"](.*)['"]$/
94
+ @current_translation.add_text($1,:to=>@last_method)
95
+ end
96
+
97
+ def translation_complete?
98
+ return false unless @current_translation
99
+ @current_translation.complete?
100
+ end
101
+
102
+ def store_translation
103
+ @translations += [@current_translation] if @current_translation.complete?
104
+ end
105
+
106
+ def start_new_translation
107
+ store_translation if translation_complete?
108
+ @current_translation = Translation.new
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,44 @@
1
+ module GetPomo
2
+ class Translation
3
+ FUZZY_REGEX = /fuzzy\n$/
4
+ attr_accessor :msgid, :msgstr, :comment
5
+
6
+ def add_text(text,options)
7
+ to = options[:to]
8
+ if to.to_sym == :msgid_plural
9
+ self.msgid = [msgid] unless msgid.is_a? Array
10
+ msgid[1] = msgid[1].to_s + text
11
+ elsif to.to_s =~ /^msgstr\[(\d)\]$/
12
+ self.msgstr ||= []
13
+ msgstr[$1.to_i] = msgstr[$1.to_i].to_s + text
14
+ else
15
+ #simple form
16
+ send("#{to}=",send(to).to_s+text)
17
+ end
18
+ end
19
+
20
+ def to_hash
21
+ {:msgid=>msgid,:msgstr=>msgstr,:comment=>comment}.reject{|k,value|value.nil?}
22
+ end
23
+
24
+ def complete?
25
+ not msgid.nil? and not msgstr.nil?
26
+ end
27
+
28
+ def fuzzy?
29
+ comment =~ FUZZY_REGEX
30
+ end
31
+
32
+ def fuzzy=(value)
33
+ if value and not fuzzy?
34
+ add_text "\nfuzzy", :to=>:comment
35
+ else
36
+ self.comment = comment.to_s.split(/$/).reject{|line|line=~FUZZY_REGEX}.join("\n")
37
+ end
38
+ end
39
+
40
+ def plural?
41
+ msgid.is_a? Array or msgstr.is_a? Array
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ grammar Po
2
+ rule translation
3
+ msgid whitespace string
4
+ end
5
+
6
+ rule string
7
+ string whitespace string
8
+ end
9
+
10
+ rule quote
11
+ '"'
12
+ end
13
+
14
+ rule text
15
+ [^"]*
16
+ end
17
+
18
+ rule whitespace
19
+ [ \t\n\r]+
20
+ end
21
+
22
+ rule msgid
23
+ "msgid"
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'treetop'
3
+
4
+ Treetop.load "po"
5
+ parser = PoParser.new
6
+ puts parser.parse(%Q(msgid "xxx" "yyy"))
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,58 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+ require 'get_pomo/mo_file'
3
+
4
+ include GetPomo
5
+ describe GetPomo::MoFile do
6
+ it "parses empty mo file" do
7
+ MoFile.parse(File.read('spec/files/empty.mo')).should == []
8
+ end
9
+
10
+ it "parses empty strings" do
11
+ MoFile.parse(File.read('spec/files/empty.mo')).should == []
12
+ end
13
+
14
+ it "reads singulars" do
15
+ t = MoFile.parse(File.read('spec/files/singular.mo'))[0]
16
+ t.to_hash.should == {:msgid=>'Back',:msgstr=>'Zurück'}
17
+ end
18
+
19
+ it "reads plurals" do
20
+ t = MoFile.parse(File.read('spec/files/plural.mo'))[0]
21
+ t.to_hash.should == {:msgid=>['Axis','Axis'],:msgstr=>['Achse','Achsen']}
22
+ end
23
+
24
+ describe 'instance methods' do
25
+ it "combines multiple translations" do
26
+ m = MoFile.new
27
+ m.add_translations_from_text(File.read('spec/files/plural.mo'))
28
+ m.add_translations_from_text(File.read('spec/files/singular.mo'))
29
+ m.should have(2).translations
30
+ m.translations[0].msgid.should_not == m.translations[1].msgid
31
+ end
32
+
33
+ it "can be initialized with translations" do
34
+ m = MoFile.new(:translations=>['x'])
35
+ m.translations.should == ['x']
36
+ end
37
+
38
+ it "does not generate duplicate translations" do
39
+ second_version = File.read('spec/files/singular_2.mo')
40
+ m = MoFile.new
41
+ m.add_translations_from_text(File.read('spec/files/singular.mo'))
42
+ m.add_translations_from_text(second_version)
43
+ m.to_text.should == second_version
44
+ end
45
+ end
46
+
47
+ it "reads metadata" do
48
+ meta = MoFile.parse(File.read('spec/files/complex.mo')).detect {|t|t.msgid == ''}
49
+ meta.msgstr.should_not be_empty
50
+ end
51
+
52
+ describe :to_text do
53
+ it "writes singulars" do
54
+ text = File.read('spec/files/singular.mo')
55
+ MoFile.to_text(MoFile.parse(text)).should == text
56
+ end
57
+ end
58
+ end