Skip to content

Commit

Permalink
Merge 7d0af7a into 6f89af8
Browse files Browse the repository at this point in the history
  • Loading branch information
NthPortal committed Sep 10, 2017
2 parents 6f89af8 + 7d0af7a commit 22e7fd3
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 26 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -13,7 +13,7 @@ A Scala library for handling conversions between types by throwing exceptions or
### SBT (Scala 2.11 and 2.12)

```sbtshell
"com.nthportal" %% "convert" % "0.2.0"
"com.nthportal" %% "convert" % "0.3.0"
```

### Maven
Expand All @@ -24,7 +24,7 @@ A Scala library for handling conversions between types by throwing exceptions or
<dependency>
<groupId>com.nthportal</groupId>
<artifactId>convert_2.12</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
```

Expand All @@ -34,6 +34,6 @@ A Scala library for handling conversions between types by throwing exceptions or
<dependency>
<groupId>com.nthportal</groupId>
<artifactId>convert_2.11</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
```
4 changes: 3 additions & 1 deletion build.sbt
Expand Up @@ -3,7 +3,7 @@ name := "convert"
description := "A Scala library for handling conversions between types by throwing exceptions or returning Options " +
"containing the results."

val rawVersion = "0.2.0"
val rawVersion = "0.3.0"
isSnapshot := false
version := rawVersion + { if (isSnapshot.value) "-SNAPSHOT" else "" }

Expand All @@ -22,6 +22,8 @@ libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.0.1+" % Test
)

autoAPIMappings := true

scalacOptions ++= {
if (isSnapshot.value) Seq()
else scalaVersion.value split '.' map { _.toInt } match {
Expand Down
53 changes: 47 additions & 6 deletions src/main/scala/com/nthportal/convert/Convert.scala
Expand Up @@ -2,7 +2,7 @@ package com.nthportal.convert

import com.nthportal.convert.SpecializationTypes.specTypes

import scala.language.higherKinds
import scala.language.{higherKinds, implicitConversions}
import scala.reflect.ClassTag
import scala.util.control.{ControlThrowable, NonFatal}

Expand Down Expand Up @@ -31,11 +31,12 @@ import scala.util.control.{ControlThrowable, NonFatal}
* val res1: Boolean = parseBoolean("true")(Convert.Valid)
* val res2: Option[Boolean] = parseBoolean("true")(Convert.Any)
* }}}
*
* @define withinConversion This method MUST be called within a conversion
* block from this Convert instance.
*/
sealed trait Convert {
self =>

/** A function which takes the result type of a conversion as input,
* and yields the return type of the conversion block.
*
Expand Down Expand Up @@ -84,8 +85,8 @@ sealed trait Convert {
def unwrap[@specialized(specTypes) T](result: Result[T]): T

/** Tests an expression, failing the conversion if false. Analogous to
* [[scala.Predef.require(boolean):Unit Predef.require]], except that
* it fails the conversion instead of always throwing an exception.
* [[scala.Predef.require(requirement:<?>):Unit* Predef.require]], except
* that it fails the conversion instead of always throwing an exception.
*
* $withinConversion
*
Expand All @@ -97,8 +98,8 @@ sealed trait Convert {
}

/** Tests an expression, failing the conversion if false. Analogous to
* [[scala.Predef.require(boolean,=>Any):Unit Predef.require]], except
* that it fails the conversion instead of always throwing an exception.
* [[scala.Predef.require(requirement:<?>,message:<?>):Unit* Predef.require]],
* except that it fails the conversion instead of always throwing an exception.
*
* $withinConversion
*
Expand Down Expand Up @@ -143,6 +144,42 @@ sealed trait Convert {
case NonFatal(e: Exception) if matches(e) => fail(e)
}
}

/** An object containing an implicit conversion from `Result[T]` to `T`.
* The implicit conversion allows the automatic unwrapping of
* [[Result Results]], without explicitly calling [[unwrap]].
*
* '''USE THIS IMPLICIT CONVERSION AT YOUR OWN RISK'''
*
* This implicit conversion can improve code readability; however, it
* should be used with care. As with all implicit conversions, it may
* be difficult to tell when it is applied by the compiler, leading to
* unexpected behavior which is difficult to debug.
*
* Additionally, because [[unwrap]] MUST be called within a conversion block,
* this implicit conversion MUST be called within a conversion block as well.
* However, if imported in the wrong scope, the compiler may insert a call
* to this implicit conversion ''outside'' of any conversion block. Use this
* implicit conversion with care.
*/
object AutoUnwrap {
/** Automatically unwraps the [[Result]] of another conversion.
*
* $withinConversion
*
* @param result the result of another conversion
* @tparam T the type of the `Result[T]`
* @return the result of the other conversion, not wrapped as a `Result`
*/
implicit def autoUnwrap[T](result: Result[T]): T = unwrap(result)
}

/** A wrapper object for an implicit reference to the enclosing [[Convert]]. */
object Implicit {
/** Import to bring the enclosing [[Convert]] into scope as an implicit. */
implicit val ref: self.type = self
}

}

