|
@@ -43,14 +43,14 @@ import URL.{EmbeddedSymbol,QuerySymbol}
|
|
|
*
|
|
*
|
|
|
* @author Thomas Flucke
|
|
* @author Thomas Flucke
|
|
|
*/
|
|
*/
|
|
|
-object PlayParser extends Parser {
|
|
|
|
|
- val importPattern = raw"^\s*->".r
|
|
|
|
|
- val modifierPattern = raw"^\s*+\s*(\w+)".r
|
|
|
|
|
|
|
+case class PlayParser(url: URL) extends Parser {
|
|
|
|
|
+ val importPattern = raw"^\s*->".r
|
|
|
|
|
+ val modifierPattern = raw"^\s*+\s*(\w+)".r
|
|
|
val routeStartPattern = raw"^\s*#\s*Shared\s+Route\s*$$".r
|
|
val routeStartPattern = raw"^\s*#\s*Shared\s+Route\s*$$".r
|
|
|
- val emptyLinePattern = raw"^\s*#?\s*$$".r
|
|
|
|
|
|
|
+ val emptyLinePattern = raw"^\s*#?\s*$$".r
|
|
|
val nonCommentPattern = raw"^\s*[^#]".r
|
|
val nonCommentPattern = raw"^\s*[^#]".r
|
|
|
- val propPattern = raw"^\s*#\s*(\w+)\s*:\s*([^\s]+)\s*$$".r
|
|
|
|
|
- val routePattern =
|
|
|
|
|
|
|
+ val propPattern = raw"^\s*#\s*(\w+)\s*:\s*([^\s]+)\s*$$".r
|
|
|
|
|
+ val routePattern =
|
|
|
raw"^\s*(GET|PATCH|POST|PUT|DELETE|HEAD)\s+(/[^\s]*)+\s+(.+)".r
|
|
raw"^\s*(GET|PATCH|POST|PUT|DELETE|HEAD)\s+(/[^\s]*)+\s+(.+)".r
|
|
|
|
|
|
|
|
private def nextOption(it: Iterator[String]) =
|
|
private def nextOption(it: Iterator[String]) =
|
|
@@ -59,14 +59,14 @@ object PlayParser extends Parser {
|
|
|
def parseFile(input: File, log: Logger): Seq[RouteDef] = {
|
|
def parseFile(input: File, log: Logger): Seq[RouteDef] = {
|
|
|
def parseIterator(it: Iterator[String]): Seq[RouteDef] = {
|
|
def parseIterator(it: Iterator[String]): Seq[RouteDef] = {
|
|
|
nextOption(it) match {
|
|
nextOption(it) match {
|
|
|
- case Some(routeStartPattern()) =>
|
|
|
|
|
|
|
+ case Some(routeStartPattern()) =>
|
|
|
parseRoute(it, input.getName(), log) match {
|
|
parseRoute(it, input.getName(), log) match {
|
|
|
case Success(route) => route +: parseIterator(it)
|
|
case Success(route) => route +: parseIterator(it)
|
|
|
- case Failure(err) =>
|
|
|
|
|
|
|
+ case Failure(err) =>
|
|
|
log.error(err.getMessage())
|
|
log.error(err.getMessage())
|
|
|
parseIterator(it)
|
|
parseIterator(it)
|
|
|
}
|
|
}
|
|
|
- case Some(importPattern()) =>
|
|
|
|
|
|
|
+ case Some(importPattern()) =>
|
|
|
// TODO
|
|
// TODO
|
|
|
log.error("Importing route files is not yet supported.")
|
|
log.error("Importing route files is not yet supported.")
|
|
|
parseIterator(it)
|
|
parseIterator(it)
|
|
@@ -74,63 +74,68 @@ object PlayParser extends Parser {
|
|
|
// TODO
|
|
// TODO
|
|
|
log.warn(s"Route modifier ($mod) is not recognized and will be ignored.")
|
|
log.warn(s"Route modifier ($mod) is not recognized and will be ignored.")
|
|
|
parseIterator(it)
|
|
parseIterator(it)
|
|
|
- case Some(_) => parseIterator(it)
|
|
|
|
|
- case None => Nil
|
|
|
|
|
|
|
+ case Some(_) => parseIterator(it)
|
|
|
|
|
+ case None => Nil
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
parseIterator(Source.fromFile(input).getLines())
|
|
parseIterator(Source.fromFile(input).getLines())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private def parseRoute(
|
|
private def parseRoute(
|
|
|
- it: Iterator[String],
|
|
|
|
|
|
|
+ it: Iterator[String],
|
|
|
filename: String,
|
|
filename: String,
|
|
|
- log: Logger,
|
|
|
|
|
- props: Map[String, String] = Map.empty
|
|
|
|
|
|
|
+ log: Logger,
|
|
|
|
|
+ props: Map[String, String] = Map.empty
|
|
|
): Try[RouteDef] = nextOption(it) match {
|
|
): Try[RouteDef] = nextOption(it) match {
|
|
|
- case None => Failure(
|
|
|
|
|
|
|
+ case None => Failure(
|
|
|
new ParseException(s"Incomplete route in file $filename.", 0)
|
|
new ParseException(s"Incomplete route in file $filename.", 0)
|
|
|
)
|
|
)
|
|
|
- case Some(emptyLinePattern()) => parseRoute(it, filename, log, props)
|
|
|
|
|
- case Some(propPattern(prop, value)) =>
|
|
|
|
|
|
|
+ case Some(emptyLinePattern()) =>
|
|
|
|
|
+ parseRoute(it, filename, log, props)
|
|
|
|
|
+ case Some(propPattern(prop, value)) =>
|
|
|
parseRoute(it, filename, log, props ++ Seq((prop -> value)))
|
|
parseRoute(it, filename, log, props ++ Seq((prop -> value)))
|
|
|
- case Some(modifierPattern(mod)) =>
|
|
|
|
|
|
|
+ case Some(modifierPattern(mod)) =>
|
|
|
log.warn(s"Route modifier ($mod) is not recognized and will be ignored.")
|
|
log.warn(s"Route modifier ($mod) is not recognized and will be ignored.")
|
|
|
parseRoute(it, filename, log, props)
|
|
parseRoute(it, filename, log, props)
|
|
|
case Some(routePattern(method, path, scala)) => {
|
|
case Some(routePattern(method, path, scala)) => {
|
|
|
- val symbolReg = raw"^:(\w+)$$".r
|
|
|
|
|
- val longSymReg = raw"^\*(\w+)$$".r
|
|
|
|
|
- val regexSymReg = raw"^\$$(\w+)<.*>$$".r
|
|
|
|
|
|
|
+ val symbolReg = raw"^:(\w+)$$".r
|
|
|
|
|
+ val longSymReg = raw"^\*(\w+)$$".r
|
|
|
|
|
+ val regexSymReg = raw"^\$$(\w+)<.*>$$".r
|
|
|
val (retMime, retType) = getReturnType(props.get("mime"), props.get("type"))
|
|
val (retMime, retType) = getReturnType(props.get("mime"), props.get("type"))
|
|
|
- val url = URL.fromString(path) {
|
|
|
|
|
- case symbolReg(sym) => Some(EmbeddedSymbol(sym))
|
|
|
|
|
- case longSymReg(sym) => Some(EmbeddedSymbol(sym))
|
|
|
|
|
|
|
+ val routeUrl = URL.fromString(path) {
|
|
|
|
|
+ case symbolReg(sym) => Some(EmbeddedSymbol(sym))
|
|
|
|
|
+ case longSymReg(sym) => Some(EmbeddedSymbol(sym))
|
|
|
case regexSymReg(sym) => Some(EmbeddedSymbol(sym))
|
|
case regexSymReg(sym) => Some(EmbeddedSymbol(sym))
|
|
|
- case _ => None
|
|
|
|
|
|
|
+ case _ => None
|
|
|
}
|
|
}
|
|
|
parseScalaLine(scala, log).map({
|
|
parseScalaLine(scala, log).map({
|
|
|
- case (pack, obj, function, args) => RouteDef(method, url.copy(query =
|
|
|
|
|
- args.map({
|
|
|
|
|
- case (sym, FullName(Seq("Seq"), _), _) =>
|
|
|
|
|
- QuerySymbol(sym.name, QuerySymbol.repeatEncoder)
|
|
|
|
|
- case (sym, FullName(Seq("scala", "collection", "Seq"), _), _) =>
|
|
|
|
|
- QuerySymbol(sym.name, QuerySymbol.repeatEncoder)
|
|
|
|
|
- case (sym, FullName(Seq("Option"), _), _) =>
|
|
|
|
|
- QuerySymbol(sym.name, QuerySymbol.optionEncoder)
|
|
|
|
|
- case (sym, FullName(Seq("scala", "Option"), _), _) =>
|
|
|
|
|
- QuerySymbol(sym.name, QuerySymbol.optionEncoder)
|
|
|
|
|
- case (sym, _, _) => QuerySymbol(sym.name)
|
|
|
|
|
- }).filterNot(url.path.flatMap({
|
|
|
|
|
- case Right(sym) => Some(sym)
|
|
|
|
|
- case Left(_) => None
|
|
|
|
|
- }).contains).map(arg => (arg.name, Right(arg))).toMap
|
|
|
|
|
- ), pack, obj, function, args,
|
|
|
|
|
|
|
+ case (pack, obj, function, args) => RouteDef(
|
|
|
|
|
+ method,
|
|
|
|
|
+ url + routeUrl.copy(query =
|
|
|
|
|
+ args.map({
|
|
|
|
|
+ case (sym, FullName(Seq("Seq"), _), _) =>
|
|
|
|
|
+ QuerySymbol(sym.name, QuerySymbol.repeatEncoder)
|
|
|
|
|
+ case (sym, FullName(Seq("scala", "collection", "Seq"), _), _) =>
|
|
|
|
|
+ QuerySymbol(sym.name, QuerySymbol.repeatEncoder)
|
|
|
|
|
+ case (sym, FullName(Seq("Option"), _), _) =>
|
|
|
|
|
+ QuerySymbol(sym.name, QuerySymbol.optionEncoder)
|
|
|
|
|
+ case (sym, FullName(Seq("scala", "Option"), _), _) =>
|
|
|
|
|
+ QuerySymbol(sym.name, QuerySymbol.optionEncoder)
|
|
|
|
|
+ case (sym, _, _) =>
|
|
|
|
|
+ QuerySymbol(sym.name)
|
|
|
|
|
+ }).filterNot(routeUrl.path.flatMap({
|
|
|
|
|
+ case Right(sym) => Some(sym)
|
|
|
|
|
+ case Left(_) => None
|
|
|
|
|
+ }).contains).map(arg => (arg.name, Right(arg))).toMap
|
|
|
|
|
+ ), pack, obj, function, args,
|
|
|
props.get("body").map(FullName(_)),
|
|
props.get("body").map(FullName(_)),
|
|
|
props.get("content").map(Format.fromString _) getOrElse Format.JSON,
|
|
props.get("content").map(Format.fromString _) getOrElse Format.JSON,
|
|
|
FullName(retType),
|
|
FullName(retType),
|
|
|
- retMime)
|
|
|
|
|
|
|
+ retMime
|
|
|
|
|
+ )
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
- case Some(line) =>
|
|
|
|
|
|
|
+ case Some(line) =>
|
|
|
log.verbose(s"Unparsable line in $filename:\n$line\n.")
|
|
log.verbose(s"Unparsable line in $filename:\n$line\n.")
|
|
|
parseRoute(it, filename, log, props)
|
|
parseRoute(it, filename, log, props)
|
|
|
}
|
|
}
|
|
@@ -145,7 +150,7 @@ object PlayParser extends Parser {
|
|
|
Symbol(fn),
|
|
Symbol(fn),
|
|
|
argStr.map(str => str.substring(1, str.length - 1).split(',').map({str =>
|
|
argStr.map(str => str.substring(1, str.length - 1).split(',').map({str =>
|
|
|
val (name, rest) = str.trim.span(x => x.isLetterOrDigit || x == '_')
|
|
val (name, rest) = str.trim.span(x => x.isLetterOrDigit || x == '_')
|
|
|
- val rest1 = rest.trim
|
|
|
|
|
|
|
+ val rest1 = rest.trim
|
|
|
val (typ, rest2) = if (rest1.nonEmpty && rest1(0) == ':')
|
|
val (typ, rest2) = if (rest1.nonEmpty && rest1(0) == ':')
|
|
|
rest1.substring(1).trim.span(x =>
|
|
rest1.substring(1).trim.span(x =>
|
|
|
x.isLetterOrDigit || x == '_' || x == '.' || x == '[' || x == ']'
|
|
x.isLetterOrDigit || x == '_' || x == '.' || x == '[' || x == ']'
|
|
@@ -165,20 +170,21 @@ object PlayParser extends Parser {
|
|
|
None
|
|
None
|
|
|
}
|
|
}
|
|
|
}).toSeq.flatten).getOrElse(Nil)))
|
|
}).toSeq.flatten).getOrElse(Nil)))
|
|
|
- case _ => Failure(new ParseException(s"Unparsable scala line:\n$line\n.", 0))
|
|
|
|
|
|
|
+ case _ =>
|
|
|
|
|
+ Failure(new ParseException(s"Unparsable scala line:\n$line\n.", 0))
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
def getReturnType(mime: Option[String], typ: Option[String]) =
|
|
def getReturnType(mime: Option[String], typ: Option[String]) =
|
|
|
(mime, typ) match {
|
|
(mime, typ) match {
|
|
|
- case (Some(retMime), Some(retType)) => (retMime.trim, retType.trim)
|
|
|
|
|
- case (Some("text/json"), None) => ("application/json", "Any")
|
|
|
|
|
|
|
+ case (Some(retMime), Some(retType)) => (retMime.trim, retType.trim)
|
|
|
|
|
+ case (Some("text/json"), None) => ("application/json", "Any")
|
|
|
case (Some("application/json"), None) => ("application/json", "Any")
|
|
case (Some("application/json"), None) => ("application/json", "Any")
|
|
|
- case (Some("text/plain"), None) => ("text/plain", "String")
|
|
|
|
|
- case (Some(_), None) => ???
|
|
|
|
|
- case (None, Some("String")) => ("text/plain", "String")
|
|
|
|
|
|
|
+ case (Some("text/plain"), None) => ("text/plain", "String")
|
|
|
|
|
+ case (Some(_), None) => ???
|
|
|
|
|
+ case (None, Some("String")) => ("text/plain", "String")
|
|
|
case (None, Some("java.lang.String")) => ("text/plain", "String")
|
|
case (None, Some("java.lang.String")) => ("text/plain", "String")
|
|
|
- case (None, Some(retType)) => ("application/json", retType.trim)
|
|
|
|
|
- case (None, None) => ("text/plain", "String")
|
|
|
|
|
|
|
+ case (None, Some(retType)) => ("application/json", retType.trim)
|
|
|
|
|
+ case (None, None) => ("text/plain", "String")
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|