Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scala 2.13 and 2.12 support #3

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
sudo: false
language: scala
scala:
- 2.11.7
- 2.13.2
- 2.12.11
script: sbt ++$TRAVIS_SCALA_VERSION -Dfile.encoding=UTF8 -J-XX:MaxPermSize=1024M test

# Use https (public access) instead of git for git-submodules. This modifies only Travis-CI behavior!
Expand Down
12 changes: 8 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ val publishSettings = Seq(
}
)

lazy val scala213 = "2.13.2"
lazy val scala212 = "2.12.11"
lazy val supportedScalaVersions = List(scala213, scala212)

val commonSettings = publishSettings ++ Seq(
scalaVersion := "2.11.7",
scalaVersion := crossScalaVersions.value.head,
crossScalaVersions := supportedScalaVersions,
organization := "com.github.fomkin",
version := "0.2.0-SNAPSHOT",
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.0.0-M7" % "test",
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.0" % Test,
scalacOptions ++= Seq(
"-deprecation",
"-feature",
Expand All @@ -45,11 +49,11 @@ val commonSettings = publishSettings ++ Seq(
)
)

lazy val `petrovich-scala` = crossProject.crossType(CrossType.Pure).
lazy val `petrovich-scala` = crossProject(JSPlatform, JVMPlatform).
settings(commonSettings: _*).
settings(
normalizedName := "petrovich-scala",
sourceGenerators in Compile <+= sourceManaged in Compile map GenRules
sourceGenerators in Compile += sourceManaged in Compile map GenRules
)

lazy val petrovichJS = `petrovich-scala`.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ object PersonPart {

sealed trait NamePart extends PersonPart {

def transform(f: String String): NamePart
def transform(f: String => String): NamePart

def tpe: NamePartType = this match {
case _: FirstName NamePartType.FirstName
case _: MiddleName NamePartType.MiddleName
case _: LastName NamePartType.LastName
case _: FirstName => NamePartType.FirstName
case _: MiddleName => NamePartType.MiddleName
case _: LastName => NamePartType.LastName
}
}

Expand All @@ -26,14 +26,14 @@ object PersonPart {

implicit class NamePartOps(val self: NamePart) extends AnyVal {
def inflect(gender: Gender, gcase: Case): NamePart = {
self transform { s
self transform { s =>
val ruleSets: RuleSets = rules.ruleSetsByNamePartType(self.tpe)
if (s.contains(ComplexNameDelimiter)) {
// This is a complex name
val complexNameParts = s.split('-').toList
val firstPart = complexNameParts.head
val res = ruleSets(gender, firstPart, List(Tag.FirstWord))(firstPart, gcase) :: {
for (part complexNameParts.tail)
for (part <- complexNameParts.tail)
yield ruleSets(gender, part, Nil)(part, gcase)
}
res.mkString(ComplexNameDelimiter)
Expand All @@ -49,15 +49,15 @@ object PersonPart {
}

case class FirstName(value: String) extends NamePart {
def transform(f: String String): FirstName = FirstName(f(value))
def transform(f: String => String): FirstName = FirstName(f(value))
}

case class MiddleName(value: String) extends NamePart {
def transform(f: String String): MiddleName = MiddleName(f(value))
def transform(f: String => String): MiddleName = MiddleName(f(value))
}

case class LastName(value: String) extends NamePart {
def transform(f: String String): LastName = LastName(f(value))
def transform(f: String => String): LastName = LastName(f(value))
}

sealed trait Gender extends PersonPart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,32 @@ package object petrovich {
implicit class PersonOps(val self: Person) extends AnyVal {

def nameParts: List[NamePart] = self collect {
case x: NamePart x
case x: NamePart => x
}

def first: Option[String] = {
val xs = self collect { case FirstName(value) value }
val xs = self collect { case FirstName(value) => value }
xs.headOption
}

def middle: Option[String] = {
val xs = self collect { case MiddleName(value) value }
val xs = self collect { case MiddleName(value) => value }
xs.headOption
}

def last: Option[String] = {
val xs = self collect { case LastName(value) value }
val xs = self collect { case LastName(value) => value }
xs.headOption
}

def gender: Gender = {
val xs = self collect { case value: Gender value }
val xs = self collect { case value: Gender => value }
xs.headOption getOrElse {
def cantDetectGender = new PetrovichException("Can't detect gender")
middle.getOrElse(throw cantDetectGender).toLowerCase match {
case s if s.endsWith("ич") Gender.Male
case s if s.endsWith("на") Gender.Female
case _ Gender.Androgynous
case s if s.endsWith("ич") => Gender.Male
case s if s.endsWith("на") => Gender.Female
case _ => Gender.Androgynous
}
}
}
Expand All @@ -63,7 +63,7 @@ package object petrovich {
// look over possible names of properties,
// inflect them and add to result object
gender :: {
for (namePart nameParts)
for (namePart <- nameParts)
yield namePart.inflect(gender, gcase)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ case class Rule(gender: Gender, test: List[String], mods: Seq[String], tags: Tag
* Apply found rule to given name
*/
def apply(name: String, gcase: Case): String = gcase match {
case Case.Nominative name
case _
case Case.Nominative => name
case _ =>
@tailrec def rec(s: String, tl: List[Char]): String = tl match {
case Nil s
case Rule.Dot :: _ s
case Rule.Dash :: xs rec(s.substring(0, s.length - 1), xs)
case x :: xs rec(s + x, xs)
case Nil => s
case Rule.Dot :: _ => s
case Rule.Dash :: xs => rec(s.substring(0, s.length - 1), xs)
case x :: xs => rec(s + x, xs)
}
rec(name, mods(gcase.index).to[List])
rec(name, mods(gcase.index).toList)
}
}

Expand All @@ -37,12 +37,12 @@ object Rule {
/**
* Local search in rulesets of exceptions or suffixes
*/
def search(gender: Gender, name: String, matchWholeWord: Boolean, tags: Tags): Option[Rule] = {
def searchRule(gender: Gender, name: String, matchWholeWord: Boolean, tags: Tags): Option[Rule] = {
@tailrec def rec(tl: RuleSet): Option[Rule] = tl match {
case Nil None
case rule :: xs if tags.diff(rule.tags).nonEmpty rec(xs)
case rule :: xs if rule.gender != Gender.Androgynous && gender != rule.gender rec(xs)
case rule :: xs
case Nil => None
case rule :: xs if tags.diff(rule.tags).nonEmpty => rec(xs)
case rule :: xs if rule.gender != Gender.Androgynous && gender != rule.gender => rec(xs)
case rule :: xs =>
val s = name.toLowerCase
def matchSample(sample: String): Boolean = {
if (matchWholeWord) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ case class RuleSets(exceptions: Option[RuleSet], suffixes: RuleSet) {
* Find groups of rules in exceptions or suffixes of given nametype
*/
def apply(gender: Gender, name: String, tags: Tags = Nil): Option[Rule] = {
exceptions.flatMap(_.search(gender, name, matchWholeWord = true, tags)).
fold(suffixes.search(gender, name, matchWholeWord = false, tags))(Some[Rule])
exceptions.flatMap(_.searchRule(gender, name, matchWholeWord = true, tags)).
fold(suffixes.searchRule(gender, name, matchWholeWord = false, tags))(Some(_))
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import org.scalatest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import petrovich._

class PetrovichSpec extends FlatSpec with Matchers {
class PetrovichSpec extends AnyFlatSpec with Matchers {

"Petrovich" should "detect female" in {
val person = FirstName("Светлана") :: MiddleName("Андреевна")
Expand Down
83 changes: 40 additions & 43 deletions project/GenRules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,46 @@ import java.nio.charset.{StandardCharsets, Charset}

import sbt.File
import sbt._
import upickle.Js

import ujson.Obj
import upickle.default._

object GenRules extends (File Seq[File]) {
object GenRules extends (File => Seq[File]) {

case class Rule(gender: String, test: Seq[String], mods: Seq[String], tags: Seq[String]) {
def gen: String = {
val genGender = "Gender." + gender.charAt(0).toUpper + gender.substring(1)
val genTest = test.map(x s""""$x"""").mkString(", ")
val genMods = mods.map(x s""""$x"""").mkString(", ")
val genTags = tags.map(x s"""Tag("$x")""").mkString(", ")
val genTest = test.map(x => s""""$x"""").mkString(", ")
val genMods = mods.map(x => s""""$x"""").mkString(", ")
val genTags = tags.map(x => s"""Tag("$x")""").mkString(", ")
s""" Rule(
| gender = $genGender,
| test = List($genTest),
| mods = Seq($genMods),
| tags = List($genTags)
| )""".stripMargin

}
}

object Rule {
implicit val rule2Reader = upickle.default.Reader[Rule] {
case obj: Js.Obj =>
val map = obj.value.toMap
Rule(
gender = readJs[String](map("gender")),
test = readJs[Seq[String]](map("test")),
mods = readJs[Seq[String]](map("mods")),
tags = map.get("tags").fold(Seq.empty[String])(readJs[Seq[String]])
)
implicit val rule2Reader = upickle.default.reader[Obj].map { obj =>
val map = obj.value.toMap
Rule(
gender = read[String](map("gender")),
test = read[Seq[String]](map("test")),
mods = read[Seq[String]](map("mods")),
tags = map.get("tags").fold(Seq.empty[String])(read[Seq[String]])
)
}
}

case class RuleSets(exceptions: Option[Seq[Rule]], suffixes: Seq[Rule]) {
def gen: String = {
def rs(xs: Seq[Rule]) = xs.map(_.gen).mkString(",\n")
val es = exceptions match {
case None "None,"
case Some(x)
case None => "None,"
case Some(x) =>
val rsx = rs(x)
s"Some(List(\n$rsx)\n" +
s" ),"
Expand All @@ -51,46 +50,44 @@ object GenRules extends (File ⇒ Seq[File]) {
s"""RuleSets(
| exceptions = $es
| suffixes = $ss
| )
| )
| )""".stripMargin
}
}

object RuleSets {
implicit val ruleSets2Reader = upickle.default.Reader[RuleSets] {
case obj: Js.Obj =>
val map = obj.value.toMap
RuleSets(
map.get("exceptions").map(readJs[Seq[Rule]]),
readJs[Seq[Rule]](map("suffixes"))
)
implicit val ruleSets2Reader = upickle.default.reader[Obj].map { obj =>
val map = obj.value.toMap
RuleSets(
map.get("exceptions").map(read[Seq[Rule]]),
read[Seq[Rule]](map("suffixes"))
)
}
}

def apply(dir: File): Seq[File] = {
val genFile = dir / "petrovich" / "rules" / "package.scala"
val json = {
val raw = IO.read(file("petrovich-rules") / "rules.json", StandardCharsets.UTF_8)
upickle.json.read(raw) match {
case x: Js.Obj x.value.toMap
case _ ⇒ fail("Invalid rules file")
ujson.read(raw) match {
case x: Obj => x.value.toMap
case _ => throw new RuntimeException("Invalid rules file")
}
}
val ruleSetsByNamePartType = json map {
case ("lastname", v)
val rs = readJs[RuleSets](v)
case ("lastname", v) =>
val rs = read[RuleSets](v)
s" NamePartType.LastName -> ${rs.gen}"
case ("firstname", v)
val rs = readJs[RuleSets](v)
case ("firstname", v) =>
val rs = read[RuleSets](v)
s" NamePartType.FirstName -> ${rs.gen}"
case ("middlename", v)
val rs = readJs[RuleSets](v)
case ("middlename", v) =>
val rs = read[RuleSets](v)
s" NamePartType.MiddleName -> ${rs.gen}"
case (k, _) => fail(s"Invalid rules file (unknown name part: $k)")
case (k, _) => throw new RuntimeException(s"Invalid rules file (unknown name part: $k)")
}
IO.write(genFile,
s"""
|package petrovich
s"""package petrovich
|
|import data._
|import data.PersonPart._
Expand All @@ -100,8 +97,8 @@ object GenRules extends (File ⇒ Seq[File]) {
|// To update code run `reload` in SBT console
|package object rules {
| val ruleSetsByNamePartType: Map[NamePartType, RuleSets] = Map(
|${ruleSetsByNamePartType.mkString(",\n")}
| )
|${ruleSetsByNamePartType.mkString(",\n")}
| )
|}
""".stripMargin
)
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.8
sbt.version=1.3.10
5 changes: 3 additions & 2 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.1")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")

// Why I don't use my own library Pushka?
// Because I wasn't made a version for 2.10.*
libraryDependencies += "com.lihaoyi" %% "upickle" % "0.3.6"
libraryDependencies += "com.lihaoyi" %% "upickle" % "1.1.0"