Merge pull request #3666 from isaacl/anyvalSteroids

[WIP] Refactor lila implicits
This commit is contained in:
Thibault Duplessis 2017-10-04 17:40:56 -05:00 committed by GitHub
commit 22a3ec7781
13 changed files with 516 additions and 509 deletions

View file

@ -9,15 +9,7 @@ import play.twirl.api.Html
import lila.api.Env.{ current => apiEnv }
object Environment
extends scalaz.syntax.ToIdOps
with scalaz.std.OptionInstances
with scalaz.std.OptionFunctions
with scalaz.std.StringInstances
with scalaz.syntax.std.ToOptionIdOps
with scalalib.OrnicarMonoid.Instances
with scalalib.Zero.Instances
with scalalib.OrnicarOption
with lila.LilaSteroids
extends lila.Steroids
with StringHelper
with JsonHelper
with AssetHelper

View file

@ -0,0 +1,69 @@
package lila
import scala.util.Try
import scala.concurrent.Future
import scala.concurrent.duration._
import ornicar.scalalib
import ornicar.scalalib.Zero
import org.joda.time.DateTime
import com.typesafe.config.Config
import play.api.libs.json.{ JsObject, JsValue }
import lila.common.implicits._
trait Lilaisms
extends scalalib.Common
with lila.common.base.LilaTypes
with scalalib.OrnicarMonoid.Instances
with scalalib.OrnicarNonEmptyList
with scalalib.OrnicarOption
with scalalib.Regex
with scalalib.Validation
with scalalib.Zero.Instances
with scalalib.Zero.Syntax
with scalaz.std.ListFunctions
with scalaz.std.ListInstances
with scalaz.std.OptionFunctions
with scalaz.std.OptionInstances
with scalaz.std.StringInstances
with scalaz.std.TupleInstances
with scalaz.syntax.std.ToListOps
with scalaz.syntax.std.ToOptionIdOps
with scalaz.syntax.ToApplyOps
with scalaz.syntax.ToEqualOps
with scalaz.syntax.ToFunctorOps
with scalaz.syntax.ToIdOps
with scalaz.syntax.ToMonoidOps
with scalaz.syntax.ToShowOps
with scalaz.syntax.ToTraverseOps
with scalaz.syntax.ToValidationOps {
@inline implicit def toPimpedFuture[A](f: Fu[A]) = new PimpedFuture(f)
@inline implicit def toPimpedFutureBoolean(f: Fu[Boolean]) = new PimpedFutureBoolean(f)
@inline implicit def toPimpedFutureOption[A](f: Fu[Option[A]]) = new PimpedFutureOption(f)
@inline implicit def toPimpedFutureValid[A](f: Fu[Valid[A]]) = new PimpedFutureValid(f)
@inline implicit def toPimpedJsObject(jo: JsObject) = new PimpedJsObject(jo)
@inline implicit def toPimpedJsValue(jv: JsValue) = new PimpedJsValue(jv)
@inline implicit def toPimpedBoolean(b: Boolean) = new PimpedBoolean(b)
@inline implicit def toPimpedInt(i: Int) = new PimpedInt(i)
@inline implicit def toPimpedLong(l: Long) = new PimpedLong(l)
@inline implicit def toPimpedFloat(f: Float) = new PimpedFloat(f)
@inline implicit def toPimpedDouble(d: Double) = new PimpedDouble(d)
@inline implicit def toPimpedTryList[A](l: List[Try[A]]) = new PimpedTryList(l)
@inline implicit def toPimpedList[A](l: List[A]) = new PimpedList(l)
@inline implicit def toPimpedSeq[A](l: List[A]) = new PimpedSeq(l)
@inline implicit def toPimpedByteArray(ba: Array[Byte]) = new PimpedByteArray(ba)
@inline implicit def toPimpedOption[A](a: Option[A]) = new PimpedOption(a)
@inline implicit def toPimpedString(s: String) = new PimpedString(s)
@inline implicit def toPimpedConfig(c: Config) = new PimpedConfig(c)
@inline implicit def toPimpedDateTime(d: DateTime) = new PimpedDateTime(d)
@inline implicit def toPimpedValid[A](v: Valid[A]) = new PimpedValid(v)
@inline implicit def toPimpedTry[A](t: Try[A]) = new PimpedTry(t)
@inline implicit def toPimpedFiniteDuration(d: FiniteDuration) = new PimpedFiniteDuration(d)
implicit val dateTimeOrdering: Ordering[DateTime] = Ordering.fromLessThan(_ isBefore _)
}

View file

