grosser-pomo 0.4.1

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/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ A simple and extendable .mo and .po file parser/generator.
2
+ --mo file parser and writer are missing atm--
3
+
4
+ Advanteges over [original po-parser](http://github.com/mutoh/gettext/blob/abf96713327cc4c5d35f0a772f3b75ff4819450c/lib/gettext/poparser.rb):
5
+
6
+ - simple architecture + easy to extend/modify
7
+ - emtpy msgstr translations are read
8
+ - comments are included
9
+ - fuzzy can be set/unset
10
+ - multiple translations can be combined in a new po file(with comments and fuzzy and ...)
11
+ - po files can be written from any kind of input
12
+
13
+ Setup
14
+ =====
15
+ sudo gem install grosser-pomo -s http://gems.github.com/
16
+
17
+ ###Static interface
18
+ #parse po files
19
+ translations = Pomo::PoFile.parse(File.read('xxx.po')) + Pomo::PoFile.parse(File.read('yyy.po'))
20
+
21
+ #and use the data...
22
+ msgids = translations.reject{|t|t.plural? or t.fuzzy?}.map(&:msgid)
23
+
24
+ #or write a new po file (unique by msgid)...
25
+ File.open('xxx.po','w){|f|f.print(Pomo::PoFile.to_text(translations))}
26
+
27
+ ###Instance interface
28
+ p = PoMo::PoFile.new
29
+ p.add_translations_from_text(File.read('...'))
30
+ ...
31
+ p.translations
32
+ p.to_text
33
+
34
+ TODO
35
+ ====
36
+ - extracting of version/pluralisation_rule/plurals/translator... (from msgid "")
37
+ - mo writing/reading (this is the hardest part imo...)
38
+
39
+ Author
40
+ ======
41
+ [Michael Grosser](http://pragmatig.wordpress.com)
42
+ grosser.michael@gmail.com
43
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 4
3
+ :patch: 1
4
+ :major: 0
data/lib/pomo.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'pomo/po_file'
2
+ module Pomo
3
+ end
@@ -0,0 +1,117 @@
1
+ require 'pomo/translation'
2
+
3
+ module Pomo
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
+ def self.unique_translations(translations)
15
+ last_seen_at_index = {}
16
+ translations.each_with_index {|translation,index|last_seen_at_index[translation.msgid]=index}
17
+ last_seen_at_index.values.sort.map{|index| translations[index]}
18
+ end
19
+
20
+ attr_reader :translations
21
+
22
+ def initialize(options = {})
23
+ @translations = options[:translations] || []
24
+ end
25
+
26
+ #the text is split into lines and then converted into logical translations
27
+ #each translation consists of comments(that come before a translation)
28
+ #and a msgid / msgstr
29
+ def add_translations_from_text(text)
30
+ start_new_translation
31
+ text.split(/$/).each_with_index do |line,index|
32
+ @line_number = index + 1
33
+ next if line.empty?
34
+ if method_call? line
35
+ parse_method_call line
36
+ elsif comment? line
37
+ add_comment line
38
+ else
39
+ add_string line
40
+ end
41
+ end
42
+ start_new_translation #instance_variable has to be overwritten or errors can occur on next add
43
+ translations
44
+ end
45
+
46
+ def to_text
47
+ self.class.unique_translations(translations).map {|translation|
48
+ comment = translation.comment.to_s.split(/\n|\r\n/).map{|line|"##{line}\n"}*''
49
+ msgid_and_msgstr = if translation.plural?
50
+ msgids =
51
+ %Q(msgid "#{translation.msgid[0]}"\n)+
52
+ %Q(msgid_plural "#{translation.msgid[1]}"\n)
53
+
54
+ msgstrs = []
55
+ translation.msgstr.each_with_index do |msgstr,index|
56
+ msgstrs << %Q(msgstr[#{index}] "#{msgstr}")
57
+ end
58
+
59
+ msgids + (msgstrs*"\n")
60
+ else
61
+ %Q(msgid "#{translation.msgid}"\n)+
62
+ %Q(msgstr "#{translation.msgstr}")
63
+ end
64
+
65
+ comment + msgid_and_msgstr
66
+ } * "\n\n"
67
+ end
68
+
69
+ private
70
+
71
+ #e.g. # fuzzy
72
+ def comment?(line)
73
+ line =~ /^\s*#/
74
+ end
75
+
76
+ def add_comment(line)
77
+ start_new_translation if translation_complete?
78
+ @current_translation.add_text(line.strip.sub('#','')+"\n",:to=>:comment)
79
+ end
80
+
81
+ #msgid "hello"
82
+ def method_call?(line)
83
+ line =~ /^\s*[a-z]/
84
+ end
85
+
86
+ #msgid "hello" -> method call msgid + add string "hello"
87
+ def parse_method_call(line)
88
+ method, string = line.match(/^\s*([a-z0-9_\[\]]+)(.*)/)[1..2]
89
+ raise "no method found" unless method
90
+
91
+ start_new_translation if method == 'msgid' and translation_complete?
92
+ @last_method = method.to_sym
93
+ add_string(string)
94
+ end
95
+
96
+ #"hello" -> hello
97
+ def add_string(string)
98
+ return if string.strip.empty?
99
+ raise "not string format: #{string.inspect} on line #{@line_number}" unless string.strip =~ /^['"](.*)['"]$/
100
+ @current_translation.add_text($1,:to=>@last_method)
101
+ end
102
+
103
+ def translation_complete?
104
+ return false unless @current_translation
105
+ @current_translation.complete?
106
+ end
107
+
108
+ def store_translation
109
+ @translations += [@current_translation] if @current_translation.complete?
110
+ end
111
+
112
+ def start_new_translation
113
+ store_translation if translation_complete?
114
+ @current_translation = Translation.new
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,44 @@
1
+ module Pomo
2
+ class Translation
3
+ FUZZY_REGEX = /^\s*fuzzy\s*$/
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.to_s.strip.empty? 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,110 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ include Pomo
4
+ describe Pomo::PoFile do
5
+ it "parses nothing" do
6
+ PoFile.parse("").should be_empty
7
+ end
8
+
9
+ it "parses a simple msgid and msgstr" do
10
+ t = PoFile.parse(%Q(msgid "xxx"\nmsgstr "yyy"))
11
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy'}
12
+ end
13
+
14
+ it "parses a simple msgid and msg with additional whitespace" do
15
+ t = PoFile.parse(%Q( msgid "xxx" \n msgstr "yyy" ))
16
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy'}
17
+ end
18
+
19
+ it "parses a multiline msgid/msgstr" do
20
+ t = PoFile.parse(%Q(msgid "xxx"\n"aaa"\n\n\nmsgstr ""\n"bbb"))
21
+ t[0].to_hash.should == {:msgid=>'xxxaaa',:msgstr=>'bbb'}
22
+ end
23
+
24
+ it "parses simple comments" do
25
+ t = PoFile.parse(%Q(#test\nmsgid "xxx"\nmsgstr "yyy"))
26
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy',:comment=>"test\n"}
27
+ end
28
+
29
+ it "parses comments above msgstr" do
30
+ t = PoFile.parse(%Q(#test\nmsgid "xxx"\n#another\nmsgstr "yyy"))
31
+ t[0].to_hash.should == {:msgid=>'xxx',:msgstr=>'yyy',:comment=>"test\nanother\n"}
32
+ end
33
+
34
+ describe "instance interface" do
35
+ it "adds two different translations" do
36
+ p = PoFile.new
37
+ p.add_translations_from_text(%Q(msgid "xxx"\nmsgstr "yyy"))
38
+ p.add_translations_from_text(%Q(msgid "aaa"\nmsgstr "yyy"))
39
+ p.translations[1].to_hash.should == {:msgid=>'aaa',:msgstr=>'yyy'}
40
+ end
41
+
42
+ it "can be initialized with translations" do
43
+ p = PoFile.new(:translations=>['xxx'])
44
+ p.translations[0].should == 'xxx'
45
+ end
46
+
47
+ it "can be converted to text" do
48
+ text = %Q(msgid "xxx"\nmsgstr "aaa")
49
+ p = PoFile.new
50
+ p.add_translations_from_text(text)
51
+ p.to_text.should == text
52
+ end
53
+
54
+ it "keeps uniqueness when converting to_text" do
55
+ text = %Q(msgid "xxx"\nmsgstr "aaa")
56
+ p = PoFile.new
57
+ p.add_translations_from_text(%Q(msgid "xxx"\nmsgstr "yyy"))
58
+ p.add_translations_from_text(text)
59
+ p.to_text.should == text
60
+ end
61
+ end
62
+
63
+ it "adds plural translations" do
64
+ t = PoFile.parse(%Q(msgid "singular"\nmsgid_plural "plural"\nmsgstr[0] "one"\nmsgstr[1] "many"))
65
+ t[0].to_hash.should == {:msgid=>['singular','plural'],:msgstr=>['one','many']}
66
+ end
67
+
68
+ it "does not fail on empty string" do
69
+ PoFile.parse(%Q(\n\n\n\n\n))
70
+ end
71
+
72
+ it "shows line number for invalid strings" do
73
+ begin
74
+ PoFile.parse(%Q(\n\n\n\n\nmsgstr "))
75
+ flunk
76
+ rescue Exception => e
77
+ e.to_s.should =~ /line 5/
78
+ end
79
+ end
80
+
81
+ describe :to_text do
82
+ it "is empty when not translations where added" do
83
+ PoFile.to_text([]).should == ""
84
+ end
85
+
86
+ it "preserves simple syntax" do
87
+ text = %Q(msgid "x"\nmsgstr "y")
88
+ PoFile.to_text(PoFile.parse(text)).should == text
89
+ end
90
+
91
+ it "adds comments" do
92
+ t = Pomo::Translation.new
93
+ t.msgid = 'a'
94
+ t.msgstr = 'b'
95
+ t.add_text("c\n",:to=>:comment)
96
+ t.add_text("d\n",:to=>:comment)
97
+ PoFile.to_text([t]).should == %Q(#c\n#d\nmsgid "a"\nmsgstr "b")
98
+ end
99
+
100
+ it "uses plural notation" do
101
+ text = %Q(#awesome\nmsgid "one"\nmsgid_plural "many"\nmsgstr[0] "1"\nmsgstr[1] "n")
102
+ PoFile.to_text(PoFile.parse(text)).should == text
103
+ end
104
+
105
+ it "only uses the latest of identicals msgids" do
106
+ text = %Q(msgid "one"\nmsgstr "1"\nmsgid "one"\nmsgstr "001")
107
+ PoFile.to_text(PoFile.parse(text)).should == %Q(msgid "one"\nmsgstr "001")
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,119 @@
1
+ require File.expand_path("../spec_helper", File.dirname(__FILE__))
2
+
3
+ require 'pomo/translation'
4
+
5
+ describe Pomo::Translation do
6
+ describe :complete? do
7
+ it{should_not be_complete}
8
+
9
+ it "is complete if it has a msgid and a msgstr" do
10
+ subject.msgid="x"
11
+ subject.msgstr = "y"
12
+ should be_complete
13
+ end
14
+
15
+ it "is not complete if it has an complete msgid" do
16
+ subject.msgid=""
17
+ should_not be_complete
18
+ end
19
+
20
+ it "is not complete if it has a nil msgstr" do
21
+ subject.msgid="x"
22
+ should_not be_complete
23
+ end
24
+
25
+ it "is complete if it has an complete msgstr" do
26
+ subject.msgid = "x"
27
+ subject.msgstr = ""
28
+ should be_complete
29
+ end
30
+ end
31
+
32
+ describe :add_text do
33
+ it "adds a simple msgid" do
34
+ subject.add_text("x",:to=>:msgid)
35
+ subject.msgid.should == "x"
36
+ end
37
+
38
+ it "converts msgid to plural when msgid_plural is added" do
39
+ subject.add_text("x",:to=>:msgid)
40
+ subject.add_text("y",:to=>:msgid_plural)
41
+ subject.msgid.should == ["x",'y']
42
+ end
43
+
44
+ it "can add additional text to msgid plurals" do
45
+ subject.add_text("y",:to=>:msgid_plural)
46
+ subject.add_text("a",:to=>:msgid_plural)
47
+ subject.msgid.should == [nil,'ya']
48
+ end
49
+
50
+ it "can add plural msggstr" do
51
+ subject.add_text("x",:to=>'msgstr[0]')
52
+ subject.msgstr.should == ['x']
53
+ end
54
+
55
+ it "can add multiple plural msggstr" do
56
+ subject.add_text("x",:to=>'msgstr[0]')
57
+ subject.add_text("a",:to=>'msgstr[1]')
58
+ subject.add_text("y",:to=>'msgstr[1]')
59
+ subject.msgstr.should == ['x','ay']
60
+ end
61
+ end
62
+
63
+ describe :plural? do
64
+ it{should_not be_plural}
65
+
66
+ it "is plural if msgid is plural" do
67
+ subject.add_text("x",:to=>:msgid_plural)
68
+ should be_plural
69
+ end
70
+
71
+ it "is plural if msgstr is plural" do
72
+ subject.add_text("x",:to=>"msgstr[0]")
73
+ should be_plural
74
+ end
75
+
76
+ it "is not plural if simple strings where added" do
77
+ subject.msgid = "a"
78
+ subject.msgstr = "a"
79
+ should_not be_plural
80
+ end
81
+ end
82
+
83
+ describe :fuzzy? do
84
+ it{should_not be_fuzzy}
85
+
86
+ it "is fuzzy if a fuzzy comment was added" do
87
+ subject.add_text("fuzzy",:to=>:comment)
88
+ should be_fuzzy
89
+ end
90
+
91
+ it "can be made fuzzy by using fuzzy=" do
92
+ subject.fuzzy = true
93
+ should be_fuzzy
94
+ end
95
+
96
+ it "can be made unfuzzy by using fuzzy=" do
97
+ subject.fuzzy = false
98
+ should_not be_fuzzy
99
+ end
100
+
101
+ it "changes comment when made fuzzy through fuzzy=" do
102
+ subject.comment = "hello"
103
+ subject.fuzzy = true
104
+ subject.comment.should == "hello\nfuzzy"
105
+ end
106
+
107
+ it "changes empty comment when made fuzzy through fuzzy=" do
108
+ subject.fuzzy = true
109
+ subject.comment.should == "\nfuzzy"
110
+ end
111
+
112
+ it "preserves comments when making fuzzy/unfuzzy" do
113
+ subject.comment = "hello"
114
+ subject.fuzzy = true
115
+ subject.fuzzy = false
116
+ subject.comment.should == "hello"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,10 @@
1
+ # ---- requirements
2
+ $LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__))
3
+ require 'pomo'
4
+
5
+ # ---- Helpers
6
+ def pending_it(text,&block)
7
+ it text do
8
+ pending(&block)
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grosser-pomo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-31 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: grosser.michael@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - README.markdown
27
+ - lib/pomo
28
+ - lib/pomo/po_file.rb
29
+ - lib/pomo/translation.rb
30
+ - lib/pomo.rb
31
+ - spec/pomo
32
+ - spec/pomo/po_file_spec.rb
33
+ - spec/pomo/translation_spec.rb
34
+ - spec/spec_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/grosser/pomo
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --inline-source
40
+ - --charset=UTF-8
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.2.0
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: "Ruby/Gettext: A .po and .mo file parser/generator"
62
+ test_files: []
63
+