polint 0.0.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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