@ -1,12 +1,14 @@
package lila
import scala.concurrent.duration.{ Duration, FiniteDuration, MILLISECONDS }
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.Try
import ornicar.scalalib
import scalaz.{ Monad, Monoid, OptionT, ~> }
trait PackageObject extends Steroids with WithFuture {
trait PackageObject extends Lilaisms {
// case object Key(value: String) extends AnyVal with StringValue
trait StringValue extends Any {
@ -37,46 +39,14 @@ trait PackageObject extends Steroids with WithFuture {
def unapply[A, B](x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x)
}
implicit final class LilaPimpedString(s: String) {
def parseIntOption(str: String): Option[Int] =
Try(java.lang.Integer.parseInt(str)).toOption
def boot[A](v: => A): A = lila.common.Chronometer.syncEffect(v) { lap =>
lila.log.boot.info(s"${lap.millis}ms $s")
}
}
def parseFloatOption(str: String): Option[Float] =
Try(java.lang.Float.parseFloat(str)).toOption
implicit final class LilaPimpedValid[A](v: Valid[A]) {
def future: Fu[A] = v fold (errs => fufail(errs.shows), fuccess)
}
implicit final class LilaPimpedTry[A](v: scala.util.Try[A]) {
def fold[B](fe: Exception => B, fa: A => B): B = v match {
case scala.util.Failure(e: Exception) => fe(e)
case scala.util.Failure(e) => throw e
case scala.util.Success(a) => fa(a)
}
def future: Fu[A] = fold(Future.failed, fuccess)
}
def parseIntOption(str: String): Option[Int] = try {
Some(java.lang.Integer.parseInt(str))
} catch {
case e: NumberFormatException => None
}
def parseFloatOption(str: String): Option[Float] = try {
Some(java.lang.Float.parseFloat(str))
} catch {
case e: NumberFormatException => None
}
def parseLongOption(str: String): Option[Long] = try {
Some(java.lang.Long.parseLong(str))
} catch {
case e: NumberFormatException => None
}
def parseLongOption(str: String): Option[Long] =
Try(java.lang.Long.parseLong(str)).toOption
def intBox(in: Range.Inclusive)(v: Int): Int =
math.max(in.start, math.min(v, in.end))
@ -88,25 +58,13 @@ trait PackageObject extends Steroids with WithFuture {
math.max(in.start, math.min(v, in.end))
}
trait WithFuture extends scalalib.Validation {
type Fu[+A] = Future[A]
type Funit = Fu[Unit]
def fuccess[A](a: A) = Future successful a
def fufail[A <: Throwable, B](a: A): Fu[B] = Future failed a
def fufail[A](a: String): Fu[A] = fufail(common.LilaException(a))
def fufail[A](a: Failures): Fu[A] = fufail(common.LilaException(a))
val funit = fuccess(())
}
trait WithPlay { self: PackageObject =>
import play.api.libs.json._
import scalalib.Zero
import ornicar.scalalib.Zero
implicit def playExecutionContext = play.api.libs.concurrent.Execution.defaultContext
val directEC = lila.PimpedFuture.DirectExecutionContext
val directEC = lila.common.implicits.DirectExecutionContext
implicit val LilaFutureMonad = new Monad[Fu] {
override def map[A, B](fa: Fu[A])(f: A => B) = fa map f
@ -134,72 +92,11 @@ trait WithPlay { self: PackageObject =>
Future sequence t
}
implicit def LilaPimpedFuture[A](fua: Fu[A]): PimpedFuture.LilaPimpedFuture[A] =
new PimpedFuture.LilaPimpedFuture(fua)
implicit final class LilaPimpedFutureZero[A: Zero](fua: Fu[A]) {
def nevermind: Fu[A] = fua recover {
case e: lila.common.LilaException => zero[A]
case e: java.util.concurrent.TimeoutException => zero[A]
case e: Exception =>
lila.log("common").warn("Future.nevermind", e)
zero[A]
}
}
implicit final class LilaPimpedFutureOption[A](fua: Fu[Option[A]]) {
def flatten(msg: => String): Fu[A] = fua flatMap {
_.fold[Fu[A]](fufail(msg))(fuccess(_))
}
def orElse(other: => Fu[Option[A]]): Fu[Option[A]] = fua flatMap {
_.fold(other) { x => fuccess(x.some) }
}
def getOrElse(other: => Fu[A]): Fu[A] = fua flatMap { _.fold(other)(fuccess) }
}
implicit final class LilaPimpedFutureValid[A](fua: Fu[Valid[A]]) {
def flatten: Fu[A] = fua flatMap { _.fold[Fu[A]](fufail(_), fuccess(_)) }
}
implicit final class LilaPimpedFutureBoolean(fua: Fu[Boolean]) {
def >>&(fub: => Fu[Boolean]): Fu[Boolean] =
fua flatMap { _.fold(fub, fuccess(false)) }
def >>|(fub: => Fu[Boolean]): Fu[Boolean] =
fua flatMap { _.fold(fuccess(true), fub) }
def unary_! = fua dmap (!_)
}
implicit final class LilaPimpedBooleanWithFuture(self: Boolean) {
def optionFu[A](v: => Fu[A]): Fu[Option[A]] = if (self) v map (_.some) else fuccess(none)
}
implicit final class LilaPimpedActorSystem(self: akka.actor.ActorSystem) {
def lilaBus = lila.common.Bus(self)
}
implicit final class LilaPimpedFiniteDuration(self: FiniteDuration) {
def toCentis = chess.Centis {
// divide by Double, then round, to avoid rounding issues with just `/10`!
math.round {
if (self.unit eq MILLISECONDS) self.length / 10d
else self.toMillis / 10d
}
}
def abs = if (self.length < 0) -self else self
}
implicit val LilaFiniteDurationZero: Zero[FiniteDuration] = Zero.instance(Duration.Zero)
implicit val LilaCentisZero: Zero[chess.Centis] = Zero instance chess.Centis(0)

View file

@ -1,16 +0,0 @@
package lila.common
import scala.concurrent.duration._
import java.util.concurrent.TimeUnit
import com.typesafe.config.Config
object PimpedConfig {
implicit final class LilaPimpedConfig(val config: Config) extends AnyVal {
def millis(name: String): Int = config.getDuration(name, TimeUnit.MILLISECONDS).toInt
def seconds(name: String): Int = config.getDuration(name, TimeUnit.SECONDS).toInt
def duration(name: String): FiniteDuration = millis(name).millis
}
}

View file

@ -1,134 +0,0 @@
package lila
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.duration._
import scala.concurrent.{ Future, ExecutionContext }
object PimpedFuture {
private type Fu[A] = Future[A]
object DirectExecutionContext extends ExecutionContext {
override def execute(command: Runnable): Unit = command.run()
override def reportFailure(cause: Throwable): Unit =
throw new IllegalStateException("lila DirectExecutionContext failure", cause)
}
final class LilaPimpedFuture[A](val fua: Fu[A]) extends AnyVal {
def dmap[B](f: A => B): Fu[B] = fua.map(f)(DirectExecutionContext)
def dforeach[B](f: A => Unit): Unit = fua.foreach(f)(DirectExecutionContext)
def >>-(sideEffect: => Unit): Fu[A] = fua andThen {
case _ => sideEffect
}
def >>[B](fub: => Fu[B]): Fu[B] = fua flatMap (_ => fub)
def void: Fu[Unit] = fua.map(_ => ())(DirectExecutionContext)
def inject[B](b: => B): Fu[B] = fua.map(_ => b)(DirectExecutionContext)
def injectAnyway[B](b: => B): Fu[B] = fold(_ => b, _ => b)
def effectFold(fail: Exception => Unit, succ: A => Unit) {
fua onComplete {
case scala.util.Failure(e: Exception) => fail(e)
case scala.util.Failure(e) => throw e // Throwables
case scala.util.Success(e) => succ(e)
}
}
def fold[B](fail: Exception => B, succ: A => B): Fu[B] =
fua map succ recover { case e: Exception => fail(e) }
def flatFold[B](fail: Exception => Fu[B], succ: A => Fu[B]): Fu[B] =
fua flatMap succ recoverWith { case e: Exception => fail(e) }
def logFailure(logger: => lila.log.Logger, msg: Exception => String): Fu[A] =
addFailureEffect { e => logger.warn(msg(e), e) }
def logFailure(logger: => lila.log.Logger): Fu[A] = logFailure(logger, _.toString)
def addFailureEffect(effect: Exception => Unit) = {
fua onFailure {
case e: Exception => effect(e)
}
fua
}
def addEffect(effect: A => Unit): Fu[A] = {
fua foreach effect
fua
}
def addEffects(fail: Exception => Unit, succ: A => Unit): Fu[A] = {
fua onComplete {
case scala.util.Failure(e: Exception) => fail(e)
case scala.util.Failure(e) => throw e // Throwables
case scala.util.Success(e) => succ(e)
}
fua
}
def addEffectAnyway(inAnyCase: => Unit): Fu[A] = {
fua onComplete {
case _ => inAnyCase
}
fua
}
def mapFailure(f: Exception => Exception) = fua recover {
case cause: Exception => throw f(cause)
}
def prefixFailure(p: => String) = mapFailure { e =>
common.LilaException(s"$p ${e.getMessage}")
}
def thenPp: Fu[A] = {
effectFold(
e => println("[failure] " + e),
a => println("[success] " + a)
)
fua
}
def thenPp(msg: String): Fu[A] = {
effectFold(
e => println(s"[$msg] [failure] $e"),
a => println(s"[$msg] [success] $a")
)
fua
}
def await(duration: FiniteDuration): A =
scala.concurrent.Await.result(fua, duration)
def awaitOrElse(duration: FiniteDuration, default: => A): A = try {
scala.concurrent.Await.result(fua, duration)
} catch {
case _: Exception => default
}
def awaitSeconds(seconds: Int): A =
await(seconds.seconds)
def withTimeout(duration: FiniteDuration, error: => Throwable)(implicit system: akka.actor.ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future failed error)
)
}
def withTimeoutDefault(duration: FiniteDuration, default: => A)(implicit system: akka.actor.ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future(default))
)
}
def chronometer = lila.common.Chronometer(fua)
def mon(path: lila.mon.RecPath) = chronometer.mon(path).result
}
}

View file

@ -4,9 +4,10 @@ import play.api.libs.json._
object PimpedJson {
def anyValWriter[O, A: Writes](f: O => A): Writes[O] = Writes[O] { o =>
def anyValWriter[O, A: Writes](f: O => A) = Writes[O] { o =>
Json toJson f(o)
}
def intAnyValWriter[O](f: O => Int): Writes[O] = anyValWriter[O, Int](f)
def stringAnyValWriter[O](f: O => String): Writes[O] = anyValWriter[O, String](f)
@ -14,85 +15,4 @@ object PimpedJson {
def intIsoWriter[O](iso: Iso[Int, O]): Writes[O] = anyValWriter[O, Int](iso.to)
def stringIsoReader[O](iso: Iso[String, O]): Reads[O] = Reads.of[String] map iso.from
implicit final class LilaPimpedJsObject(val js: JsObject) extends AnyVal {
def str(key: String): Option[String] =
(js \ key).asOpt[String]
def int(key: String): Option[Int] =
(js \ key).asOpt[Int]
def long(key: String): Option[Long] =
(js \ key).asOpt[Long]
def boolean(key: String): Option[Boolean] =
(js \ key).asOpt[Boolean]
def obj(key: String): Option[JsObject] =
(js \ key).asOpt[JsObject]
def arr(key: String): Option[JsArray] =
(js \ key).asOpt[JsArray]
def arrAs[A](key: String)(as: JsValue => Option[A]): Option[List[A]] =
arr(key) map { j =>
(j.value.map(as)(scala.collection.breakOut): List[Option[A]]).flatten
}
def ints(key: String): Option[List[Int]] = arrAs(key)(_.asOpt[Int])
def strs(key: String): Option[List[String]] = arrAs(key)(_.asOpt[String])
def objs(key: String): Option[List[JsObject]] = arrAs(key)(_.asOpt[JsObject])
def get[A: Reads](key: String): Option[A] =
(js \ key).asOpt[A]
def noNull = JsObject {
js.fields collect {
case (key, value) if value != JsNull => key -> value
}
}
def add(pair: (String, Boolean)): JsObject =
if (pair._2) js + (pair._1 -> JsBoolean(true))
else js
def add[A: Writes](pair: (String, Option[A])): JsObject =
pair._2.fold(js) { a => js + (pair._1 -> Json.toJson(a)) }
}
implicit final class LilaPimpedJsValue(val js: JsValue) extends AnyVal {
def str(key: String): Option[String] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[String]
}
def int(key: String): Option[Int] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[Int]
}
def long(key: String): Option[Long] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[Long]
}
def boolean(key: String): Option[Boolean] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[Boolean]
}
def obj(key: String): Option[JsObject] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[JsObject]
}
def arr(key: String): Option[JsArray] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[JsArray]
}
}
}
}

