PoParser 0.1.0

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.
@@ -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