Skip to content

Commit 70514d9

Browse files
committed
Fix #12 Provide a way to get error message when parsing fails
1 parent 9018df9 commit 70514d9

File tree

10 files changed

+78
-61
lines changed

10 files changed

+78
-61
lines changed

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
1.6
2+
3+
* Provide a way to get error message when parsing fails
4+
https://github.com/xitrum-framework/scaposer/issues/12
5+
16
1.5:
27

38
* Use the original string when it's still not translated

README.rst

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ See `Scaladoc <http://xitrum-framework.github.io/scaposer/>`_.
2121
msgstr "Bonjour"
2222
"""
2323

24-
val poo = scaposer.Parser.parsePo(string) // => An Option[scaposer.Po] (None on failure)
24+
val poe = scaposer.Parser.parsePo(string)
25+
// => An Either, Left((error msg, error position)) on failure, or Right(scaposer.Po) on success
2526

2627
Use ``t`` methods to get the translations:
2728

2829
::
2930

30-
val po = poo.get // => A scaposer.Po
31-
po.t("Hello") // => "Bonjour"
31+
val po = poe.right.get // => A scaposer.Po
32+
po.t("Hello") // => "Bonjour"
3233

3334
If there's no translation, or the translation is an empty string (not translated yet),
34-
the input is returned:
35+
the original input is returned:
3536

3637
::
3738

@@ -53,7 +54,7 @@ Context
5354
msgstr "Salut"
5455
"""
5556

56-
val po = Parser.parsePo(string).get
57+
val po = Parser.parsePo(string).right.get
5758
po.t("Casual", "Hello") // => "Salut"
5859

5960
If there's no translation for the context, the translation without context is tried:
@@ -87,7 +88,7 @@ It just removes spaces in the expression and performs string comparison. See
8788
msgstr[1] "J'ai %d pommes"
8889
"""
8990

90-
val po = Parser.parsePo(string).get
91+
val po = Parser.parsePo(string).right.get
9192
po.t("I have one apple", "I have %d apples", 1)
9293
po.t("I have one apple", "I have %d apples", 2)
9394
po.t("A context", "I have one apple", "I have %d apples", 3)
@@ -113,6 +114,6 @@ build.sbt example:
113114

114115
::
115116

116-
libraryDependencies += "tv.cntt" %% "scaposer" % "1.5"
117+
libraryDependencies += "tv.cntt" %% "scaposer" % "1.6"
117118

118-
Scaposer is used in `Xitrum <https://github.com/xitrum-framework/xitrum>`_.
119+
Scaposer is used in `Xitrum web framework <https://github.com/xitrum-framework/xitrum>`_.

build.sbt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
organization := "tv.cntt"
1+
organization := "tv.cntt"
2+
name := "scaposer"
3+
version := "1.7-SNAPSHOT"
24

3-
name := "scaposer"
4-
5-
version := "1.6-SNAPSHOT"
6-
7-
scalaVersion := "2.11.4"
8-
9-
crossScalaVersions := Seq("2.11.4", "2.10.4")
5+
scalaVersion := "2.11.6"
6+
crossScalaVersions := Seq("2.11.6", "2.10.5")
107

118
scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked")
129

@@ -16,11 +13,11 @@ scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked")
1613
// java.lang.UnsupportedClassVersionError: Unsupported major.minor version 51.0
1714
javacOptions ++= Seq("-source", "1.6", "-target", "1.6")
1815

19-
// Scala 2.11 does not include scala.util.parsing.combinator
16+
// Scala 2.11 core does not include scala.util.parsing.combinator
2017
libraryDependencies := {
2118
CrossVersion.partialVersion(scalaVersion.value) match {
2219
case Some((2, scalaMajor)) if scalaMajor >= 11 =>
23-
libraryDependencies.value :+ "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.2"
20+
libraryDependencies.value :+ "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.4"
2421
case _ =>
2522
libraryDependencies.value
2623
}

project/build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version=0.13.7
1+
sbt.version=0.13.8

project/plugins.sbt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
// Run sbt eclipse to create Eclipse project file
2-
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0")
3-
4-
// Run sbt gen-idea to create IntelliJ project file
5-
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
2+
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "4.0.0")

src/main/scala/scaposer/Parser.scala

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
package scaposer
22

33
import scala.util.parsing.combinator.JavaTokenParsers
4+
import scala.util.parsing.input.Position
5+
6+
// We only want to expose parsePo method.
7+
object Parser {
8+
private val parser = new Parser
9+
10+
/**
11+
* Returns an `Either`,
12+
* `Left((error msg, error position))` on failure,
13+
* or `Right(scaposer.Po)` on success.
14+
*/
15+
def parsePo(po: String) = parser.parsePo(po)
16+
}
417

518
/** See http://www.gnu.org/software/hello/manual/gettext/PO-Files.html */
6-
object Parser extends JavaTokenParsers {
19+
private class Parser extends JavaTokenParsers {
720
private def mergeStrs(quoteds: List[String]): String = {
821
// Removes the first and last quote (") character of strings
922
// and concats them
@@ -73,19 +86,25 @@ object Parser extends JavaTokenParsers {
7386

7487
private def exp = rep(singular | plural)
7588

76-
def parsePo(po: String): Option[Po] = {
77-
val parseRet = parseAll(exp, po)
78-
if (parseRet.successful) {
79-
val translations = parseRet.get
80-
val body = translations.foldLeft(
81-
Map.empty[(Option[String], String), Seq[String]]
82-
) { (acc, t) =>
83-
val item = (t.ctxo, t.singular) -> t.strs
84-
acc + item
85-
}
86-
Some(new Po(body))
87-
} else {
88-
None
89+
/**
90+
* Returns an `Either`,
91+
* `Left((error msg, error position))` on failure,
92+
* or `Right(scaposer.Po)` on success.
93+
*/
94+
def parsePo(po: String): Either[(String, Position), Po] = {
95+
parseAll(exp, po) match {
96+
case NoSuccess(msg, next) =>
97+
val errorPos = next.pos
98+
Left((msg, errorPos))
99+
100+
case Success(translations, _) =>
101+
val body = translations.foldLeft(
102+
Map.empty[(Option[String], String), Seq[String]]
103+
) { (acc, t) =>
104+
val item = (t.ctxo, t.singular) -> t.strs
105+
acc + item
106+
}
107+
Right(new Po(body))
89108
}
90109
}
91110
}

src/main/scala/scaposer/Po.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package scaposer
22

33
/**
4-
* body is a map of (ctxo, singular) -> strs
5-
* See also class Translation.
4+
* `body` is a map of `(ctxo, singular) -> strs`, see class [[Translation]].
65
*/
76
class Po(val body: Map[(Option[String], String), Seq[String]]) {
87
def ++(other: Po): Po = {
@@ -35,7 +34,7 @@ class Po(val body: Map[(Option[String], String), Seq[String]]) {
3534
val buffer = new StringBuilder
3635
buffer.append("\n")
3736
body.foreach { case ((ctxo, singular), strs) =>
38-
if (!ctxo.isEmpty) {
37+
if (ctxo.isDefined) {
3938
buffer.append(ctxo.get)
4039
buffer.append("\n")
4140
}
@@ -44,7 +43,7 @@ class Po(val body: Map[(Option[String], String), Seq[String]]) {
4443
buffer.append("\n")
4544

4645
if (strs.size == 1) {
47-
buffer.append(strs(0))
46+
buffer.append(strs.head)
4847
buffer.append("\n")
4948
} else {
5049
strs.zipWithIndex.foreach { case (str, index) =>
@@ -65,7 +64,7 @@ class Po(val body: Map[(Option[String], String), Seq[String]]) {
6564
body.get((ctxo, singular)) match {
6665
case Some(strs) =>
6766
// Newly created .pot/.po files have keys but the values are all empty
68-
val str = strs(0)
67+
val str = strs.head
6968
if (str.isEmpty) singular else str
7069

7170
case None =>
@@ -104,7 +103,7 @@ class Po(val body: Map[(Option[String], String), Seq[String]]) {
104103
case None => None
105104

106105
case Some(strs) =>
107-
val header = strs(0)
106+
val header = strs.head
108107
header.lines.find(_.startsWith("Plural-Forms")) match {
109108
case None => None
110109

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package scaposer
22

3-
// msgid_plural in po file is for translators. We do not need to store it here
4-
// because it is available in the translation methods of class Po.
5-
class Translation(
6-
val ctxo: Option[String],
7-
val singular: String,
8-
val strs: Seq[String]
9-
)
3+
/**
4+
* Represents a translation item.
5+
* `msgid_plural` in po file is for translators. We do not need to store it here
6+
* because it is available in the translation methods of class [[Po]].
7+
*/
8+
case class Translation(ctxo: Option[String], singular: String, strs: Seq[String])

src/test/scala/scaposer/ParserSpec.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ class ParserSpec extends Specification {
1010
""".stripMargin
1111