View file

@ -1,152 +0,0 @@
package lila
import ornicar.scalalib
import ornicar.scalalib.Zero
import org.joda.time.DateTime
import scala.util.Try
trait Steroids
extends scalalib.Validation
with scalalib.Common
with scalalib.Regex
with scalalib.OrnicarMonoid.Instances
with scalalib.Zero.Syntax
with scalalib.Zero.Instances
with scalalib.OrnicarOption
with scalalib.OrnicarNonEmptyList
with scalaz.std.OptionInstances
with scalaz.std.OptionFunctions
with scalaz.syntax.std.ToOptionIdOps
with scalaz.std.ListInstances
with scalaz.std.ListFunctions
with scalaz.syntax.std.ToListOps
with scalaz.std.StringInstances
with scalaz.std.TupleInstances
with scalaz.syntax.ToIdOps
with scalaz.syntax.ToEqualOps
with scalaz.syntax.ToApplyOps
with scalaz.syntax.ToValidationOps
with scalaz.syntax.ToFunctorOps
with scalaz.syntax.ToMonoidOps
with scalaz.syntax.ToTraverseOps
with scalaz.syntax.ToShowOps
with LilaSteroids
trait LilaSteroids {
import Wrappers._
@inline implicit def toLilaPimpedOption[A](a: Option[A]) = new LilaPimpedOption(a)
@inline implicit def toLilaPimpedTryList[A](l: List[Try[A]]) = new LilaPimpedTryList(l)
@inline implicit def toLilaPimpedList[A](l: List[A]) = new LilaPimpedList(l)
@inline implicit def toLilaPimpedSeq[A](l: List[A]) = new LilaPimpedSeq(l)
@inline implicit def toLilaPimpedDateTime(d: DateTime) = new LilaPimpedDateTime(d)
@inline implicit def toLilaPimpedBoolean(b: Boolean) = new LilaPimpedBoolean(b)
@inline implicit def toLilaPimpedInt(i: Int) = new LilaPimpedInt(i)
@inline implicit def toLilaPimpedFloat(f: Float) = new LilaPimpedFloat(f)
@inline implicit def toLilaPimpedDouble(d: Double) = new LilaPimpedDouble(d)
@inline implicit def toLilaPimpedByteArray(ba: Array[Byte]) = new LilaPimpedByteArray(ba)
implicit val dateTimeOrdering: Ordering[DateTime] = Ordering.fromLessThan(_ isBefore _)
}
object Wrappers {
final class LilaPimpedDateTime(private val date: DateTime) extends AnyVal {
def getSeconds: Long = date.getMillis / 1000
def getCentis: Long = date.getMillis / 10
}
final class LilaPimpedTryList[A](private val list: List[Try[A]]) extends AnyVal {
def sequence: Try[List[A]] = (Try(List[A]()) /: list) {
(a, b) => a flatMap (c => b map (d => d :: c))
} map (_.reverse)
}
final class LilaPimpedList[A](private val list: List[A]) extends AnyVal {
def sortLike[B](other: List[B], f: A => B): List[A] = list.sortWith {
case (x, y) => other.indexOf(f(x)) < other.indexOf(f(y))
}
}
final class LilaPimpedSeq[A](private val seq: Seq[A]) extends AnyVal {
def has(a: A) = seq contains a
}
/*
* Replaces scalaz boolean ops
* so ?? works on Zero and not Monoid
*/
final class LilaPimpedBoolean(private val self: Boolean) extends AnyVal {
def ??[A](a: => A)(implicit z: Zero[A]): A = if (self) a else Zero[A].zero
def !(f: => Unit) = if (self) f
def fold[A](t: => A, f: => A): A = if (self) t else f
def ?[X](t: => X) = new { def |(f: => X) = if (self) t else f }
def option[A](a: => A): Option[A] = if (self) Some(a) else None
}
final class LilaPimpedInt(private val self: Int) extends AnyVal {
def atLeast(bottomValue: Int): Int = self max bottomValue
def atMost(topValue: Int): Int = self min topValue
}
final class LilaPimpedFloat(private val self: Float) extends AnyVal {
def atLeast(bottomValue: Float): Float = self max bottomValue
def atMost(topValue: Float): Float = self min topValue
}
final class LilaPimpedDouble(private val self: Double) extends AnyVal {
def atLeast(bottomValue: Double): Double = self max bottomValue
def atMost(topValue: Double): Double = self min topValue
}
final class LilaPimpedByteArray(private val self: Array[Byte]) extends AnyVal {
def toBase64 = java.util.Base64.getEncoder.encodeToString(self)
}
/*
* Replaces scalaz option ops
* so ~ works on Zero and not Monoid
*/
final class LilaPimpedOption[A](private val self: Option[A]) extends AnyVal {
import scalaz.std.{ option => o }
def fold[X](some: A => X, none: => X): X = self match {
case None => none
case Some(a) => some(a)
}
def |(a: => A): A = self getOrElse a
def unary_~(implicit z: Zero[A]): A = self getOrElse z.zero
def orDefault(implicit z: Zero[A]): A = self getOrElse z.zero
def toSuccess[E](e: => E): scalaz.Validation[E, A] = o.toSuccess(self)(e)
def toFailure[B](b: => B): scalaz.Validation[A, B] = o.toFailure(self)(b)
def err(message: => String): A = self.getOrElse(sys.error(message))
def ifNone(n: => Unit): Unit = if (self.isEmpty) n
def has(a: A) = self contains a
}
}