object Convert {
Expand All @@ -166,6 +203,8 @@ object Convert {
* and throws an exception if the conversion fails.
*/
object Valid extends Convert {
self: Type.Valid =>

override type Result[T] = T

override def conversion[@specialized(specTypes) T](res: => T): T = res
Expand Down Expand Up @@ -195,6 +234,8 @@ object Convert {
* the conversion fails.
*/
object Any extends Convert {
self: Type.Any =>

override type Result[T] = Option[T]

override def conversion[@specialized(specTypes) T](res: => T): Option[T] = {
Expand Down
44 changes: 28 additions & 16 deletions src/test/scala/com/nthportal/convert/ConvertTest.scala
Expand Up @@ -8,10 +8,6 @@ class ConvertTest extends FlatSpec with Matchers with OptionValues {

behavior of "Convert.Valid"

it should "have the correct type" in {
"val c: Convert.Type.Valid = Convert.Valid" should compile
}

it should "fail" in {
val c = Convert.Valid

Expand All @@ -33,16 +29,27 @@ class ConvertTest extends FlatSpec with Matchers with OptionValues {
}

it should "unwrap results" in {
val c = Convert.Valid
import c.Implicit.ref

c.conversion { c.unwrap(parseInt("1")) * 2 } shouldBe 2

a[NumberFormatException] should be thrownBy c.conversion {
c.unwrap(parseInt("not a number")) * 2
}
}

it should "auto-unwrap results" in {
implicit val c = Convert.Valid

c.conversion {
val res = c.unwrap(parseInt("1"))
res * 2
import c.AutoUnwrap.autoUnwrap
parseInt("1") * 2
} shouldBe 2

a[NumberFormatException] should be thrownBy c.conversion {
val res = c.unwrap(parseInt("not a number"))
res * 2
import c.AutoUnwrap.autoUnwrap
parseInt("not a number") * 2
}
}

Expand All @@ -58,10 +65,6 @@ class ConvertTest extends FlatSpec with Matchers with OptionValues {

behavior of "Convert.Any"

it should "have the correct type" in {
"val c: Convert.Type.Any = Convert.Any" should compile
}

it should "fail" in {
val c = Convert.Any

Expand All @@ -80,16 +83,25 @@ class ConvertTest extends FlatSpec with Matchers with OptionValues {
}

it should "unwrap results" in {
val c = Convert.Any
import c.Implicit.ref

c.conversion { c.unwrap(parseInt("1")) * 2 }.value shouldBe 2

c.conversion { c.unwrap(parseInt("not a number")) * 2 } shouldBe empty
}

it should "auto-unwrap results" in {
implicit val c = Convert.Any

c.conversion {
val res = c.unwrap(parseInt("1"))
res * 2
import c.AutoUnwrap.autoUnwrap
parseInt("1") * 2
}.value shouldBe 2

c.conversion {
val res = c.unwrap(parseInt("not a number"))
res * 2
import c.AutoUnwrap.autoUnwrap
parseInt("not a number") * 2
} shouldBe empty
}

Expand Down

0 comments on commit 22e7fd3

Please sign in to comment.