polint 0.0.3 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d287c6785e99e624589ae4ac2b01308c7469c9d1
4
- data.tar.gz: 152df4d5a346490c23aac535ca0b6c0e18817608
3
+ metadata.gz: d183c6f2819c7036fef793ace5644f76349b3f24
4
+ data.tar.gz: e8a9bcda101105ce78e492790021fae4643cc015
5
5
  SHA512:
6
- metadata.gz: 13caed3a5b182a0c401d40a3ac09d423e232a7226db0dac6c45b54c29a485f06e8b7b9d4d8fb74dc09e220986fe831841b720ede161ef42cda687a2efa4862a5
7
- data.tar.gz: 31f8801ab58bfd387fbba735b0985fb27d4640752ac79f9a5f87c2e7dd0787b99350b265e07874a1899b0aa82cf526461a4d273d2364d80c7450204b3ef112ad
6
+ metadata.gz: 0dbb533fad098c6a220f317b8e88c910e2c80fa8fee04902b934c0f067b66d1f278b77c99319d1f3566d35329362ec5e30d22d14fada4e16b93706eb5b002ce4
7
+ data.tar.gz: 14b55b4f3a1d6078fe003ff097d6a574c7a77a483b2bcc7d513d8654b565258c375715531c82ccc420753d3bb4e30cde10788986e2a57811bd259948d5c72772
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/Gemfile.lock CHANGED
@@ -1,29 +1,86 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polint (0.0.3)
4
+ polint (0.1.0)
5
+ parslet (~> 1.7, >= 1.7.1)
5
6
  term-ansicolor
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
- coderay (1.1.0)
11
+ blankslate (3.1.3)
12
+ byebug (9.0.5)
13
+ coderay (1.1.1)
14
+ diff-lcs (1.2.5)
15
+ ffi (1.9.10)
16
+ formatador (0.2.5)
17
+ guard (2.14.0)
18
+ formatador (>= 0.2.4)
19
+ listen (>= 2.7, < 4.0)
20
+ lumberjack (~> 1.0)
21
+ nenv (~> 0.1)
22
+ notiffany (~> 0.0)
23
+ pry (>= 0.9.12)
24
+ shellany (~> 0.0)
25
+ thor (>= 0.18.1)
26
+ guard-compat (1.2.1)
27
+ guard-rspec (4.7.2)
28
+ guard (~> 2.1)
29
+ guard-compat (~> 1.1)
30
+ rspec (>= 2.99.0, < 4.0)
31
+ listen (3.1.5)
32
+ rb-fsevent (~> 0.9, >= 0.9.4)
33
+ rb-inotify (~> 0.9, >= 0.9.7)
34
+ ruby_dep (~> 1.2)
35
+ lumberjack (1.0.10)
11
36
  method_source (0.8.2)
12
- pry (0.10.1)
37
+ nenv (0.3.0)
38
+ notiffany (0.1.0)
39
+ nenv (~> 0.1)
40
+ shellany (~> 0.0)
41
+ parslet (1.7.1)
42
+ blankslate (>= 2.0, <= 4.0)
43
+ pry (0.10.3)
13
44
  coderay (~> 1.1.0)
14
45
  method_source (~> 0.8.1)
15
46
  slop (~> 3.4)
16
- rake (10.4.2)
47
+ pry-byebug (3.4.0)
48
+ byebug (~> 9.0)
49
+ pry (~> 0.10)
50
+ rake (10.5.0)
51
+ rb-fsevent (0.9.7)
52
+ rb-inotify (0.9.7)
53
+ ffi (>= 0.5.0)
54
+ rspec (3.4.0)
55
+ rspec-core (~> 3.4.0)
56
+ rspec-expectations (~> 3.4.0)
57
+ rspec-mocks (~> 3.4.0)
58
+ rspec-core (3.4.4)
59
+ rspec-support (~> 3.4.0)
60
+ rspec-expectations (3.4.0)
61
+ diff-lcs (>= 1.2.0, < 2.0)
62
+ rspec-support (~> 3.4.0)
63
+ rspec-mocks (3.4.1)
64
+ diff-lcs (>= 1.2.0, < 2.0)
65
+ rspec-support (~> 3.4.0)
66
+ rspec-support (3.4.1)
67
+ ruby_dep (1.3.1)
68
+ shellany (0.0.1)
17
69
  slop (3.6.0)
18
- term-ansicolor (1.3.0)
70
+ term-ansicolor (1.3.2)
19
71
  tins (~> 1.0)
20
- tins (1.3.5)
72
+ thor (0.19.1)
73
+ tins (1.10.2)
21
74
 
22
75
  PLATFORMS
23
76
  ruby
24
77
 
25
78
  DEPENDENCIES
26
79
  bundler (~> 1.7)
80
+ guard-rspec
27
81
  polint!
28
- pry
82
+ pry-byebug
29
83
  rake (~> 10.0)