View file

@ -0,0 +1,18 @@
package lila.common.base
import ornicar.scalalib
import scala.concurrent.Future
import lila.common.LilaException
trait LilaTypes extends scalalib.Validation {
type Fu[+A] = Future[A]
type Funit = Fu[Unit]
def fuccess[A](a: A) = Future successful a
def fufail[A <: Throwable, B](a: A): Fu[B] = Future failed a
def fufail[A](a: String): Fu[A] = fufail(LilaException(a))
def fufail[A](a: Failures): Fu[A] = fufail(LilaException(a))
val funit = fuccess(())
}
object LilaTypes extends LilaTypes

View file

@ -0,0 +1,169 @@
package lila.common.implicits
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.duration._
import scala.concurrent.{ Future, ExecutionContext }
import lila.common.base.LilaTypes._
import ornicar.scalalib.Zero
object DirectExecutionContext extends ExecutionContext {
override def execute(command: Runnable): Unit = command.run()
override def reportFailure(cause: Throwable): Unit =
throw new IllegalStateException("lila DirectExecutionContext failure", cause)
}
final class PimpedFuture[A](private val fua: Fu[A]) extends AnyVal {
private type Fu[A] = Future[A]
def dmap[B](f: A => B): Fu[B] = fua.map(f)(DirectExecutionContext)
def dforeach[B](f: A => Unit): Unit = fua.foreach(f)(DirectExecutionContext)
def >>-(sideEffect: => Unit): Fu[A] = fua andThen {
case _ => sideEffect
}
def >>[B](fub: => Fu[B]): Fu[B] = fua flatMap (_ => fub)
def void: Fu[Unit] = fua.map(_ => ())(DirectExecutionContext)
def inject[B](b: => B): Fu[B] = fua.map(_ => b)(DirectExecutionContext)
def injectAnyway[B](b: => B): Fu[B] = fold(_ => b, _ => b)
def effectFold(fail: Exception => Unit, succ: A => Unit) {
fua onComplete {
case scala.util.Failure(e: Exception) => fail(e)
case scala.util.Failure(e) => throw e // Throwables
case scala.util.Success(e) => succ(e)
}
}
def fold[B](fail: Exception => B, succ: A => B): Fu[B] =
fua map succ recover { case e: Exception => fail(e) }
def flatFold[B](fail: Exception => Fu[B], succ: A => Fu[B]): Fu[B] =
fua flatMap succ recoverWith { case e: Exception => fail(e) }
def logFailure(logger: => lila.log.Logger, msg: Exception => String): Fu[A] =
addFailureEffect { e => logger.warn(msg(e), e) }
def logFailure(logger: => lila.log.Logger): Fu[A] = logFailure(logger, _.toString)
def addFailureEffect(effect: Exception => Unit) = {
fua onFailure {
case e: Exception => effect(e)
}
fua
}
def addEffect(effect: A => Unit): Fu[A] = {
fua foreach effect
fua
}
def addEffects(fail: Exception => Unit, succ: A => Unit): Fu[A] = {
fua onComplete {
case scala.util.Failure(e: Exception) => fail(e)
case scala.util.Failure(e) => throw e // Throwables
case scala.util.Success(e) => succ(e)
}
fua
}
def addEffectAnyway(inAnyCase: => Unit): Fu[A] = {
fua onComplete {
case _ => inAnyCase
}
fua
}
def mapFailure(f: Exception => Exception) = fua recover {
case cause: Exception => throw f(cause)
}
def prefixFailure(p: => String) = mapFailure { e =>
lila.common.LilaException(s"$p ${e.getMessage}")
}
def thenPp: Fu[A] = {
effectFold(
e => println("[failure] " + e),
a => println("[success] " + a)
)
fua
}
def thenPp(msg: String): Fu[A] = {
effectFold(
e => println(s"[$msg] [failure] $e"),
a => println(s"[$msg] [success] $a")
)
fua
}
def await(duration: FiniteDuration): A =
scala.concurrent.Await.result(fua, duration)
def awaitOrElse(duration: FiniteDuration, default: => A): A = try {
scala.concurrent.Await.result(fua, duration)
} catch {
case _: Exception => default
}
def awaitSeconds(seconds: Int): A =
await(seconds.seconds)
def withTimeout(duration: FiniteDuration, error: => Throwable)(implicit system: akka.actor.ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future failed error)
)
}
def withTimeoutDefault(duration: FiniteDuration, default: => A)(implicit system: akka.actor.ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future(default))
)
}
def chronometer = lila.common.Chronometer(fua)
def mon(path: lila.mon.RecPath) = chronometer.mon(path).result
def nevermind(implicit z: Zero[A]): Fu[A] = fua recover {
case e: lila.common.LilaException => z.zero
case e: java.util.concurrent.TimeoutException => z.zero
case e: Exception =>
lila.log("common").warn("Future.nevermind", e)
z.zero
}
}
final class PimpedFutureBoolean(private val fua: Fu[Boolean]) extends AnyVal {
def >>&(fub: => Fu[Boolean]): Fu[Boolean] =
fua flatMap { if (_) fub else fuccess(false) }
def >>|(fub: => Fu[Boolean]): Fu[Boolean] =
fua flatMap { if (_) fuccess(true) else fub }
def unary_! = fua.map { !_ }(DirectExecutionContext)
}
final class PimpedFutureOption[A](private val fua: Fu[Option[A]]) extends AnyVal {
def flatten(msg: => String): Fu[A] = fua flatMap {
_.fold[Fu[A]](fufail(msg))(fuccess(_))
}
def orElse(other: => Fu[Option[A]]): Fu[Option[A]] = fua flatMap {
_.fold(other) { x => fuccess(Some(x)) }
}
def getOrElse(other: => Fu[A]): Fu[A] = fua flatMap { _.fold(other)(fuccess) }
}
final class PimpedFutureValid[A](private val fua: Fu[Valid[A]]) extends AnyVal {
def flatten: Fu[A] = fua flatMap { _.fold[Fu[A]](fufail(_), fuccess(_)) }
}

