From a625f4f516f282f43d1298d14d67f19eca52fe27 Mon Sep 17 00:00:00 2001 From: Robin Hillyard Date: Sun, 22 Dec 2024 12:30:53 -0500 Subject: [PATCH] Cosmetics only. Added FPSpec. --- README.md | 2 + .../scala/com/phasmidsoftware/core/FP.scala | 92 ++++++++-- .../com/phasmidsoftware/kmldoc/KML.scala | 10 +- .../com/phasmidsoftware/xml/Extractor.scala | 114 +++++++++--- .../phasmidsoftware/core/emptyResource.txt | 0 .../phasmidsoftware/core/oneLineResource.txt | 1 + .../com/phasmidsoftware/core/testFile.txt | 0 .../com/phasmidsoftware/core/FPSpec.scala | 165 ++++++++++++++++++ .../phasmidsoftware/xml/ExtractorsSpec.scala | 3 +- 9 files changed, 337 insertions(+), 50 deletions(-) create mode 100644 src/test/resources/com/phasmidsoftware/core/emptyResource.txt create mode 100644 src/test/resources/com/phasmidsoftware/core/oneLineResource.txt create mode 100644 src/test/resources/com/phasmidsoftware/core/testFile.txt create mode 100644 src/test/scala/com/phasmidsoftware/core/FPSpec.scala diff --git a/README.md b/README.md index 8486386..ecd529d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ # KML Doctor Utilities to parse, doctor, and render KML files. +The reference documentation for KML is here: +https://developers.google.com/kml/documentation/kmlreference Versions ======== diff --git a/src/main/scala/com/phasmidsoftware/core/FP.scala b/src/main/scala/com/phasmidsoftware/core/FP.scala index 002ef39..b7f877f 100644 --- a/src/main/scala/com/phasmidsoftware/core/FP.scala +++ b/src/main/scala/com/phasmidsoftware/core/FP.scala @@ -18,6 +18,8 @@ object FP { /** * Sequence method to combine elements of Try. * + * TESTME + * * @param xys an Iterator of Try[X] * @tparam X the underlying type * @return a Try of Iterator[X] @@ -27,8 +29,6 @@ object FP { /** * Method to transform a Seq of Try[X] into a Try of Seq[X]. * - * TODO move this to FP. - * * @param xys a Seq of Try[X]. * @tparam X the underlying type. * @return a Try of Seq[X]. @@ -38,14 +38,15 @@ object FP { } /** - * Method to transform a Seq of Option[X] into a Option of Seq[X]. + * Method to transform a `Seq[Option[X]]` into an `Option[Seq[X]]`. + * NOTE this is pessimistic. * - * @param xys a Seq of Try[X]. + * @param xos a `Seq[Option[X]]`]. * @tparam X the underlying type. - * @return a Try of Seq[X]. + * @return an `Option[Seq[X]]` which is defined only if all elements of the input were defined. */ - def sequence[X](xys: Iterable[Option[X]]): Option[Seq[X]] = xys.foldLeft(Option(Seq[X]())) { - (xsy, xy) => for (xs <- xsy; x <- xy) yield xs :+ x + def sequence[X](xos: Iterable[Option[X]]): Option[Seq[X]] = xos.foldLeft(Option(Seq[X]())) { + (xso, xo) => for (xs <- xso; x <- xo) yield xs :+ x } /** @@ -54,8 +55,6 @@ object FP { * Non-fatal failures are eliminated from consideration, although each one invokes the function f. * Fatal failures are retained so that the result will be a Failure. * - * TODO move this to FP. - * * @param f a function Throwable => Unit which will process each non-fatal failure. * @param xys a Seq of Try[X]. * @tparam X the underlying type. @@ -107,6 +106,8 @@ object FP { /** * Method to partition an method to combine elements of Try as an Iterator. * + * TESTME + * * @param xys an Iterator of Try[X]. * @tparam X the underlying type. * @return a tuple of two iterators of Try[X], the first one being successes, the second one being failures. @@ -116,6 +117,8 @@ object FP { /** * Method to partition an method to combine elements of Try. * + * TESTME + * * @param xys a Seq of Try[X]. * @tparam X the underlying type. * @return a tuple of two Seqs of Try[X], the first one being successes, the second one being failures. @@ -125,6 +128,8 @@ object FP { /** * Method to yield a URL for a given resourceForClass in the classpath for C. * + * TESTME + * * @param resourceName the name of the resourceForClass. * @tparam C a class of the package containing the resourceForClass. * @return a Try[URL]. @@ -156,13 +161,13 @@ object FP { } /** - * Method to transform a a Try[X] into an Option[X]. + * Method to transform a `Try[X]` into an `Option[X]`. * But, unlike "toOption," a Failure can be logged. * * @param f a function to process any Exception (typically a logging function). - * @param xy the input Try[X}. + * @param xy the input `Try[X]`. * @tparam X the underlying type. - * @return an Option[X]. + * @return an `Option[X]`. */ def tryToOption[X](f: Throwable => Unit)(xy: Try[X]): Option[X] = xy match { case Success(x) => Some(x) @@ -170,22 +175,60 @@ object FP { case Failure(x) => throw x } + /** + * Converts an `Option[X]` to a `Try[X]`. + * If the `Option` is `Some`, it returns a `Success` containing the value. + * If the `Option` is `None`, it returns a `Failure` containing the provided or default `Throwable`. + * + * TESTME + * + * @param xo the input `Option[X]` to convert. + * @param throwable the `Throwable` to use if the input is `None`. Defaults to `NoSuchElementException`. + * @tparam X the underlying type of the `Option` and `Try`. + * @return a `Try[X]` representing either the success or failure of the conversion. + */ def optionToTry[X](xo: Option[X], throwable: Throwable = new NoSuchElementException("optionToTry: None")): Try[X] = xo match { case Some(x) => Success(x) case None => Failure(throwable) } + /** + * Attempts to evaluate the provided expression and wraps the result in a `Success` if it is non-null. + * If the value is `null`, a `Failure` wrapping a `NoSuchElementException` with the provided message is returned. + * + * TESTME + * + * @param x a lazily-evaluated expression of type `X`. + * @param msg a message to include in the exception if the result is `null`. + * @tparam X the type of the expression's result. + * @return a `Try[X]` containing `Success(x)` if `x` is non-null, or `Failure` if `x` is `null`. + */ def tryNotNull[X](x: => X)(msg: String): Try[X] = Option(x) match { case Some(x) => Success(x) case None => Failure(new NoSuchElementException(s"tryNotNull: null: $msg")) } /** Uncurrying for functions of arity 6. + * + * TESTME */ def uncurried[T1, T2, T3, T4, T5, T6, R](f: T1 => T2 => T3 => T4 => T5 => T6 => R): (T1, T2, T3, T4, T5, T6) => R = { (x1, x2, x3, x4, x5, x6) => f(x1)(x2)(x3)(x4)(x5)(x6) } + /** + * Applies a transformation function to the successful value of a Try if the value satisfies a predicate. + * If the predicate fails, it returns a Failure with an exception indicating the reason. + * + * TESTME + * + * @param p a predicate function to test the successful value of the input Try. + * @param predicate a descriptive string used in the exception message if the predicate fails. Default is "predicate". + * @param f a transformation function applied to the value if the predicate passes. + * @tparam X the input type of the Try. + * @tparam Y the output type after applying the transformation function. + * @return a function that transforms a Try[X] to a Try[Y] based on the predicate and transformation function. + */ def mapTryGuarded[X, Y](p: X => Boolean, predicate: String = "predicate")(f: X => Y): Try[X] => Try[Y] = { case Success(x) if p(x) => Success(f(x)) case Success(x) => Failure(FPException(s"mapTryGuarded: $predicate failed for $x")) @@ -193,11 +236,18 @@ object FP { } } +/** + * The `TryUsing` object provides utility methods to manage resources safely + * and effectively using Scala's `Using` and `Try`. + * It encapsulates resource management in a functional way, ensuring proper release of resources. + * The methods in this object extend the functionality of `Using.apply` + * by offering flattening operations over nested `Try`. + */ object TryUsing { /** - * This method is to Using.apply as flatMap is to Map. + * This method is to `Using.apply` as `flatMap` is to `map`. * - * @param resource a resource which is used by f and will be managed via Using.apply + * @param resource a resource which is used by f and will be managed via `Using.apply` * @param f a function of R => Try[A]. * @tparam R the resource type. * @tparam A the underlying type of the result. @@ -206,10 +256,12 @@ object TryUsing { def apply[R: Releasable, A](resource: => R)(f: R => Try[A]): Try[A] = Using(resource)(f).flatten /** - * This method is similar to apply(r) but it takes a Try[R] as its parameter. - * The definition of f is the same as in the other apply, however. + * This method is similar to `apply(r)` but it takes a `Try[R]` as its parameter. + * The definition of `f` is the same as in the other apply, however. * - * @param ry a Try[R] which is passed into f and will be managed via Using.apply + * TESTME + * + * @param ry a Try[R] which is passed into f and will be managed via `Using.apply` * @param f a function of R => Try[A]. * @tparam R the resource type. * @tparam A the underlying type of the result. @@ -218,4 +270,10 @@ object TryUsing { def apply[R: Releasable, A](ry: Try[R])(f: R => Try[A]): Try[A] = for (r <- ry; a <- apply(r)(f)) yield a } +/** + * This class represents an exception related to functional programming operations. + * + * @param msg A message describing the exception. + * @param eo An optional underlying cause for the exception, as a Throwable. + */ case class FPException(msg: String, eo: Option[Throwable] = None) extends Exception(msg, eo.orNull) diff --git a/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala b/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala index b8596e9..19b6243 100644 --- a/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala +++ b/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala @@ -16,7 +16,6 @@ import com.phasmidsoftware.xml._ import java.io.PrintStream import java.net.URL import org.slf4j.{Logger, LoggerFactory} -import scala.collection.immutable.Seq import scala.io.Source import scala.reflect.ClassTag import scala.util._ @@ -402,9 +401,9 @@ object SubStyleData extends Extractors with Renderers { case class Placemark(Geometry: Seq[Geometry])(val featureData: FeatureData) extends Feature with HasName with Mergeable[Placemark] with Invertible[Placemark] { def invert: Option[Placemark] = { - val gs: Seq[Option[Geometry]] = for (g <- Geometry) yield g.invert - val lso: Option[Seq[Geometry]] = FP.sequence(gs) - for (ls <- lso) yield Placemark(ls)(featureData) + val gos: Seq[Option[Geometry]] = for (g <- Geometry) yield g.invert + val gso: Option[Seq[Geometry]] = FP.sequence(gos) + for (gs <- gso) yield Placemark(gs)(featureData) } /** @@ -486,7 +485,8 @@ case class Placemark(Geometry: Seq[Geometry])(val featureData: FeatureData) exte private def joinMatchingPlacemarks(name: String, feature: Feature, mergeName: Boolean) = feature match { case q: Placemark if q.name.matches(name) => merge(q, mergeName) - case _ => None // FIXME can result in this Placemark being lost if name doesn't match q.name + // FIXME Issue #20 can result in this Placemark being lost if name doesn't match q.name + case _ => None } } diff --git a/src/main/scala/com/phasmidsoftware/xml/Extractor.scala b/src/main/scala/com/phasmidsoftware/xml/Extractor.scala index b2893b9..3b88a36 100644 --- a/src/main/scala/com/phasmidsoftware/xml/Extractor.scala +++ b/src/main/scala/com/phasmidsoftware/xml/Extractor.scala @@ -14,8 +14,8 @@ import scala.util.{Failure, Success, Try} import scala.xml.{Node, NodeSeq} /** - * Trait to define the behavior of an extractor (parser) which can will take an XML Node - * and return Try[T] where T is the underlying type of the Extractor. + * Trait to define the behavior of an extractor (parser) that takes an XML `Node` + * and return `Try[T]` where `T` is the underlying type of the `Extractor`. * * @tparam T the type to be constructed. */ @@ -23,46 +23,47 @@ trait Extractor[T] extends NamedFunction[Extractor[T]] { self => /** - * Method to convert a Node into a Try[T]. + * Method to convert a `Node` into a `Try[T]`. * - * @param node a Node. - * @return a Try[T]. + * @param node a `Node`. + * @return a `Try[T]`. */ def extract(node: Node): Try[T] /** - * Method to map this Extractor[T] into an Extractor[U]. + * Method to map this `Extractor[T]` into an `Extractor[U]`. * - * @param f a T => U. + * @param f a `T => U`. * @tparam U the underlying type of the result. - * @return an Extractor[U]. + * @return an `Extractor[U]`. */ def map[U](f: T => U): Extractor[U] = (node: Node) => self.extract(node) map f /** - * Method to flatMap this Extractor[T] into an Extractor[U]. + * Method to `flatMap` this `Extractor[T]` into an `Extractor[U]`. * - * @param f a T => Try[U]. + * @param f a `T => Try[U]`. * @tparam U the underlying type of the result. - * @return an Extractor[U]. + * @return an `Extractor[U]`. */ def flatMap[U](f: T => Try[U]): Extractor[U] = (node: Node) => self.extract(node) flatMap f /** - * Method to create an Extractor[T] such that, if this Extractor[T] fails, then we invoke the (implicit) Extractor[P] instead. + * Method to create an `Extractor[T]` such that, if this `Extractor[T]` fails, then we invoke the (implicit) `Extractor[P]` instead. * * TESTME * - * @tparam P the type of the alternative Extractor. P must provide implicit evidence of Extractor[P] and P must be a sub-class of T. - * @return an Extractor[T]. + * @tparam P the type of the alternative `Extractor`. + * `P` must provide implicit evidence of `Extractor[P]` and `P` must be a sub-class of `T`. + * @return an `Extractor[T]`. */ def |[P <: T : Extractor](): Extractor[T] = (node: Node) => self.extract(node) orElse implicitly[Extractor[P]].mapTo[T].extract(node) /** - * Method to create an Extractor[P] which instantiates a Try[T] but treats it as a Try[P] where P is a super-class of T. + * Method to create an `Extractor[P]` which instantiates a `Try[T]` but treats it as a `Try[P]` where `P` is a super-class of `T`. * - * @tparam P the type of the Extractor we wish to return. - * @return an Extractor[P]. + * @tparam P the type of the `Extractor` we wish to return. + * @return an `Extractor[P]`. */ private def mapTo[P >: T]: Extractor[P] = (node: Node) => self.extract(node) } @@ -281,6 +282,20 @@ object Extractor { case _ => None } + /** + * Extracts a field value from a given XML node based on the specified field name. + * + * CONSIDER improving the model on which this extraction is based. It's messy! + * + * @param field The name of the field to extract. This may represent different types + * of elements or attributes, such as node content, attributes, optional attributes, + * plurals, singletons, etc. + * @param node The XML node from which the field value is to be extracted. + * @tparam P The type of the field value, constrained by the `Extractor` type class. + * @return A tuple containing the field description as a `String` and a `Try[P]` representing + * the success or failure of the extraction process. In case of failure, the `Try` includes + * information about the cause of failure. + */ private def doExtractField[P: Extractor](field: String, node: Node): (String, Try[P]) = field match { // NOTE special name for the (text) content of a node. @@ -295,10 +310,22 @@ object Extractor { // NOTE optional members such that the name begins with "maybe" case optional(x) => s"optional: $x" -> extractOptional[P](node / x) // NOTE this is the default case which is used for a singleton entity (plural entities would be extracted using extractChildren). - // FIXME why would we be looking for a singleton LinearRing in a node which is an extrude node? + // TODO Issue #21 why would we be looking for a singleton LinearRing in a node which is an extrude node? case x => s"singleton: $x" -> extractSingleton[P](node / x) } + /** + * Extracts an attribute value from the given XML node based on its name. + * The extraction utilizes an implicit `Extractor` for the specified type `P`. + * Returns a `Failure` if the attribute is not found and `optional` is false, + * or if the attribute cannot be uniquely determined. + * + * @param node the XML node from which the attribute value is retrieved (a `Node`). + * @param x the name of the attribute to be extracted. + * @param optional a flag indicating if the attribute is optional. Defaults to false. + * @tparam P the type of the result, constrained by the implicit `Extractor` type class. + * @return a `Try[P]` containing the extracted value, or a failure if extraction fails. + */ private def extractAttribute[P: Extractor](node: Node, x: String, optional: Boolean = false): Try[P] = (for (ns <- node.attribute(x)) yield for (n <- ns) yield Extractor.extract[P](n)) match { case Some(py :: Nil) => py @@ -441,6 +468,15 @@ object MultiExtractor { * requires implicit evidence of Extractor[P]. */ case class MultiExtractorBase[P: Extractor](range: Range) extends MultiExtractor[Seq[P]] { + /** + * Extracts a sequence of elements of type `P` from the provided `NodeSeq`, ensuring that the number of extracted elements + * falls within a specified range. If the extraction succeeds but the resulting sequence does not meet the range requirements, + * a failure is returned. Non-fatal issues during the extraction are logged and ignored. + * + * @param nodeSeq the sequence of XML nodes from which elements of type `P` are to be extracted. + * @return a `Try` containing a sequence of extracted elements of type `P` if successful and the number of elements is within the specified range; + * otherwise, a failure with the corresponding exception. + */ def extract(nodeSeq: NodeSeq): Try[Seq[P]] = sequenceForgiving(logWarning)(nodeSeq map Extractor.extract[P]) match { case x@Success(ps) if range.contains(ps.size) => x @@ -448,6 +484,12 @@ case class MultiExtractorBase[P: Extractor](range: Range) extends MultiExtractor case x@Failure(_) => x } + /** + * Logs a warning message or takes appropriate action based on the type of exception encountered during XML processing. + * + * @param x the exception to evaluate and log if necessary. + * @return Unit, as this method is side-effecting and logs messages without returning a value. + */ private def logWarning(x: Throwable): Unit = x match { case MissingFieldException(_, "singleton", _) if range.start <= 0 => // NOTE: OK -- no need to log anything. case XmlException(message, x) => logger.warn("MultiExtractorBase: $message" + x.getLocalizedMessage) @@ -477,20 +519,18 @@ object MultiExtractorBase { } /** - * Trait which extends a function of type String => Extractor[T]. - * When the apply method is invoked with a particular label, an appropriate Extractor[T] is returned. - * - * CONSIDER renaming this because it isn't an extractor, but a function which creates an extractor from a String. + * Trait which extends a function of type `String => Extractor[T]`. + * When the `apply` method is invoked with a particular label, an appropriate `Extractor[T]` is returned. * * @tparam T the underlying type of the result of invoking apply. T may be an Iterable type. */ trait TagToExtractorFunc[T] extends (String => Extractor[T]) { /** - * Method to yield an Extractor[T], given a label. + * Method to yield an `Extractor[T]`, given `label`. * - * @param label the label of a node or sequence of nodes we wish to extract. - * @return an Extractor[T]. + * @param label the label of a node or sequence of nodes we wish to extract (a `String`). + * @return an `Extractor[T]`. */ def apply(label: String): Extractor[T] } @@ -501,22 +541,42 @@ trait TagToExtractorFunc[T] extends (String => Extractor[T]) { * @tparam T the underlying type of the resulting sequence when invoking apply. */ trait TagToSequenceExtractorFunc[T] extends TagToExtractorFunc[Seq[T]] { + /** + * A sequence of tag names used to identify or categorize elements in the context of the trait. + */ val tags: Seq[String] + /** + * A string representing a pseudo identifier or key relevant to the context of the trait. + * This value is used as part of the trait functionality, such as validation or categorization. + */ val pseudo: String + /** + * Validates if the input string matches the pseudo identifier. + * + * @param w the input string to validate. + * @return true if the input string matches the pseudo identifier, false otherwise. + */ def valid(w: String): Boolean = w == pseudo + /** + * A MultiExtractor instance used for extracting a sequence of type `T` from an XML `NodeSeq`. + * This value represents a reusable mechanism to perform the extraction process + * based on specific tags or identifiers within the XML structure. + * + * @tparam T the underlying type of the sequence to be extracted. + */ val tsm: MultiExtractor[Seq[T]] } /** - * Class which extends an Extractor of Seq[T]. + * Class which extends an `Extractor` of `Seq[T]`. * * NOTE: used by subclassExtractor1 method (itself unused). * * @param labels a set of labels (tags) - * @param tsm an (implicit) MultiExtractor of Seq[T]. + * @param tsm an (implicit) `MultiExtractor` of `Seq[T]`. * @tparam T the type to be constructed. */ class SubclassExtractor[T](val labels: Seq[String])(implicit tsm: MultiExtractor[Seq[T]]) extends Extractor[Seq[T]] { diff --git a/src/test/resources/com/phasmidsoftware/core/emptyResource.txt b/src/test/resources/com/phasmidsoftware/core/emptyResource.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/com/phasmidsoftware/core/oneLineResource.txt b/src/test/resources/com/phasmidsoftware/core/oneLineResource.txt new file mode 100644 index 0000000..980a0d5 --- /dev/null +++ b/src/test/resources/com/phasmidsoftware/core/oneLineResource.txt @@ -0,0 +1 @@ +Hello World! diff --git a/src/test/resources/com/phasmidsoftware/core/testFile.txt b/src/test/resources/com/phasmidsoftware/core/testFile.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/test/scala/com/phasmidsoftware/core/FPSpec.scala b/src/test/scala/com/phasmidsoftware/core/FPSpec.scala new file mode 100644 index 0000000..1a70297 --- /dev/null +++ b/src/test/scala/com/phasmidsoftware/core/FPSpec.scala @@ -0,0 +1,165 @@ +package com.phasmidsoftware.core + +import com.phasmidsoftware.core.FP._ +import java.io.InputStream +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should +import scala.collection.mutable +import scala.io.Source +import scala.util.control.NonFatal +import scala.util.{Failure, Success, Try} + +class FPSpec extends AnyFlatSpec with should.Matchers { + + behavior of "FP" + + it should "sequence an Iterable" in { + sequence(List(Some(1), Some(2))) shouldBe Some(List(1, 2)) + sequence(List(Some(1), None)) shouldBe None + } + + it should "sequenceForgivingTransform" in { + + val sb = new StringBuilder() + val failure = Failure[Int](new NoSuchElementException("test")) + val success = Success(1) + + val triedInts1: Seq[Try[Int]] = Seq() + sequenceForgivingTransform[Int](triedInts1)(x => Success(Some(x)), _ => Success(None)) shouldBe Success(Nil) + val triedInts2 = Seq(success, failure) + val z: Try[Seq[Int]] = sequenceForgivingTransform[Int](triedInts2)(x => Success(Some(x)), _ => Success(None)) + sequenceForgivingTransform[Int](triedInts2)(x => Success(Some(x)), _ => Success(None)) shouldBe Success(List(1)) + } + + it should "optionToTry" in { + + } + + it should "indexFound" in { + indexFound("junk", 0) shouldBe Success(0) + indexFound("junk", -1) should matchPattern { case Failure(FPException("Header column 'junk' not found", None)) => } + } + + it should "sequence" in { + val try1 = Success(1) + val try2 = Success(2) + val try3 = Failure(FPException("")) + sequence(Seq(try1, try2)) shouldBe Success(Seq(1, 2)) + val result: Try[Iterable[Int]] = sequence(Seq(try1, try3)) + result should matchPattern { case Failure(_) => } + } + + it should "sequenceForgivingWith" in { + val try2 = Success(1) + val try3 = Success(2) + val try1 = Failure(FPException("")) + val sb = new mutable.StringBuilder() + val handleException: PartialFunction[Throwable, Try[Option[Int]]] = { + case NonFatal(x) => sb.append(s"forgiving: $x"); Success(None) + case x => Failure(x) + } + val result: Try[Iterable[Int]] = sequenceForgivingWith(Seq(try1, try2, try3))(handleException) + result should matchPattern { case Success(List(1, 2)) => } + sb.toString shouldBe "forgiving: com.phasmidsoftware.core.FPException: " + } + + it should "sequenceForgivingTransform 1" in { + val sb = new mutable.StringBuilder() + val handleException: PartialFunction[Throwable, Try[Option[Int]]] = { + case NonFatal(x) => sb.append(s"forgiving: $x"); Success(None) + case x => Failure(x) + } + val xs = Seq(Success(0), Success(1), Success(2)) + val result: Try[Iterable[Int]] = sequenceForgivingTransform(xs)(x => Success(Some(x + 1)), handleException) + result should matchPattern { case Success(List(1, 2, 3)) => } + sb.toString shouldBe "" + } + + it should "sequenceForgivingTransform 2" in { + val sb = new mutable.StringBuilder() + val handleException: PartialFunction[Throwable, Try[Option[Int]]] = { + case NonFatal(x) => sb.append(s"forgiving: $x"); Success(None) + case x => Failure(x) + } + val xs = Seq(Failure(FPException("")), Success(1), Success(2)) + val result: Try[Iterable[Int]] = sequenceForgivingTransform(xs)(x => Success(Some(x + 1)), handleException) + result should matchPattern { case Success(List(2, 3)) => } + sb.toString shouldBe "forgiving: com.phasmidsoftware.core.FPException: " + } + + it should "sequenceForgiving 0" in { + val try2 = Success(1) + val try3 = Success(2) + val try1 = Failure(FPException("")) + val result: Try[Seq[Int]] = sequenceForgiving(x => System.err.println(s"failure ignored: ${x}"))(Seq(try1, try2, try3)) + result should matchPattern { case Success(List(1, 2)) => } + } + + it should "sequenceForgiving 2" in { + val try1 = Success(1) + val try2 = Success(2) + val try3 = Failure(new OutOfMemoryError("")) + val result: Try[Iterable[Int]] = sequenceForgiving(x => System.err.println(s"failure ignored: ${x}"))(Seq(try1, try2, try3)) + result should matchPattern { case Failure(_) => } + } + it should "tryToOption" in { + val sb = new mutable.StringBuilder + + def logFunction(x: Throwable): Unit = { + sb.append(x.getLocalizedMessage) + } + + val f: Try[Int] => Option[Int] = tryToOption(logFunction)(_) + f(Success(1)) shouldBe Some(1) + sb.toString() shouldBe "" + f(Failure(new Exception("failure"))) shouldBe None + sb.toString() shouldBe "failure" + } + + behavior of "TryUsing" + + it should "return success" in { + lazy val i: InputStream = getClass.getResourceAsStream("oneLineResource.txt") + println(i) + val zy: Try[String] = TryUsing(Source.fromInputStream(i))(s => Try(s.getLines().toList.head)) + zy should matchPattern { case Success("Hello World!") => } + } + + it should "return failure(0)" in { + val wy = TryUsing(Source.fromResource(null))(s => Try(s.getLines().toList.head)) + wy should matchPattern { case Failure(_) => } + wy.recover { + case _: NullPointerException => Success(()) + case e => fail(s"wrong exception: $e") + } + } + + it should "return failure(1)" in { + lazy val i: InputStream = getClass.getResourceAsStream(null) + val wy = TryUsing(Source.fromInputStream(i))(s => Try(s.getLines().toList.head)) + wy should matchPattern { case Failure(_) => } + wy.recover { + case _: NullPointerException => Success(()) + case e => fail(s"wrong exception: $e") + } + } + + it should "return failure(2)" in { + lazy val i: InputStream = getClass.getResourceAsStream("emptyResource.txt") + val wy = TryUsing(Source.fromInputStream(i))(s => Try(s.getLines().toList.head)) + wy should matchPattern { case Failure(_) => } + wy.recover { + case _: NoSuchElementException => Success(()) + case e => fail(s"wrong exception: $e") + } + } + + behavior of "resourceForClass" + it should "get resources in this package" in { + resourceForClass("testFile.txt", getClass) should matchPattern { case Success(_) => } + resourceForClass("testFile.txt") should matchPattern { case Success(_) => } + resourceForClass(".txt", getClass) should matchPattern { case Failure(_) => } + } + + +} diff --git a/src/test/scala/com/phasmidsoftware/xml/ExtractorsSpec.scala b/src/test/scala/com/phasmidsoftware/xml/ExtractorsSpec.scala index 24a8971..9da6661 100644 --- a/src/test/scala/com/phasmidsoftware/xml/ExtractorsSpec.scala +++ b/src/test/scala/com/phasmidsoftware/xml/ExtractorsSpec.scala @@ -250,7 +250,7 @@ class ExtractorsSpec extends AnyFlatSpec with should.Matchers with PrivateMethod extracted shouldBe Success(Document3(1, None, List(Empty, Empty))) } - // TODO this should be just like the previous test + // TODO Issue #22 this should be just like the previous test ignore should "extractor3B" in { val xml: Elem = @@ -352,6 +352,7 @@ class ExtractorsSpec extends AnyFlatSpec with should.Matchers with PrivateMethod implicit val extractorSeq: MultiExtractor[Seq[Simple4]] = multiExtractorBase[Simple4](Positive) } + // TODO Issue #23 ignore should "extract MyContainer without inner boundary" in { val xml =