84
+
85
+ BUNDLED WITH
86
+ 1.12.5
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ guard :rspec, cmd: "bundle exec rspec" do
2
+ require "guard/rspec/dsl"
3
+ dsl = Guard::RSpec::Dsl.new(self)
4
+
5
+ rspec = dsl.rspec
6
+ watch(rspec.spec_helper) { rspec.spec_dir }
7
+ watch(rspec.spec_support) { rspec.spec_dir }
8
+ watch(rspec.spec_files)
9
+
10
+ ruby = dsl.ruby
11
+ dsl.watch_spec_files_for(ruby.lib_files)
12
+
13
+ watch(%r{^spec/data/.*\.po}) { 'spec/lib/polint_spec.rb' }
14
+ end
data/lib/polint.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # coding: utf-8
2
2
  require 'polint/version'
3
+ require 'polint/parser'
4
+ require 'polint/transform'
5
+ require 'term/ansicolor'
3
6
  require 'singleton'
4
7
 
5
8
  module Polint
@@ -38,6 +41,34 @@ module Polint
38
41
 
39
42
  private
40
43
 
44
+ def error(key, val, contexts, message)
45
+ log key, val, contexts, "Error: #{message}", :red
46
+ end
47
+
48
+ def warn(key, val, contexts, message)
49
+ log key, val, contexts, "Warning: #{message}", :yellow
50
+ end
51
+
52
+ def log(key, val, contexts, message, color)
53
+ if key =~ /(\d+):(.*)/
54
+ lineno = $1
55
+ file_and_line = "#{@pofile}:#{$1}:"
56
+ key = $2
57
+ else
58
+ lineno = nil
59
+ file_and_line = "#{@pofile}:"
60
+ end
61
+ puts "#{file_and_line}#{term.public_send(color)} #{message}.#{term.clear}"
62
+ if @verbose
63
+ contexts.each do |context|
64
+ puts "#{term.blue}CONTEXT:#{term.clear} #{context}"
65
+ end
66
+ puts "#{term.blue}KEY:#{term.clear} #{key}"
67
+ puts "#{term.blue}TRN:#{term.clear} #{val}"
68
+ puts "–" * 80
69
+ end
70
+ end
71
+
41
72
  def term
42
73
  @term ||= ENV['NOCOLOR'] ? BlackHole.instance : Term::ANSIColor
43
74
  end
@@ -77,39 +108,49 @@ module Polint
77
108
  io.write data.join
78
109
  io.flush
79
110
  io.close_write
80
- data = io.read.split(/\n/)
111
+ data = io.read
81
112
  end
82
113
  success and return data
83
114
  die "Error while loading the PO file '#{@pofile}', aborting."
84
115
  end
85
116
 
86
- def parse_data(lines)
87
- current = []
88
- context = []
89
- next_is_fuzzy = false
90
-
91
- lines.each do |line|
92
- case line
117
+ def parse_data(data)
118
+ return if data.nil? || data.empty?
93
119
 
94
- when /^#, fuzzy/
95
- next_is_fuzzy = true
120
+ tree = Polint::Parser.new.parse(data)
121
+ tree = Polint::Transform.new.apply(tree)
96
122
 
97
- when /^#: (.*)/
98
- context << $1
123
+ die 'No Plural-Forms header found' unless tree[:headers].key?('Plural-Forms')
124
+ nplurals = tree[:headers]['Plural-Forms'][:nplurals]
99
125
 
100
- when /^(msgid|msgid_plural)\s+"(.*)"$/
101
- current << $2
126
+ tree[:translations].each do |translation|
127
+ msgid, msgid_plural = translation[:msgid][:text], translation[:msgid_plural][:text]
128
+ contexts = translation[:references]
129
+ fuzzy = translation[:flags].include?(:fuzzy)
102
130
 
103
- when /^(msgstr(?:\[([01])\])?)\s+"(.*)"$/
104
- index = $2.to_i # $2 will be nil for non-plural, so index will be 0
105
- check_pair(current[index], $3, context, next_is_fuzzy)
131
+ if msgid_plural.nil?
132
+ if translation[:msgstrs].size != 1
133
+ error msgid, nil, contexts, "#{translation[:msgstrs].size} plurals found but none expected"
134
+ @errors += 1
135
+ end
106
136
 
137
+ check_pair(msgid, translation[:msgstrs][0][:text], translation[:references], fuzzy)
107
138
  else
108
- current = []
109
- context = []
110
- next_is_fuzzy = false
139
+ if translation[:msgstrs].size != nplurals
140
+ error msgid, nil, contexts, "#{translation[:msgstrs].size} plurals found but #{nplurals} expected"
141
+ @errors += 1
142
+ end
143
+
144
+ plural_attr = (msgid_plural.scan(AttributeRe).uniq - msgid.scan(AttributeRe).uniq).first
145
+ translation[:msgstrs].each do |msgstr|
146
+ val = msgstr[:text]
147
+ msgid_check = val.scan(AttributeRe).include?(plural_attr) ? msgid_plural : msgid
148
+ check_pair(msgid_check, val, contexts, fuzzy)
149
+ end
111
150
  end
112
151
  end
152
+ rescue Parslet::ParseFailed => e
153
+ die e.cause.ascii_tree
113
154
  end
114
155
 
115
156
  # Check for errors in a key/translation pair.