View file

@ -0,0 +1,84 @@
package lila.common.implicits
import play.api.libs.json._
final class PimpedJsObject(private val js: JsObject) extends AnyVal {
def str(key: String): Option[String] =
(js \ key).asOpt[String]
def int(key: String): Option[Int] =
(js \ key).asOpt[Int]
def long(key: String): Option[Long] =
(js \ key).asOpt[Long]
def boolean(key: String): Option[Boolean] =
(js \ key).asOpt[Boolean]
def obj(key: String): Option[JsObject] =
(js \ key).asOpt[JsObject]
def arr(key: String): Option[JsArray] =
(js \ key).asOpt[JsArray]
def arrAs[A](key: String)(as: JsValue => Option[A]): Option[List[A]] =
arr(key) map { j =>
(j.value.map(as)(scala.collection.breakOut): List[Option[A]]).flatten
}
def ints(key: String): Option[List[Int]] = arrAs(key)(_.asOpt[Int])
def strs(key: String): Option[List[String]] = arrAs(key)(_.asOpt[String])
def objs(key: String): Option[List[JsObject]] = arrAs(key)(_.asOpt[JsObject])
def get[A: Reads](key: String): Option[A] =
(js \ key).asOpt[A]
def noNull = JsObject {
js.fields collect {
case (key, value) if value != JsNull => key -> value
}
}
def add(pair: (String, Boolean)): JsObject =
if (pair._2) js + (pair._1 -> JsBoolean(true))
else js
def add[A: Writes](pair: (String, Option[A])): JsObject =
pair._2.fold(js) { a => js + (pair._1 -> Json.toJson(a)) }
}
final class PimpedJsValue(private val js: JsValue) extends AnyVal {
def str(key: String): Option[String] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[String]
}
def int(key: String): Option[Int] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[Int]
}
def long(key: String): Option[Long] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[Long]
}
def boolean(key: String): Option[Boolean] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[Boolean]
}
def obj(key: String): Option[JsObject] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[JsObject]
}
def arr(key: String): Option[JsArray] =
js.asOpt[JsObject] flatMap { obj =>
(obj \ key).asOpt[JsArray]
}
}

