PoParser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +6 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +159 -0
- data/Rakefile +1 -0
- data/lib/poparser/comment.rb +28 -0
- data/lib/poparser/constants.rb +18 -0
- data/lib/poparser/entry.rb +154 -0
- data/lib/poparser/message.rb +51 -0
- data/lib/poparser/parser.rb +66 -0
- data/lib/poparser/po.rb +134 -0
- data/lib/poparser/tokenizer.rb +36 -0
- data/lib/poparser/transformer.rb +59 -0
- data/lib/poparser/version.rb +3 -0
- data/lib/poparser.rb +21 -0
- data/poparser.gemspec +31 -0
- data/spec/poparser/comment_spec.rb +16 -0
- data/spec/poparser/entry_spec.rb +85 -0
- data/spec/poparser/fixtures/multiline.po +6 -0
- data/spec/poparser/fixtures/plural.po +6 -0
- data/spec/poparser/fixtures/tokenizer.po +7 -0
- data/spec/poparser/message_spec.rb +34 -0
- data/spec/poparser/parser_spec.rb +69 -0
- data/spec/poparser/po_spec.rb +48 -0
- data/spec/poparser/poparser_spec.rb +10 -0
- data/spec/poparser/test.po +51 -0
- data/spec/poparser/tokenizer_spec.rb +14 -0
- data/spec/poparser/transformer_spec.rb +24 -0
- data/spec/poparser/version_spec.rb +8 -0
- data/spec/spec_helper.rb +22 -0
- metadata +191 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 93ef3b40331e9ddb92b0e2e80cecdda8931c792d
|
4
|
+
data.tar.gz: fb04bd5618a796539cc22654c35f819cfaa1ba95
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5db31ba15259828ab3fbd12f39ac9929ce3baa7244e2f51511ac9d46f31278c13aa40606af22c249e74d2ae3ccbc65b4996f9b0b0a62936dedaa2724260609ac
|
7
|
+
data.tar.gz: 351db54dfc9b1e1b75945c13a1ecc4ad7da2cd7c6d4a0e659466f904f1b319c538081042dbb4b790ffd6ebbf580e9686fa066dd510eaad2870cfdacac5ff9c35
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Arash Mousavi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
# Poparser
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/arashm/PoParser.svg?branch=master)](https://travis-ci.org/arashm/PoParser)
|
4
|
+
[![Coverage Status](https://img.shields.io/coveralls/arashm/PoParser.svg)](https://coveralls.io/r/arashm/PoParser)
|
5
|
+
|
6
|
+
A Ruby PO file parser, editor and generator. PO files are translation files generated by GNU/Gettext tool. This GEM is compatible with [GNU PO file specification](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html). report misbehaviours and bugs, to the [issue tracker](https://github.com/arashm/PoParser/issues).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'poparser'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install poparser
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Working with the GEM is pretty easy:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
path = Pathname.new('example.po')
|
28
|
+
po = PoParser.parse(path)
|
29
|
+
=> <PoParser::Po, Translated: 68.1% Untranslated: 20.4% Fuzzy: 11.5%>
|
30
|
+
```
|
31
|
+
|
32
|
+
The `parse` method returns a `PO` object which contains all `Entries`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# get all entries
|
36
|
+
po.entries # or .all alias
|
37
|
+
|
38
|
+
# get all fuzzy entries
|
39
|
+
po.fuzzy
|
40
|
+
|
41
|
+
# get all untranslated entries
|
42
|
+
po.untranslated
|
43
|
+
|
44
|
+
# get all translated entries
|
45
|
+
po.translated
|
46
|
+
|
47
|
+
# returns a hash representation of the PO file
|
48
|
+
po.to_h
|
49
|
+
|
50
|
+
# returns a string representation of the PO file
|
51
|
+
po.to_s
|
52
|
+
```
|
53
|
+
|
54
|
+
You can add a new entry to the PO file:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
new_entry = {
|
58
|
+
translator_comment: 'comment',
|
59
|
+
refrence: 'refrence comment',
|
60
|
+
msgid: 'untranslated',
|
61
|
+
msgstr: 'translated string'
|
62
|
+
}
|
63
|
+
|
64
|
+
po.add_entry(new_entry)
|
65
|
+
|
66
|
+
# There's also an alias for add_entry
|
67
|
+
po << new_entry
|
68
|
+
```
|
69
|
+
|
70
|
+
You can pass an array of hashes to `new_entry` and it will be added to `PO` file.
|
71
|
+
|
72
|
+
### Entry
|
73
|
+
|
74
|
+
Each entry can have following properties (for more information see [GNU PO file specification](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html)):
|
75
|
+
|
76
|
+
```
|
77
|
+
translator_comment
|
78
|
+
refrence
|
79
|
+
extracted_comment
|
80
|
+
flag
|
81
|
+
previous_untraslated_string
|
82
|
+
msgid
|
83
|
+
msgid_plural
|
84
|
+
msgstr
|
85
|
+
msgctxt
|
86
|
+
```
|
87
|
+
|
88
|
+
#### Working with entries
|
89
|
+
|
90
|
+
The `PO` object contains many `Entry` objects. Number of methods are available to check state of the `Entry`:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
entry.untranslated? # or .incomplete? alias
|
94
|
+
#=> false
|
95
|
+
entry.translated? # or .complete? alias
|
96
|
+
#=> true
|
97
|
+
entry.fuzzy?
|
98
|
+
#=> true
|
99
|
+
entry.plural?
|
100
|
+
#=> false
|
101
|
+
```
|
102
|
+
|
103
|
+
You can get or edit each of property of the `Entry`:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
entry.msgid
|
107
|
+
#=> "This is an msgid that needs to get translated"
|
108
|
+
entry.translate = "This entry is translated" # or msgstr= alias
|
109
|
+
entry.msgstr
|
110
|
+
#=> "This entry is translated"
|
111
|
+
```
|
112
|
+
|
113
|
+
You can mark an entry as fuzzy:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
entry.flag_as_fuzzy
|
117
|
+
entry.fuzzy?
|
118
|
+
#=> true
|
119
|
+
```
|
120
|
+
|
121
|
+
It's possible to get Hash and String representation of the `Entry`:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
entry.to_h
|
125
|
+
entry.to_s(true)
|
126
|
+
```
|
127
|
+
|
128
|
+
### Searching
|
129
|
+
|
130
|
+
`PO` is an `Enumerable`. All exciting methods from `Enumerable` are available in `PO`. the `PO` yields `Entry`.
|
131
|
+
|
132
|
+
### Saving
|
133
|
+
You can simply save the PO file using the `PO` object:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
po.save_file
|
137
|
+
```
|
138
|
+
|
139
|
+
If you want to save as the file in diffrent location change the `path`:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
po.path
|
143
|
+
#=> example.po
|
144
|
+
po.path = 'example2.po'
|
145
|
+
po.save_file
|
146
|
+
```
|
147
|
+
|
148
|
+
##To-Do
|
149
|
+
|
150
|
+
* Streaming support
|
151
|
+
* Better error reporting
|
152
|
+
|
153
|
+
## Contributing
|
154
|
+
|
155
|
+
1. Fork it ( http://github.com/arashm/poparser/fork )
|
156
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
157
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
158
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
159
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PoParser
|
2
|
+
class Comment
|
3
|
+
attr_accessor :type, :str
|
4
|
+
|
5
|
+
def initialize(type, str)
|
6
|
+
@type = type
|
7
|
+
@str = str
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s(with_label = false)
|
11
|
+
return @str unless with_label
|
12
|
+
if @str.is_a? Array
|
13
|
+
string = []
|
14
|
+
@str.each do |str|
|
15
|
+
string << "#{COMMENTS_LABELS[@type]} #{str}\n".gsub(/[^\S\n]+$/, '')
|
16
|
+
end
|
17
|
+
return string.join
|
18
|
+
else
|
19
|
+
# removes the space but not newline at the end
|
20
|
+
"#{COMMENTS_LABELS[@type]} #{@str}\n".gsub(/[^\S\n]+$/, '')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_str
|
25
|
+
@str
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module PoParser
|
2
|
+
COMMENTS_LABELS = {
|
3
|
+
:translator_comment => '#',
|
4
|
+
:refrence => '#:',
|
5
|
+
:extracted_comment => '#.',
|
6
|
+
:flag => '#,',
|
7
|
+
:previous_untraslated_string => '#|',
|
8
|
+
}
|
9
|
+
|
10
|
+
ENTRIES_LABELS = {
|
11
|
+
:msgid => 'msgid',
|
12
|
+
:msgid_plural => 'msgid_plural',
|
13
|
+
:msgstr => 'msgstr',
|
14
|
+
:msgctxt => 'msgctxt'
|
15
|
+
}
|
16
|
+
|
17
|
+
LABELS = COMMENTS_LABELS.merge(ENTRIES_LABELS).keys
|
18
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module PoParser
|
2
|
+
class Entry
|
3
|
+
# TODO: raise error if a label is not known
|
4
|
+
def initialize(args= {})
|
5
|
+
# Defining all instance variables to prevent warnings
|
6
|
+
LABELS.each do |label|
|
7
|
+
instance_variable_set "@#{label.to_s}".to_sym, nil
|
8
|
+
end
|
9
|
+
|
10
|
+
# Set passed arguments
|
11
|
+
args.each do |type, string|
|
12
|
+
if COMMENTS_LABELS.include? type
|
13
|
+
instance_variable_set "@#{type.to_s}".to_sym, Comment.new(type, string)
|
14
|
+
elsif ENTRIES_LABELS.include? type
|
15
|
+
instance_variable_set "@#{type.to_s}".to_sym, Message.new(type, string)
|
16
|
+
elsif type.to_s.match(/^msgstr\[[0-9]\]/)
|
17
|
+
# If it's a plural msgstr
|
18
|
+
@msgstr ||= []
|
19
|
+
@msgstr << Message.new(type, string)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
define_writer_methods
|
24
|
+
define_reader_methods
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks if the entry is untraslated
|
28
|
+
#
|
29
|
+
# @return [Boolean]
|
30
|
+
def untranslated?
|
31
|
+
@msgstr.nil? || @msgstr.to_s == ''
|
32
|
+
end
|
33
|
+
alias_method :incomplete? , :untranslated?
|
34
|
+
|
35
|
+
# Checks if the entry is translated
|
36
|
+
#
|
37
|
+
# @return [Boolean]
|
38
|
+
def translated?
|
39
|
+
not untranslated?
|
40
|
+
end
|
41
|
+
alias_method :complete? , :translated?
|
42
|
+
|
43
|
+
# Checks if the entry is plural
|
44
|
+
#
|
45
|
+
# @return [Boolean]
|
46
|
+
def plural?
|
47
|
+
@msgid_plural != nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Checks if the entry is fuzzy
|
51
|
+
#
|
52
|
+
# @return [Boolean]
|
53
|
+
def fuzzy?
|
54
|
+
@flag.to_s == 'fuzzy'
|
55
|
+
end
|
56
|
+
|
57
|
+
# Flag the entry as Fuzzy
|
58
|
+
# @return [Entry]
|
59
|
+
def flag_as_fuzzy
|
60
|
+
@flag = 'fuzzy'
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Set flag to a custome string
|
65
|
+
def flag_as(flag)
|
66
|
+
raise ArgumentError if flag.class != String
|
67
|
+
@flag = flag
|
68
|
+
end
|
69
|
+
|
70
|
+
# Convert entry to a hash key value
|
71
|
+
# @return [Hash]
|
72
|
+
def to_h
|
73
|
+
hash = {}
|
74
|
+
instance_variables.each do |label|
|
75
|
+
object = instance_variable_get(label)
|
76
|
+
# If it's a plural msgstr
|
77
|
+
if object.is_a? Array
|
78
|
+
object.each do |entry|
|
79
|
+
hash[entry.type] = entry.to_s if not entry.nil?
|
80
|
+
end
|
81
|
+
else
|
82
|
+
hash[object.type] = object.to_s if not object.nil?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
hash
|
86
|
+
end
|
87
|
+
|
88
|
+
# Convert entry to a string
|
89
|
+
# @return [String]
|
90
|
+
def to_s
|
91
|
+
lines = []
|
92
|
+
LABELS.each do |label|
|
93
|
+
object = instance_variable_get("@#{label}".to_sym)
|
94
|
+
# If it's a plural msgstr
|
95
|
+
if object.is_a? Array
|
96
|
+
object.each do |entry|
|
97
|
+
lines << entry.to_s(true) if not entry.nil?
|
98
|
+
end
|
99
|
+
else
|
100
|
+
lines << object.to_s(true) if not object.nil?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
lines.join
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def define_writer_methods
|
110
|
+
COMMENTS_LABELS.each do |type, mark|
|
111
|
+
unless Entry.method_defined? "#{type}=".to_sym
|
112
|
+
self.class.send(:define_method, "#{type}=".to_sym, lambda { |val|
|
113
|
+
if instance_variable_get("@#{type}".to_sym).is_a? Comment
|
114
|
+
comment = instance_variable_get "@#{type}".to_sym
|
115
|
+
comment.type = type
|
116
|
+
comment.str = val
|
117
|
+
else
|
118
|
+
instance_variable_set "@#{type}".to_sym, Comment.new(type, val)
|
119
|
+
end
|
120
|
+
instance_variable_get "@#{type}".to_sym
|
121
|
+
})
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
ENTRIES_LABELS.each do |type, mark|
|
126
|
+
unless Entry.method_defined? "#{type}=".to_sym
|
127
|
+
self.class.send(:define_method, "#{type}=".to_sym, lambda { |val|
|
128
|
+
if instance_variable_get("@#{type}".to_sym).is_a? Message
|
129
|
+
message = instance_variable_get "@#{type}".to_sym
|
130
|
+
message.type = type
|
131
|
+
message.str = val
|
132
|
+
else
|
133
|
+
instance_variable_set "@#{type}".to_sym, Message.new(type, val)
|
134
|
+
end
|
135
|
+
instance_variable_get "@#{type}".to_sym
|
136
|
+
})
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
self.class.send(:alias_method, :translate, :msgstr=)
|
141
|
+
end
|
142
|
+
|
143
|
+
def define_reader_methods
|
144
|
+
LABELS.each do |label|
|
145
|
+
unless Entry.method_defined? "#{label}".to_sym
|
146
|
+
self.class.send(:define_method, label.to_sym) do
|
147
|
+
instance_variable_get "@#{label}".to_sym
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module PoParser
|
2
|
+
class Message
|
3
|
+
attr_accessor :type
|
4
|
+
attr_writer :str
|
5
|
+
|
6
|
+
def initialize(type, str)
|
7
|
+
@type = type
|
8
|
+
@str = str
|
9
|
+
|
10
|
+
remove_empty_line
|
11
|
+
end
|
12
|
+
|
13
|
+
def str
|
14
|
+
@str.join
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s(with_label = false)
|
18
|
+
return @str unless with_label
|
19
|
+
if @str.is_a? Array
|
20
|
+
remove_empty_line
|
21
|
+
# multiline messages should be started with an empty line
|
22
|
+
lines = ["#{label} \"\"\n"]
|
23
|
+
@str.each do |str|
|
24
|
+
lines << "\"#{str}\"\n"
|
25
|
+
end
|
26
|
+
return lines.join
|
27
|
+
else
|
28
|
+
"#{label} \"#{@str}\"\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_str
|
33
|
+
@str.join
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def remove_empty_line
|
38
|
+
if @str.is_a? Array
|
39
|
+
@str.shift if @str.first == ''
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def label
|
44
|
+
if @type.to_s.match(/msgstr\[[0-9]\]/)
|
45
|
+
@type
|
46
|
+
else
|
47
|
+
ENTRIES_LABELS[@type]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module PoParser
|
2
|
+
class Parser < Parslet::Parser
|
3
|
+
root(:document)
|
4
|
+
|
5
|
+
rule(:document) { lines.repeat }
|
6
|
+
rule(:lines) { comments | entries }
|
7
|
+
|
8
|
+
# Comments
|
9
|
+
rule(:comments) do
|
10
|
+
refrence |
|
11
|
+
extracted_comment | flag |
|
12
|
+
previous_untraslated_string |
|
13
|
+
translator_comment
|
14
|
+
end
|
15
|
+
|
16
|
+
rule(:translator_comment) { spaced('#') >> comment_text_line.as(:translator_comment) }
|
17
|
+
rule(:extracted_comment) { spaced('#.') >> comment_text_line.as(:extracted_comment) }
|
18
|
+
rule(:refrence) { spaced('#:') >> comment_text_line.as(:refrence) }
|
19
|
+
rule(:flag) { spaced('#,') >> comment_text_line.as(:flag) }
|
20
|
+
rule(:previous_untraslated_string){ spaced('#|') >> comment_text_line.as(:previous_untraslated_string) }
|
21
|
+
|
22
|
+
# Entries
|
23
|
+
rule(:entries) do
|
24
|
+
msgid.as(:msgid) |
|
25
|
+
msgid_plural.as(:msgid_plural) |
|
26
|
+
msgstr.as(:msgstr) |
|
27
|
+
msgstr_plural.as(:msgstr_plural) |
|
28
|
+
msgctxt.as(:msgctxt)
|
29
|
+
end
|
30
|
+
|
31
|
+
rule(:multiline) { str('"').present? >> msg_text_line.repeat.maybe }
|
32
|
+
rule(:msgid) { spaced('msgid') >> msg_text_line >> multiline.repeat }
|
33
|
+
rule(:msgid_plural) { spaced('msgid_plural') >> msg_text_line >> multiline.repeat }
|
34
|
+
|
35
|
+
rule(:msgstr) { spaced('msgstr') >> msg_text_line >> multiline.repeat }
|
36
|
+
rule(:msgstr_plural){ str('msgstr') >> bracketed(match["[0-9]"].as(:plural_id)) >> space? >> msg_text_line >> multiline.repeat }
|
37
|
+
rule(:msgctxt) { spaced('msgctxt') >> msg_text_line >> multiline.repeat }
|
38
|
+
|
39
|
+
# Helpers
|
40
|
+
rule(:space) { match['[^\S\n]'] } #match only whitespace and not newline
|
41
|
+
rule(:space?) { space.maybe }
|
42
|
+
rule(:newline) { match["\n"] }
|
43
|
+
rule(:eol) { newline | any.absent? }
|
44
|
+
rule(:character) { escaped | text }
|
45
|
+
rule(:text) { any }
|
46
|
+
rule(:escaped) { str('\\') >> any }
|
47
|
+
rule(:msg_line_end){ str('"') >> eol }
|
48
|
+
|
49
|
+
rule(:comment_text_line) do
|
50
|
+
(eol.absent? >> character).repeat.maybe.as(:text) >> eol
|
51
|
+
end
|
52
|
+
|
53
|
+
rule(:msg_text_line) do
|
54
|
+
str('"') >> (msg_line_end.absent? >> character).repeat.maybe.as(:text) >> msg_line_end
|
55
|
+
end
|
56
|
+
|
57
|
+
def bracketed(atom)
|
58
|
+
str('[') >> atom >> str(']')
|
59
|
+
end
|
60
|
+
|
61
|
+
def spaced(character)
|
62
|
+
str(character) >> space?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
data/lib/poparser/po.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
module PoParser
|
2
|
+
# Po class keeps all entries of a Po file
|
3
|
+
#
|
4
|
+
class Po
|
5
|
+
include Enumerable
|
6
|
+
attr_reader :entries
|
7
|
+
attr_accessor :path
|
8
|
+
alias_method :all, :entries
|
9
|
+
|
10
|
+
def initialize(args = {})
|
11
|
+
@entries = []
|
12
|
+
@path = args.fetch(:path, nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
# add new entries to po file
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# entry = {
|
19
|
+
# translator_comment: 'comment',
|
20
|
+
# refrence: 'refrense comment',
|
21
|
+
# flag: 'fuzzy',
|
22
|
+
# msgstr: 'translatable string',
|
23
|
+
# msgstr: 'translation'
|
24
|
+
# }
|
25
|
+
# add_entry(entry)
|
26
|
+
#
|
27
|
+
# @param entry [Hash, Array] a hash of entry contents or an array of hashes
|
28
|
+
def add_entry(entry)
|
29
|
+
if entry.kind_of? Hash
|
30
|
+
@entries << Entry.new(entry)
|
31
|
+
@entries.last
|
32
|
+
elsif entry.kind_of? Array
|
33
|
+
entry.each do |en|
|
34
|
+
@entries << Entry.new(en)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise ArgumentError, 'Must be a hash or an array of hashes'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
alias_method :<<, :add_entry
|
41
|
+
|
42
|
+
# Finds all entries that are flaged as fuzzy
|
43
|
+
#
|
44
|
+
# @return [Array] an array of fuzzy entries
|
45
|
+
def fuzzy
|
46
|
+
find_all do |entry|
|
47
|
+
entry.fuzzy?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Finds all entries that are untranslated
|
52
|
+
#
|
53
|
+
# @return [Array] an array of untranslated entries
|
54
|
+
def untranslated
|
55
|
+
find_all do |entry|
|
56
|
+
entry.untranslated?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Finds all entries that are translated
|
61
|
+
#
|
62
|
+
# @return [Array] an array of translated entries
|
63
|
+
def translated
|
64
|
+
find_all do |entry|
|
65
|
+
entry.translated?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Shows statistics and status of the provided file in percentage.
|
70
|
+
#
|
71
|
+
# @return [Hash] a hash of translated, untranslated and fuzzy percentages
|
72
|
+
def stats
|
73
|
+
untranslated_size = untranslated.size
|
74
|
+
translated_size = translated.size
|
75
|
+
fuzzy_size = fuzzy.size
|
76
|
+
|
77
|
+
{
|
78
|
+
translated: percentage(translated_size - fuzzy_size),
|
79
|
+
untranslated: percentage(untranslated_size),
|
80
|
+
fuzzy: percentage(fuzzy_size)
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
# Converts Po file to an hashes of entries
|
85
|
+
#
|
86
|
+
# @return [Array] array of hashes of entries
|
87
|
+
def to_h
|
88
|
+
array = []
|
89
|
+
@entries.each do |entry|
|
90
|
+
array << entry.to_h
|
91
|
+
end
|
92
|
+
array
|
93
|
+
end
|
94
|
+
|
95
|
+
# Shows a String representation of the Po file
|
96
|
+
#
|
97
|
+
# @return [String]
|
98
|
+
def to_s
|
99
|
+
array = []
|
100
|
+
@entries.each do |entry|
|
101
|
+
array << entry.to_s
|
102
|
+
end
|
103
|
+
array.join("\n")
|
104
|
+
end
|
105
|
+
|
106
|
+
# Saves the file to the provided path
|
107
|
+
def save_file
|
108
|
+
raise ArgumentError, 'Need a Path to save the file' if @path.nil?
|
109
|
+
File.open(@path, 'w') do |f|
|
110
|
+
f.write to_s
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def each
|
115
|
+
@entries.each do |entry|
|
116
|
+
yield entry
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def inspect
|
121
|
+
"<#{self.class.name}, Translated: #{stats[:translated]}% Untranslated: #{stats[:untranslated]}% Fuzzy: #{stats[:fuzzy]}%>"
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
# calculates percentages based on total number of entries
|
126
|
+
#
|
127
|
+
# @param [Integer] number of entries
|
128
|
+
# @return [Float] percentage of the provided entries
|
129
|
+
def percentage(size)
|
130
|
+
total = @entries.size
|
131
|
+
((size.to_f / total) * 100).round(1)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|