@@ -129,36 +170,18 @@ module Polint
129
170
  @errors += 1 if not_in_val.any? || not_in_key.any? || is_empty
130
171
  @fuzzies += 1 if is_fuzzy
131
172
 
132
- if key =~ /(\d+):(.*)/
133
- lineno = $1
134
- file_and_line = "#{@pofile}:#{$1}:"
135
- key = $2
136
- else
137
- lineno = nil
138
- file_and_line = "#{@pofile}:"
139
- end
140
-
141
173
  if is_empty
142
- puts "#{file_and_line}#{term.red} Error: translated string empty.#{term.clear}"
174
+ error key, val, contexts, "translated string empty"
143
175
  elsif not_in_key.any?
144
176
  not_in_key.each do |name|
145
- puts "#{file_and_line}#{term.red} Error: #{name} absent from reference string.#{term.clear}"
177
+ error key, val, contexts, "#{name} absent from reference string"
146
178
  end
147
179
  elsif not_in_val.any?
148
180
  not_in_val.each do |name|
149
- puts "#{file_and_line}#{term.yellow} Warning: #{name} absent from translated string.#{term.clear}"
181
+ warn key, val, contexts, "#{name} absent from translated string"
150
182
  end
151
183
  elsif is_fuzzy
152
- puts "#{file_and_line}#{term.yellow} Warning: translation is fuzzy.#{term.clear}"
153
- end
154
-
155
- if @verbose
156
- contexts.each do |context|
157
- puts "#{term.blue}CONTEXT:#{term.clear} #{context}"
158
- end
159
- puts "#{term.blue}KEY:#{term.clear} #{key}"
160
- puts "#{term.blue}TRN:#{term.clear} #{val}"
161
- puts "–" * 80
184
+ warn key, val, contexts, "translation is fuzzy"
162
185
  end
163
186
  end
164
187
  end