View file

@ -0,0 +1,54 @@
package lila.common.implicits
import java.lang.Math.{ min, max }
import scala.concurrent.ExecutionContext
import lila.common.base.LilaTypes._
import ornicar.scalalib.Zero
final class PimpedBoolean(private val self: Boolean) extends AnyVal {
/**
* Replaces scalaz boolean ops
* so ?? works on Zero and not Monoid
*/
def ??[A](a: => A)(implicit z: Zero[A]): A = if (self) a else z.zero
def !(f: => Unit) = if (self) f
def fold[A](t: => A, f: => A): A = if (self) t else f
def ?[X](t: => X) = new { def |(f: => X) = if (self) t else f }
def option[A](a: => A): Option[A] = if (self) Some(a) else None
def optionFu[A](v: => Fu[A])(implicit ec: ExecutionContext): Fu[Option[A]] =
if (self) v map { Some(_) } else fuccess(None)
}
final class PimpedLong(private val self: Long) extends AnyVal {
def atLeast(bottomValue: Long): Long = max(self, bottomValue)
def atMost(topValue: Long): Long = min(self, topValue)
}
final class PimpedInt(private val self: Int) extends AnyVal {
def atLeast(bottomValue: Int): Int = max(self, bottomValue)
def atMost(topValue: Int): Int = min(self, topValue)
}
final class PimpedFloat(private val self: Float) extends AnyVal {
def atLeast(bottomValue: Float): Float = max(self, bottomValue)
def atMost(topValue: Float): Float = min(self, topValue)
}
final class PimpedDouble(private val self: Double) extends AnyVal {
def atLeast(bottomValue: Double): Double = max(self, bottomValue)
def atMost(topValue: Double): Double = min(self, topValue)
}

