PoParser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'ap'
2
+
3
+ module PoParser
4
+ # Feed each block of PO file to Parser.
5
+ class Tokenizer
6
+ def initialize
7
+ @parser = Parser.new
8
+ @po = Po.new
9
+ end
10
+
11
+ def extract_entries(path)
12
+ @po.path = path
13
+ block = ''
14
+ File.open(path, 'r') do |f|
15
+ f.each_line do |line|
16
+ if line.match(/^\n$/)
17
+ @po << parse_block(block)
18
+ block = ''
19
+ elsif f.eof?
20
+ block += line
21
+ @po << parse_block(block)
22
+ else
23
+ block += line
24
+ end
25
+ end
26
+ end
27
+ @po
28
+ end
29
+
30
+ private
31
+ def parse_block(block)
32
+ parsed_hash = @parser.parse(block)
33
+ Transformer.new.transform(parsed_hash)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ module PoParser
2
+ # Converts the array returned from {Parser} to a useable hash
3
+ class Transformer
4
+ def initialize
5
+ @hash = {}
6
+ super
7
+ end
8
+
9
+ def transform(obj)
10
+ apply_transforms(obj).each do |hash|
11
+ merge(hash)
12
+ end
13
+ @hash
14
+ end
15
+
16
+ private
17
+ # @Note: There was a problem applying all rules together. I don't know
18
+ # in what order Parslet run rules, but it's not in order. I end up
19
+ # making to seperate transform and feed one output to the other.
20
+ def first_transform
21
+ Parslet::Transform.new do
22
+ rule(:msgstr_plural => subtree(:plural)) do
23
+ if plural.is_a? Array
24
+ { "msgstr\[#{plural[0][:plural_id]}\]".to_sym => plural }
25
+ else
26
+ { "msgstr\[#{plural[:plural_id]}\]".to_sym => plural }
27
+ end
28
+ end
29
+
30
+ rule(:text => simple(:txt)) { txt.to_s.chomp }
31
+ end
32
+ end
33
+
34
+ def second_transform
35
+ Parslet::Transform.new do
36
+ rule(:plural_id => simple(:id), :text => simple(:txt)) { txt }
37
+ end
38
+ end
39
+
40
+ def apply_transforms(hash)
41
+ first = first_transform.apply(hash)
42
+ second_transform.apply(first)
43
+ end
44
+
45
+ # Merges two hashed together. If both hashes have common keys it
46
+ # will create an array of them
47
+ #
48
+ # @return [Hash]
49
+ def merge(newh)
50
+ @hash.merge!(newh) do |key, oldval, newval|
51
+ if oldval.is_a? Array
52
+ oldval << newval
53
+ else
54
+ Array.new [oldval, newval]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module PoParser
2
+ VERSION = "0.1.0"
3
+ end
data/lib/poparser.rb ADDED
@@ -0,0 +1,21 @@
1
+ # External Libs
2
+ require 'parslet'
3
+
4
+ # Local files
5
+ require 'poparser/constants'
6
+ require 'poparser/parser'
7
+ require 'poparser/transformer'
8
+ require 'poparser/tokenizer'
9
+ require 'poparser/comment'
10
+ require 'poparser/message'
11
+ require 'poparser/entry'
12
+ require 'poparser/po'
13
+ require 'poparser/version'
14
+
15
+ module PoParser
16
+ class << self
17
+ def parse(path)
18
+ Tokenizer.new.extract_entries(path)
19
+ end
20
+ end
21
+ end
data/poparser.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'poparser/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "PoParser"
8
+ spec.version = PoParser::VERSION
9
+ spec.authors = ["Arash Mousavi"]
10
+ spec.email = ["mousavi.arash@gmail.com"]
11
+ spec.summary = %q{A PO file parser, editor and generator.}
12
+ spec.description = %q{A PO file parser, editor and generator. PO files are translation files generated by GNU/Gettext tool.}
13
+ spec.homepage = "http://github.com/arashm/poparser"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^spec/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ # Runtime deps
22
+ spec.add_runtime_dependency "parslet"
23
+
24
+ # Development deps
25
+ spec.add_development_dependency "bundler", "~> 1.5"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 2.14"
28
+ spec.add_development_dependency "guard-rspec"
29
+ spec.add_development_dependency "pry-debugger"
30
+ spec.add_development_dependency "awesome_print"
31
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PoParser::Comment do
5
+ it 'converts the comment to string' do
6
+ comment = PoParser::Comment.new(:translator_comment, "this is a line")
7
+ result = "# this is a line\n"
8
+ expect(comment.to_s(true)).to eq(result)
9
+ end
10
+
11
+ it 'converts array of same comment to string' do
12
+ comment = PoParser::Comment.new(:translator_comment, ["this is a line", "this is another line"])
13
+ result = "# this is a line\n# this is another line\n"
14
+ expect(comment.to_s(true)).to eq(result)
15
+ end
16
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PoParser::Entry do
5
+ before(:each) do
6
+ @entry = PoParser::Entry.new
7
+ end
8
+
9
+ let(:labels) do
10
+ [:refrence, :extracted_comment, :flag, :previous_untraslated_string,
11
+ :translator_comment, :msgid, :msgid_plural, :msgstr, :msgctxt]
12
+ end
13
+
14
+ it 'should respond to labels' do
15
+ labels.each do |label|
16
+ @entry.should respond_to label
17
+ end
18
+ end
19
+
20
+ it 'should show a hash presentation of a entry' do
21
+ @entry.msgid = 'string'
22
+ @entry.msgstr = 'reshte'
23
+ expect(@entry.to_h).to eq({:msgid=>"string", :msgstr=>"reshte"})
24
+ end
25
+
26
+ it 'should translate the entry' do
27
+ @entry.translate ('this entry is translated')
28
+ expect(@entry.msgstr.to_s).to eq 'this entry is translated'
29
+ end
30
+
31
+ it 'checks if the entry is translated' do
32
+ expect(@entry.translated?).to be_false
33
+ @entry.translate ''
34
+ expect(@entry.translated?).to be_false
35
+ @entry.translate 'translated'
36
+ expect(@entry.complete?).to be_true
37
+ end
38
+
39
+ context 'Plural' do
40
+ it 'returns false if it\'s not plural' do
41
+ expect(@entry.plural?).to be_false
42
+ end
43
+
44
+ it 'returns true if it\'s plural' do
45
+ @entry.msgid_plural = 'sth'
46
+ expect(@entry.plural?).to be_true
47
+ end
48
+ end
49
+
50
+ context 'Flags' do
51
+ it 'should check if a entry is fuzzy' do
52
+ expect(@entry.fuzzy?).to be_false
53
+ @entry.flag = 'fuzzy'
54
+ expect(@entry.fuzzy?).to be_true
55
+ end
56
+
57
+ it 'should flag a entry as fuzzy' do
58
+ expect(@entry.flag_as_fuzzy).to be_true
59
+ expect(@entry.flag).to eq('fuzzy')
60
+ end
61
+
62
+ it 'should be able to set a custome flag' do
63
+ expect(@entry.flag_as 'python-format').to be_true
64
+ expect(@entry.flag).to eq('python-format')
65
+ end
66
+ end
67
+
68
+ context 'Convertion to string' do
69
+ it 'should be able to show string representaion of entries' do
70
+ @entry.flag = 'fuzzy'
71
+ @entry.msgid = 'string'
72
+ @entry.msgstr = 'reshte'
73
+ result = "#, fuzzy\nmsgid \"string\"\nmsgstr \"reshte\"\n"
74
+ expect(@entry.to_s).to eq result
75
+ end
76
+
77
+ it 'convert multiline entries to string' do
78
+ @entry.flag = 'fuzzy'
79
+ @entry.msgid = ['first line', 'second line']
80
+ @entry.msgstr = ['first line', 'second line']
81
+ result = "#, fuzzy\nmsgid \"\"\n\"first line\"\n\"second line\"\nmsgstr \"\"\n\"first line\"\n\"second line\"\n"
82
+ expect(@entry.to_s).to eq(result)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,6 @@
1
+ msgid ""
2
+ "first"
3
+ "second"
4
+ msgstr ""
5
+ "aval"
6
+ "dovom"
@@ -0,0 +1,6 @@
1
+ msgid " including <a href=\"%(img_url)s\">%(stats)s image</a>"
2
+ msgid_plural " including <a href=\"%(img_url)s\">%(stats)s images</a>"
3
+ msgstr[0] ""
4
+ "sad ads fdsaf ds fdfs dsa "
5
+ msgstr[1] ""
6
+ "sad ads fdsaf ds fdfs dsa "
@@ -0,0 +1,7 @@
1
+ #: templates:105
2
+ msgid "Afrikaans"
3
+ msgstr "آفریقایی"
4
+
5
+ #, fuzzy
6
+ msgid "Afrikaans"
7
+ msgstr "آفریقایی"
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PoParser::Message do
5
+ it 'converts the message to string' do
6
+ message = PoParser::Message.new(:msgid, "this is a line")
7
+ result = "msgid \"this is a line\"\n"
8
+ expect(message.to_s(true)).to eq(result)
9
+ end
10
+
11
+ it 'converts array of same message to string' do
12
+ message = PoParser::Message.new(:msgid, ["this is a line", "this is another line"])
13
+ result = "msgid \"\"\n\"this is a line\"\n\"this is another line\"\n"
14
+ expect(message.to_s(true)).to eq(result)
15
+ end
16
+
17
+ it 'shows one line string for multiline entries' do
18
+ message = PoParser::Message.new(:msgid, ["", "this is a line ", "this is another line"])
19
+ result = "this is a line this is another line"
20
+ expect(message.str).to eq result
21
+ end
22
+
23
+ it 'converts plural msgstr correctly' do
24
+ message = PoParser::Message.new(:"msgstr[0]", "this is a line")
25
+ result = "msgstr[0] \"this is a line\"\n"
26
+ expect(message.to_s(true)).to eq(result)
27
+ end
28
+
29
+ it 'converts multiline plural msgstr correctly' do
30
+ message = PoParser::Message.new(:"msgstr[0]", ["this is a line", "this is another line"])
31
+ result = "msgstr[0] \"\"\n\"this is a line\"\n\"this is another line\"\n"
32
+ expect(message.to_s(true)).to eq(result)
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+ #encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ describe PoParser::Parser do
5
+ let(:po) { PoParser::Parser.new }
6
+
7
+ context(:comments) do
8
+
9
+ let(:tc) { po.translator_comment }
10
+ let(:rc) { po.refrence }
11
+ let(:ec) { po.extracted_comment }
12
+ let(:fc) { po.flag }
13
+ let(:pusc){ po.previous_untraslated_string }
14
+
15
+ it 'parses the translator comment' do
16
+ tc.should parse("# Persian translation for damned-lies 123123\n")
17
+ tc.should parse("# Copyright (C) 2012 damned-lies's COPYRIGHT HOLDER\n")
18
+ tc.should parse("# Arash Mousavi <mousavi.arash@gmail.com>, 2014.\n")
19
+ end
20
+
21
+ it 'parses refrence comment' do
22
+ rc.should parse("#: database-content.py:1 database-content.py:129 settings.py:52\n")
23
+ end
24
+
25
+ it 'parses extracted_comment' do
26
+ ec.should parse("#. database-content.py:1 database-content.py:129 settings.py:52\n")
27
+ end
28
+
29
+ it 'parses flag_comment' do
30
+ fc.should parse("#, python-format\n")
31
+ end
32
+
33
+ it 'parses previous_untraslated_string' do
34
+ pusc.should parse("#| msgid \"\"\n")
35
+ pusc.should parse("#| \"Hello,\\n\"\n")
36
+ pusc.should parse("#| \"The new state of %(module)s - %(branch)s - %(domain)s (%(language)s) is \"\n")
37
+ end
38
+
39
+ end
40
+
41
+ context 'Entries' do
42
+ let(:msgid) { po.msgid }
43
+ let(:msgstr){ po.msgstr }
44
+ let(:pofile){ Pathname.new('spec/poparser/fixtures/multiline.po').realpath }
45
+
46
+ it 'parses msgid' do
47
+ msgid.should parse "msgid \"The new state of %(module)s - %(branch)s - %(domain)s (%(language)s) is now \"\n"
48
+ msgid.should parse "msgid \"The new \"state\" of %(module)s - %(branch)s - %(domain)s (%(language)s) is now \"\n"
49
+ end
50
+
51
+ it 'parses msgstr' do
52
+ msgstr.should parse "msgstr \"The new state of %(module)s - %(branch)s - %(domain)s (%(language)s) is now \"\n"
53
+ msgstr.should parse "msgstr \"فعالیت نامعتبر. شاید یک نفر دیگر دقیقا قبل از شما یک فعالیت دیگر ارسال کرده ۱۲۳۱۲۳۱safda \"\n"
54
+ end
55
+
56
+ it 'parses multiline entries' do
57
+ data = pofile.read
58
+ result = [{:msgid=>[{:text=>""}, {:text=>"first"}, {:text=>"second"}]}, {:msgstr=>[{:text=>""}, {:text=>"aval"}, {:text=>"dovom"}]}]
59
+ expect(po.parse data).to eq(result)
60
+ end
61
+
62
+ it 'parses plural msgstr entries' do
63
+ str = "msgstr[0] \"\""
64
+ result = [{:msgstr_plural=>{:plural_id=>"0", :text=>""}}]
65
+ expect(po.parse(str)).to eq(result)
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PoParser::Po do
5
+ let (:entry) do
6
+ {
7
+ translator_comment: 'comment',
8
+ refrence: 'refrence comment',
9
+ msgid: 'untranslated',
10
+ msgstr: 'translated string'
11
+ }
12
+ end
13
+
14
+ before(:each) do
15
+ @po = PoParser::Po.new
16
+ end
17
+
18
+ it 'should be able to add an entry to Po' do
19
+ # << is an alias for Po#add_entry
20
+ expect(@po << entry).to be_a_kind_of PoParser::Entry
21
+ end
22
+
23
+ it 'should be able to add multiple entries' do
24
+ entries = [entry, entry.dup]
25
+ expect(@po << entries).to be_a_kind_of Array
26
+ end
27
+
28
+ it 'returns all fuzzy entries' do
29
+ entry2, entry3 = entry.dup, entry.dup
30
+ [entry2, entry3].each { |en| en[:flag] = 'fuzzy' }
31
+ @po << [entry, entry2, entry3]
32
+ expect(@po.fuzzy.size).to eq 2
33
+ end
34
+
35
+ it 'returns all untraslated strings' do
36
+ entry2, entry3 = entry.dup, entry.dup
37
+ [entry2, entry3].each { |en| en[:msgstr] = '' }
38
+ @po << [entry, entry2, entry3]
39
+ expect(@po.untranslated.size).to eq 2
40
+ end
41
+
42
+ it 'shows stats' do
43
+ entry2, entry3 = entry.dup, entry.dup
44
+ [entry2, entry3].each { |en| en[:msgstr] = '' }
45
+ @po << [entry, entry2, entry3]
46
+ ap @po.stats
47
+ end
48
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ describe PoParser do
5
+ let(:po_file) { Pathname.new('spec/poparser/fixtures/tokenizer.po').realpath }
6
+
7
+ it 'parses a file' do
8
+ expect(PoParser.parse(po_file)).to be_a_kind_of PoParser::Po
9
+ end
10
+ end
@@ -0,0 +1,51 @@
1
+ # Persian translation for damned-lies.
2
+ # Copyright (C) 2012 damned-lies's COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the damned-lies package.
4
+ # Arash Mousavi <mousavi.arash@gmail.com>, 2014.
5
+ #
6
+ msgid ""
7
+ msgstr ""
8
+ "Project-Id-Version: damned-lies master\n"
9
+ "Report-Msgid-Bugs-To: \n"
10
+ "POT-Creation-Date: 2012-05-04 12:56+0000\n"
11
+ "PO-Revision-Date: 2014-05-15 22:24+0330\n"
12
+ "Last-Translator: Arash Mousavi <mousavi.arash@gmail.com>\n"
13
+ "Language-Team: Persian <fa@li.org>\n"
14
+ "MIME-Version: 1.0\n"
15
+ "Content-Type: text/plain; charset=UTF-8\n"
16
+ "Content-Transfer-Encoding: 8bit\n"
17
+ "Plural-Forms: nplurals=1; plural=0;\n"
18
+ "X-Generator: Poedit 1.6.4\n"
19
+
20
+ #: database-content.py:1 database-content.py:129 settings.py:52
21
+ msgid "Afrikaans"
22
+ msgstr "آفریقایی"
23
+
24
+ #: templates/vertimus/vertimus_detail.html:105
25
+ #, python-format
26
+ msgid " including <a href=\"%(img_url)s\">%(stats)s image</a>"
27
+ msgid_plural " including <a href=\"%(img_url)s\">%(stats)s images</a>"
28
+ msgstr[0] ""
29
+ msgstr[1] ""
30
+
31
+ #: templates/vertimus/vertimus_detail.html:136 vertimus/forms.py:79
32
+ msgid "Invalid action. Someone probably posted another action just before you."
33
+ msgstr ""
34
+ "فعالیت نامعتبر. شاید یک نفر دیگر دقیقا قبل از شما یک فعالیت دیگر ارسال کرده "
35
+ "است."
36
+
37
+ #: vertimus/models.py:470
38
+ #, python-format
39
+ #| msgid ""
40
+ #| "Hello,\n"
41
+ #| "\n"
42
+ #| "The new state of %(module)s - %(branch)s - %(domain)s (%(language)s) is "
43
+ #| "now '%(new_state)s'.\n"
44
+ #| "%(url)s\n"
45
+ #| "\n"
46
+ msgid ""
47
+ "The new state of %(module)s - %(branch)s - %(domain)s (%(language)s) is now "
48
+ "'%(new_state)s'."
49
+ msgstr ""
50
+ "وضعیت جدید %(module)s - %(branch)s - %(domain)s (%(language)s) هم‌اکنون "
51
+ "«%(new_state)s» است."
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ require "spec_helper.rb"
3
+
4
+ describe PoParser::Tokenizer do
5
+ let(:token) { PoParser::Tokenizer.new }
6
+ let(:po_file){ Pathname.new('spec/poparser/fixtures/tokenizer.po').realpath }
7
+ let(:result) { [{:refrence=>"templates:105", :msgid=>"Afrikaans", :msgstr=>"آفریقایی"}, {:flag=>"fuzzy", :msgid=>"Afrikaans", :msgstr=>"آفریقایی" }] }
8
+
9
+ it 'should be able to extracts entries' do
10
+ expect(
11
+ token.extract_entries(po_file).to_h
12
+ ).to eq(result)
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ describe PoParser::Transformer do
5
+ let(:trans){ PoParser::Transformer.new }
6
+
7
+ it 'transforms the returned array from parslet to a usable hash' do
8
+ parslet_array = [{:translator_comment=>"Persian translation\n"}, {:translator_comment=>"Copyright\n"}, {:msgid=>"\"test\"\n"}]
9
+ transformed_hash = {:translator_comment=>["Persian translation\n", "Copyright\n"], :msgid=>"\"test\"\n"}
10
+ expect(trans.transform(parslet_array)).to eq(transformed_hash)
11
+ end
12
+
13
+ it 'transforms plural msgstr forms correctly' do
14
+ data = [{:msgstr_plural=>{:plural_id=>"0", :text=>"this is a txt"}}]
15
+ result = { :'msgstr[0]' => "this is a txt" }
16
+ expect(trans.transform(data)).to eq(result)
17
+ end
18
+
19
+ it 'transforms multiline plural msgstr forms correctly' do
20
+ data = [{:msgstr_plural=>[{:plural_id=>"0", :text=>"this is a txt"}, {:text => 'some text'}]}]
21
+ result = { :'msgstr[0]' => ["this is a txt", "some text"] }
22
+ expect(trans.transform(data)).to eq(result)
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ # Just as test to ensure that test suit is working correctly
4
+ describe 'Version' do
5
+ it 'shows the version correctly' do
6
+ expect(PoParser::VERSION).to eq('0.0.1')
7
+ end
8
+ end
@@ -0,0 +1,22 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ require 'poparser'
8
+ require 'parslet/rig/rspec'
9
+ require 'coveralls'
10
+ Coveralls.wear!
11
+
12
+ RSpec.configure do |config|
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ config.run_all_when_everything_filtered = true
15
+ config.filter_run :focus
16
+
17
+ # Run specs in random order to surface order dependencies. If you find an
18
+ # order dependency and want to debug it, you can fix the order by providing
19
+ # the seed, which is printed after each run.
20
+ # --seed 1234
21
+ config.order = 'random'
22
+ end