@@ -0,0 +1,52 @@
1
+ require 'parslet'
2
+
3
+ module Polint
4
+ class Parser < Parslet::Parser
5
+
6
+ rule(:endl) { str("\n").maybe }
7
+ rule(:sp) { str(' ') }
8
+ rule(:htab) { str("\t") }
9
+ rule(:wsp) { sp | htab }
10
+ rule(:lwsp) { wsp.repeat }
11
+ rule(:tcase) { match(/[A-Z]/).repeat(1) >> match(/[a-z]/).repeat }
12
+ rule(:blank_line) { endl }
13
+
14
+ rule(:quote) { str('"') }
15
+ rule(:quoted_char) { match(/[^"]/) }
16
+ rule(:quoted_pair) { str('\\') >> quote }
17
+ rule(:quoted_string) { quote >> (quoted_pair | quoted_char).repeat.as(:quoted_string) >> quote >> lwsp }
18
+ rule(:quoted_strings) { (quoted_string >> endl).repeat(1) }
19
+
20
+ rule(:header_sep) { str(':') >> lwsp }
21
+ rule(:header_name) { (tcase >> (str('-') >> tcase).repeat).as(:name) >> header_sep }
22
+ rule(:header_value) { match(/[^\n"]/).repeat.as(:value) }
23
+ rule(:raw_header) { header_name >> header_value }
24
+ rule(:nplurals) { str('nplurals=') >> match(/[0-9]/).repeat.as(:nplurals) >> str(';') >> lwsp }
25
+ rule(:plural) { str('plural=') >> match(/[^\n"]/).repeat.as(:plural) }
26
+ rule(:plural_forms_header) { str('Plural-Forms').as(:name) >> header_sep >> (nplurals >> plural).as(:value) }
27
+ rule(:unquoted_header) { plural_forms_header | raw_header }
28
+ rule(:header) { quote >> unquoted_header >> quote >> lwsp }
29
+ rule(:header_lines) { (header >> endl).repeat(1) }
30
+ rule(:headers) { str('msgid') >> lwsp >> quote >> quote >> endl >> str('msgstr') >> lwsp >> ((quoted_string >> endl).maybe >> header_lines).as(:headers) >> blank_line }
31
+
32
+ rule(:start_comment) { str('#') }
33
+ rule(:flag) { str(',') >> lwsp >> match(/[a-z\-]/).repeat(1).as(:flag) >> lwsp }
34
+ rule(:flag_comment) { start_comment >> flag.repeat(1).as(:flags) >> endl }
35
+ rule(:reference_comment) { start_comment >> str(':') >> lwsp >> match(/[^\n]/).repeat.as(:reference) >> endl }
36
+ rule(:comment) { flag_comment | reference_comment }
37
+ rule(:comments) { comment.repeat }
38
+
39
+ rule(:msgid) { str('msgid') >> lwsp >> quoted_strings.as(:msgid) }
40
+ rule(:msgid_plural) { str('msgid_plural') >> lwsp >> quoted_strings.as(:msgid_plural) }
41
+
42
+ rule(:index) { str('[') >> match(/[0-9]/).repeat(1).as(:index) >> str(']') }
43
+ rule(:msgstr) { str('msgstr') >> (index.maybe >> lwsp >> quoted_strings).as(:msgstr) }
44
+
45
+ rule(:translation) { (comments >> msgid >> msgid_plural.maybe >> msgstr.repeat(1)).as(:translation) >> blank_line }
46
+ rule(:translations) { translation.repeat }
47
+
48
+ rule(:file) { (headers >> translations).as(:file) }
49
+ root(:file)
50
+
51
+ end
52
+ end
@@ -0,0 +1,64 @@
1
+ require 'parslet'
2
+
3
+ module Polint
4
+ class Transform < Parslet::Transform
5
+
6
+ rule(flag: simple(:f)) { f.to_sym }
7
+
8
+ rule(quoted_string: simple(:qs)) { qs }
9
+ rule(quoted_string: sequence(:qs)) { '' } # empty string is parsed as []
10
+
11
+ rule(name: simple(:k), value: simple(:v)) { [k.to_s, v.to_s] }
12
+ rule(name: simple(:k), value: subtree(:v)) { [k.to_s, v] }
13
+ rule(nplurals: simple(:n), plural: simple(:p)) { { nplurals: n.to_i, plural: p } }
14
+ rule(headers: subtree(:items)) do
15
+ items.shift if items.first == ''
16
+ { headers: items.to_h }
17
+ end
18
+
19
+ rule(msgid: sequence(:items)) { { msgid: { text: items.join("\n") } } }
20
+ rule(msgid_plural: sequence(:items)) { { msgid_plural: { text: items.join("\n") } } }
21
+ rule(msgstr: subtree(:items)) do
22
+ msgstr = {}
23
+ msgstr[:index] = items.shift[:index].to_i if items.first.is_a?(Hash)
24
+ msgstr[:text] = items.join("\n")
25
+ { msgstr: msgstr }
26
+ end
27
+
28
+ rule(translation: subtree(:items)) do
29
+ translation = {
30
+ flags: [],
31
+ references: [],
32
+ msgid: {},
33
+ msgid_plural: {},
34
+ msgstrs: []
35
+ }
36
+ items.each do |hash|
37
+ k, v = hash.to_a.first
38
+ case k
39
+ when :flags then translation[:flags] |= v
40
+ when :reference then translation[:references] << v
41
+ when :msgid, :msgid_plural then translation[k] = v
42
+ when :msgstr then translation[:msgstrs] << v
43
+ end
44
+ end
45
+ { translation: translation }
46
+ end
47
+
48
+ rule(file: subtree(:items)) do
49
+ file = {
50
+ headers: {},
51
+ translations: []
52
+ }
53
+ items.each do |hash|
54
+ k, v = hash.to_a.first
55
+ case k
56
+ when :headers then file[:headers] = v
57
+ when :translation then file[:translations] << v
58
+ end
59
+ end
60
+ file
61
+ end
62
+
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module Polint
2
- VERSION = "0.0.3"
2
+ VERSION = "0.2.0"
3
3
  end
data/polint.gemspec CHANGED
@@ -6,8 +6,8 @@ require 'polint/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "polint"
8
8
  spec.version = Polint::VERSION
9
- spec.authors = ["Julien Letessier"]
10
- spec.email = ["julien.letessier@gmail.com"]
9
+ spec.authors = ["Julien Letessier", "Greg Beech"]
10
+ spec.email = ["julien.letessier@gmail.com", "greg@gregbeech.com"]
11
11
  spec.summary = %q{A linter for Uniforum PO files.}
12
12
  spec.homepage = ""
13
13
  spec.license = "MIT"
@@ -19,7 +19,9 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.7"
21
21
  spec.add_development_dependency "rake", "~> 10.0"
22
- spec.add_development_dependency "pry"
22
+ spec.add_development_dependency "pry-byebug"
23
+ spec.add_development_dependency "guard-rspec"
23
24
 
24
25
  spec.add_dependency "term-ansicolor"
26
+ spec.add_dependency "parslet", "~> 1.7", ">= 1.7.1"
25
27
  end
@@ -0,0 +1,22 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: ar\n"
4
+ "MIME-Version: 1.0\n"
5
+ "Content-Type: text/plain; charset=UTF-8\n"
6
+ "Content-Transfer-Encoding: 8bit\n"
7
+ "Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
8
+ "X-Generator: PhraseApp (phraseapp.com)\n"
9
+
10
+ #: ../some_file.rb:51
11
+ msgid "Hello World"
12
+ msgstr "مرحبا بالعالم"
13
+
14
+ #: ../some_file.rb:51
15
+ msgid "Hello World Plural"
16
+ msgid_plural "Hello %{n} Worlds Plural"
17
+ msgstr[0] "مرحبا العالمي الجمع"
18
+ msgstr[1] "مرحبا %{n} العالمين الجمع"
19
+ msgstr[2] "مرحبا %{n} العالمين الجمع"
20
+ msgstr[3] "مرحبا %{n} العالمين الجمع"
21
+ msgstr[4] "مرحبا %{n} العالمين الجمع"
22
+ msgstr[5] "مرحبا %{n} العالمين الجمع"
@@ -0,0 +1,18 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: en\n"
4
+ "MIME-Version: 1.0\n"
5
+ "Content-Type: text/plain; charset=UTF-8\n"
6
+ "Content-Transfer-Encoding: 8bit\n"
7
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
8
+ "X-Generator: PhraseApp (phraseapp.com)\n"
9
+
10
+ #: ../some_file.rb:51
11
+ msgid "Hello World"
12
+ msgstr "Hello World"
13
+
14
+ #: ../some_file.rb:51
15
+ msgid "Hello World Plural"
16
+ msgid_plural "Hello %{n} Worlds Plural"
17
+ msgstr[0] "Hello World Plural"
18
+ msgstr[1] "Hello %{n} Worlds Plural"
@@ -0,0 +1,18 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: fr\n"
4
+ "MIME-Version: 1.0\n"
5
+ "Content-Type: text/plain; charset=UTF-8\n"
6
+ "Content-Transfer-Encoding: 8bit\n"
7
+ "Plural-Forms: nplurals=2; plural=(n > 1);\n"
8
+ "X-Generator: PhraseApp (phraseapp.com)\n"
9
+
10
+ #: ../some_file.rb:51
11
+ msgid "Hello World"
12
+ msgstr "Bonjour le monde"
13
+
14
+ #: ../some_file.rb:51
15
+ msgid "Hello World Plural"
16
+ msgid_plural "Hello %{n} Worlds Plural"
17
+ msgstr[0] "Bonjour tout le monde pluriel"
18
+ msgstr[1] "Bonjour %{n} mondes pluriels"
@@ -0,0 +1,17 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Language: zh\n"
4
+ "MIME-Version: 1.0\n"
5
+ "Content-Type: text/plain; charset=UTF-8\n"
6
+ "Content-Transfer-Encoding: 8bit\n"
7
+ "Plural-Forms: nplurals=1; plural=0;\n"
8
+ "X-Generator: PhraseApp (phraseapp.com)\n"
9
+
10
+ #: ../some_file.rb:51
11
+ msgid "Hello World"
12
+ msgstr "你好,世界"
13
+
14
+ #: ../some_file.rb:51
15
+ msgid "Hello World Plural"
16
+ msgid_plural "Hello %{n} Worlds Plural"
17
+ msgstr[0] "你好%{n}世界多元"
@@ -0,0 +1,258 @@
1
+ require 'polint/parser'
2
+
3
+ RSpec.describe Polint::Parser do
4
+ let(:parser) { described_class.new }
5
+ let(:rule) { :translation }
6
+ let(:line) { '' }
7
+ let(:tree) { parser.send(rule).parse(line) }
8
+
9
+ describe 'rule(:quoted_string)' do
10
+ let(:rule) { :quoted_string }
11
+
12
+ context 'when matching a simple string' do
13
+ let(:line) { '"Hello World"' }
14
+ it { expect(tree).to eq quoted_string: 'Hello World' }
15
+ end
16
+
17
+ context 'when matching a string with embedded quotes' do
18
+ let(:line) { '"Hello \\"my\\" World"' }
19
+ it { expect(tree).to eq quoted_string: 'Hello \\"my\\" World' }
20
+ end
21
+ end
22
+
23
+ describe 'rule(:headers)' do
24
+ let(:rule) { :headers }
25
+ let(:line) { lines.join("\n") }
26
+
27
+ context 'when matching a plural forms header' do
28
+ let(:lines) {
29
+ [
30
+ 'msgid ""',
31
+ 'msgstr ""',
32
+ '"Language: ar\n"',
33
+ '"MIME-Version: 1.0\n"',
34
+ '"Content-Type: text/plain; charset=UTF-8\n"',
35
+ '"Content-Transfer-Encoding: 8bit\n"',
36
+ '"Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"',
37
+ '"X-Generator: PhraseApp (phraseapp.com)\n"'
38
+ ]
39
+ }
40
+ it {
41
+ expect(tree).to eq headers: [
42
+ { quoted_string: [] },
43
+ { name: 'Language', value: 'ar\n' },
44
+ { name: 'MIME-Version', value: '1.0\n' },
45
+ { name: 'Content-Type', value: 'text/plain; charset=UTF-8\n' },
46
+ { name: 'Content-Transfer-Encoding', value: '8bit\n' },
47
+ { name: 'Plural-Forms', value: { nplurals: '6', plural: ' n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n' } },
48
+ { name: 'X-Generator', value: 'PhraseApp (phraseapp.com)\n' }
49
+ ]
50
+ }
51
+ end
52
+
53
+ context 'when matching an unquoted header' do
54
+ let(:line) { 'Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n' }
55
+ it { expect{ tree }.to raise_error Parslet::ParseFailed }
56
+ end
57
+ end
58
+
59
+ describe 'rule(:flag_comment)' do
60
+ let(:rule) { :flag_comment }
61
+
62
+ context 'when matching a fuzzy comment' do
63
+ let(:line) { '#, fuzzy' }
64
+ it { expect(tree).to eq flags: [{ flag: 'fuzzy' }] }
65
+ end
66
+
67
+ context 'when matching a multi-flag comment' do
68
+ let(:line) { '#, java-format, fuzzy' }
69
+ it { expect(tree).to eq flags: [{ flag: 'java-format' }, { flag: 'fuzzy' }] }
70
+ end
71
+
72
+ context 'when matching a non-flags comment' do
73
+ let(:line) { '#: ../../some_file.rb:34' }
74
+ it { expect{ tree }.to raise_error Parslet::ParseFailed }
75
+ end
76
+ end
77
+
78
+ describe 'rule(:reference_comment)' do
79
+ let(:rule) { :reference_comment }
80
+
81
+ context 'when matching a reference comment' do
82
+ let(:line) { '#: ../../some_file.rb:34' }
83
+ it { expect(tree).to eq reference: '../../some_file.rb:34' }
84
+ end
85
+
86
+ context 'when matching a non-reference comment' do
87
+ let(:line) { '#, fuzzy' }
88
+ it { expect{ tree }.to raise_error Parslet::ParseFailed }
89
+ end
90
+ end
91
+
92
+ describe 'rule(:msgid)' do
93
+ let(:rule) { :msgid }
94
+
95
+ context 'when matching a single-line msgid' do
96
+ let(:line) { 'msgid "Hello World"' }
97
+ it { expect(tree).to eq msgid: [{ quoted_string: 'Hello World' }] }
98
+ end
99
+
100
+ context 'when matching a multi-line msgid' do
101
+ let(:line) { %{msgid ""\n"Hello World"\n"Hello Again"} }
102
+ it { expect(tree).to eq msgid: [{ quoted_string: [] }, { quoted_string: 'Hello World' }, { quoted_string: 'Hello Again'}] }
103
+ end
104
+
105
+ context 'when matching a msgid with embedded quotes' do
106
+ let(:line) { 'msgid "Hello \\"my\\" World"' }
107
+ it { expect(tree).to eq msgid: [{ quoted_string: 'Hello \\"my\\" World' }] }
108
+ end
109
+
110
+ context 'when matching an unquoted msgid' do
111
+ let(:line) { 'msgid Unquoted String' }
112
+ it { expect{ tree }.to raise_error Parslet::ParseFailed }
113
+ end
114
+ end
115
+
116
+ describe 'rule(:msgid_plural)' do
117
+ let(:rule) { :msgid_plural }
118
+
119
+ context 'when matching a single-line msgid_plural' do
120
+ let(:line) { 'msgid_plural "Hello %{n} Worlds"' }
121
+ it { expect(tree).to eq msgid_plural: [{ quoted_string: 'Hello %{n} Worlds' }] }
122
+ end
123
+
124
+ context 'when matching a multi-line msgid_plural' do
125
+ let(:line) { %{msgid_plural ""\n"Hello %{n} Worlds"\n"Hello Again"} }
126
+ it { expect(tree).to eq msgid_plural: [{ quoted_string: [] }, { quoted_string: 'Hello %{n} Worlds' }, { quoted_string: 'Hello Again'}] }
127
+ end
128
+
129
+ context 'when matching an unquoted msgid_plural' do
130
+ let(:line) { 'msgid_plural Unquoted String' }
131
+ it { expect{ tree }.to raise_error Parslet::ParseFailed }
132
+ end
133
+ end
134
+
135
+ describe 'rule(:msgstr)' do
136
+ let(:rule) { :msgstr }
137
+
138
+ context 'when matching a single-line msgstr' do
139
+ let(:line) { 'msgstr "Hello World"' }
140
+ it { expect(tree).to eq msgstr: [{ quoted_string: 'Hello World' }] }
141
+ end
142
+
143
+ context 'when matching a single-line msgstr with an index' do
144
+ let(:line) { 'msgstr[3] "Hello World"' }
145
+ it { expect(tree).to eq msgstr: [{ index: '3' }, { quoted_string: 'Hello World' }] }
146
+ end
147
+
148
+ context 'when matching a multi-line msgstr' do
149
+ let(:line) { %{msgstr ""\n"Hello World"\n"Hello Again"} }
150
+ it { expect(tree).to eq msgstr: [{ quoted_string: [] }, { quoted_string: 'Hello World' }, { quoted_string: 'Hello Again'}] }
151
+ end
152
+
153
+ context 'when matching a multi-line msgstr with an index' do
154
+ let(:line) { %{msgstr[1] ""\n"Hello World"\n"Hello Again"} }
155
+ it { expect(tree).to eq msgstr: [{ index: '1' }, { quoted_string: [] }, { quoted_string: 'Hello World' }, { quoted_string: 'Hello Again'}] }
156
+ end
157
+
158
+ context 'when matching an unquoted msgstr' do
159
+ let(:line) { 'msgstr Unquoted String' }
160
+ it { expect{ tree }.to raise_error Parslet::ParseFailed }
161
+ end
162
+ end
163
+
164
+ describe 'rule(:translation)' do
165
+ let(:rule) { :translation }
166
+ let(:line) { lines.join("\n") }
167
+
168
+ context 'when matching a singular single-line msgstr with comments' do
169
+ let(:lines) {
170
+ [
171
+ '#, fuzzy',
172
+ '#: ../../some_file.rb:34',
173
+ 'msgid "Hello World"',
174
+ 'msgstr "Hello World"'
175
+ ]
176
+ }
177
+ it {
178
+ expect(tree).to eq translation: [
179
+ { flags: [{ flag: 'fuzzy' }] },
180
+ { reference: '../../some_file.rb:34' },
181
+ { msgid: [{ quoted_string: 'Hello World' }] },
182
+ { msgstr: [{ quoted_string: 'Hello World' }] }
183
+ ]
184
+ }
185
+ end
186
+
187
+ context 'when matching a plural single-line msgstr with comments' do
188
+ let(:lines) {
189
+ [
190
+ '#, fuzzy',
191
+ '#: ../../some_file.rb:34',
192
+ 'msgid "Hello World"',
193
+ 'msgid_plural "Hello %{n} Worlds"',
194
+ 'msgstr[0] "Hello World"',
195
+ 'msgstr[1] "Hello %{n} Worlds"'
196
+ ]
197
+ }
198
+ it {
199
+ expect(tree).to eq translation: [
200
+ { flags: [{ flag: 'fuzzy' }] },
201
+ { reference: '../../some_file.rb:34' },
202
+ { msgid: [{ quoted_string: 'Hello World' }] },
203
+ { msgid_plural: [{ quoted_string: 'Hello %{n} Worlds' }] },
204
+ { msgstr: [{ index: '0' }, { quoted_string: 'Hello World' }] },
205
+ { msgstr: [{ index: '1' }, { quoted_string: 'Hello %{n} Worlds' }] }
206
+ ]
207
+ }
208
+ end
209
+
210
+ # TODO: Multiline strings and negative cases
211
+ end
212
+
213
+ describe 'rule(:translations)' do
214
+ let(:rule) { :translations }
215
+ let(:line) { lines.join("\n") }
216
+
217
+ context 'when matching multiple translations' do
218
+ let(:lines) {
219
+ [
220
+ '#, fuzzy',
221
+ '#: ../../some_file.rb:34',
222
+ 'msgid "Hello World"',
223
+ 'msgstr "Hello World"',
224
+ '',
225
+ '#, fuzzy',
226
+ '#: ../../some_file.rb:34',
227
+ 'msgid "Hello World"',
228
+ 'msgid_plural "Hello %{n} Worlds"',
229
+ 'msgstr[0] "Hello World"',
230
+ 'msgstr[1] "Hello %{n} Worlds"'
231
+ ]
232
+ }
233
+ it {
234
+ expect(tree).to eq [
235
+ {
236
+ translation: [
237
+ { flags: [{ flag: 'fuzzy' }] },
238
+ { reference: '../../some_file.rb:34' },
239
+ { msgid: [{ quoted_string: 'Hello World' }] },
240
+ { msgstr: [{ quoted_string: 'Hello World' }] }
241
+ ]
242
+ },
243
+ {
244
+ translation: [
245
+ { flags: [{ flag: 'fuzzy' }] },
246
+ { reference: '../../some_file.rb:34' },
247
+ { msgid: [{ quoted_string: 'Hello World' }] },
248
+ { msgid_plural: [{ quoted_string: 'Hello %{n} Worlds' }] },
249
+ { msgstr: [{ index: '0' }, { quoted_string: 'Hello World' }] },
250
+ { msgstr: [{ index: '1' }, { quoted_string: 'Hello %{n} Worlds' }] }
251
+ ]
252
+ }
253
+ ]
254
+ }
255
+ end
256
+ end
257
+
258
+ end
@@ -0,0 +1,61 @@
1
+ require 'polint/transform'
2
+
3
+ RSpec.describe Polint::Transform do
4
+ let(:transform) { described_class.new }
5
+ let(:output) { transform.apply(tree) }
6
+
7
+ context 'with a headers tree' do
8
+ let(:tree) {
9
+ {
10
+ headers: [
11
+ { quoted_string: [] },
12
+ { name: 'Language', value: 'ar\n' },
13
+ { name: 'MIME-Version', value: '1.0\n' },
14
+ { name: 'Content-Type', value: 'text/plain; charset=UTF-8\n' },
15
+ { name: 'Content-Transfer-Encoding', value: '8bit\n' },
16
+ { name: 'Plural-Forms', value: { nplurals: '6', plural: ' n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n' } },
17
+ { name: 'X-Generator', value: 'PhraseApp (phraseapp.com)\n' }
18
+ ]
19
+ }
20
+ }
21
+ it {
22
+ expect(output).to eq headers: {
23
+ 'Language' => 'ar\n',
24
+ 'MIME-Version' => '1.0\n',
25
+ 'Content-Type' => 'text/plain; charset=UTF-8\n',
26
+ 'Content-Transfer-Encoding' => '8bit\n',
27
+ 'Plural-Forms' => { nplurals: 6, plural: ' n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n' },
28
+ 'X-Generator' => 'PhraseApp (phraseapp.com)\n'
29
+ }
30
+ }
31
+ end
32
+
33
+ context 'with a plural tree' do
34
+ let(:tree) {
35
+ {
36
+ translation: [
37
+ { flags: [{ flag: 'fuzzy' }] },
38
+ { reference: '../../some_file.rb:34' },
39
+ { reference: '../../some_other_file.rb:283' },
40
+ { msgid: [{ quoted_string: [] }, { quoted_string: 'Hello World' }, { quoted_string: 'Hello Again'}] },
41
+ { msgid_plural: [{ quoted_string: [] }, { quoted_string: 'Hello %{n} Worlds' }, { quoted_string: 'Hello Again'}] },
42
+ { msgstr: [{ index: '0' }, { quoted_string: [] }, { quoted_string: 'Hello World' }, { quoted_string: 'Hello Again'}] },
43
+ { msgstr: [{ index: '1' }, { quoted_string: [] }, { quoted_string: 'Hello %{n} Worlds' }, { quoted_string: 'Hello Again'}] }
44
+ ]
45
+ }
46
+ }
47
+ it {
48
+ expect(output).to eq translation: {
49
+ flags: [:fuzzy],
50
+ references: ['../../some_file.rb:34', '../../some_other_file.rb:283'],
51
+ msgid: { text: "\nHello World\nHello Again" },
52
+ msgid_plural: { text: "\nHello %{n} Worlds\nHello Again" },
53
+ msgstrs: [
54
+ { index: 0, text: "\nHello World\nHello Again" },
55
+ { index: 1, text: "\nHello %{n} Worlds\nHello Again" }
56
+ ]
57
+ }
58
+ }
59
+ end
60
+
61
+ end
@@ -0,0 +1,15 @@
1
+ require 'polint'
2
+
3
+ RSpec.describe Polint::Checker do
4
+
5
+ Dir.glob(File.join(__dir__, '..', 'data', '*-valid.po')) do |file|
6
+ context "#{File.basename(file, '.po').split('-').join(' ')}" do
7
+ let(:checker) { described_class.new(file) }
8
+
9
+ it 'has no errors' do
10
+ expect(checker.run).to eq 0
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,12 @@
1
+ require 'pry'
2
+
3
+ # rspec seems to complain about this class not having a #cause method; not sure
4
+ # why but this is a quick hacky fix just to get everything passing. it doesn't
5
+ # seem to affect the results of the tests anyway.
6
+ module Parslet
7
+ class Cause
8
+ def cause
9
+ StandardError.new
10
+ end
11
+ end
12
+ end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Letessier
8
+ - Greg Beech
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-05-06 00:00:00.000000000 Z
12
+ date: 2016-06-22 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
@@ -39,7 +40,21 @@ dependencies:
39
40
  - !ruby/object:Gem::Version
40
41
  version: '10.0'
41
42
  - !ruby/object:Gem::Dependency
42
- name: pry
43
+ name: pry-byebug
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: guard-rspec
43
58
  requirement: !ruby/object:Gem::Requirement
44
59
  requirements:
45
60
  - - ">="
@@ -66,24 +81,57 @@ dependencies:
66
81
  - - ">="
67
82
  - !ruby/object:Gem::Version
68
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: parslet
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.7'
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 1.7.1
94
+ type: :runtime
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '1.7'
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 1.7.1
69
104
  description:
70
105
  email:
71
106
  - julien.letessier@gmail.com
107
+ - greg@gregbeech.com
72
108
  executables:
73
109
  - polint
74
110
  extensions: []
75
111
  extra_rdoc_files: []
76
112
  files:
77
113
  - ".gitignore"
114
+ - ".rspec"
78
115
  - Gemfile
79
116
  - Gemfile.lock
117
+ - Guardfile
80
118
  - LICENSE.txt
81
119
  - README.md
82
120
  - Rakefile
83
121
  - bin/polint
84
122
  - lib/polint.rb
123
+ - lib/polint/parser.rb
124
+ - lib/polint/transform.rb
85
125
  - lib/polint/version.rb
86
126
  - polint.gemspec
127
+ - spec/data/ar-valid.po
128
+ - spec/data/en-valid.po
129
+ - spec/data/fr-valid.po
130
+ - spec/data/zh-valid.po
131
+ - spec/lib/polint/parser_spec.rb
132
+ - spec/lib/polint/transform_spec.rb
133
+ - spec/lib/polint_spec.rb
134
+ - spec/spec_helper.rb
87
135
  homepage: ''
88
136
  licenses:
89
137
  - MIT
@@ -104,9 +152,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
152
  version: '0'
105
153
  requirements: []
106
154
  rubyforge_project:
107
- rubygems_version: 2.4.5
155
+ rubygems_version: 2.5.1
108
156
  signing_key:
109
157
  specification_version: 4
110
158
  summary: A linter for Uniforum PO files.
111
- test_files: []
112
- has_rdoc:
159
+ test_files:
160
+ - spec/data/ar-valid.po
161
+ - spec/data/en-valid.po
162
+ - spec/data/fr-valid.po
163
+ - spec/data/zh-valid.po
164
+ - spec/lib/polint/parser_spec.rb
165
+ - spec/lib/polint/transform_spec.rb
166
+ - spec/lib/polint_spec.rb
167
+ - spec/spec_helper.rb