View file

@ -0,0 +1,22 @@
package lila.common.implicits
import java.util.Base64
import scala.util.Try
final class PimpedTryList[A](private val list: List[Try[A]]) extends AnyVal {
def sequence: Try[List[A]] = Try(list map { _.get })
}
final class PimpedList[A](private val list: List[A]) extends AnyVal {
def sortLike[B](other: List[B], f: A => B): List[A] = list.sortWith {
(x, y) => other.indexOf(f(x)) < other.indexOf(f(y))
}
}
final class PimpedSeq[A](private val seq: Seq[A]) extends AnyVal {
def has(a: A) = seq contains a
}
final class PimpedByteArray(private val self: Array[Byte]) extends AnyVal {
def toBase64 = Base64.getEncoder.encodeToString(self)
}

View file

@ -0,0 +1,84 @@
package lila.common.implicits
import java.util.concurrent.TimeUnit
import scala.concurrent.duration._
import scala.concurrent.Future
import scala.util.Try
import com.typesafe.config.Config
import org.joda.time.DateTime
import ornicar.scalalib.Zero
import scalaz._
import Scalaz._
import lila.common.base.LilaTypes._
final class PimpedOption[A](private val self: Option[A]) extends AnyVal {
import scalaz.std.{ option => o }
def fold[X](some: A => X, none: => X): X = self.fold(none)(some)
def |(a: => A): A = self getOrElse a
def unary_~(implicit z: Zero[A]): A = self getOrElse z.zero
def orDefault(implicit z: Zero[A]): A = self getOrElse z.zero
def toSuccess[E](e: => E): scalaz.Validation[E, A] = o.toSuccess(self)(e)
def toFailure[B](b: => B): scalaz.Validation[A, B] = o.toFailure(self)(b)
def err(message: => String): A = self.getOrElse(sys.error(message))
def ifNone(n: => Unit): Unit = if (self.isEmpty) n
def has(a: A) = self contains a
}
final class PimpedString(private val s: String) extends AnyVal {
def boot[A](v: => A): A = lila.common.Chronometer.syncEffect(v) { lap =>
lila.log.boot.info(s"${lap.millis}ms $s")
}
}
final class PimpedConfig(private val config: Config) extends AnyVal {
def millis(name: String): Int = config.getDuration(name, TimeUnit.MILLISECONDS).toInt
def seconds(name: String): Int = config.getDuration(name, TimeUnit.SECONDS).toInt
def duration(name: String): FiniteDuration = millis(name).millis
}
final class PimpedDateTime(private val date: DateTime) extends AnyVal {
def getSeconds: Long = date.getMillis / 1000
def getCentis: Long = date.getMillis / 10
}
final class PimpedValid[A](private val v: Valid[A]) extends AnyVal {
def future: Fu[A] = v fold (errs => fufail(errs.shows), fuccess)
}
final class PimpedTry[A](private val v: Try[A]) extends AnyVal {
def fold[B](fe: Exception => B, fa: A => B): B = v match {
case scala.util.Failure(e: Exception) => fe(e)
case scala.util.Failure(e) => throw e
case scala.util.Success(a) => fa(a)
}
def future: Fu[A] = fold(Future.failed, fuccess)
}
final class PimpedFiniteDuration(private val d: FiniteDuration) extends AnyVal {
def toCentis = chess.Centis {
// divide by Double, then round, to avoid rounding issues with just `/10`!
math.round {
if (d.unit eq MILLISECONDS) d.length / 10d
else d.toMillis / 10d
}
}
def abs = if (d.length < 0) -d else d
}