1212
"PO singular string" should {
13-
"be some" in {
14-
Parser.parsePo(strPoSimple) must beSome
13+
"be Right" in {
14+
Parser.parsePo(strPoSimple) must beRight
1515
}
1616
}
1717

@@ -24,8 +24,8 @@ class ParserSpec extends Specification {
2424
""".stripMargin
2525

2626
"PO singular string with \"" should {
27-
"not be none" in {
28-
Parser.parsePo(strPoWithSlash) must not beNone
27+
"be Right" in {
28+
Parser.parsePo(strPoWithSlash) must beRight
2929
}
3030
}
3131

@@ -38,8 +38,8 @@ class ParserSpec extends Specification {
3838
""".stripMargin
3939

4040
"PO singular string with \" with no doublequotes at the end" should {
41-
"be none" in {
42-
Parser.parsePo(strPoWithError) must beNone
41+
"be Left" in {
42+
Parser.parsePo(strPoWithError) must beLeft
4343
}
4444
}
4545

@@ -55,8 +55,8 @@ class ParserSpec extends Specification {
5555
""".stripMargin
5656

5757
"PO string with tabulation character" should {
58-
"not be none" in {
59-
Parser.parsePo(strPoWithWhiteSpaces) must not beNone
58+
"be Right" in {
59+
Parser.parsePo(strPoWithWhiteSpaces) must beRight
6060
}
6161
}
6262
}

src/test/scala/scaposer/PoSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class PoSpec extends Specification {
1414
|msgstr[0] "Yksi ankanpoikanen"
1515
|msgstr[1] "$n ankanpoikasta"
1616
""".stripMargin
17-
).get
17+
).right.get
1818

1919
"work" in {
2020
po.t("One duckling", "$n ducklings", 2) must equalTo ("$n ankanpoikasta")
@@ -24,7 +24,7 @@ class PoSpec extends Specification {
2424
}
2525

2626
"Missing translations" should {
27-
val po = Parser.parsePo("").get
27+
val po = Parser.parsePo("").right.get
2828

2929
"be pluralized with the n != 1 rule" in {
3030
po.t("One monkey", "$n monkeys", 2) must equalTo ("$n monkeys")
@@ -39,7 +39,7 @@ class PoSpec extends Specification {
3939
|msgid "Could not login."
4040
|msgstr ""
4141
""".stripMargin
42-
).get
42+
).right.get
4343

4444
"use msgid instead of the meaningless empty msgstr" in {
4545
po.t("Could not login.") must equalTo ("Could not login.")

0 commit comments

Comments
 (0)