├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.sbt ├── docs ├── Documentation.md ├── ReleaseNotes-1.0.0.md ├── ReleaseNotes-1.1.0.md ├── ReleaseNotes-1.2.0.md ├── ReleaseNotes-1.3.0.md ├── ReleaseNotes-1.4.0.md ├── ReleaseNotes-1.5.0.md ├── ReleaseNotes-1.6.0.md ├── bootstrap-3.3.5.css ├── index.html └── main.js ├── project ├── .gitignore ├── CiPublishPlugin.scala ├── Documentation.scala ├── build.properties └── plugins.sbt ├── src ├── main │ └── scala │ │ └── pimpathon │ │ ├── FilterMonadic.scala │ │ ├── GenTraversableLike.scala │ │ ├── any.scala │ │ ├── argonaut.scala │ │ ├── array.scala │ │ ├── boolean.scala │ │ ├── builder.scala │ │ ├── classtag.scala │ │ ├── dynamicVariable.scala │ │ ├── either.scala │ │ ├── file.scala │ │ ├── frills │ │ ├── GenTraversableLike.scala │ │ ├── any.scala │ │ ├── function.scala │ │ ├── list.scala │ │ ├── set.scala │ │ ├── string.scala │ │ ├── try.scala │ │ └── vector.scala │ │ ├── function.scala │ │ ├── java │ │ ├── io │ │ │ ├── InputStream.scala │ │ │ ├── OutputStream.scala │ │ │ └── package.scala │ │ └── util │ │ │ ├── Callable.scala │ │ │ ├── concurrent │ │ │ ├── ThreadFactory.scala │ │ │ └── atomic │ │ │ │ └── AtomicReference.scala │ │ │ └── date.scala │ │ ├── list.scala │ │ ├── map.scala │ │ ├── multimap.scala │ │ ├── mutableMap.scala │ │ ├── nestedMap.scala │ │ ├── numeric.scala │ │ ├── option.scala │ │ ├── ordering.scala │ │ ├── package.scala │ │ ├── properties.scala │ │ ├── random.scala │ │ ├── runnable.scala │ │ ├── scalaz │ │ ├── either.scala │ │ ├── nel.scala │ │ └── std │ │ │ ├── boolean.scala │ │ │ ├── either.scala │ │ │ └── option.scala │ │ ├── set.scala │ │ ├── stream.scala │ │ ├── string.scala │ │ ├── threadLocal.scala │ │ ├── throwable.scala │ │ ├── try.scala │ │ ├── tuple.scala │ │ └── vector.scala └── test │ └── scala │ └── pimpathon │ ├── FilterMonadic.scala │ ├── GenTraversableLike.scala │ ├── PSpec.scala │ ├── RunnableTest.scala │ ├── ThreadLocal.scala │ ├── ThrowableTest.scala │ ├── any.scala │ ├── argonautTests │ ├── CodecJsonTest.scala │ ├── DecodeJsonTest.scala │ ├── EncodeJsonTest.scala │ ├── JsonTest.scala │ ├── JsonUtil.scala │ └── TraversalFrills.scala │ ├── array.scala │ ├── boolean.scala │ ├── builder.scala │ ├── classtag.scala │ ├── dynamicVariable.scala │ ├── either.scala │ ├── file.scala │ ├── frills │ ├── AnyTest.scala │ ├── ListTest.scala │ ├── StringTest.scala │ ├── TryTest.scala │ └── function.scala │ ├── function.scala │ ├── java │ ├── io │ │ ├── InputStream.scala │ │ └── OutputStream.scala │ └── util │ │ ├── CallableTest.scala │ │ ├── DateTest.scala │ │ └── concurrent │ │ ├── ThreadFactoryTest.scala │ │ └── atomic │ │ └── AtomicReferenceTest.scala │ ├── list.scala │ ├── map.scala │ ├── multiMap.scala │ ├── mutableMap.scala │ ├── nestedMap.scala │ ├── numeric.scala │ ├── option.scala │ ├── ordering.scala │ ├── random.scala │ ├── scalaz │ ├── either.scala │ ├── nel.scala │ └── std │ │ └── option.scala │ ├── set.scala │ ├── stream.scala │ ├── string.scala │ ├── try.scala │ ├── tuple.scala │ └── util.scala └── version.sbt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: ["*"] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - uses: actions/checkout@v2.3.4 10 | with: 11 | fetch-depth: 0 12 | - id: file_changes 13 | uses: trilom/file-changes-action@v1.2.3 14 | - uses: olafurpg/setup-scala@v10 15 | - run: sbt clean test 16 | - if: always() 17 | uses: mikepenz/action-junit-report@v2 18 | with: 19 | report_paths: 'target/test-reports/**/*.xml' 20 | - if: contains(steps.file_changes.outputs.files, 'version.sbt') 21 | uses: blended-zio/setup-gpg@v3 22 | - if: contains(steps.file_changes.outputs.files, 'version.sbt') 23 | run: sbt ci-publish 24 | env: 25 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 26 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 27 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 28 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | target 3 | .idea 4 | .bsp 5 | .idea_modules 6 | projectFilesBackup 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pimpathon 2 | ========= 3 | 4 | [![Build Status][badge-build]][link-build] 5 | [![Release Artifacts][badge-release]][link-release] 6 | [![Maven Central][badge-maven]][link-maven] 7 | 8 | **Pimpathon** is a library that extends Scala & Java classes with 'extension methods' via the [Pimp my Library][pimp-my-library] pattern. 9 | 10 | Pimpathon contains pimps for classes in core scala & java libraries and pimps for external libraries. To avoid 11 | name clash the pimps for core classes are called XPimps (ListPimps, etc.) and those for external libraries are called 12 | XFrills (ListFrills, etc.) 13 | 14 | ### Using Pimpathon 15 | 16 | Release artefacts are published to Sonatype and are built using Github Actions. 17 | 18 | To include the repositories in your SBT build you should add: 19 | 20 | ```scala 21 | resolvers += Resolver.sonatypeRepo("releases") 22 | ``` 23 | 24 | To include pimpathon as a dependency you should add: 25 | 26 | ```scala 27 | libraryDependencies += "com.github.stacycurl" %% "pimpathon" % "1.8.27" intransitive() 28 | ``` 29 | 30 | 'intransitive' means that even though pimpathon pimps a few third party libraries it won't force you to depend on them, 31 | you'll only get pimps for types in libraries you already depend on. 32 | 33 | 34 | Builds are available for Scala 2.11.7 & 2.12.12 35 | 36 | 37 | [Documentation][doc] 38 | 39 | ### Contributors 40 | 41 | + Corina Usher [@coughlac](https://twitter.com/coughlac) 42 | + Howard Branch [@purestgreen](https://twitter.com/purestgreen) 43 | + Julien Truffaut [@julien-truffaut](https://twitter.com/julien-truffaut) 44 | + Raymond Barlow [@raymanoz](https://twitter.com/raymanoz) 45 | + Sam Halliday [@fommil](https://twitter.com/fommil) 46 | + Shing Hing Man 47 | + Stacy Curl [@stacycurl](https://twitter.com/stacycurl) 48 | + Xavier GUIHOT (http://xavierguihot.com) 49 | 50 | [pimp-my-library]:http://www.artima.com/weblogs/viewpost.jsp?thread=179766 51 | [doc]: https://rawgit.com/stacycurl/pimpathon/master/docs/index.html 52 | [olddoc]: https://github.com/stacycurl/pimpathon/blob/master/docs/Documentation.md 53 | 54 | 55 | [badge-build]: https://github.com/stacycurl/pimpathon/actions/workflows/build.yml/badge.svg 56 | [link-build]: https://github.com/stacycurl/pimpathon/actions/ 57 | 58 | [badge-release]: https://img.shields.io/nexus/r/https/oss.sonatype.org/com.github.stacycurl/pimpathon_2.12.svg "Sonatype Releases" 59 | [link-release]: https://oss.sonatype.org/content/repositories/releases/com/github/stacycurl/pimpathon_2.12/ "Sonatype Releases" 60 | 61 | [badge-maven]: https://maven-badges.herokuapp.com/maven-central/com.github.stacycurl/pimpathon_2.12/badge.svg 62 | [link-maven]: https://maven-badges.herokuapp.com/maven-central/com.github.stacycurl/pimpathon_2.12 -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.jsuereth.sbtpgp.SbtPgp.autoImport._ 2 | import sbt.Keys._ 3 | import sbt._ 4 | 5 | inThisBuild(List( 6 | organization := "com.github.stacycurl", 7 | homepage := Some(url("https://github.com/stacycurl/pimpathon")), 8 | licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 9 | developers := List( 10 | Developer("stacycurl", "Stacy Curl", "stacy.curl@gmail.com", url("https://github.com/stacycurl")) 11 | ), 12 | usePgpKeyHex("pimpathon ci") 13 | )) 14 | 15 | val pimpathon: Project = (project in file(".") 16 | settings( 17 | organization := "com.github.stacycurl", 18 | scalaVersion := "2.12.12", 19 | scalacOptions := Seq("-feature", "-Xfatal-warnings", "-deprecation", "-unchecked", "-target:jvm-1.8"), 20 | javacOptions := Seq("-source", "1.8", "-target", "1.8", "-Xlint"), 21 | maxErrors := 1, 22 | parallelExecution in Test := true, 23 | resolvers += Resolver.sonatypeRepo("releases"), 24 | libraryDependencies ++= scalaVersion(dependencies("2.12.12" → List( 25 | "org.scala-lang" % "scala-compiler" % "2.12.0" exclude("org.scala-lang.modules", "scala-xml_2.12"), 26 | "org.scala-lang" % "scala-library" % "2.12.0" % "test", 27 | "com.github.julien-truffaut" %% "monocle-core" % "1.3.2" % "provided", 28 | "io.argonaut" %% "argonaut" % "6.3.3" % "provided", 29 | "io.argonaut" %% "argonaut-monocle" % "6.3.3" % "provided", 30 | "org.scalaz" %% "scalaz-core" % "7.3.3" % "provided", 31 | "io.gatling" %% "jsonpath" % "0.6.8" % "provided", 32 | "junit" % "junit" % "4.11" % "test", 33 | "com.github.stacycurl" %% "delta-argonaut" % "1.2.0" % "test", 34 | "com.github.stacycurl" %% "delta-matchers" % "1.2.0" % "test" 35 | ))).value, 36 | doc := version.apply(Documentation.generate).value, 37 | initialize := { 38 | val _ = initialize.value 39 | require(sys.props("java.specification.version") == "1.8", "Java 8 is required for this project.") 40 | }, 41 | addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.3") 42 | ) 43 | ) 44 | 45 | def dependencies(modules: (String, List[ModuleID])*)(version: String): List[sbt.ModuleID] = modules.toMap.apply(version) -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.0.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.0.0 2 | 3 | ### Additions 4 | 5 | + [A].calc(f: A => B): B 6 | + [A].cond(Predicate[A], A => B, A => B): B 7 | + [A].attempt(A => B): Try[B] (Either[Throwable, B] for 2.9) 8 | + [A].filterSelf(Predicate[A]): Option[A] 9 | + [A].lpair(A => B): (B, A) 10 | + [A].rpair(A => B): (A, B) 11 | + [A].partialMatch(PartialFunction[A, B]): Option[B] 12 | + [A].tapIf(Predicate[A])((A => Unit)\*): A 13 | + [A].tapUnless(Predicate[A])((A => Unit)\*): A 14 | + [A].tap((A => Discarded)\*): A 15 | + [A].update(A => Discarded): A 16 | + [A].withSideEffect(A => Discarded): A 17 | + [A].withFinally(A => Unit)(A => B): B 18 | + (A, B).to[C]: (C, C) 19 | + (A, B).calc((A, B) => C): C 20 | + Option[A].getOrThrow(String): A 21 | + Option[A].getOrThrow(=> Exception): A 22 | + Option[A].toTry: Try[A] 23 | + Either[L, R].map(L => M, R => S): Either[M, S] 24 | + Either[L, R].tap(L => Unit, R => Unit): Either[L, R] 25 | + Either[L, R].rightOr(L => R): R 26 | + Either[L, R].leftOr(R => L): L 27 | + Either[Throwable, R].toTry: Try[R] 28 | + Try[A].toEither: Either[Throwable, A] 29 | + FilterMonadic[(K, V)].toMultiMap: MultiMap[List, K, V] 30 | + GTL[A].collectAttributeCounts(PartialFunction[A, B]): Map[B, Int] 31 | + GTL[A].optAttributeCounts(A => Option[B]): Map[B, Int] 32 | + GTL[A].attributeCounts(A => B): Map[B, Int] 33 | + GTL[V].asMap.withKeys(V => K): Map[K, V] 34 | + GTL[K].asMap.withValues(K => V): Map[K, V] 35 | + GTL[V].asMap.withSomeKeys(V => Option[K]): Map[K, V] 36 | + GTL[K].asMap.withSomeValues(K => Option[V]): Map[K, V] 37 | + GTL[V].asMap.withManyKeys(V => List[K]): Map[K, V] 38 | + GTL[V].asMap.withPFKeys(PartialFunction[V, K]): Map[K, V] 39 | + GTL[K].asMap.withPFValues(PartialFunction[K, V]): Map[K, V] 40 | + GTL[V].asMultiMap.withKeys(V => K): MultiMap[GTL, K, V] 41 | + GTL[K].asMultiMap.withValues(K => V): MultiMap[GTL, K, V] 42 | + GTL[V].asMultiMap.withSomeKeys(V => Option[K]): MultiMap[GTL, K, V] 43 | + GTL[K].asMultiMap.withSomeValues(K => Option[V]): MultiMap[GTL, K, V] 44 | + GTL[V].asMultiMap.withManyKeys(V => List[K]): MultiMap[GTL, K, V] 45 | + GTL[V].asMultiMap.withPFKeys(PartialFunction[V, K]): MultiMap[GTL, K, V] 46 | + GTL[K].asMultiMap.withPFValues(PartialFunction[K, V]): Map[GTL, K, V] 47 | + GTL[K].as[F[_. _]].with\*(K => V): F[K, V] 48 | + List[A].batchBy(A => B): List[List[A]] 49 | + List[A].const(B): List[B] 50 | + List[A].countWithSize(Predicate[A]): Option[(Int, Int)] 51 | + List[A].distinctBy(A => B): List[A] 52 | + List[A].emptyTo(List[A]): List[A] 53 | + List[A].fraction(Predicate[A]): Double 54 | + List[A].headTail: (A, List[A]) 55 | + List[A].headTailOption: Option[(A, List[A])] 56 | + List[A].initLast: (List[A], A) 57 | + List[A].initLastOption: Option[(List[A], A)] 58 | + List[A].mapNonEmpty(List[A] => B): Option[B] 59 | + List[A].prefixPadTo(Int, A): List[A] 60 | + List[A].seqMap(A => Option[B]): Option[List[B]] 61 | + List[A].sharedPrefix(List[A]): (List[A], List[A], List[A]) 62 | + List[A].tailOption: Option[List[A]] 63 | + List[A].uncons(B, List[A] => B): B 64 | + List[A].unconsC(=> B, A => List[A] => B): B 65 | + List[A].zipWith(List[B])(((A, B)) => C): List[C] 66 | + List[K].zipToMap(List[V]): Map[K, V] 67 | + Set[A].mutable: mutable.Set[A] 68 | + Set[A].toMutable: mutable.Set[A] 69 | + Set[A].powerSet: Set[Set[A]] 70 | + Stream.continuallyWhile(=> A)(Predicate[A]): Stream[A] 71 | + Array[Byte].toHex: String 72 | + Array[Byte].toHex(Int): String 73 | + Map[K, V].andThenM(Map[V, W]): Map[K, W] 74 | + Map[K, V].sorted(Ordering[K]): SortedMap[K, V] 75 | + Map[K, V].reverse(Set[K] => K): Map[V, K] 76 | + Map[K, V].reverseToMultiMap: MultiMap[Set, K, V] 77 | + Map[K, V].mutable: mutable.Map[K, V] 78 | + Map[K, V].toMutable: mutable.Map[K, V] 79 | + Map[K, V].entryForMinKey(Ordering[K]): Option[(K, V)] 80 | + Map[K, V].entryForMaxValue(Ordering[V]): Option[(K, V)] 81 | + Map[K, V].entryForMinKey(Ordering[K]): Option[(K, V)] 82 | + Map[K, V].entryForMaxValue(Ordering[V]): Option[(K, V)] 83 | + Map[K, V].containsAll(Option[K]): Boolean 84 | + Map[K, V].containsAll(GTL[K]): Boolean 85 | + Map[K, V].containsAny(Option[K]): Boolean 86 | + Map[K, V].containsAny(GTL[K]): Boolean 87 | + Map[K, V].get(Option[K]): Option[V] 88 | + Map[K, V].mapNonEmpty(Map[K, V] => A): Option[A] 89 | + Map[K, V].mapValuesEagerly(V => W): Map[K, W] 90 | + Map[K, V].keyForMinValue(Ordering[V]): Option[K] 91 | + Map[K, V].keyForMaxValue(Ordering[V]): Option[K] 92 | + Map[K, V].valueForMinKey(Ordering[K]): Option[V] 93 | + Map[K, V].valueForMaxKey(Ordering[K]): Option[V] 94 | + Map[K, V].emptyTo(Map[K, V]): Map[K, V] 95 | + Map[K, V].uncons(A, Map[K, V] => A): A 96 | + Map[K, V].getOrThrow(K, String): V 97 | + Map[K, V].getOrThrow(K, => Exception): V 98 | + Map[K, V].valueExists(Predicate[V]): Boolean 99 | + Map[K, V].filterValues(Predicate[V]): Map[K, V] 100 | + Map[K, V].filterValuesNot(Predicate[V]): Map[K, V] 101 | + Map[K, V].filterKeysNot(Predicate[K]): Map[K, V] 102 | + Map[K, V].findEntryWithValue(Predicate[V]): Option[(K, V)] 103 | + Map[K, V].findEntryWithKey(Predicate[K]): Option[(K, V)] 104 | + Map[K, V].findValue(Predicate[V]): Option[V] 105 | + Map[K, V].findKey(Predicate[K]): Option[K] 106 | + MultiMap[F, K, V].select(F[V] => W): Map[K, W] 107 | + MultiMap[F, K, V].merge(MultiMap[F, K, V]): MultiMap[F, K, V] 108 | + Predicate[A].ifSome: Predicate[Option[A]] 109 | + Predicate[A].and(Predicate[A]): Predicate[A] 110 | + Predicate[A].or(Predicate[A]): Predicate[A] 111 | + Predicate[A].exists: Predicate[List[A]] 112 | + Predicate[A].forall: Predicate[List[A]] 113 | + (PartialFunction[A, B] \*\*\* PartialFunction[C, D]): PartialFunction[(A, C), (B, D)] 114 | + Numeric[A].xmap(A => B, B => A): Numeric[B] 115 | + String.sharedPrefix(String): (String, String, String) 116 | + String.md5: String 117 | + String.prefixPadTo(Int, Char): String 118 | + String.suffixWith(String): String 119 | + String.prefixWith(String): String 120 | + threadLocal.create(A): ThreadLocal[A] 121 | + InputStream.closeAfter(InputStream => A): A 122 | + InputStream.attemptClose: Try[Unit] 123 | + InputStream.closeUnless(Boolean): InputStream 124 | + InputStream.closeIf(Boolean): InputStream 125 | + InputStream.drain(OutputStream, closeIn?, closeOut): InputStream 126 | + InputStream.>>(OutputStream): InputStream 127 | + OutputStream.closeAfter(OutputStream => A): A 128 | + OutputStream.attemptClose: Try[Unit] 129 | + OutputStream.closeUnless(Boolean): OutputStream 130 | + OutputStream.closeIf(Boolean): OutputStream 131 | + OutputStream.drain(InputStream, closeOut?, closeIn?): OutputStream 132 | + OutputStream.<<(InputStream): OutputStream 133 | + File.outputStream: FileOutputStream 134 | + File.outputStream(append?): FileOutputStream 135 | + File.deleteRecursivelyOnExit(): File 136 | + File.deleteRecursively(): File 137 | + File.relativeTo(File): File 138 | + File.md5(): String 139 | + File.writeLines(List[String], append?): File 140 | + File.writeBytes(Array[Byte], append?): File 141 | + File.readBytes(): Array[Byte] 142 | + File.readLines(): List[String] 143 | + (File / String): File 144 | + File.tree: Stream[File] 145 | + File.children: Stream[File] 146 | + File.path: List[String] 147 | + File.changeToDirectory(): File 148 | + File.named(String): File 149 | + File.create(directory?): File 150 | + File.source(): BufferedSource 151 | + file.tempFile(String, String): File 152 | + file.tempDir(String, String): File 153 | + file.file(String): File 154 | + file.withTempDirectory(File => A): A 155 | + file.withTempDirectory(String, String)(File => A): A 156 | + file.withTempFile(File => A): A 157 | + file.withTempFile(String, String)(File => A): A 158 | + file.files(File, String\*): Stream[File] 159 | + file.cwd: File 160 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.1.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.1.0 2 | 3 | ### Breaking changes & bug fixes 4 | + Remove support for null files 5 | + File.children & File.tree can now handle unreadable directories 6 | 7 | ### Removals 8 | 9 | + Either[L, R].map(L => M, R => S): Either[M, S] 10 | + Map[K, V].entryForMinKey(Ordering[K]): Option[(K, V)] 11 | + Map[K, V].entryForMaxValue(Ordering[V]): Option[(K, V)] 12 | + Map[K, V].entryForMinKey(Ordering[K]): Option[(K, V)] 13 | + Map[K, V].entryForMaxValue(Ordering[V]): Option[(K, V)] 14 | + Map[K, V].findEntryWithValue(Predicate[V]): Option[(K, V)] 15 | + Map[K, V].findEntryWithKey(Predicate[K]): Option[(K, V)] 16 | + Map[K, V].keyForMinValue(Ordering[V]): Option[K] 17 | + Map[K, V].keyForMaxValue(Ordering[V]): Option[K] 18 | + Map[K, V].valueForMinKey(Ordering[K]): Option[V] 19 | + Map[K, V].valueForMaxKey(Ordering[K]): Option[V] 20 | 21 | 22 | ### Additions 23 | + [A].addTo(M.Builder[A, To]): A 24 | + [A].|>(A => B): B 25 | + [A].update now an alias for tap 26 | + [A].withSideEffect now an alias for tap 27 | + (A, B).tmap(A => C, B => D): (C, D) 28 | + implicit conversion of Either to RightProjection (and back again) 29 | + Either[L, R].bimap(L => M, R => S): Either[M, S] 30 | + Either[L, R].leftMap(L => M): Either[M, R] 31 | + Either[L, R].rightMap(R => S): Either[L, S] 32 | + List[A].tapNonEmpty(List[A] => Unit): List[A] 33 | + List[A].tapEmpty(=> Unit): Lis[A] 34 | + List[A].tap(empty: => Unit, nonEmpty: List[A] => Unit): List[A] 35 | + stream.cond(Boolean, => Stream[A]): Stream[A] 36 | + GTL[A].ungroupBy(A => B): GTL[GTL[A]] 37 | + Map[K, V].entryFor.minKey(Ordering[K]): Option[(K, V)] 38 | + Map[K, V].entryFor.maxValue(Ordering[V]): Option[(K, V)] 39 | + Map[K, V].entryFor.minKey(Ordering[K]): Option[(K, V)] 40 | + Map[K, V].entryFor.maxValue(Ordering[V]): Option[(K, V)] 41 | + Map[K, V].entryFor.matchingValue(Predicate[V]): Option[(K, V)] 42 | + Map[K, V].entryFor.matchingKey(Predicate[K]): Option[(K, V)] 43 | + Map[K, V].keyFor.minValue(Ordering[V]): Option[K] 44 | + Map[K, V].keyFor.maxValue(Ordering[V]): Option[K] 45 | + Map[K, V].valueFor.minKey(Ordering[K]): Option[V] 46 | + Map[K, V].valueFor.maxKey(Ordering[K]): Option[V] 47 | + MultiMap[F, K, V].append(K, F[V]): MultiMap[F, K, V] 48 | + PartialFunction[A, B].either: A => Either[A, B] 49 | + InputStream.buffered: BufferedInputStream 50 | + OutputStream.buffered: BufferedOutputStream 51 | + File.touch(): File 52 | + File.className(File): String 53 | + File.file(String, String): File 54 | + File.isContainedIn(File): Boolean 55 | + File.contains(File): Boolean 56 | + File.isChildOf(File): Boolean 57 | + File.isParentOf(File): Boolean 58 | + File.isJar: Boolean 59 | + File.isClass: Boolean 60 | + File.isJava: Boolean 61 | + File.isScala: Boolean 62 | + File.hasExtension(String): Boolean 63 | + File.childDirs: Stream[File] 64 | + File.missing: Boolean 65 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.2.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.2.0 2 | 3 | ### Breaking changes & bug fixes 4 | + InputStreamUtils.copy(InputStream, OutputStream, closeIn?, closeOut?): Unit -- no longer has an Array[Byte] buffer parameter, instead the InputStreamUtils constructor has a bufferSize parameter. 5 | 6 | ### Additions 7 | + [A].ifSelf(Predicate[A]): Option[A] 8 | + [A].unlessSelf(Predicate[A]): Option[A] -- (and an alias: filterNotSelf) 9 | + Either[L, R].rescue(PartialFunction[L, R]): Either[L, R] (alias for valueOr) 10 | + Either[L, R].rescue(L => R): R (alias for rightOr) 11 | + Either[L, R].valueOr(PartialFunction[L, R]): Either[L, R] 12 | + Either[L, R].valueOr(L => R): R (alias for rightOr) 13 | + Either[L, R].rightFlatMap(R => Either[L, R]): Either[L, R] 14 | + Either[L, R].leftFlatMap(L => Either[L, R]): Either[L, R] 15 | + Array[Byte].copyUpToN(Long, InputStream, OutputStream): Int 16 | + Array[Byte].readUpToN(Long, InputStream): Int 17 | + Stream[A].tailOption: Option[Stream[A]] 18 | + Stream[A].uncons(=> B, Stream[A] => B): B 19 | + Map[K, V].composeM(Map[C, K]): Map[C, V] 20 | + Map[K, V].keyExists(Predicate[K]): Boolean 21 | + Map[K, V].mapKeysEagerly(K => C): Map[C, V] 22 | + Map[K, V].partitionValuesBy(PartialFunction[V, W]): (Map[K, W], Map[K, V])) -- Changed in 1.3.0 23 | + Map[K, V].partitionKeysBy(PartialFunction[K, C]): (Map[C, V], Map[K, V])) -- Changed in 1.3.0 24 | + Map[K, V].updateKeys(PartialFunction[K, C]): Map[C, V] 25 | + Map[K, V].updateKeys(K => Option[C]): Map[C, V] 26 | + Map[K, V].updateValue(key: K, f: V => Option[V]): Map[K, V] 27 | + Map[K, V].updateValues(PartialFunction[V, W]): Map[K, W] 28 | + Map[K, V].updateValues(V => Option[W]]): Map[K, W] 29 | + MultiMap[F, K, V].multiMap.head: Map[K, V] 30 | + MultiMap[F, K, V].multiMap.tail: MultiMap[F, K, V] 31 | + MultiMap[F, K, V].multiMap.reverse: MultiMap[F, V, K] 32 | + MultiMap[F, K, V].pop(K): MultiMap[F, K, V] 33 | + PartialFunction[A, B].toLeft: A => Either[B, A] 34 | + PartialFunction[A, B].toRight: A => Either[A, B] 35 | + File.writeLines now appends a trailing newline 36 | + File.write(String, append?): File 37 | + Random.between(Int, Int): Int 38 | + OutputStream.writeN(InputStream, Long): OutputStream 39 | + OutputStream.writeUpToN(InputStream, Long): Long 40 | + InputStream.readN(OutputStream, Long): InputStream 41 | + InputStream.readUpToN(OutputStream, Long): Long 42 | 43 | ### Refactoring & Cleanup 44 | + Experimenting with expressing tests as single assertions 45 | + Refactored some InputStream & OutputStream methods 46 | + Refactoring test util.createInputStream 47 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.3.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.3.0 2 | 3 | ### Breaking changes & bug fixes 4 | + Map[K, V].partitionKeysBy(PartialFunction[K, C]): (Map[K, V], Map[C, V]) -- tuple reversed so now 'errors' are on the left 5 | + Map[K, V].partitionValuesBy(PartialFunction[V, W]): (Map[K, V], Map[K, W])) -- tuple reversed so now 'errors' are on the left 6 | + Multimap[F, V, K].multiMap.head succeeds for empty values 7 | 8 | ### Additions 9 | + [A].tapPF(PartialFunction[A, Discarded]): A 10 | + [A].transform(PartialFunction[A, A]): A 11 | + [A].unfold(A => Option[(B, A)]): Stream[B] 12 | + Boolean.asInt: Int 13 | + Boolean.either(R).or(L): Either[L, R] -- as in scalaz 14 | + List[Either[L, R]].partitionEithers: (List[L], List[R]) 15 | + List[A].partitionBy(PartialFunction[A, B]): (List[A], List[B]) 16 | + List[A].countBy(A => B): MultiMap[List, Int, A] 17 | + List[A].duplicatesBy(A => B): List[A] 18 | + List[A].amass(PartialFunction[A, List[B]]): List[B] 19 | + List[A].sizeGT(Int): Boolean 20 | + Map[K, V].partitionEntriesBy(PartialFunction[(K, V), (C, W)]): (Map[K, V], Map[C, W]) 21 | + Map[K, V].mapEntries(K => V => (C, W)): Map[C, W] 22 | + MultiMap[F, K, V].sequence: F[Map[K, V]] 23 | + MultiMap[F, K, V].headTailOption: Option[(Map[K, V], MultiMap[F, K, V])] 24 | + MultiMap[F, K, V].multiMap.mapEntries(K => F[V] => (C, F[W]): MultiMap[F, C, W] 25 | + MultiMap[F, K, V].multiMap.values: F[V] 26 | + Predicate[A].guard(A => B): PartialFunction[A, B] 27 | + (A => B => C).tupled: ((A, B)) => C 28 | + (A => B).guardWith(Predicate[A]): PartialFunction[A, B] 29 | + PartialFunction[A, B].partition(CC[A]): (CC[A], CC[B]) 30 | + PartialFunction[A, B].isUndefinedAt(A): Boolean 31 | + PartialFunction[A, B].second[C]: PartialFunction[(C, A), (C, B)] 32 | + PartialFunction[A, B].first[C]: PartialFunction[(A, C), (B, C)] 33 | + PartialFunction[A, A].unify: A => A 34 | + GTL[K].asMap.withConstValue[V]: Map[K, V] 35 | + GTL[A].asMap.withEntries(A => (K, V)): Map[K, V] 36 | + GTL[(K, V)].toMultiMap[F[_]]: MultiMap[F, K, V] 37 | + Generalize argument of partitionByPF from List to GenTraversableLike 38 | + Generalize List.partitionEithers to GenTraversableLike 39 | + mutable.Builder[A, B] +++= TraversableOnce[TraversableOnce[A]] 40 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.4.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.4.0 2 | 3 | ### Breaking changes & bug fixes 4 | + File.write overwrites by default (i.e. append = false, was true before) 5 | + File.writeLines overwrites by default (i.e. append = false, was true before) 6 | + File.writeBytes overwrites by default (i.e. append = false, was true before) 7 | + File.outputStream overwrites by default (i.e. append = false, was true before) 8 | 9 | ### Removals 10 | + List[A].mapNonEmpty(List[A] => B): Option[B] 11 | + Map[K, V].mapNonEmpty(Map[K, V] => B): Option[B] 12 | + File.outputStream: FileOutputStream 13 | 14 | ### Additions 15 | + frills sub-project 16 | + [A].addTo(Growable[A]): A 17 | + [A].calcIf(Predicate[A])(A => B): Option[B] 18 | + [A].calcUnless(Predicate[A])(A => B): Option[B] 19 | + [A].calcPF(PartialFunction[A, B]): Option[B] 20 | + [A].tryFinally(A => B)(A => Unit): B 21 | + [A].passes.one(Predicate[A]*): Option[A] 22 | + [A].passes.all(Predicate[A]*): Option[A] 23 | + [A].passes.none(Predicate[A]*): Option[A] 24 | + [A].passes.some(Predicate[A]*): Option[A] 25 | + [A].fails.one(Predicate[A]*): Option[A] 26 | + [A].fails.all(Predicate[A]*): Option[A] 27 | + [A].fails.none(Predicate[A]*): Option[A] 28 | + [A].fails.some(Predicate[A]*): Option[A] 29 | + [A].removeFrom(Shrinkable[A]): A 30 | + (A, B).calcC(A => B => C): C 31 | + (A, B).tap((A => B => Discarded)*): (A, B) 32 | + (A, B).addTo(Growable[A], Growable[B]): (A, B) 33 | + (A, B).removeFrom(Shrinkable[A], Shrinkable[B]): (A, B) 34 | + Boolean.option(A): Option[A] 35 | + Option[A].toSuccessNel(E): ValidationNel[E, A] 36 | + Option[A].tap(none: => Unit, some: A => Unit): Option[A] 37 | + Option[A].tapNone(=> Unit): Option[A] 38 | + Option[A].tapSome(A => Unit): Option[A] 39 | + Option[A].invert(A): Option[A] 40 | + Either[L, R].addTo(Growable[L], Growable[R]): Either[L, R] 41 | + Either[L, R].removeFrom(Shrinkable[L], Shrinkable[R]): Either[L, R] 42 | + Either[L, R].tapLeft(L => Unit): Either[L, R] 43 | + Either[L, R].tapRight(R => Unit): Either[L, R] 44 | + GTL[A].seqFold(B)((B, A) => Option[B]): Option[B] 45 | + GTL[A].seqMap[To](A => Option[B]): Option[To] 46 | + GTL[A].asMap.withUniqueKeys(A => K): Option[Map[K, A]] 47 | + GTL[A].asMultiMap[F[_]].withUniqueKeys(A => K): Option[MultiMap[F, K, A]] 48 | + List[A].calcIfNonEmpty(List[A] => B): Option[B] 49 | + List[A].duplicates: List[A] 50 | + List[A].mapIfNonEmpty(A => B): Option[List[B]] 51 | + List[A].unsnocC(=> B, List[A] => A => B): B 52 | + List[A].onlyOption: Option[A] 53 | + List[List[A]].cartesianProduct: List[List[A]] 54 | + Array[A].copyTo(srcPos, Array[A], destPos, length): Array[A] 55 | + Map[K, V].calcIfNonEmpty(Map[K, V] => B): Option[B] 56 | + MultiMap[F, K, V].flatMapValues(V => F[W]): MultiMap[F, K, W] 57 | + MultiMap[F, K, V].flatMapValuesU(V => G[W]): MultiMap[G, K, W] 58 | + MultiMap[F, K, V].multiMap.mapEntriesU(K => F[V] => (C, G[W]): MultiMap[G, C, W] 59 | + MultiMap[F, K, V].multiMap.sliding(Int): F[MultiMap[F, K, V]] 60 | + MultiMap[F, K, V].getOrEmpty(K): F[V] 61 | + MultiMap[F, K, V].onlyOption: Option[Map[K, V]] 62 | + NestedMap[K1, K2, V].append(K1, K2, V): NestedMap[K1, K2, V] 63 | + NestedMap[K1, K2, V] + ((K1, K2, V)): NestedMap[K1, K2, V] 64 | + NestedMap[K1, K2, V].flipNesting: NestedMap[K2, K1, V] 65 | + NestedMap[K1, K2, V].getOrEmpty(K1): Map[K2, V] 66 | + NestedMap[K1, K2, V].nestedMap.mapValuesEagerly(V => W): NestedMap[K1, K2, W] 67 | + NestedMap[K1, K2, V].nestedMap.mapKeysEagerly(K2 => C): NestedMap[K1, C, W] 68 | + function.and(Predicate[A]*): Predicate[A] 69 | + function.or(Predicate[A]*): Predicate[A] 70 | + function.nand(Predicate[A]*): Predicate[A] 71 | + function.nor(Predicate[A]*): Predicate[A] 72 | + String.emptyTo(String): String 73 | + FileUtils append constructor parameter (specifies the default value of append method parameter) 74 | + File.ancestors: Stream[File] 75 | + File.isAncestorOf(File): Boolean 76 | + pimpathon.java.io forwarding package object 77 | + InputStream.gunzip: GZIPInputStream 78 | + OutputStream.gzip: GZIPOutputStream 79 | + implicit conversion from () => A to Callable[A] 80 | + callable.create(=> A): Callable[A] 81 | + implicit conversion from () => Discarded to Runnable 82 | + runnable.create(=> Unit): Runnable 83 | + classTag.className[A]: String 84 | + classTag.simpleClassName[A]: String 85 | + classTag.klassOf[A]: String 86 | + mutable.Builder[A, B].on(C => A): mutable.Builder[C, B] 87 | + mutable.Builder[A, B].reset(): B 88 | + mutable.Builder[A, B].run((M.Builder[A, B] => Discarded)*): B 89 | + mutable.Map[K, V].retainKeys(Predicate[K]): mutable.Map[K, V] 90 | + mutable.Map[K, V].retainValues(Predicate[V]): mutable.Map[K, V] 91 | + argonaut.Json.filterNulls: Json 92 | + scalaz.NonEmptyList[A].distinct: NonEmptyList[A] 93 | + NonEmptyList[V].asMap.withKeys(V => K): Map[K, V] 94 | + NonEmptyList[V].asMultiMap[CC[_]].with*(V => K): MultiMap[CC, K, V] 95 | + NonEmptyList[K].as[F[_. _]].with*(K => V): F[K, V] 96 | + NonEmptyList[A].distinctBy(A => B): NonEmptyList[A] 97 | + NonEmptyList[A: Order].max: A 98 | + NonEmptyList[A: Order].min: A 99 | -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.5.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.5.0-Snapshot 2 | 3 | ### Breaking changes & bug fixes 4 | + Moved runnable from pimpathon.java.lang to pimpathon package 5 | 6 | ### Removals 7 | 8 | ### Additions 9 | + [A].ensure(=> E)(Predicate[A]): Validation[E, A] 10 | + [A].ensureNel(=> E)(Predicate[A]): ValidationNel[E, A] 11 | + [A: Numeric].bounded(A, A): A 12 | + Boolean.cond(=> A, => A): A 13 | + Boolean.implies(Boolean): Boolean 14 | + Boolean.nor(Boolean): Boolean 15 | + Boolean.nand(Boolean): Boolean 16 | + Option[E].toFailureNel(A): ValidationNel[E, A] 17 | + Either[Throwable, R].getMessage: Option[String] 18 | + Try[A].getMessage: Option[String] 19 | + List[A].onlyOrThrow(List[A] => Exception): A 20 | + List[A].onlyEither: Either[List[A], A] 21 | + List[A].toNel: Option[NonEmptyList[A]] 22 | + List[A].zipExact(List[B]): (List[(A, B)], Option[Either[List[A], List[B]]]) 23 | + List[A].zipExactWith(List[B])((A, B) ⇒ C): (List[C], Option[Either[List[A], List[B]]]) 24 | + GTL[A].apoFold(B)((B, A) ⇒ Either[C, B]): Either[C, B] 25 | + Set[A].notContains(A): Boolean 26 | + Map[K, V].collectKeys(PartialFunction[K, C]): Map[C, V] 27 | + Map[K, V].collectValues(PartialFunction[V, W]): Map[K, W] 28 | + Map[K, V].containsEntry(K, V): Boolean 29 | + Map[K, V].containsEntry((K, V)): Boolean 30 | + Predicate[A].cond(=> B, => B): A => B 31 | + (A => B).attempt: A => Try[B] 32 | + Callable[A].attempt: Callable[Try[A]] 33 | + Date.addDay(Int): Date 34 | + InputStream.toByteArray: Array[Byte] 35 | + File.canon: File 36 | + File.readString()(implicit Codec): String 37 | + File.source()(implicit Codec): BufferedSource -- Added implicit Codec argument 38 | + File.readLines()(implicit Codec): List[String] -- Added implicit Codec argument 39 | + Throwable.stackTraceAsString: String -------------------------------------------------------------------------------- /docs/ReleaseNotes-1.6.0.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 1.6.0 2 | 3 | ### Breaking changes & bug fixes 4 | 5 | ### Removals 6 | 7 | ### Additions 8 | + [A].isOneOf(A*): Boolean 9 | + [A].isNotOneOf(A*): Boolean 10 | + [A].containedIn(Set[A]): Boolean 11 | + [A].notContainedIn(Set[A]): Boolean 12 | + Option[A].amass(PartialFunction[A, Option[B]]): Option[B] 13 | + Try[A].fold(Throwable => B, A => B): Option[String] 14 | + Try[A].toDisjunction: Throwable \/ A 15 | + List[A].initOption: Option[List[A]] 16 | + List[A].onlyDisjunction: List[A] \/ A 17 | + List[A].sortPromoting(A*): List[A] 18 | + List[A].sortDemoting(A*): List[A] 19 | + Stream[A].unconsC(=> B, A => (=> Stream[A]) => B): B 20 | + Stream[A].lazyScanLeft(B)((B, A) => B): Stream[B] 21 | + Stream[A].reverseInits: Stream[Stream[A]] 22 | + GTL[A].asMap.withEntries(A => K, A => V): Map[K, V] 23 | + GTL[A].asMap.withEntries(A => K1, A => K2, A => V): Map[K1, Map[K2, V]] 24 | + GTL[A].asMap.withEntries(A => K1, A => K2, A => K3, A => V): Map[K1, Map[K2, Map[K3, V]]] 25 | + GTL[A].asMap.withEntries(A => K1, A => K2, A => K3, A => K4, A => V): Map[K1, Map[K2, Map[K3, Map[K4, V]]] 26 | + GTL[A].asMap.withSomeEntries(A => Option[(K, V)]): Map[K, V] 27 | + GTL[A].asMap.withSomeEntries(A => Option[K], A => Option[V]): Map[K, V] 28 | + GTL[A].asMap.withPFEntries(PartialFunction[A, (K, V)]): Map[K, V] 29 | + GTL[A].asMap.withPFEntries(PartialFunction[A, K], PartialFunction[A, V]): Map[K, V] 30 | + GTL[A].asMultiMap.withEntries(A => K, A => V): MultiMap[GTL, K, V] 31 | + GTL[A].asMultiMap.withEntries(A => K1, A => K2, A => V): Map[K1, MultiMap[GTL, K2, V]] 32 | + GTL[A].asMultiMap.withEntries(A => K1, A => K2, A => K3, A => V): Map[K1, Map[K2, MultiMap[GTL, K3, V]]] 33 | + GTL[A].asMultiMap.withEntries(A => K1, A => K2, A => K3, A => K4, A => V): Map[K1, Map[K2, Map[K3, MultiMap[GTL, K4, V]]] 34 | + GTL[A].asMultiMap.withSomeEntries(A => Option[(K, V)]): MultiMap[GTL, K, V] 35 | + GTL[A].asMultiMap.withSomeEntries(A => Option[K], A => Option[V]): MultiMap[GTL, K, V] 36 | + GTL[A].asMultiMap.withPFEntries(PartialFunction[A, (K, V)]): MultiMap[GTL, K, V] 37 | + GTL[A].asMultiMap.withPFEntries(PartialFunction[A, K] PartialFunction[A, V]): MultiMap[GTL, K, V] 38 | + GTL[A].all(A): Boolean 39 | + GTL[A].none(A): Boolean 40 | + GTL[L \/ R].partitionDisjunctions: (GTL[L], GTL[R]) 41 | + file.resource(String): Option[File] 42 | + (L \/ R).tap(L => Discarded, R => Discarded): L \/ R 43 | + (L \/ R).tapLeft(L => Discarded): L \/ R 44 | + (L \/ R).tapRight(R => Discarded): L \/ R 45 | + (L \/ R).addTo(Growable[L], Growable[R]): L \/ R 46 | + (L \/ R).removeFrom(Shrinkable[L], Shrinkable[R]): L \/ R 47 | + (L \/ (L \/ R)).flatten: L \/ R 48 | + ((L \/ R) \/ R).flatten: L \/ R 49 | + PartialFunction[A, B].map(B => C): PartialFunction[A, C] 50 | + PartialFunction[A, B].contramap(C => A): PartialFunction[C, B] 51 | + (PartialFunction[A, B] &&& PartialFunction[A, C]): PartialFunction[A, (B, C)] 52 | + Ordering[A].promote(A*): Ordering[A] 53 | + Ordering[A].demote(A*): Ordering[A] 54 | + String.toByteArray: Array[Byte] 55 | + String.toByteArray(Charset): Array[Byte] 56 | + ThreadFactory.naming(Int => String): ThreadFactory 57 | + argonaut.CodecJson[A].beforeDecode(Json => Json): CodecJson[A] 58 | + argonaut.CodecJson[A].afterDecode(A => A): CodecJson[A] 59 | + argonaut.CodecJson[A].beforeEncode(A => A): CodecJson[A] 60 | + argonaut.CodecJson[A].afterEncode(Json => Json): CodecJson[A] 61 | + argonaut.CodecJson[A].andThen(Json => Json): CodecJson[A] 62 | + argonaut.CodecJson[A].compose(Json => Json): CodecJson[A] 63 | + argonaut.CodecJson[Map[K, V]].xmapKeys(K ⇒ C)(C ⇒ K): CodecJson[Map[C, V]] 64 | + argonaut.CodecJson[Map[K, V]].xmapValues(V => W)(W => V): CodecJson[Map[K, W]] 65 | + argonaut.DecodeJson[A].beforeDecode(Json => Json): DecodeJson[A] 66 | + argonaut.DecodeJson[A].compose(Json => Json): DecodeJson[A] 67 | + argonaut.DecodeJson[A].upcast[B >: A]: DecodeJson[B] 68 | + argonaut.DecodeJson[Map[K, V]].mapKeys(K => C): DecodeJson[Map[C, V]] 69 | + argonaut.DecodeJson[Map[K, V]].mapValues(V => W): DecodeJson[Map[K, W]] 70 | + argonaut.EncodeJson[A].afterEncode(Json => Json): EncodeJson[A] 71 | + argonaut.EncodeJson[A].andThen(Json => Json): EncodeJson[A] 72 | + argonaut.EncodeJson[A].downcast[B <: A]: EncodeJson[B] 73 | + argonaut.EncodeJson[Map[K, V]].contramapKeys(C => K): EncodeJson[Map[C, V]] 74 | + argonaut.EncodeJson[Map[K, V]].contramapValues(W => V): EncodeJson[Map[K, W]] 75 | -------------------------------------------------------------------------------- /docs/main.js: -------------------------------------------------------------------------------- 1 | function toArray(xs) { 2 | return Array.prototype.slice.call(xs); 3 | } 4 | 5 | function filterTocType(category) { 6 | nameFilter.value = category; 7 | filterToc(); 8 | } 9 | 10 | function filterToc() { 11 | var f = filterElement.bind(null, nameFilter.value); 12 | funcs.forEach(f); 13 | } 14 | 15 | function filterElement(nameFilter, elem) { 16 | var name = elem.getAttribute('data-name'); 17 | var category = elem.getAttribute('data-category'); 18 | var matches = strIn(nameFilter, name) || nameFilter.toLowerCase() === category.toLowerCase(); 19 | elem.style.display = matches ? '' : 'none'; 20 | } 21 | 22 | function findFirst(funcs) { 23 | var found; 24 | funcs.forEach(function(fu) { 25 | if (!found && fu.offsetParent) found = fu; 26 | }); 27 | 28 | return found; 29 | } 30 | 31 | function gotoFirst(e) { 32 | if (!e.detail) { 33 | return; 34 | } 35 | 36 | var func = findFirst(funcs); 37 | if (func) { 38 | console.log(func); 39 | 40 | var onHashChange = function() { 41 | e.target.focus(); 42 | window.removeEventListener('hashchange', onHashChange); 43 | }; 44 | 45 | // Hash change blurs input, put focus back to input 46 | window.addEventListener('hashchange', onHashChange); 47 | window.location.hash = func.getAttribute('data-name'); 48 | } 49 | } 50 | 51 | function strIn(a, b) { 52 | a = a.toLowerCase(); 53 | b = b.toLowerCase(); 54 | return b.indexOf(a) >= 0; 55 | } 56 | 57 | function scrollToTop() { 58 | var main = document.querySelector('main'); 59 | main.scrollTop = 0; 60 | } 61 | 62 | function tryToggleCollapse(elem) { 63 | var sel = elem && elem.getAttribute('data-collapser'); 64 | if (!sel) { return; } 65 | elem 66 | .classList 67 | .toggle('open'); 68 | document 69 | .querySelector(sel) 70 | .classList 71 | .toggle('in'); 72 | } 73 | 74 | function isTopLink(elem) { 75 | return elem.getAttribute('href') === '#'; 76 | } 77 | 78 | function isAnchorLink(elem) { 79 | return elem.tagName === 'A' && elem.getAttribute('href').charAt(0) === '#'; 80 | } 81 | 82 | function closeNav() { 83 | document.getElementById('open-nav').checked = false; 84 | } 85 | 86 | function dispatchEvent(event) { 87 | var target = event.target; 88 | var parent = target.parentNode; 89 | var category = target.getAttribute('data-category'); 90 | 91 | if (isAnchorLink(target)) { 92 | closeNav(); 93 | } 94 | if (category) { 95 | filterTocType(category); 96 | } 97 | if (isTopLink(target)) { 98 | scrollToTop(target); 99 | } else { 100 | tryToggleCollapse(target); 101 | tryToggleCollapse(parent); 102 | } 103 | } 104 | 105 | function keypress(e) { 106 | if (e.which == 13) { 107 | e.target.dispatchEvent(new window.CustomEvent('enter', { 108 | detail: e.target.value 109 | })); 110 | } 111 | } 112 | 113 | var nameFilter = document.getElementById('name-filter'); 114 | var funcs = toArray(document.querySelectorAll('.toc .func')); 115 | filterToc(); 116 | 117 | document.body.addEventListener('click', dispatchEvent, false); 118 | nameFilter.addEventListener('input', filterToc, false); 119 | nameFilter.addEventListener('keypress', keypress, false); 120 | nameFilter.addEventListener('enter', gotoFirst); 121 | 122 | var currentFocus = window.location.hash; 123 | window.location.hash = ""; 124 | window.location.hash = currentFocus; -------------------------------------------------------------------------------- /project/.gitignore: -------------------------------------------------------------------------------- 1 | project 2 | target 3 | -------------------------------------------------------------------------------- /project/CiPublishPlugin.scala: -------------------------------------------------------------------------------- 1 | import com.jsuereth.sbtpgp.SbtPgp 2 | import com.typesafe.sbt.GitPlugin 3 | import sbt.Keys._ 4 | import sbt.{Def, _} 5 | import sbt.plugins.JvmPlugin 6 | import xerial.sbt.Sonatype 7 | import xerial.sbt.Sonatype.autoImport._ 8 | 9 | import scala.sys.process._ 10 | import scala.util.control.NonFatal 11 | 12 | object CiPublishPlugin extends AutoPlugin { 13 | 14 | override def trigger: PluginTrigger = allRequirements 15 | override def requires: Plugins = JvmPlugin && SbtPgp && GitPlugin && Sonatype 16 | 17 | def isSecure: Boolean = 18 | System.getenv("PGP_SECRET") != null 19 | 20 | def setupGpg(): Unit = { 21 | val versionLine = List("gpg", "--version").!!.linesIterator.toList.head 22 | 23 | println(versionLine) 24 | 25 | val TaggedVersion = """(\d{1,14})([\.\d{1,14}]*)((?:-\w+)*)""".r 26 | 27 | val gpgVersion: Long = versionLine.split(" ").last match { 28 | case TaggedVersion(m, _, _) ⇒ m.toLong 29 | case _ ⇒ 0L 30 | } 31 | 32 | // https://dev.gnupg.org/T2313 33 | val importCommand = if (gpgVersion < 2L) "--import" else "--batch --import" 34 | 35 | val secret = sys.env("PGP_SECRET") 36 | 37 | (s"echo $secret" #| "base64 --decode" #| s"gpg $importCommand").! 38 | } 39 | 40 | private def gitHubScmInfo(user: String, repo: String) = 41 | ScmInfo( 42 | url(s"https://github.com/$user/$repo"), 43 | s"scm:git:https://github.com/$user/$repo.git", 44 | Some(s"scm:git:git@github.com:$user/$repo.git") 45 | ) 46 | 47 | override lazy val buildSettings: Seq[Def.Setting[_]] = List( 48 | scmInfo ~= { 49 | case Some(info) ⇒ Some(info) 50 | case None ⇒ { 51 | import scala.sys.process._ 52 | val identifier = """([^\/]+?)""" 53 | val GitHubHttps = s"https://github.com/$identifier/$identifier(?:\\.git)?".r 54 | val GitHubGit = s"git://github.com:$identifier/$identifier(?:\\.git)?".r 55 | val GitHubSsh = s"git@github.com:$identifier/$identifier(?:\\.git)?".r 56 | try { 57 | val remote = List("git", "ls-remote", "--get-url", "origin").!!.trim() 58 | remote match { 59 | case GitHubHttps(user, repo) ⇒ Some(gitHubScmInfo(user, repo)) 60 | case GitHubGit(user, repo) ⇒ Some(gitHubScmInfo(user, repo)) 61 | case GitHubSsh(user, repo) ⇒ Some(gitHubScmInfo(user, repo)) 62 | case _ ⇒ None 63 | } 64 | } catch { 65 | case NonFatal(_) ⇒ None 66 | } 67 | } 68 | } 69 | ) 70 | 71 | override lazy val globalSettings: Seq[Def.Setting[_]] = List( 72 | publishArtifact.in(Test) := false, 73 | publishMavenStyle := true, 74 | commands += Command.command("ci-publish")(currentState ⇒ { 75 | if (!isSecure) { 76 | println("No access to secret variables, skipping publish") 77 | currentState 78 | } else { 79 | println("Running ci-publish") 80 | setupGpg() 81 | // https://github.com/olafurpg/sbt-ci-release/issues/64 82 | 83 | "set pgpSecretRing := pgpSecretRing.value" :: 84 | "set pgpPublicRing := pgpPublicRing.value" :: 85 | "+publishSigned" :: 86 | "sonatypeBundleRelease" :: 87 | currentState 88 | } 89 | }) 90 | ) 91 | 92 | override lazy val projectSettings: Seq[Def.Setting[_]] = List( 93 | publishConfiguration := publishConfiguration.value.withOverwrite(true), 94 | publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true), 95 | publishTo := sonatypePublishToBundle.value 96 | ) 97 | } -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.9 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value 2 | 3 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") 4 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0") 5 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.7") 6 | addSbtPlugin("com.github.stacycurl" % "sbt-tcr" % "1.0.1") 7 | 8 | scalacOptions += "-deprecation" 9 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/FilterMonadic.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.{higherKinds, implicitConversions} 4 | 5 | import scala.collection.breakOut 6 | import scala.collection.generic.FilterMonadic 7 | import scala.collection.immutable.{Map ⇒ ▶:} 8 | 9 | import pimpathon.multiMap._ 10 | 11 | 12 | object filterMonadic extends filterMonadic 13 | 14 | trait filterMonadic { 15 | implicit def filterMonadicTuple2Pimps[K, V, Repr](fm: FilterMonadic[(K, V), Repr]) 16 | : FilterMonadicTuple2Pimps[K, V, Repr] = new FilterMonadicTuple2Pimps[K, V, Repr](fm) 17 | 18 | class FilterMonadicTuple2Pimps[K, V, Repr](fm: FilterMonadic[(K, V), Repr]) { 19 | def toMultiMap[F[_]](implicit fcbf: CCBF[V, F]): K ▶: F[V] = fm.map(kv ⇒ kv)(breakOut) 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/any.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.{PartialFunction => ~>} 4 | import scala.collection.generic.{Growable, Shrinkable} 5 | import scala.util.Try 6 | import pimpathon.boolean._ 7 | import pimpathon.function._ 8 | 9 | import scala.reflect.ClassTag 10 | 11 | 12 | object any { 13 | implicit class AnyPimps[A](val self: A) extends AnyVal { 14 | def calc[B](f: A ⇒ B): B = f(self) 15 | def |>[B](f: A ⇒ B): B = f(self) 16 | def calcIf[B](p: Predicate[A])(f: A ⇒ B): Option[B] = p(self).option(f(self)) 17 | def calcUnless[B](p: Predicate[A])(f: A ⇒ B): Option[B] = (!p(self)).option(f(self)) 18 | def calcPF[B](pf: A ~> B): Option[B] = pf.lift(self) 19 | def transform(pf: A ~> A): A = pf.unify(self) 20 | def transformIf(condition: Boolean)(f: A ⇒ A): A = if (condition) f(self) else self 21 | 22 | def tapIf[Discarded](p: Predicate[A])(actions: (A ⇒ Discarded)*): A = if (p(self)) tap(actions: _*) else self 23 | def tapUnless[Discarded](p: Predicate[A])(actions: (A ⇒ Discarded)*): A = if (p(self)) self else tap(actions: _*) 24 | 25 | def tapPF[Discarded](action: A ~> Discarded): A = { action.lift(self); self } 26 | 27 | def castTo[B](implicit tag: ClassTag[B]): Option[B] = 28 | if (tag.runtimeClass.isAssignableFrom(self.getClass)) Some(self.asInstanceOf[B]) else None 29 | 30 | def attempt[B](f: A ⇒ B): Try[B] = Try(f(self)) 31 | 32 | def partialMatch[B](pf: A ~> B): Option[B] = PartialFunction.condOpt(self)(pf) 33 | 34 | def lpair[B](f: A ⇒ B): (B, A) = (f(self), self) 35 | def rpair[B](f: A ⇒ B): (A, B) = (self, f(self)) 36 | 37 | def filterSelf(p: Predicate[A]): Option[A] = p(self).option(self) 38 | def ifSelf(p: Predicate[A]): Option[A] = p(self).option(self) 39 | 40 | def filterNotSelf(p: Predicate[A]): Option[A] = (!p(self)).option(self) 41 | def unlessSelf(p: Predicate[A]): Option[A] = (!p(self)).option(self) 42 | 43 | def isOneOf(as: A*): Boolean = as.contains(self) 44 | def isNotOneOf(as: A*): Boolean = !as.contains(self) 45 | 46 | def containedIn(s: Set[A]): Boolean = s.contains(self) 47 | def notContainedIn(s: Set[A]): Boolean = !s.contains(self) 48 | 49 | def passes: AnyCapturer[A] = new AnyCapturer[A](self, b ⇒ b.option(self)) 50 | def fails: AnyCapturer[A] = new AnyCapturer[A](self, b ⇒ (!b).option(self)) 51 | 52 | def withFinally[B](f: A ⇒ Unit)(t: A ⇒ B): B = try t(self) finally f(self) 53 | def tryFinally[B](t: A ⇒ B)(f: A ⇒ Unit): B = try t(self) finally f(self) 54 | 55 | def cond[B](p: Predicate[A], ifTrue: A ⇒ B, ifFalse: A ⇒ B): B = if (p(self)) ifTrue(self) else ifFalse(self) 56 | 57 | def addTo(as: Growable[A]): A = tap(as += _) 58 | def removeFrom(as: Shrinkable[A]): A = tap(as -= _) 59 | 60 | def unfold[B](f: A ⇒ Option[(B, A)]): Stream[B] = f(self).fold(Stream.empty[B])(ba ⇒ ba._1 #:: ba._2.unfold(f)) 61 | 62 | def tap[Discarded](actions: (A ⇒ Discarded)*): A = { actions.foreach(action ⇒ action(self)); self } 63 | 64 | def bounded(lower: A, upper: A)(implicit na: Numeric[A]): A = na.min(na.max(lower, self), upper) 65 | 66 | def indent: String = self.toString.split("\n").map(" " + _).mkString("\n") 67 | } 68 | 69 | class AnyCapturer[A](a: A, andThen: Boolean ⇒ Option[A]) { 70 | def one(disjuncts: Predicate[A]*): Option[A] = andThen(function.or(disjuncts: _*).apply(a)) 71 | def all(conjuncts: Predicate[A]*): Option[A] = andThen(function.and(conjuncts: _*).apply(a)) 72 | def none(conjuncts: Predicate[A]*): Option[A] = andThen(function.nand(conjuncts: _*).apply(a)) 73 | def some(disjuncts: Predicate[A]*): Option[A] = andThen(function.nor(disjuncts: _*).apply(a)) 74 | } 75 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/array.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.io.{InputStream, OutputStream} 4 | import _root_.java.math.BigInteger 5 | import _root_.java.nio.charset.Charset 6 | 7 | import pimpathon.any._ 8 | import pimpathon.string._ 9 | 10 | 11 | object array { 12 | implicit class ArrayPimps[A](val self: Array[A]) extends AnyVal { 13 | def copyTo(srcPos: Int, dest: Array[A], destPos: Int, length: Int): Array[A] = 14 | dest.tap(_ ⇒ System.arraycopy(self, srcPos, dest, destPos, length)) 15 | } 16 | 17 | implicit class ByteArrayPimps(val self: Array[Byte]) extends AnyVal { 18 | def toHex(length: Int): String = toHex.prefixPadTo(length, '0') 19 | def toHex: String = new BigInteger(1, self).toString(16) 20 | 21 | def copyUpToN(n: Long, is: InputStream, os: OutputStream): Int = 22 | readUpToN(n, is).tapUnless(_ == -1)(os.write(self, 0, _)) 23 | 24 | def readUpToN(n: Long, is: InputStream): Int = 25 | if (n == 0) -1 else is.read(self, 0, math.min(n, self.length).toInt) 26 | 27 | def asString: String = new String(self, Charset.forName("UTF-8")) 28 | def asString(charset: Charset): String = new String(self, charset) 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/boolean.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | 4 | object boolean { 5 | implicit class BooleanPimps(val self: Boolean) extends AnyVal { 6 | def asInt: Int = if (self) 1 else 0 7 | def either[R](right: R): EitherCapturer[R] = new EitherCapturer[R](self, right) 8 | def option[A](a: ⇒ A): Option[A] = if (self) Some(a) else None 9 | def implies(rhs: Boolean): Boolean = !self || rhs 10 | def nor(rhs: Boolean): Boolean = !(self || rhs) 11 | def nand(rhs: Boolean): Boolean = !(self && rhs) 12 | def cond[A](ifTrue: ⇒ A, ifFalse: ⇒ A): A = if (self) ifTrue else ifFalse 13 | 14 | def tapFalse[Discarded](ifFalse: ⇒ Discarded): Boolean = { 15 | if (!self) ifFalse 16 | self 17 | } 18 | 19 | def tapTrue[Discarded](ifTrue: ⇒ Discarded): Boolean = { 20 | if (self) ifTrue 21 | self 22 | } 23 | } 24 | 25 | class EitherCapturer[R](value: Boolean, right: R) { 26 | def or[L](left: ⇒ L): Either[L, R] = if (value) Right(right) else Left(left) 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/builder.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.collection.generic.CanBuildFrom 4 | import scala.collection.{mutable ⇒ M} 5 | 6 | import pimpathon.any._ 7 | 8 | 9 | object builder { 10 | implicit class CanBuildFromPimps[Elem, To](private val self: CanBuildFrom[Nothing, Elem, To]) { 11 | def mapResult[X](f: To ⇒ X): CanBuildFrom[Nothing, Elem, X] = new CanBuildFrom[Nothing, Elem, X] { 12 | def apply(from: Nothing): M.Builder[Elem, X] = self.apply(from).mapResult(f) 13 | def apply(): M.Builder[Elem, X] = self.apply().mapResult(f) 14 | } 15 | 16 | def on[C](f: C ⇒ Elem): CanBuildFrom[Nothing, C, To] = new CanBuildFrom[Nothing, C, To] { 17 | def apply(from: Nothing): M.Builder[C, To] = self.apply(from).on(f) 18 | def apply(): M.Builder[C, To] = self.apply().on(f) 19 | } 20 | } 21 | 22 | implicit class BuilderPimps[A, B](val self: M.Builder[A, B]) extends AnyVal { 23 | def +++=(xss: TraversableOnce[TraversableOnce[A]]): M.Builder[A, B] = self.tap(b ⇒ xss.foreach(b ++= _)) 24 | def on[C](f: C ⇒ A): M.Builder[C, B] = new ContramappedBuilder(self, f) 25 | 26 | def run[Discarded](actions: (M.Builder[A, B] ⇒ Discarded)*): B = self.tap(actions: _*).reset() 27 | def reset(): B = self.result().tap(_ ⇒ self.clear()) 28 | } 29 | 30 | private class ContramappedBuilder[A, B, C](builder: M.Builder[A, B], f: C ⇒ A) extends M.Builder[C, B] { 31 | def +=(elem: C): this.type = { builder += f(elem); this } 32 | def result(): B = builder.result() 33 | def clear(): Unit = builder.clear() 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/classtag.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.reflect.ClassTag 4 | 5 | 6 | object classTag { 7 | def className[A: ClassTag]: String = klassOf[A].getName 8 | def simpleClassName[A: ClassTag]: String = klassOf[A].getSimpleName 9 | def klassOf[A](implicit tag: ClassTag[A]): Class[A] = tag.runtimeClass.asInstanceOf[Class[A]] 10 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/dynamicVariable.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.DynamicVariable 4 | 5 | object dynamicVariable { 6 | implicit class DynamicVariablePimps[A](val self: DynamicVariable[A]) extends AnyVal { 7 | def modify(f: A ⇒ A): DynamicVariable[A] = { 8 | self.value = f(self.value) 9 | self 10 | } 11 | 12 | def withModification[B](f: A ⇒ A)(thunk: ⇒ B): B = { 13 | self.withValue(f(self.value))(thunk) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/either.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.implicitConversions 4 | 5 | import scala.collection.generic.{Growable, Shrinkable} 6 | import scala.util.{Failure, Success, Try} 7 | import scala.{PartialFunction => ~>} 8 | 9 | import pimpathon.function.PartialFunctionPimps 10 | 11 | 12 | object either { 13 | implicit class EitherPimps[L, R](val self: Either[L, R]) extends AnyVal { 14 | def leftMap[M](f: L ⇒ M): Either[M, R] = bimap[M, R](f, identity[R]) 15 | def rightMap[S](f: R ⇒ S): Either[L, S] = bimap[L, S](identity[L], f) 16 | 17 | def valueOr(lr: L ⇒ R): R = rightOr(lr) 18 | def valueOr(pf: L ~> R): Either[L, R] = rescue(pf) 19 | 20 | def rescue(lr: L ⇒ R): R = rightOr(lr) 21 | def rescue(pf: L ~> R): Either[L, R] = leftFlatMap(pf.either) 22 | 23 | def leftOr(rl: R ⇒ L): L = self.fold(identity, rl) 24 | def rightOr(lr: L ⇒ R): R = self.fold(lr, identity) 25 | 26 | def bimap[M, S](lf: L ⇒ M, rf: R ⇒ S): Either[M, S] = self.fold(l ⇒ Left[M, S](lf(l)), r ⇒ Right[M, S](rf(r))) 27 | 28 | def leftFlatMap(f: L ⇒ Either[L, R]): Either[L, R] = self.fold(f, Right(_)) 29 | def rightFlatMap(f: R ⇒ Either[L, R]): Either[L, R] = self.fold(Left(_), f) 30 | 31 | def addTo(ls: Growable[L], rs: Growable[R]): Either[L, R] = tap(ls += _, rs += _) 32 | def removeFrom(ls: Shrinkable[L], rs: Shrinkable[R]): Either[L, R] = tap(ls -= _, rs -= _) 33 | 34 | def tapLeft[Discarded](l: L ⇒ Discarded): Either[L, R] = tap(l, _ ⇒ {}) 35 | def tapRight[Discarded](r: R ⇒ Discarded): Either[L, R] = tap(_ ⇒ {}, r) 36 | def tap[Discarded](l: L ⇒ Discarded, r: R ⇒ Discarded): Either[L, R] = { self.fold(l, r); self } 37 | 38 | def getMessage(implicit ev: L <:< Throwable): Option[String] = self.fold(t ⇒ Some(t.getMessage), _ ⇒ None) 39 | def toTry(implicit ev: L <:< Throwable): Try[R] = self.fold(Failure(_), Success(_)) 40 | def toOption: Option[R] = self.fold(_ ⇒ None, Some(_)) 41 | } 42 | 43 | implicit class EitherPimpsNestedL[L, R](val self: Either[Either[L, R], R]) { 44 | def flatten: Either[L, R] = self.fold(identity, Right(_)) 45 | } 46 | 47 | implicit class EitherFrillsNestedR[L, R](val self: Either[L, Either[L, R]]) { 48 | def flatten: Either[L, R] = self.fold(Left(_), identity) 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/file.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.io.{RandomAccessFile, File, FileOutputStream} 4 | import scala.io.{Codec, BufferedSource, Source} 5 | import scala.util.Properties 6 | 7 | import pimpathon.any._ 8 | import pimpathon.java.io.outputStream._ 9 | import pimpathon.list._ 10 | import pimpathon.string._ 11 | 12 | 13 | object file extends FileUtils() 14 | 15 | case class FileUtils ( 16 | suffix: String = ".tmp", prefix: String = "temp", append: Boolean = false, 17 | private val currentTime: () ⇒ Long = () ⇒ System.currentTimeMillis() 18 | ) { 19 | 20 | implicit class FilePimps(private val self: File) { 21 | require(Option(self).isDefined, "FileOps cannot be used with null files") 22 | 23 | def missing: Boolean = !self.exists 24 | def isScala: Boolean = hasExtension("scala") 25 | def isJava: Boolean = hasExtension("java") 26 | def isClass: Boolean = hasExtension("class") 27 | def isJar: Boolean = hasExtension("jar") 28 | def hasExtension(extension: String): Boolean = self.getName.endsWith(extension) 29 | def isChildOf(other: File): Boolean = other.isParentOf(self) 30 | def isParentOf(other: File): Boolean = other.getParentFile.equals(self) 31 | def isContainedIn(other: File): Boolean = other.contains(self) 32 | def contains(other: File): Boolean = isAncestorOf(other) 33 | def isAncestorOf(other: File): Boolean = other.ancestors.contains(self) 34 | 35 | def /(name: String): File = new File(self, name) 36 | def named(name: String = self.getName): File = new NamedFile(self, name) 37 | def canon: File = self.attempt(_.getCanonicalFile).getOrElse(self.getAbsoluteFile) 38 | 39 | def relativeTo(dir: File): File = sharedPaths(dir) |> { case (relativeFile, relativeDir) ⇒ 40 | new File((relativeDir.const("..") ++ relativeFile).mkString(File.separator)) 41 | } 42 | 43 | def changeToDirectory(): File = self.tapIf(_.isFile)(_.delete(), _.mkdir()) 44 | 45 | def create(directory: Boolean = false): File = 46 | self.tap(_.getParentFile.mkdirs(), f ⇒ if (directory) f.mkdir() else f.createNewFile()) 47 | 48 | def deleteRecursively(): File = self.tap(_.tree.reverse.foreach(_.delete())) 49 | def deleteRecursivelyOnExit(): File = self.tap(f ⇒ Runtime.getRuntime.addShutdownHook(DeleteRecursively(f))) 50 | 51 | def touch(): File = create().tap(_.setLastModified(currentTime())) 52 | 53 | def tree: Stream[File] = stream.cond(self.exists, self #:: children.flatMap(_.tree)) 54 | def children: Stream[File] = stream.cond(self.isDirectory && self.canRead, self.listFiles.toStream) 55 | def childDirs: Stream[File] = children.filter(_.isDirectory) 56 | 57 | def ancestors: Stream[File] = Stream.iterate(self)(_.getParentFile).takeWhile(_ != null) 58 | 59 | def path: List[String] = self.getAbsolutePath.split(separator).toList.filterNot(Set("", ".")) 60 | 61 | def md5(): String = readLines().mkString("\n").md5 62 | 63 | def readString()(implicit codec: Codec): String = new String(readBytes(), codec.charSet) 64 | 65 | def readBytes(): Array[Byte] = new RandomAccessFile(self, "r").withFinally(_.close())(raf ⇒ { 66 | new Array[Byte](raf.length().toInt).tap(raf.read) 67 | }) 68 | 69 | def readLines()(implicit codec: Codec): List[String] = source().withFinally(_.close())(_.getLines().toList) 70 | 71 | def write(contents: String, append: Boolean = append): File = 72 | writeString(contents, append) 73 | 74 | def writeString(contents: String, append: Boolean = append): File = 75 | writeBytes(contents.getBytes, append) 76 | 77 | def prependLines(lines: List[String]): File = 78 | prependBytes(linesToBytes(lines)) 79 | 80 | def prependBytes(bytes: Array[Byte]): File = 81 | writeBytes(bytes ++ readBytes(), append = false) 82 | 83 | def writeLines(lines: List[String], append: Boolean = append): File = 84 | writeBytes(linesToBytes(lines), append) 85 | 86 | def writeBytes(bytes: Array[Byte], append: Boolean = append): File = 87 | self.tap(_.outputStream(append).closeAfter(_.write(bytes))) 88 | 89 | def outputStream(append: Boolean = append): FileOutputStream = new FileOutputStream(self, append) 90 | def source()(implicit codec: Codec): BufferedSource = Source.fromFile(self) 91 | 92 | def className(classDir: File): String = sharedPaths(classDir)._1.mkString(".").stripSuffix(".class") 93 | 94 | private def separator: String = File.separator.replace("\\", "\\\\") 95 | private def sharedPaths(other: File) = self.path.sharedPrefix(other.path) |> (t ⇒ (t._2, t._3)) 96 | private def linesToBytes(lines: List[String]): Array[Byte] = (lines.mkString("\n") + "\n").getBytes 97 | } 98 | 99 | def cwd: File = file(Properties.userDir) 100 | def file(name: String): File = new File(name) 101 | def file(parent: String, name: String): File = new File(parent, name) 102 | def file(parent: File, name: String): File = new File(parent, name) 103 | def files(parent: File, names: String*): Stream[File] = names.toStream.map(parent / _) 104 | 105 | def resource(name: String): Option[File] = 106 | Option(Thread.currentThread().getContextClassLoader.getResource(name)).map(url ⇒ file(url.getPath)) 107 | 108 | def tempFile(suffix: String = suffix, prefix: String = prefix): File = 109 | File.createTempFile(prefix, suffix).tap(_.deleteOnExit()) 110 | 111 | def tempDir(suffix: String = suffix, prefix: String = prefix): File = 112 | File.createTempFile(prefix, suffix).changeToDirectory().tap(_.deleteRecursivelyOnExit()) 113 | 114 | def withTempDirectory[A](f: File ⇒ A): A = withTempDirectory(suffix)(f) 115 | 116 | def withTempDirectory[A](suffix: String, prefix: String = prefix)(f: File ⇒ A): A = 117 | withTempFile[A](suffix, prefix)(tmp ⇒ f(tmp.changeToDirectory())) 118 | 119 | def withTempFile[A](f: File ⇒ A): A = withTempFile(suffix)(f) 120 | 121 | def withTempFile[A](suffix: String, prefix: String = prefix)(f: File ⇒ A): A = 122 | File.createTempFile(prefix, suffix).calc(file ⇒ try f(file) finally file.deleteRecursively()) 123 | 124 | 125 | class NamedFile(file: File, name: String) extends File(file.getPath) { 126 | override def toString: String = name 127 | } 128 | 129 | case class DeleteRecursively(file: File) extends Thread { 130 | override def run(): Unit = file.deleteRecursively() 131 | } 132 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/GenTraversableLike.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.CCBF 4 | import pimpathon.genTraversableLike.GenTraversableLikePimpsMixin 5 | 6 | import scala.language.{higherKinds, implicitConversions, reflectiveCalls} 7 | import scala.collection.{GenTraversable, GenTraversableLike} 8 | import scalaz.\/ 9 | 10 | import pimpathon.scalaz.either._ 11 | import pimpathon.tuple._ 12 | import scalaz.syntax.std.either._ 13 | import scalaz.syntax.std.option._ 14 | 15 | 16 | object genTraversableLike { 17 | trait GenTraversableLikeFrillsMixin[A, CC[_]] extends GenTraversableLikePimpsMixin[A, CC] { 18 | def onlyOrDisjunction[B](f: CC[A] ⇒ B): B \/ A = onlyOption.toRightDisjunction(f(cc)) 19 | def onlyDisjunction: CC[A] \/ A = onlyEither.toDisjunction 20 | } 21 | 22 | trait GenTraversableLikeOfDisjunctionFrillsMixin[L, R] { 23 | def partitionDisjunctions[That[_]](implicit lcbf: CCBF[L, That], rcbf: CCBF[R, That]): (That[L], That[R]) = 24 | (lcbf.apply(), rcbf.apply()).tap(l ⇒ r ⇒ gtl.foreach(_.addTo(l, r))).tmap(_.result(), _.result()) 25 | 26 | protected def gtl: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] 27 | } 28 | 29 | implicit class GenTraversableLikeOfDisjunctionFrills[L, R]( 30 | self: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] 31 | ) extends GenTraversableLikeOfDisjunctionFrillsMixin[L, R] { 32 | 33 | protected def gtl: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] = self 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/any.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import scala.language.higherKinds 4 | 5 | import argonaut.{CodecJson, Json} 6 | import monocle.Traversal 7 | import pimpathon.Descendant 8 | import pimpathon.argonaut._ 9 | import pimpathon.function._ 10 | import scalaz.{Failure, NonEmptyList, Success, Validation, ValidationNel} 11 | 12 | 13 | object any { 14 | implicit class AnyFrills[A](private val self: A) extends AnyVal { 15 | def ensure[E](e: ⇒ E)(p: Predicate[A]): Validation[E, A] = if (p(self)) Success(self) else Failure(e) 16 | def ensureNel[E](e: ⇒ E)(p: Predicate[A]): ValidationNel[E, A] = if (p(self)) Success(self) else Failure(NonEmptyList(e)) 17 | 18 | def descendant(paths: String*)(implicit A: CodecJson[A]): Descendant[A, Json, Json] = { 19 | val aj: Traversal[A, Json] = A.traversalToJson 20 | 21 | Descendant(self, 22 | paths.map(Descendant.Descender.traversal(aj, _))(collection.breakOut), 23 | () ⇒ paths.flatMap(Descendant.Descender.ancestors(aj, _))(collection.breakOut) 24 | ) 25 | } 26 | 27 | def descendant(implicit A: CodecJson[A]): Descendant[A, Json, Json] = { 28 | val aj: Traversal[A, Json] = A.traversalToJson 29 | 30 | Descendant[A, Json, Json](self, List(aj), () ⇒ List("" -> aj)) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/function.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import scala.language.higherKinds 4 | 5 | import scala.{PartialFunction => ~>} 6 | import scalaz.\/ 7 | 8 | 9 | object function { 10 | implicit class PartialFunctionFrills[In, Out](private val self: In ~> Out) { 11 | def \/[In2](rhs: In2 ~> Out): (In \/ In2) ~> Out = new ((In \/ In2) ~> Out) { 12 | def isDefinedAt(in: In \/ In2): Boolean = in.fold(self.isDefinedAt, rhs.isDefinedAt) 13 | def apply(in: In \/ In2): Out = in.fold(self.apply, rhs.apply) 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/list.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.genTraversableLike.GTLGT 4 | 5 | import scala.collection.{GenTraversable, GenTraversableLike} 6 | import scala.collection.immutable.List 7 | import scalaz.{NonEmptyList, \/} 8 | 9 | import pimpathon.frills.genTraversableLike.{GenTraversableLikeFrillsMixin, GenTraversableLikeOfDisjunctionFrillsMixin} 10 | 11 | import pimpathon.list._ 12 | 13 | 14 | object list { 15 | implicit class ListFrills[A](private val self: List[A]) extends GenTraversableLikeFrillsMixin[A, List] { 16 | def toNel: Option[NonEmptyList[A]] = self.unconsC(None, head ⇒ tail ⇒ Some(NonEmptyList.fromSeq(head, tail))) 17 | 18 | protected def gtl: GTLGT[A] = self 19 | protected def cc: List[A] = self 20 | } 21 | 22 | implicit class ListOfDisjunctionsFrills[L, R]( 23 | private val self: List[L \/ R] 24 | ) extends GenTraversableLikeOfDisjunctionFrillsMixin[L, R] { 25 | 26 | protected def gtl: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] = self 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/set.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.genTraversableLike.GTLGT 4 | 5 | import scala.collection.{GenTraversable, GenTraversableLike} 6 | import scalaz.\/ 7 | 8 | import pimpathon.frills.genTraversableLike.{GenTraversableLikeFrillsMixin, GenTraversableLikeOfDisjunctionFrillsMixin} 9 | 10 | 11 | object set { 12 | implicit class SetFrills[A](private val self: Set[A]) extends GenTraversableLikeFrillsMixin[A, Set] { 13 | protected def gtl: GTLGT[A] = self 14 | protected def cc: Set[A] = self 15 | } 16 | 17 | implicit class SetOfDisjunctionsFrills[L, R]( 18 | private val self: Set[L \/ R] 19 | ) extends GenTraversableLikeOfDisjunctionFrillsMixin[L, R] { 20 | 21 | protected def gtl: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] = self 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/string.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import scalaz.NonEmptyList 4 | 5 | 6 | object string { 7 | implicit class StringFrills(private val self: String) extends AnyVal { 8 | def splitToNel(by: String): NonEmptyList[String] = self.split(by).toList match { 9 | case head :: tail ⇒ NonEmptyList.fromSeq(head, tail) 10 | case _ ⇒ NonEmptyList(self) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/try.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import scala.util.Try 4 | import scalaz.{-\/, \/, \/-} 5 | 6 | 7 | 8 | object pimpTry { 9 | implicit class TryFrills[A](val self: Try[A]) extends AnyVal { 10 | def toDisjunction: Throwable \/ A = self.fold(-\/(_), \/-(_)) 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/frills/vector.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.genTraversableLike.GTLGT 4 | 5 | import scala.collection.{GenTraversable, GenTraversableLike} 6 | import scalaz.{NonEmptyList, \/} 7 | 8 | import pimpathon.frills.genTraversableLike.{GenTraversableLikeFrillsMixin, GenTraversableLikeOfDisjunctionFrillsMixin} 9 | 10 | import pimpathon.boolean._ 11 | 12 | 13 | object vector { 14 | implicit class VectorFrills[A](private val self: Vector[A]) extends GenTraversableLikeFrillsMixin[A, Vector] { 15 | def toNel: Option[NonEmptyList[A]] = self.nonEmpty.option(NonEmptyList.fromSeq(self.head, self.tail)) 16 | 17 | protected def gtl: GTLGT[A] = self 18 | protected def cc: Vector[A] = self 19 | } 20 | 21 | implicit class VectorOfDisjunctionsFrills[L, R]( 22 | private val self: Vector[L \/ R] 23 | ) extends GenTraversableLikeOfDisjunctionFrillsMixin[L, R] { 24 | 25 | protected def gtl: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] = self 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/function.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.higherKinds 4 | import scala.runtime.AbstractPartialFunction 5 | 6 | import scala.{PartialFunction ⇒ ~>} 7 | import scala.collection.{GenTraversable, GenTraversableLike} 8 | 9 | import pimpathon.boolean._ 10 | import pimpathon.genTraversableLike._ 11 | 12 | import scala.util.Try 13 | 14 | 15 | object function { 16 | type Predicate[-A] = A ⇒ Boolean 17 | type T2[A] = (A, A) 18 | type T3[A] = (A, A, A) 19 | type T4[A] = (A, A, A, A) 20 | type T5[A] = (A, A, A, A, A) 21 | 22 | implicit class FunctionPimps[A, B](val self: A ⇒ B) extends AnyVal { 23 | def attempt: A ⇒ Try[B] = a ⇒ Try(self(a)) 24 | def guardWith(p: Predicate[A]): A ~> B = p guard self 25 | def tuple2: T2[A] ⇒ T2[B] = a ⇒ (self(a._1), self(a._2)) 26 | def tuple3: T3[A] ⇒ T3[B] = a ⇒ (self(a._1), self(a._2), self(a._3)) 27 | def tuple4: T4[A] ⇒ T4[B] = a ⇒ (self(a._1), self(a._2), self(a._3), self(a._4)) 28 | def tuple5: T5[A] ⇒ T5[B] = a ⇒ (self(a._1), self(a._2), self(a._3), self(a._4), self(a._5)) 29 | } 30 | 31 | implicit class Function2Pimps[A, B, C](val self: (A, B) ⇒ C) extends AnyVal { 32 | def tuple2: (T2[A], T2[B]) ⇒ T2[C] = (a,b) ⇒ (self(a._1, b._1), self(a._2, b._2)) 33 | def tuple3: (T3[A], T3[B]) ⇒ T3[C] = (a,b) ⇒ (self(a._1, b._1), self(a._2, b._2), self(a._3, b._3)) 34 | def tuple4: (T4[A], T4[B]) ⇒ T4[C] = (a,b) ⇒ (self(a._1, b._1), self(a._2, b._2), self(a._3, b._3), self(a._4, b._4)) 35 | def tuple5: (T5[A], T5[B]) ⇒ T5[C] = (a,b) ⇒ (self(a._1, b._1), self(a._2, b._2), self(a._3, b._3), self(a._4, b._4), self(a._5, b._5)) 36 | } 37 | 38 | implicit class FunctionOptionPimps[A, B](val self: A ⇒ Option[B]) extends AnyVal { 39 | def unlift: A ~> B = new AbstractPartialFunction[A, B] { // Gee thanks for making PF.Lifted & Unlifted private 40 | def isDefinedAt(a: A): Boolean = self(a).isDefined 41 | override def applyOrElse[A1 <: A, B1 >: B](a: A1, default: A1 ⇒ B1): B1 = self(a).getOrElse(default(a)) 42 | override def lift: A ⇒ Option[B] = self 43 | } 44 | } 45 | 46 | implicit class CurriedFunction2Pimps[A, B, C](val self: A ⇒ B ⇒ C) extends AnyVal { 47 | def tupled: ((A, B)) ⇒ C = Function.uncurried(self).tupled 48 | } 49 | 50 | implicit class PredicatePimps[A](val self: Predicate[A]) extends AnyVal { 51 | def cond[B](ifTrue: ⇒ B, ifFalse: ⇒ B): A ⇒ B = a ⇒ self(a).cond(ifTrue, ifFalse) 52 | 53 | def and(q: Predicate[A]): Predicate[A] = (a: A) ⇒ self(a) && q(a) 54 | def or(q: Predicate[A]): Predicate[A] = (a: A) ⇒ self(a) || q(a) 55 | def not: Predicate[A] = (a: A) ⇒ !self(a) 56 | 57 | def exists: Predicate[List[A]] = _.exists(self) 58 | def forall: Predicate[List[A]] = _.forall(self) 59 | 60 | def ifSome: Predicate[Option[A]] = _.exists(self) 61 | 62 | def first[B]: Predicate[(A, B)] = (ab: (A, B)) ⇒ self(ab._1) 63 | def second[B]: Predicate[(B, A)] = (ba: (B, A)) ⇒ self(ba._2) 64 | 65 | def guard[B](f: A ⇒ B): A ~> B = new GuardedPartialFunction[A, B](self, f) 66 | } 67 | 68 | implicit class PartialFunctionPimps[In, Out](private val self: In ~> Out) { 69 | def isUndefinedAt(in: In): Boolean = !self.isDefinedAt(in) 70 | 71 | def partition[CC[A]](ins: GenTraversableLike[In, GenTraversable[In]]) 72 | (implicit cbf: CCBF[Either[In, Out], CC], icbf: CCBF[In, CC], ocbf: CCBF[Out, CC]): (CC[In], CC[Out]) = 73 | ins.map(either).partitionEithers[CC](icbf, ocbf) 74 | 75 | def map[Out2](f: Out ⇒ Out2): In ~> Out2 = new (In ~> Out2) { 76 | def isDefinedAt(in: In): Boolean = self.isDefinedAt(in) 77 | def apply(in: In): Out2 = f(self(in)) 78 | } 79 | 80 | def contramap[In2](f: In2 ⇒ In): In2 ~> Out = new (In2 ~> Out) { 81 | def isDefinedAt(in2: In2): Boolean = self.isDefinedAt(f(in2)) 82 | def apply(in2: In2): Out = self(f(in2)) 83 | } 84 | 85 | def either: In ⇒ Either[In, Out] = toRight 86 | def toRight: In ⇒ Either[In, Out] = (in: In) ⇒ self.lift(in).toRight(in) 87 | def toLeft: In ⇒ Either[Out, In] = (in: In) ⇒ self.lift(in).toLeft(in) 88 | 89 | def first[C]: (In, C) ~> (Out, C) = ***(identityPF[C]) 90 | def second[C]: (C, In) ~> (C, Out) = identityPF[C] *** self 91 | 92 | def &&&[Out2](rhs: In ~> Out2): In ~> (Out, Out2) = new (In ~> (Out, Out2)) { 93 | def isDefinedAt(in: In): Boolean = self.isDefinedAt(in) && rhs.isDefinedAt(in) 94 | def apply(in: In): (Out, Out2) = (self(in), rhs(in)) 95 | } 96 | 97 | def ***[In2, Out2](rhs: In2 ~> Out2): (In, In2) ~> (Out, Out2) = new ((In, In2) ~> (Out, Out2)) { 98 | def isDefinedAt(in: (In, In2)): Boolean = self.isDefinedAt(in._1) && rhs.isDefinedAt(in._2) 99 | def apply(in: (In, In2)): (Out, Out2) = (self.apply(in._1), rhs.apply(in._2)) 100 | } 101 | 102 | def |||[In2](rhs: In2 ~> Out): Either[In, In2] ~> Out = new (Either[In, In2] ~> Out) { 103 | def isDefinedAt(in: Either[In, In2]): Boolean = in.fold(self.isDefinedAt, rhs.isDefinedAt) 104 | def apply(in: Either[In, In2]): Out = in.fold(self.apply, rhs.apply) 105 | } 106 | } 107 | 108 | implicit class PartialEndoFunctionPimps[A](val self: A ~> A) extends AnyVal { 109 | def unify: A ⇒ A = (a: A) ⇒ self.lift(a).getOrElse(a) 110 | } 111 | 112 | private class GuardedPartialFunction[A, B](p: Predicate[A], f: A ⇒ B) extends (A ~> B) { 113 | def isDefinedAt(a: A): Boolean = p(a) 114 | def apply(a: A): B = f(a) 115 | } 116 | 117 | def identityPF[A]: A ~> A = { case a ⇒ a } 118 | def equalC[A]: A ⇒ A ⇒ Boolean = (l: A) ⇒ (r: A) ⇒ l equals r 119 | def nand[A](ps: Predicate[A]*): Predicate[A] = and(ps: _*).not 120 | def nor[A](ps: Predicate[A]*): Predicate[A] = or(ps: _*).not 121 | def or[A](ps: Predicate[A]*): Predicate[A] = ps.foldLeft((a: A) ⇒ false)(_ or _) 122 | def and[A](ps: Predicate[A]*): Predicate[A] = ps.foldLeft((a: A) ⇒ true)(_ and _) 123 | 124 | def partialChain[A, B](first: A ⇒ B ⇒ B, rest: (A ⇒ B ⇒ B)*): A ⇒ B ⇒ B = a ⇒ Function.chain((first +: rest).map(fn ⇒ fn(a))) 125 | def partialChain2[A, B, C](first: (A, B) ⇒ C ⇒ C, rest: ((A, B) ⇒ C ⇒ C)*): (A, B) ⇒ C ⇒ C = (a, b) ⇒ Function.chain((first +: rest).map(fn ⇒ fn(a, b))) 126 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/io/InputStream.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.io 2 | 3 | import java.nio.charset.Charset 4 | 5 | import scala.language.implicitConversions 6 | 7 | import java.io._ 8 | import java.util.zip.GZIPInputStream 9 | import scala.annotation.tailrec 10 | import scala.util.Try 11 | 12 | import pimpathon.any._ 13 | import pimpathon.array._ 14 | 15 | 16 | object inputStream extends InputStreamUtils() 17 | 18 | case class InputStreamUtils(closeIn: Boolean = true, closeOut: Boolean = true, bufferSize: Int = 8192) { 19 | implicit def inputStreamPimps[IS <: InputStream](is: IS): InputStreamPimps[IS] = 20 | new InputStreamPimps[IS](is, this) 21 | 22 | def copy(is: InputStream, os: OutputStream, closeIn: Boolean = closeIn, closeOut: Boolean = closeOut): Unit = { 23 | withBuffer(buffer ⇒ { 24 | try Iterator.continually(is.read(buffer)).takeWhile(_ > 0).foreach(os.write(buffer, 0, _)) 25 | finally { 26 | if (closeIn) is.attemptClose() 27 | if (closeOut) os.attemptClose() 28 | } 29 | }) 30 | } 31 | 32 | private[io] def withBuffer[A](f: Array[Byte] ⇒ A): A = f(new Array[Byte](bufferSize)) 33 | } 34 | 35 | class InputStreamPimps[IS <: InputStream](is: IS, utils: InputStreamUtils) { 36 | import utils._ 37 | 38 | def >>(os: OutputStream): IS = drain(os, closeIn = false, closeOut = false) 39 | 40 | def drain(os: OutputStream, closeIn: Boolean = closeIn, closeOut: Boolean = closeOut): IS = 41 | is.tap(copy(_, os, closeIn, closeOut)) 42 | 43 | def closeAfter[A](f: IS ⇒ A): A = is.withFinally(_.attemptClose())(f) 44 | def closeIf(condition: Boolean): IS = is.tapIf(_ ⇒ condition)(_.close()) 45 | def closeUnless(condition: Boolean): IS = is.tapUnless(_ ⇒ condition)(_.close()) 46 | def attemptClose(): Try[Unit] = Try(is.close()) 47 | 48 | def buffered: BufferedInputStream = new BufferedInputStream(is, bufferSize) 49 | def gunzip: GZIPInputStream = new GZIPInputStream(is, bufferSize) 50 | 51 | def readN(os: OutputStream, n: Long): IS = is.tap(_.readUpToN(os, n) |> (count ⇒ if (count != n) 52 | throw new IOException(s"Failed to read $n bytes, only $count were available") 53 | )) 54 | 55 | def readUpToN(os: OutputStream, n: Long): Long = withBuffer(buffer ⇒ { 56 | require(n >= 0, "You can't read a negative number of bytes!") 57 | 58 | @tailrec def recurse(count: Long): Long = { 59 | val written = buffer.copyUpToN(n - count, is, os) 60 | 61 | if (written == -1) count else recurse(count + written) 62 | } 63 | 64 | recurse(0) 65 | }) 66 | 67 | def toByteArray: Array[Byte] = new ByteArrayOutputStream().drain(is).toByteArray 68 | 69 | def asString(charset: Charset): String = toByteArray.asString(charset) 70 | def asString: String = toByteArray.asString 71 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/io/OutputStream.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.io 2 | 3 | import scala.language.implicitConversions 4 | 5 | import java.io.{IOException, BufferedOutputStream, InputStream, OutputStream} 6 | import java.util.zip.GZIPOutputStream 7 | import scala.util.Try 8 | 9 | import pimpathon.any._ 10 | 11 | 12 | object outputStream extends OutputStreamUtils(closeOut = true, closeIn = true, bufferSize = 8192) 13 | 14 | case class OutputStreamUtils(closeOut: Boolean, closeIn: Boolean, bufferSize: Int) { 15 | implicit def outputStreamPimps[OS <: OutputStream](os: OS): OutputStreamPimps[OS] = 16 | new OutputStreamPimps[OS](os, this) 17 | } 18 | 19 | class OutputStreamPimps[OS <: OutputStream](os: OS, utils: OutputStreamUtils) { 20 | import utils._ 21 | 22 | def <<(is: InputStream): OS = drain(is, closeOut = false, closeIn = false) 23 | 24 | def drain(is: InputStream, closeOut: Boolean = closeOut, closeIn: Boolean = closeIn): OS = 25 | os.tap(is.drain(_, closeIn, closeOut)) 26 | 27 | def closeAfter[A](f: OS ⇒ A): A = os.withFinally(_.attemptClose())(f) 28 | def closeIf(condition: Boolean): OS = os.tapIf(_ ⇒ condition)(_.close()) 29 | def closeUnless(condition: Boolean): OS = os.tapUnless(_ ⇒ condition)(_.close()) 30 | def attemptClose(): Try[Unit] = Try(os.close()) 31 | 32 | def buffered: BufferedOutputStream = new BufferedOutputStream(os, bufferSize) 33 | def gzip: GZIPOutputStream = new GZIPOutputStream(os, bufferSize) 34 | 35 | def writeN(is: InputStream, n: Long): OS = os.tap(_.writeUpToN(is, n) |> (count ⇒ if (count != n) 36 | throw new IOException(s"Failed to write $n only $count were available") 37 | )) 38 | 39 | def writeUpToN(is: InputStream, n: Long): Long = is.readUpToN(os, n) 40 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/io/package.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java 2 | 3 | import scala.language.implicitConversions 4 | 5 | import java.io.{InputStream, OutputStream} 6 | 7 | 8 | package object io { 9 | implicit def inputStreamPimps[IS <: InputStream](is: IS): InputStreamPimps[IS] = 10 | new InputStreamPimps[IS](is, inputStream) 11 | 12 | implicit def outputStreamPimps[OS <: OutputStream](os: OS): OutputStreamPimps[OS] = 13 | new OutputStreamPimps[OS](os, outputStream) 14 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/util/Callable.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util 2 | 3 | import scala.language.implicitConversions 4 | 5 | import java.util.concurrent.Callable 6 | import scala.util.Try 7 | 8 | 9 | object callable { 10 | implicit def callbackFromThunk[A](thunk: () ⇒ A): Callable[A] = create(thunk()) 11 | 12 | def create[A](action: ⇒ A): Callable[A] = () ⇒ action 13 | 14 | implicit class CallablePimps[A](val self: Callable[A]) extends AnyVal { 15 | def attempt: Callable[Try[A]] = create(Try(self.call())) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/util/concurrent/ThreadFactory.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util.concurrent 2 | 3 | import java.util.concurrent.ThreadFactory 4 | import java.util.concurrent.atomic.AtomicInteger 5 | 6 | import pimpathon.any._ 7 | 8 | 9 | object threadFactory { 10 | implicit class ThreadFactoryPimps(val self: ThreadFactory) extends AnyVal { 11 | def naming(f: Int ⇒ String): ThreadFactory = NamingThreadFactory(self, f) 12 | } 13 | 14 | case class NamingThreadFactory(adapted: ThreadFactory, f: Int ⇒ String) extends ThreadFactory { 15 | def newThread(r: Runnable): Thread = adapted.newThread(r).tap(_.setName(f(count.getAndIncrement))) 16 | 17 | private val count = new AtomicInteger(0) 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/util/concurrent/atomic/AtomicReference.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util.concurrent.atomic 2 | 3 | import java.util.concurrent.atomic.AtomicReference 4 | import pimpathon.either.EitherPimps 5 | 6 | 7 | object atomicReference { 8 | implicit class AtomicReferencePimps[A](private val self: AtomicReference[A]) extends AnyVal { 9 | def update(f: A ⇒ A): A = self.updateAndGet((a: A) ⇒ f(a)) 10 | def updateEither[L](f: A ⇒ Either[L, A]): Either[L, A] = f(self.get()).tapRight(self.set) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/java/util/date.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util 2 | 3 | import java.util.{Calendar, Date} 4 | 5 | import pimpathon.any._ 6 | 7 | 8 | object date { 9 | implicit class DatePimps(val self: Date) extends AnyVal { 10 | def addDay(offset: Int): Date = 11 | Calendar.getInstance().tap(_.setTime(self), _.add(Calendar.DAY_OF_YEAR, offset)).getTime 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/list.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.Random 4 | import scala.{PartialFunction => ~>} 5 | import scala.annotation.tailrec 6 | import scala.collection.{GenTraversable, GenTraversableLike, mutable => M} 7 | import scala.collection.immutable._ 8 | import scala.collection.immutable.{Map => ▶:} 9 | import pimpathon.genTraversableLike.{GTLGT, GenTraversableLikeOfEitherPimpsMixin, GenTraversableLikeOfTuple2Mixin} 10 | import pimpathon.any.AnyPimps 11 | import pimpathon.boolean.BooleanPimps 12 | import pimpathon.function._ 13 | import pimpathon.multiMap.{MultiMapPimps, build} 14 | import pimpathon.option._ 15 | import pimpathon.ordering._ 16 | import pimpathon.tuple._ 17 | import pimpathon.builder.BuilderPimps 18 | 19 | import scala.collection.generic.CanBuildFrom 20 | 21 | 22 | object list { 23 | implicit class ListPimps[A](private val self: List[A]) extends genTraversableLike.GenTraversableLikePimpsMixin[A, List] { 24 | def updateIf(pred: A ⇒ Boolean, updateFn: A ⇒ A): List[A] = update(updateFn.guardWith(pred)) 25 | def update(pf: A ~> A): List[A] = self.map(pf.unify) 26 | 27 | def tapEmpty[Discarded](empty: ⇒ Discarded): List[A] = tap(empty, _ ⇒ {}) 28 | def tapNonEmpty[Discarded](nonEmpty: List[A] ⇒ Discarded): List[A] = tap({}, nonEmpty) 29 | def tap[Discarded](empty: ⇒ Discarded, nonEmpty: List[A] ⇒ Discarded): List[A] = { uncons(empty, nonEmpty); self } 30 | 31 | def emptyTo(alternative: ⇒ List[A]): List[A] = uncons(alternative, _ ⇒ self) 32 | 33 | def zipToMap[B](values: List[B]): A ▶: B = zip(values).toMap 34 | 35 | case class zipWith[B](values: List[B]) { 36 | def apply[C, That](f: ((A, B)) ⇒ C)(implicit cbf: CanBuildFrom[List[C], C, That]): That = 37 | cbf.apply().run(_ ++= zip(values).map(f)) 38 | } 39 | 40 | def fraction(p: Predicate[A]): Double = countWithSize(p).fold(Double.NaN)(_.to[Double].calc(_ / _)) 41 | 42 | def countWithSize(p: Predicate[A]): Option[(Int, Int)] = calcIfNonEmpty(_.foldLeft((0, 0)) { 43 | case ((passed, size), elem) ⇒ (passed + p(elem).asInt, size + 1) 44 | }) 45 | 46 | def sizeGT(value: Int): Boolean = uncons(empty = value < 0, nonEmpty = _.tail.sizeGT(value - 1)) 47 | 48 | def duplicates: List[A] = duplicatesBy(identity[A]) 49 | def duplicatesBy[B](f: A ⇒ B): List[A] = (countBy(f) - 1).multiMap.values 50 | def distinctBy[B](f: A ⇒ B): List[A] = self.map(equalBy(f)).distinct.map(_.a) 51 | 52 | def countBy[B](f: A ⇒ B): Int ▶: List[A] = 53 | self.asMultiMap[List].withKeys(f).multiMap.mapEntries(_ ⇒ values ⇒ (values.size, values)) 54 | 55 | def batchBy[B](f: A ⇒ B): List[List[A]] = self.unconsC(empty = Nil, nonEmpty = head ⇒ tail ⇒ { 56 | val (_, lastBatch, allBatches) = tail.foldLeft((f(head), M.ListBuffer(head), M.ListBuffer[List[A]]())) { 57 | case ((currentKey, batch, batches), a) ⇒ f(a).cond(_ == currentKey, 58 | ifTrue = key ⇒ (key, batch += a, batches), 59 | ifFalse = key ⇒ (key, M.ListBuffer(a), batches += batch.toList) 60 | ) 61 | } 62 | 63 | (allBatches += lastBatch.toList).toList 64 | }) 65 | 66 | def batchWhile(p: Predicate[List[A]]): List[List[A]] = { 67 | val (last, res) = self.foldLeft((List.empty[A], List.empty[List[A]])) { 68 | case ((current, acc), a) ⇒ if (p(a :: current)) (a :: current, acc) else (a :: Nil, current.reverse :: acc) 69 | } 70 | 71 | (last.reverse :: res).reverse 72 | } 73 | 74 | def headTail: (A, List[A]) = headTailOption.getOrThrow("headTail of empty list") 75 | def initLast: (List[A], A) = initLastOption.getOrThrow("initLast of empty list") 76 | 77 | def headTailOption: Option[(A, List[A])] = unconsC(None, head ⇒ tail ⇒ Some((head, tail))) 78 | def initLastOption: Option[(List[A], A)] = uncons(None, nonEmpty ⇒ Some(nonEmpty.init, nonEmpty.last)) 79 | 80 | def tailOption: Option[List[A]] = uncons(None, nonEmpty ⇒ Some(nonEmpty.tail)) 81 | def initOption: Option[List[A]] = uncons(None, nonEmpty ⇒ Some(nonEmpty.init)) 82 | 83 | def calcIfNonEmpty[B](f: List[A] ⇒ B): Option[B] = self.calcIf(_.nonEmpty)(f) 84 | def mapIfNonEmpty[B](f: A ⇒ B): Option[List[B]] = self.calcIf(_.nonEmpty)(_.map(f)) 85 | 86 | def amass[B](pf: A ~> List[B]): List[B] = self.flatMap(a ⇒ pf.lift(a).getOrElse(Nil)) 87 | 88 | def interleave(rhs: List[A]): List[A] = { 89 | @tailrec 90 | def recurse(acc: List[A], next: List[A], after: List[A]): List[A] = next match { 91 | case Nil ⇒ acc.reverse ::: after 92 | case head :: tail ⇒ recurse(head :: acc, after, tail) 93 | } 94 | 95 | recurse(Nil, self, rhs) 96 | } 97 | 98 | def interleaveWith[B, C](rhs: List[B])(f: Either[A, B] ⇒ C): List[C] = 99 | self.map(a ⇒ f(Left(a))).interleave(rhs.map(b ⇒ f(Right(b)))) 100 | 101 | def uncons[B](empty: ⇒ B, nonEmpty: List[A] ⇒ B): B = if (self.isEmpty) empty else nonEmpty(self) 102 | 103 | def unconsC[B](empty: ⇒ B, nonEmpty: A ⇒ List[A] ⇒ B): B = self match { 104 | case Nil ⇒ empty 105 | case head :: tail ⇒ nonEmpty(head)(tail) 106 | } 107 | 108 | def unsnocC[B](empty: ⇒ B, nonEmpty: List[A] ⇒ A ⇒ B): B = initLastOption match { 109 | case None ⇒ empty 110 | case Some((init, last)) ⇒ nonEmpty(init)(last) 111 | } 112 | 113 | def const[B](elem: B): List[B] = self.map(_ ⇒ elem) 114 | 115 | def lpair[B](f: A ⇒ B): List[(B, A)] = self.map(_.lpair(f)) 116 | def rpair[B](f: A ⇒ B): List[(A, B)] = self.map(_.rpair(f)) 117 | 118 | def prefixPadTo(len: Int, elem: A): List[A] = List.fill(len - self.length)(elem) ++ self 119 | 120 | def sharedPrefix(other: List[A])(implicit compare: A ⇒ A ⇒ Boolean = equalC[A]): (List[A], List[A], List[A]) = { 121 | @tailrec def recurse(lefts: List[A], rights: List[A], acc: List[A]): (List[A], List[A], List[A]) = { 122 | (lefts, rights) match { 123 | case (left :: lhs, right :: rhs) if compare(left)(right) ⇒ recurse(lhs, rhs, left :: acc) 124 | case _ ⇒ (acc.reverse, lefts, rights) 125 | } 126 | } 127 | 128 | recurse(self, other, Nil) 129 | } 130 | 131 | def zipExact[B](bs: List[B]): (List[(A, B)], Option[Either[List[A], List[B]]]) = zipExactWith(bs)((a, b) ⇒ (a, b)) 132 | 133 | case class zipExactWith[B](other: List[B]) { 134 | def apply[C](fromTuple: (A, B) ⇒ C, fromLhs: A ⇒ C, fromRhs: B ⇒ C): List[C] = apply(fromTuple).calc { 135 | case (cs, None) ⇒ cs 136 | case (cs, Some(Left(as))) ⇒ cs ++ as.map(fromLhs) 137 | case (cs, Some(Right(bs))) ⇒ cs ++ bs.map(fromRhs) 138 | } 139 | 140 | def apply[C](fromTuple: (A, B) ⇒ C): (List[C], Option[Either[List[A], List[B]]]) = { 141 | @tailrec 142 | def recurse(la: List[A], lb: List[B], cs: List[C]): (List[C], Option[Either[List[A], List[B]]]) = (la, lb) match { 143 | case (a :: as, b :: bs) ⇒ recurse(as, bs, fromTuple(a, b) :: cs) 144 | case (Nil, Nil) ⇒ (cs.reverse, None) 145 | case (as, Nil) ⇒ (cs.reverse, Some(Left(as))) 146 | case (Nil, bs) ⇒ (cs.reverse, Some(Right(bs))) 147 | } 148 | 149 | recurse(self, other, Nil) 150 | } 151 | } 152 | 153 | def sortPromoting(first: A*)(implicit ordering: Ordering[A]): List[A] = self.sorted(ordering.promote(first: _*)) 154 | def sortDemoting(last: A*)(implicit ordering: Ordering[A]): List[A] = self.sorted(ordering.demote(last: _*)) 155 | 156 | def shuffle(): List[A] = Random.shuffle(self) 157 | 158 | private def equalBy[B](f: A ⇒ B)(a: A): EqualBy[A, B] = EqualBy(f(a))(a) 159 | private def zip[B](other: List[B]): Iterator[(A, B)] = self.iterator.zip(other.iterator) 160 | 161 | protected def gtl: GenTraversableLike[A, GenTraversable[A]] = self 162 | protected def cc: List[A] = self 163 | } 164 | 165 | implicit class ListOfEithersPimps[L, R]( 166 | private val self: List[_ <: Either[L, R]] 167 | ) extends GenTraversableLikeOfEitherPimpsMixin[L, R, List] { 168 | 169 | protected def gtl: GTLGT[Either[L, R]] = self 170 | } 171 | 172 | implicit class ListOfTuple2Pimps[K, V](private val self: List[(K, V)]) extends GenTraversableLikeOfTuple2Mixin[K, V] { 173 | def mapFirst[C](f: K ⇒ C): List[(C, V)] = mapC(k ⇒ v ⇒ (f(k), v)) 174 | def mapSecond[W](f: V ⇒ W): List[(K, W)] = mapC(k ⇒ v ⇒ (k, f(v))) 175 | def mapValues[W](f: V ⇒ W): List[(K, W)] = mapC(k ⇒ v ⇒ (k, f(v))) 176 | def mapC[W](f: K ⇒ V ⇒ W): List[W] = self.map(kv ⇒ f(kv._1)(kv._2)) 177 | 178 | def flatMapValues[W](f: V ⇒ TraversableOnce[W]): Seq[(K, W)] = 179 | self.flatMap { case (k, v) ⇒ f(v).map(vb ⇒ (k, vb)) } 180 | 181 | def keys: List[K] = self.map(_._1) 182 | def values: List[V] = self.map(_._2) 183 | 184 | protected def gtl: GTLGT[(K, V)] = self 185 | } 186 | 187 | implicit class MatrixPimps[A](val self: List[List[A]]) extends AnyVal { 188 | def cartesianProduct: List[List[A]] = self.foldRight(List(Nil): List[List[A]]) { 189 | case (item, acc) ⇒ for { a ← item; b ← acc } yield a :: b 190 | } 191 | } 192 | } 193 | 194 | case class EqualBy[A, B](b: B)(val a: A) -------------------------------------------------------------------------------- /src/main/scala/pimpathon/map.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.{PartialFunction => ~>} 4 | import scala.collection.{GenTraversable, GenTraversableLike, GenTraversableOnce, breakOut, mutable => M} 5 | import scala.collection.immutable.{SortedMap, TreeMap, Map => ▶:} 6 | import pimpathon.genTraversableLike.{GenTraversableLikeOfTuple2Mixin, GenTraversableLikePimpsMixin} 7 | import pimpathon.any._ 8 | import pimpathon.function._ 9 | import pimpathon.multiMap._ 10 | import pimpathon.tuple._ 11 | 12 | 13 | object map { 14 | implicit class MapPimps[K, V]( 15 | private val self: K ▶: V 16 | ) extends GenTraversableLikePimpsMixin[(K, V), GenTraversable] 17 | with GenTraversableLikeOfTuple2Mixin[K, V] { 18 | 19 | def containsAny(ok: Option[K]): Boolean = ok.exists(self.contains) 20 | def containsAll(ok: Option[K]): Boolean = ok.forall(self.contains) 21 | def containsAny[GK <: GenTraversableOnce[K]](gk: GK): Boolean = gk.exists(self.contains) 22 | def containsAll[GK <: GenTraversableOnce[K]](gk: GK): Boolean = gk.forall(self.contains) 23 | def containsEntry(kv: (K, V)): Boolean = containsEntry(kv._1, kv._2) 24 | def containsEntry(k: K, v: V): Boolean = self.get(k).contains(v) 25 | 26 | def get(ok: Option[K]): Option[V] = ok.flatMap(self.get) 27 | def getOrThrow(k: K, message: String): V = getOrThrow(k, new IllegalArgumentException(message)) 28 | def getOrThrow(k: K, exception: ⇒ Exception): V = self.getOrElse(k, throw exception) 29 | 30 | def getOrLeft[L](k: K, l: ⇒ L): Either[L, V] = self.get(k).toRight(l) 31 | 32 | def findKey(p: Predicate[K]): Option[K] = keyFor.matchingKey(p) 33 | def findValue(p: Predicate[V]): Option[V] = valueFor.matchingValue(p) 34 | 35 | def filterKeysNot(p: Predicate[K]): K ▶: V = self.filterNot(kv ⇒ p(kv._1)) 36 | def filterValuesNot(p: Predicate[V]): K ▶: V = self.filterNot(kv ⇒ p(kv._2)) 37 | def filterValues(p: Predicate[V]): K ▶: V = self.filter(kv ⇒ p(kv._2)) 38 | 39 | 40 | def keyExists(p: Predicate[K]): Boolean = self.exists(kv ⇒ p(kv._1)) 41 | def valueExists(p: Predicate[V]): Boolean = self.exists(kv ⇒ p(kv._2)) 42 | 43 | def emptyTo(empty: ⇒ K ▶: V): K ▶: V = uncons(empty, _ ⇒ self) 44 | def calcIfNonEmpty[A](f: K ▶: V ⇒ A): Option[A] = self.calcIf(_.nonEmpty)(f) 45 | def uncons[A](empty: ⇒ A, nonEmpty: K ▶: V ⇒ A): A = if (self.isEmpty) empty else nonEmpty(self) 46 | 47 | def reverse(f: Set[K] ⇒ K): V ▶: K = reverseToMultiMap.mapValuesEagerly(f) 48 | def reverseToMultiMap: V ▶: Set[K] = self.map(_.swap)(breakOut) 49 | 50 | def sortBy[C: Ordering](f: K ⇒ C): SortedMap[K, V] = sorted(Ordering[C].on[K](f)) 51 | def sorted(implicit ordering: Ordering[K]): SortedMap[K, V] = TreeMap.empty[K, V](ordering) ++ self 52 | 53 | def composeM[C](other: C ▶: K): C ▶: V = other.andThenM(self) 54 | def andThenM[W](other: V ▶: W): K ▶: W = updateValues(other.get _) 55 | 56 | def toMutable: M.Map[K, V] = mutable 57 | def mutable: M.Map[K, V] = M.Map.empty[K, V] ++ self 58 | 59 | def entryFor: MapAndThen[K, V, (K, V)] = new MapAndThen[K, V, (K, V)](self, identity[(K, V)]) 60 | def keyFor: MapAndThen[K, V, K] = new MapAndThen[K, V, K](self, key) 61 | def valueFor: MapAndThen[K, V, V] = new MapAndThen[K, V, V](self, value) 62 | 63 | def partitionKeys(p: Predicate[K]): (K ▶: V, K ▶: V) = self.partition(p.first[V]) 64 | def partitionValues(p: Predicate[V]): (K ▶: V, K ▶: V) = self.partition(p.second[K]) 65 | 66 | def partitionKeysBy[C](pf: K ~> C): (K ▶: V, C ▶: V) = partitionEntriesBy(pf.first[V]) 67 | def partitionValuesBy[W](pf: V ~> W): (K ▶: V, K ▶: W) = partitionEntriesBy(pf.second[K]) 68 | 69 | def partitionEntriesBy[C, W](pf: (K, V) ~> (C, W)): (K ▶: V, C ▶: W) = 70 | self.partition(pf.isUndefinedAt).tmap(identity, _.map(pf)) 71 | 72 | def mapKeysEagerly[C](f: K ⇒ C): C ▶: V = self.map { case (k, v) ⇒ (f(k), v) } 73 | def mapValuesEagerly[W](f: V ⇒ W): K ▶: W = self.map { case (k, v) ⇒ (k, f(v)) } 74 | def mapEntries[C, W](f: K ⇒ V ⇒ (C, W)): C ▶: W = self.map { case (k, v) ⇒ f(k)(v) } 75 | 76 | def mapValuesWithKey[W](f: K ⇒ V ⇒ W): K ▶: W = self.map { case (k, v) ⇒ (k, f(k)(v)) } 77 | 78 | def seqMapKeys[C](f: K ⇒ Option[C]): Option[C ▶: V] = seqMapEntries(k ⇒ v ⇒ f(k).map(_ → v)) 79 | def seqMapValues[W](f: V ⇒ Option[W]): Option[K ▶: W] = seqMapEntries(k ⇒ v ⇒ f(v).map(k → _)) 80 | def seqMapEntries[C, W](f: K ⇒ V ⇒ Option[(C, W)]): Option[C ▶: W] = self.seqMap { case (k, v) ⇒ f(k)(v) } 81 | 82 | def collectKeys[C](pf: K ~> C): C ▶: V = self.collect(pf.first) 83 | def collectValues[W](pf: V ~> W): K ▶: W = self.collect(pf.second) 84 | 85 | def updateValue(key: K, f: V ⇒ Option[V]): K ▶: V = 86 | self.get(key).flatMap(f).fold(self - key)(newValue ⇒ self + ((key, newValue))) 87 | 88 | def updateKeys[C](pf: K ~> C): C ▶: V = updateKeys(pf.lift) 89 | def updateValues[W](pf: V ~> W): K ▶: W = updateValues(pf.lift) 90 | 91 | def updateKeys[C](f: K ⇒ Option[C]): C ▶: V = self.flatMap(kv ⇒ f(kv._1).map(_ → kv._2)) 92 | def updateValues[W](f: V ⇒ Option[W]): K ▶: W = self.flatMap(kv ⇒ f(kv._2).map(kv._1 → _)) 93 | 94 | def zipWith[W, X](other: K ▶: W)(pf: (Option[V], Option[W]) ~> X): K ▶: X = 95 | self.keySet.union(other.keySet).flatMap(k ⇒ pf.lift((self.get(k), other.get(k))).map(k → _))(breakOut) 96 | 97 | protected def gtl: GenTraversableLike[(K, V), GenTraversable[(K, V)]] = self 98 | protected def cc: GenTraversable[(K, V)] = self 99 | } 100 | 101 | class MapAndThen[K, V, A](map: K ▶: V, andThen: ((K, V)) ⇒ A) { 102 | def maxValue(implicit O: Ordering[V]): Option[A] = map.calcIfNonEmpty(_.maxBy(value)).map(andThen) 103 | def minValue(implicit O: Ordering[V]): Option[A] = map.calcIfNonEmpty(_.minBy(value)).map(andThen) 104 | def maxKey(implicit O: Ordering[K]): Option[A] = map.calcIfNonEmpty(_.maxBy(key)).map(andThen) 105 | def minKey(implicit O: Ordering[K]): Option[A] = map.calcIfNonEmpty(_.minBy(key)).map(andThen) 106 | 107 | def matchingKey(p: Predicate[K]): Option[A] = map.find(kv ⇒ p(kv._1)).map(andThen) 108 | def matchingValue(p: Predicate[V]): Option[A] = map.find(kv ⇒ p(kv._2)).map(andThen) 109 | } 110 | 111 | @inline private def key[K, V]: ((K, V)) ⇒ K = (kv: (K, V)) ⇒ kv._1 112 | @inline private def value[K, V]: ((K, V)) ⇒ V = (kv: (K, V)) ⇒ kv._2 113 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/multimap.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.{higherKinds, implicitConversions} 4 | 5 | import scala.collection.{breakOut, mutable ⇒ M, GenTraversable} 6 | import scala.collection.generic.CanBuildFrom 7 | import scala.collection.immutable.{Map ⇒ ▶:} 8 | 9 | import pimpathon.any.AnyPimps 10 | import pimpathon.boolean.BooleanPimps 11 | import pimpathon.builder.BuilderPimps 12 | import pimpathon.function.CurriedFunction2Pimps 13 | import pimpathon.map.MapPimps 14 | import pimpathon.stream.StreamPimps 15 | import pimpathon.tuple.Tuple2Pimps 16 | 17 | 18 | object multiMap { 19 | type MultiMap[F[_], K, V] = K ▶: F[V] 20 | type MMCBF[F[_], K, V] = CanBuildFrom[Nothing, (K, V), K ▶: F[V]] 21 | 22 | implicit def build[F[_], K, V](implicit fcbne: CanBuildNonEmpty[V, F[V]]): MMCBF[F, K, V] = MultiMap.build 23 | 24 | implicit class MultiMapPimps[F[_], K, V](val self: K ▶: F[V]) extends AnyVal { 25 | def select[W](f: F[V] ⇒ W): K ▶: W = self.mapValuesEagerly(f) // just an alias for mapValuesEagerly 26 | 27 | def merge(other: K ▶: F[V])(implicit crf: CanRebuildFrom[F, V]): K ▶: F[V] = 28 | if (self.isEmpty) other else other.foldLeft(self) { 29 | case (acc, (key, otherValues)) ⇒ acc.append(key, otherValues) 30 | } 31 | 32 | def append(key: K, newValues: F[V])(implicit crf: CanRebuildFrom[F, V]): K ▶: F[V] = 33 | self + ((key, crf.concat(List(self.get(key), Some(newValues)).flatten))) 34 | 35 | def pop(key: K)(implicit crf: CanRebuildFrom[F, V]): K ▶: F[V] = self.updateValue(key, crf.pop) 36 | 37 | def onlyOption(implicit gtl: F[V] <:< GenTraversable[V], crf: CanRebuildFrom[F, V]): Option[K ▶: V] = 38 | headTailOption.flatMap(_.calcC(head ⇒ tail ⇒ tail.isEmpty.option(head))) 39 | 40 | def sequence(implicit bf: CCBF[K ▶: V, F], gtl: F[V] <:< GenTraversable[V], 41 | crf: CanRebuildFrom[F, V], crsm: CanRebuildFrom[F, K ▶: V] 42 | ): F[K ▶: V] = crsm.fromStream(self.unfold(_.headTailOption)) 43 | 44 | def headTailOption(implicit gtl: F[V] <:< GenTraversable[V], crf: CanRebuildFrom[F, V]) 45 | : Option[(K ▶: V, K ▶: F[V])] = multiMap.head.filterSelf(_.nonEmpty).map(_ → multiMap.tail) 46 | 47 | def flatMapValues[W](f: V ⇒ F[W])(implicit crfv: CanRebuildFrom[F, V], crfw: CanRebuildFrom[F, W]) 48 | : K ▶: F[W] = self.mapValuesEagerly(crfv.flatMap(_)(f)) 49 | 50 | def flatMapValuesU[GW](f: V ⇒ GW)(implicit crfv: CanRebuildFrom[F, V], u: CanRebuildFrom.Unapply[GW]) 51 | : K ▶: u.F[u.V] = self.mapValuesEagerly(crfv.flatMap(_)(f)) 52 | 53 | def getOrEmpty(k: K)(implicit fcbf: CCBF[V, F]): F[V] = self.getOrElse(k, fcbf.apply().result()) 54 | 55 | def multiMap: MultiMapConflictingPimps[F, K, V] = new MultiMapConflictingPimps[F, K, V](self) 56 | } 57 | 58 | 59 | class MultiMapConflictingPimps[F[_], K, V](private val self: K ▶: F[V]) { 60 | // These operations cannot be defined on MultiMapPimps because non-implicit methods of the same name exist on Map 61 | def head(implicit gtl: F[V] <:< GenTraversable[V]): K ▶: V = 62 | self.flatMap { case (k, fv) ⇒ fv.headOption.map(k → _) } 63 | 64 | def tail(implicit crf: CanRebuildFrom[F, V]): K ▶: F[V] = self.updateValues(crf.pop _) 65 | def values(implicit crf: CanRebuildFrom[F, V]): F[V] = crf.concat(self.values) 66 | 67 | def reverse(implicit crf: CanRebuildFrom[F, V], cbf: CCBF[K, F]): V ▶: F[K] = 68 | self.toStream.flatMap(kvs ⇒ crf.toStream(kvs._2).map(_ → kvs._1))(collection.breakOut) 69 | 70 | def mapValues[W](f: V ⇒ W)(implicit crfv: CanRebuildFrom[F, V], crfw: CanRebuildFrom[F, W]): K ▶: F[W] = 71 | self.mapValuesEagerly(crfv.map(_)(f)) 72 | 73 | def mapEntries[C, W](f: K ⇒ F[V] ⇒ (C, F[W]))( 74 | implicit cbmmf: MMCBF[F, C, F[W]], crf: CanRebuildFrom[F, W], crff: CanRebuildFrom[F, F[W]] 75 | ): C ▶: F[W] = self.asMultiMap[F].withEntries(f.tupled).mapValuesEagerly(crf.concat) 76 | 77 | def mapEntriesU[C, GW](f: K ⇒ F[V] ⇒ (C, GW))( 78 | implicit cbmmf: MMCBF[F, C, GW], u: CanRebuildFrom.Unapply[GW], crf: CanRebuildFrom[F, GW] 79 | ): C ▶: u.F[u.V] = self.asMultiMap[F].withEntries(f.tupled).mapValuesEagerly(u.concat[F](_)(crf)) 80 | 81 | 82 | def sliding(size: Int)(implicit bf: CCBF[K ▶: V, F], gtl: F[V] <:< GenTraversable[V], 83 | crf: CanRebuildFrom[F, V], crsm: CanRebuildFrom[F, K ▶: F[V]], fcbf: CanBuildFrom[Nothing, V, F[V]] 84 | ): F[K ▶: F[V]] = { 85 | crsm.fromStream(self.unfold(_.headTailOption).sliding(size) 86 | .map(_.flatMap[(K, V), K ▶: F[V]](_.toStream)(breakOut)).toStream) 87 | } 88 | } 89 | 90 | object MultiMap { 91 | def build[F[_], K, V](implicit fcbne: CanBuildNonEmpty[V, F[V]]): MMCBF[F, K, V] = new MultiMapCanBuildFrom[F, K, V] 92 | def empty[F[_], K, V]: K ▶: F[V] = Map.empty[K, F[V]] 93 | } 94 | 95 | 96 | trait IgnoreFromCBF[-From, -Elem, +To] extends CanBuildFrom[From, Elem, To] { 97 | override def apply(from: From): M.Builder[Elem, To] = apply() 98 | } 99 | 100 | class MultiMapCanBuildFrom[F[_], K, V](implicit fcbne: CanBuildNonEmpty[V, F[V]]) 101 | extends MMCBF[F, K, V] with IgnoreFromCBF[Nothing, (K, V), K ▶: F[V]] { 102 | 103 | def apply(): M.Builder[(K, V), K ▶: F[V]] = new MultiMapBuilder[F, K, V] 104 | } 105 | 106 | class MultiMapBuilder[F[_], K, V]( 107 | map: M.Map[K, M.Builder[V, F[V]]] = M.Map.empty[K, M.Builder[V, F[V]]] 108 | )( 109 | implicit fcbne: CanBuildNonEmpty[V, F[V]] 110 | ) 111 | extends M.Builder[(K, V), K ▶: F[V]] { 112 | 113 | def +=(elem: (K, V)): this.type = { add(elem._1, elem._2); this } 114 | def clear(): Unit = map.clear() 115 | def result(): K ▶: F[V] = map.map(kv ⇒ (kv._1, kv._2.result()))(breakOut) 116 | 117 | private def add(k: K, v: V): Unit = map.put(k, map.get(k).fold(fcbne.builder(v))(_ += v)) 118 | } 119 | 120 | trait CanRebuildFrom[F[_], V] { 121 | final def concat(ffv: F[F[V]])(implicit crff: CanRebuildFrom[F, F[V]]): F[V] = concat(crff.toStream(ffv)) 122 | final def concat(fvs: Iterable[F[V]]): F[V] = (cbf.apply() +++= fvs.map(toStream)) result() 123 | final def pop(fv: F[V]): Option[F[V]] = flatMapS(fv)(_.tailOption.filter(_.nonEmpty)) 124 | final def map[W](fv: F[V])(f: V ⇒ W)(implicit gcf: CanRebuildFrom[F, W]): F[W] = gcf.fromStream(toStream(fv).map(f)) 125 | 126 | private def flatMapS(fv: F[V])(f: Stream[V] ⇒ Option[Stream[V]]): Option[F[V]] = f(toStream(fv)).map(fromStream) 127 | 128 | final def flatMap[GW](fv: F[V])(f: V ⇒ GW)(implicit gcrf: CanRebuildFrom.Unapply[GW]): gcrf.F[gcrf.V] = 129 | gcrf.fromStream(toStream(fv).flatMap(v ⇒ gcrf.toStream(f(v)))) 130 | 131 | def fromStream(to: TraversableOnce[V]): F[V] = (cbf() ++= to).result() 132 | 133 | def toStream(fv: F[V]): Stream[V] 134 | 135 | protected val cbf: CanBuildFrom[F[V], V, F[V]] 136 | } 137 | 138 | object CanRebuildFrom { 139 | trait Unapply[FV] { 140 | type F[_] 141 | type V 142 | 143 | def concat[G[_]](g_fv: G[FV])(implicit crf: CanRebuildFrom[G, FV]): F[V] = 144 | fromStream(for { fv ← crf.toStream(g_fv); v ← toStream(fv) } yield v) 145 | 146 | def fromStream(to: TraversableOnce[V]): F[V] 147 | def toStream(fv: FV): Stream[V] 148 | } 149 | 150 | implicit def unapply[F0[_], V0](implicit crf: CanRebuildFrom[F0, V0]) 151 | : Unapply[F0[V0]] { type F[X] = F0[X]; type V = V0 } = new Unapply[F0[V0]] { 152 | type F[X] = F0[X] 153 | type V = V0 154 | 155 | def fromStream(to: TraversableOnce[V]): F[V] = crf.fromStream(to) 156 | def toStream(fv: F0[V0]): Stream[V] = crf.toStream(fv) 157 | } 158 | 159 | implicit def crf[F[_], V]( 160 | implicit cbf0: CanBuildFrom[F[V], V, F[V]], fTraversableOnce: F[V] <:< TraversableOnce[V] 161 | ): CanRebuildFrom[F, V] = new CanRebuildFrom[F, V] { 162 | def toStream(fv: F[V]): Stream[V] = fTraversableOnce(fv).toStream 163 | protected val cbf: CanBuildFrom[F[V], V, F[V]] = cbf0 164 | } 165 | } 166 | } 167 | 168 | trait CanBuildNonEmpty[-Elem, +To] { 169 | def builder(head: Elem): M.Builder[Elem, To] 170 | } 171 | 172 | object CanBuildNonEmpty { 173 | implicit def canBuildFromToCBNE[Elem, To]( 174 | implicit cbf: CanBuildFrom[Nothing, Elem, To] 175 | ): CanBuildNonEmpty[Elem, To] = new CanBuildNonEmpty[Elem, To] { 176 | def builder(head: Elem): M.Builder[Elem, To] = cbf.apply() += head 177 | } 178 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/mutableMap.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.collection.{mutable ⇒ M} 4 | 5 | import pimpathon.function._ 6 | 7 | 8 | object mutableMap { 9 | implicit class MutableMapPimps[K, V](val self: M.Map[K, V]) extends AnyVal { 10 | def retainKeys(p: Predicate[K]): M.Map[K, V] = self.retain((k, _) ⇒ p(k)) 11 | def retainValues(p: Predicate[V]): M.Map[K, V] = self.retain((_, v) ⇒ p(v)) 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/nestedMap.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.multiMap.IgnoreFromCBF 4 | 5 | import scala.collection.generic.CanBuildFrom 6 | import scala.collection.{breakOut, mutable => M} 7 | import scala.collection.immutable.{Map => ▶:} 8 | import pimpathon.builder.BuilderPimps 9 | import pimpathon.map.MapPimps 10 | 11 | 12 | object nestedMap { 13 | type NMCBF[K1, K2, V] = CanBuildFrom[Nothing, (K1, K2, V), K1 ▶: K2 ▶: V] 14 | 15 | implicit def build[K1, K2, V]: NMCBF[K1, K2, V] = new NestedMapCanBuilderFrom[K1, K2, V] 16 | 17 | implicit class NestedMapPimps[K1, K2, V](val self: K1 ▶: K2 ▶: V) extends AnyVal { 18 | def flipNesting: K2 ▶: K1 ▶: V = self.flatMap(o ⇒ o._2.map(i ⇒ (i._1, o._1, i._2)))(breakOut) 19 | def +(kkv: (K1, K2, V)): K1 ▶: K2 ▶: V = append(kkv._1, kkv._2, kkv._3) 20 | def append(k1: K1, k2: K2, v: V): K1 ▶: K2 ▶: V = self + ((k1, self.getOrEmpty(k1) + ((k2, v)))) 21 | 22 | def getOrEmpty(k1: K1): K2 ▶: V = self.getOrElse(k1, Map.empty[K2, V]) 23 | 24 | def nestedMap: NestedMapConflictingPimps[K1, K2, V] = new NestedMapConflictingPimps[K1, K2, V](self) 25 | } 26 | 27 | class NestedMapConflictingPimps[K1, K2, V](private val self: K1 ▶: K2 ▶: V) { 28 | def mapValuesEagerly[W](f: V ⇒ W): K1 ▶: K2 ▶: W = self.mapValuesEagerly(_.mapValuesEagerly(f)) 29 | def mapKeysEagerly[C](f: K2 ⇒ C): K1 ▶: C ▶: V = self.mapValuesEagerly(_.mapKeysEagerly(f)) 30 | 31 | def mapEntries[C1, C2, W](f: (K1, K2, V) ⇒ (C1, C2, W)): C1 ▶: C2 ▶: W = 32 | build[C1, C2, W]().run(_ ++= (for { (k1, k2v) <- self; (k2, v) <- k2v } yield f(k1, k2, v))) 33 | } 34 | 35 | object NestedMap { 36 | def build[K1, K2, V]: NMCBF[K1, K2, V] = new NestedMapCanBuilderFrom[K1, K2, V] 37 | def empty[K1, K2, V]: K1 ▶: K2 ▶: V = Map.empty[K1, K2 ▶: V] 38 | } 39 | 40 | class NestedMapCanBuilderFrom[K1, K2, V] extends NMCBF[K1, K2, V] 41 | with IgnoreFromCBF[Nothing, (K1, K2, V), K1 ▶: K2 ▶: V] { 42 | 43 | def apply(): M.Builder[(K1, K2, V), K1 ▶: K2 ▶: V] = new NestedMapBuilder[K1, K2, V]() 44 | } 45 | 46 | class NestedMapBuilder[K1, K2, V](map: M.Map[K1, K2 ▶: V] = M.Map.empty[K1, K2 ▶: V]) 47 | extends M.Builder[(K1, K2, V), K1 ▶: K2 ▶: V] { 48 | 49 | def +=(elem: (K1, K2, V)): this.type = { add(elem._1, elem._2, elem._3); this} 50 | def result(): K1 ▶: K2 ▶: V = map.map(entry ⇒ entry)(breakOut) 51 | def clear(): Unit = map.clear() 52 | 53 | private def add(k1: K1, k2: K2, v: V): Unit = map.put(k1, map.getOrElse(k1, Map.empty[K2, V]) + ((k2, v))) 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/numeric.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | 4 | object numeric { 5 | implicit class NumericPimps[A](val self: Numeric[A]) extends AnyVal { 6 | def xmap[B](aToB: A ⇒ B, bToA: B ⇒ A): Numeric[B] = self match { 7 | case xna: XMappedNumeric[_, _] ⇒ xna.xmap(aToB, bToA) 8 | case other ⇒ new XMappedNumeric[A, B](other, aToB, bToA) 9 | } 10 | } 11 | 12 | class XMappedNumeric[A, B](na: Numeric[A], aToB: A ⇒ B, bToA: B ⇒ A) extends Numeric[B] { 13 | def xmap[C](bToC: B ⇒ C, cToB: C ⇒ B): Numeric[C] = 14 | new XMappedNumeric[A, C](na, aToB andThen bToC, cToB andThen bToA) 15 | 16 | def compare(l: B, r: B): Int = na.compare(bToA(l), bToA(r)) 17 | 18 | def fromInt(i: Int): B = aToB(na.fromInt(i)) 19 | 20 | def toDouble(b: B): Double = na.toDouble(bToA(b)) 21 | def toFloat(b: B): Float = na.toFloat(bToA(b)) 22 | def toInt(b: B): Int = na.toInt(bToA(b)) 23 | def toLong(b: B): Long = na.toLong(bToA(b)) 24 | 25 | def negate(b: B): B = aToB(na.negate(bToA(b))) 26 | 27 | def minus(l: B, r: B): B = aToB(na.minus(bToA(l), bToA(r))) 28 | def plus(l: B, r: B): B = aToB(na.plus(bToA(l), bToA(r))) 29 | def times(l: B, r: B): B = aToB(na.times(bToA(l), bToA(r))) 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/option.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.{PartialFunction ⇒ ~>} 4 | import scala.util.Try 5 | import pimpathon.any._ 6 | 7 | 8 | object option { 9 | implicit class OptionPimps[A](val self: Option[A]) extends AnyVal { 10 | def tapNone[Discarded](none: ⇒ Discarded): Option[A] = tap(none, _ ⇒ {}) 11 | def tapSome[Discarded](some: A ⇒ Discarded): Option[A] = tap({}, some) 12 | def tap[Discarded](none: ⇒ Discarded, some: A ⇒ Discarded): Option[A] = { self.fold(none)(some); self } 13 | 14 | def getOrThrow(message: String): A = getOrThrow(new NoSuchElementException(message)) 15 | def getOrThrow(exception: ⇒ Exception): A = self.getOrElse(throw exception) 16 | 17 | def toTry: Try[A] = self.fold(pimpTry.failure[A](new NoSuchElementException))(pimpTry.success[A]) 18 | def toEither[L,R](none: ⇒ L, some: A ⇒ R): Either[L,R] = self.map(a => Right(some(a))).getOrElse(Left(none)) 19 | 20 | def invert(a: A): Option[A] = self.fold(Some(a): Option[A])(_ ⇒ None) 21 | 22 | def amass[B](pf: A ~> Option[B]): Option[B] = self.flatMap(a ⇒ pf.lift(a).flatten) 23 | 24 | def containedIn(s: A*): Boolean = self.fold(false)(_.containedIn(s.toSet)) 25 | def containedIn(s: Set[A]): Boolean = self.fold(false)(_.containedIn(s)) 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/ordering.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.math.Ordering._ 4 | import pimpathon.any._ 5 | 6 | 7 | object ordering { 8 | implicit class OrderingPimps[A](val self: Ordering[A]) extends AnyVal { 9 | def promote(as: A*): Ordering[A] = Tuple2[Option[Int], A](Option[Int](Int.reverse).reverse, self).on[A](index(as: _*)) 10 | def demote(as: A*): Ordering[A] = Tuple2[Option[Int], A](Option[Int](Int), self).on[A](index(as: _*)) 11 | 12 | def ||(next: Ordering[A]): Ordering[A] = &&(next).on[A](a ⇒ (a, a)) 13 | def &&[B](next: Ordering[B]): Ordering[(A, B)] = Ordering.Tuple2(self, next) 14 | 15 | private def index(as: A*)(a: A): (Option[Int], A) = (as.indexOf(a).filterSelf(_ >= 0), a) 16 | } 17 | 18 | implicit class OrderingCompanionPimps(private val self: Ordering.type) extends AnyVal { 19 | def sameAs[A](values: A*): Ordering[A] = { 20 | val indexes = values.zipWithIndex.toMap 21 | 22 | Ordering[Int].on[A](indexes.getOrElse(_, Integer.MAX_VALUE)) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/package.scala: -------------------------------------------------------------------------------- 1 | import scala.collection.generic.CanBuildFrom 2 | 3 | import scala.language.higherKinds 4 | 5 | package object pimpathon { 6 | type CCBF[A, CC[_]] = CanBuildFrom[Nothing, A, CC[A]] 7 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/properties.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.Properties 4 | 5 | 6 | object properties { 7 | implicit class PropertiesCompanionPimps(val self: Properties.type) extends AnyVal { 8 | def propEnvOrElse(key: String, alt: String): String = propEnvOrNone(key).getOrElse(alt) 9 | def propEnvOrNone(key: String): Option[String] = Properties.propOrNone(key).orElse(Properties.envOrNone(key)) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/random.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.Random 4 | 5 | 6 | object random { 7 | implicit class RandomPimps(val self: Random) extends AnyVal { 8 | def between(min: Char, max: Char): Char = between(min.toInt, max.toInt).toChar 9 | def between(min: Int, max: Int): Int = self.nextInt(max - min) + min 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/runnable.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.implicitConversions 4 | 5 | 6 | object runnable extends runnable 7 | 8 | trait runnable { 9 | implicit def runnableFromThunk[Discarded](thunk: () ⇒ Discarded): Runnable = create(thunk()) 10 | 11 | def create(action: ⇒ Unit): Runnable = new Runnable { 12 | override def run(): Unit = action 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/scalaz/either.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz 2 | 3 | import scala.collection.generic.{Growable, Shrinkable} 4 | import scala.util.{Failure, Success, Try} 5 | import scalaz.{-\/, \/, \/-} 6 | 7 | 8 | object either { 9 | implicit class DisjunctionFrills[L, R](val self: L \/ R) extends AnyVal { 10 | def addTo(ls: Growable[L], rs: Growable[R]): L \/ R = tap(ls += _, rs += _) 11 | def removeFrom(ls: Shrinkable[L], rs: Shrinkable[R]): L \/ R = tap(ls -= _, rs -= _) 12 | 13 | def tapLeft[Discarded](l: L ⇒ Discarded): L \/ R = tap(l, _ ⇒ {}) 14 | def tapRight[Discarded](r: R ⇒ Discarded): L \/ R = tap(_ ⇒ {}, r) 15 | def tap[Discarded](l: L ⇒ Discarded, r: R ⇒ Discarded): L \/ R = { self.fold(l, r); self } 16 | 17 | def leftFlatMap(f: L ⇒ L \/ R): L \/ R = self.fold(f, \/-(_)) 18 | 19 | def getMessage(implicit ev: L <:< Throwable): Option[String] = self.fold(t ⇒ Some(t.getMessage), _ ⇒ None) 20 | def toTry(implicit ev: L <:< Throwable): Try[R] = self.fold(Failure(_), Success(_)) 21 | } 22 | 23 | implicit class DisjunctionFrillsNestedL[L, R](val self: (L \/ R) \/ R) { 24 | def flatten: L \/ R = self.fold(identity, \/-(_)) 25 | } 26 | 27 | implicit class DisjunctionFrillsNestedR[L, R](val self: L \/ (L \/ R)) { 28 | def flatten: L \/ R = self.fold(-\/(_), identity) 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/scalaz/nel.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz 2 | 3 | import scala.collection.{GenTraversable, GenTraversableLike} 4 | import scalaz.{NonEmptyList, Order, \/} 5 | 6 | import pimpathon.CanBuildNonEmpty 7 | import pimpathon.frills.genTraversableLike.{GenTraversableLikeFrillsMixin, GenTraversableLikeOfDisjunctionFrillsMixin} 8 | 9 | import pimpathon.function._ 10 | import pimpathon.genTraversableLike._ 11 | import pimpathon.list._ 12 | import pimpathon.tuple._ 13 | 14 | 15 | object nel { 16 | implicit def canBuildNonEmpty[A]: CanBuildNonEmpty[A, NonEmptyList[A]] = 17 | (head: A) ⇒ List.newBuilder[A].mapResult(tail ⇒ NonEmptyList.fromSeq[A](head, tail)) 18 | 19 | implicit class NelFrills[A](private val self: NonEmptyList[A]) extends GenTraversableLikeFrillsMixin[A, NonEmptyList] { 20 | def unique: NonEmptyList[A] = lift(_.distinct) 21 | def uniqueBy[B](f: A ⇒ B): NonEmptyList[A] = lift(_.distinctBy(f)) 22 | def filter(p: Predicate[A]): Option[NonEmptyList[A]] = liftO(_.filter(p)) 23 | def filterNot(p: Predicate[A]): Option[NonEmptyList[A]] = liftO(_.filterNot(p)) 24 | def max(implicit o: Order[A]): A = toList.max(o.toScalaOrdering) 25 | def min(implicit o: Order[A]): A = toList.min(o.toScalaOrdering) 26 | def toList: List[A] = self.stream.toList 27 | 28 | private def lift(f: List[A] ⇒ List[A]): NonEmptyList[A] = toNel(f(toList).headTail) 29 | private def liftO(f: List[A] ⇒ List[A]): Option[NonEmptyList[A]] = f(toList).headTailOption.map(toNel) 30 | 31 | protected def gtl: GTLGT[A] = toList 32 | protected def cc: NonEmptyList[A] = self 33 | private def toNel(ht: (A, List[A])): NonEmptyList[A] = ht.calc(NonEmptyList.fromSeq) 34 | } 35 | 36 | implicit class NelOfEithersFrills[L, R]( 37 | private val self: NonEmptyList[Either[L, R]] 38 | ) extends GenTraversableLikeOfEitherPimpsMixin[L, R, NonEmptyList] { 39 | 40 | protected def gtl: GTLGT[Either[L, R]] = self.toList 41 | } 42 | 43 | implicit class NelOfTuple2Frills[K, V]( 44 | private val self: NonEmptyList[(K, V)] 45 | ) extends GenTraversableLikeOfTuple2Mixin[K, V] { 46 | 47 | protected def gtl: GTLGT[(K, V)] = self.toList 48 | } 49 | 50 | implicit class NelOfDisjunctinonsFrills[L, R]( 51 | private val self: NonEmptyList[L \/ R] 52 | ) extends GenTraversableLikeOfDisjunctionFrillsMixin[L, R] { 53 | 54 | protected def gtl: GenTraversableLike[L \/ R, GenTraversable[L \/ R]] = self.toList 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/scalaz/std/boolean.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz.std 2 | 3 | import _root_.scalaz.{\/, -\/, \/-} 4 | 5 | object boolean { 6 | implicit class BooleanFrills(val self: Boolean) extends AnyVal { 7 | def disjunction[R](right: R): DisjunctionCapturer[R] = new DisjunctionCapturer[R](self, right) 8 | } 9 | 10 | class DisjunctionCapturer[R](value: Boolean, right: R) { 11 | def or[L](left: ⇒ L): L \/ R = if (value) \/-(right) else -\/(left) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/scalaz/std/either.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz.std 2 | 3 | import scalaz.{-\/, \/, \/-} 4 | 5 | 6 | object either { 7 | implicit class EitherFrills[L, R](private val self: Either[L, R]) { 8 | def disjunction: L \/ R = self.fold(-\/(_), \/-(_)) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/pimpathon/scalaz/std/option.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz.std 2 | 3 | import scalaz.ValidationNel 4 | 5 | import scalaz.syntax.std.option._ 6 | 7 | 8 | object option { 9 | implicit class OptionFrills[A](val self: Option[A]) extends AnyVal { 10 | def toSuccessNel[E](e: ⇒ E): ValidationNel[E, A] = self.toSuccess(e).toValidationNel 11 | def toFailureNel[B](b: ⇒ B): ValidationNel[A, B] = self.toFailure(b).toValidationNel 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/set.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.genTraversableLike.{GenTraversableLikeOfTuple2Mixin, GenTraversableLikeOfEitherPimpsMixin, GTLGT} 4 | import scala.{PartialFunction => ~>} 5 | import scala.collection.immutable.TreeSet 6 | import scala.collection.{mutable ⇒ M, GenTraversable, GenTraversableLike} 7 | 8 | import pimpathon.any._ 9 | import pimpathon.list._ 10 | 11 | 12 | object set { 13 | implicit class SetPimps[A](private val self: Set[A]) extends genTraversableLike.GenTraversableLikePimpsMixin[A, Set] { 14 | def sorted(implicit ordering: Ordering[A]): TreeSet[A] = TreeSet() ++ self 15 | 16 | def notContains(elem: A): Boolean = !self.contains(elem) 17 | 18 | def powerSet: Set[Set[A]] = { 19 | def recurse(list: List[A]): List[List[A]] = 20 | list.unconsC(List(Nil), head ⇒ tail ⇒ recurse(tail) |> (ps ⇒ ps ++ ps.map(head :: _))) 21 | 22 | recurse(self.toList).map(_.toSet).toSet 23 | } 24 | 25 | def toMutable: M.Set[A] = mutable 26 | def mutable: M.Set[A] = M.Set.empty[A] ++ self 27 | 28 | def amass[B](pf: A ~> Set[B]): Set[B] = self.flatMap(a ⇒ pf.lift(a).getOrElse(Set.empty[B])) 29 | 30 | protected def gtl: GenTraversableLike[A, GenTraversable[A]] = self 31 | protected def cc: Set[A] = self 32 | } 33 | 34 | implicit class SetOfEitherPimps[L, R]( 35 | private val self: Set[_ <: Either[L, R]] 36 | ) extends GenTraversableLikeOfEitherPimpsMixin[L, R, Set] { 37 | 38 | protected def gtl: GTLGT[Either[L, R]] = self 39 | } 40 | 41 | implicit class SetOfTuple2Pimps[K, V](private val self: Set[(K, V)]) extends GenTraversableLikeOfTuple2Mixin[K, V] { 42 | protected def gtl: GTLGT[(K, V)] = self 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/stream.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.function._ 4 | 5 | 6 | object stream { 7 | def cond[A](cond: Boolean, stream: ⇒ Stream[A]): Stream[A] = if (cond) stream else Stream.empty[A] 8 | def continuallyWhile[A](elem: ⇒ A)(p: Predicate[A]): Stream[A] = Stream.continually(elem).takeWhile(p) 9 | 10 | implicit class StreamPimps[A](val self: Stream[A]) extends AnyVal { 11 | def reverseInits: Stream[Stream[A]] = lazyScanLeft(Stream.empty[A])(_ :+ _) // This is how inits _should_ be defined 12 | 13 | def lazyScanLeft[B](z: B)(op: (B, A) ⇒ B): Stream[B] = { 14 | def loop(as: ⇒ Stream[A], acc: B): Stream[B] = acc #:: as.unconsC(Stream.empty[B], h ⇒ t ⇒ loop(t, op(acc, h))) 15 | 16 | loop(self, z) 17 | } 18 | 19 | def tailOption: Option[Stream[A]] = uncons(None, _ ⇒ Some(self.tail)) 20 | def unconsC[B](empty: ⇒ B, nonEmpty: A ⇒ (⇒ Stream[A]) ⇒ B): B = uncons(empty, _ ⇒ nonEmpty(self.head)(self.tail)) 21 | def uncons[B](empty: ⇒ B, nonEmpty: Stream[A] ⇒ B): B = if (self.isEmpty) empty else nonEmpty(self) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/string.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.nio.charset.Charset 4 | import _root_.java.security.MessageDigest 5 | 6 | import pimpathon.array._ 7 | import pimpathon.list._ 8 | 9 | 10 | object string { 11 | implicit class StringPimps(val self: String) extends AnyVal { 12 | def emptyTo(alternative: ⇒ String): String = if (self.isEmpty) alternative else self 13 | 14 | def quote: String = quoteWith('"') 15 | def quoteWith(char: Char): String = s"$char$self$char" 16 | def unquote: String = self.stripAffixes("\"", "\"") 17 | 18 | def hyphenate: String = splitByCase("-").toLowerCase 19 | 20 | def wrap(length: Int): String = 21 | self.split(" ").toList.batchWhile(_.mkString(" ").length <= length).map(_.mkString(" ")).mkString("\n") 22 | 23 | def pascal: String = 24 | splitByCase("=").split("=").map(_.toLowerCase.capitalize).mkString("") 25 | 26 | def stripAffixes(prefix: String, suffix: String): String = self.stripPrefix(prefix).stripSuffix(suffix) 27 | def affixWith(prefix: String, suffix: String): String = prefixWith(prefix).suffixWith(suffix) 28 | 29 | def prefixWith(prefix: String): String = if (self.startsWith(prefix)) self else prefix + self 30 | def suffixWith(suffix: String): String = if (self.endsWith(suffix)) self else self + suffix 31 | 32 | def sharedPrefix(other: String): (String, String, String) = { 33 | val (prefix, rest, otherRest) = self.toList.sharedPrefix(other.toList) 34 | 35 | (fromChars(prefix), fromChars(rest), fromChars(otherRest)) 36 | } 37 | 38 | def prefixPadTo(len: Int, elem: Char): String = (elem.toString * (len - self.length)) + self 39 | 40 | def md5: String = MessageDigest.getInstance("MD5").digest(self.getBytes).toHex(length = 32) 41 | 42 | def toByteArray: Array[Byte] = self.getBytes(Charset.forName("UTF-8")) 43 | def toByteArray(charset: Charset): Array[Byte] = self.getBytes(charset) 44 | 45 | private def splitByCase(sep: String = " "): String = 46 | self.replaceAll("""(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])""", sep) 47 | } 48 | 49 | def fromChars(chars: List[Char]): String = 50 | (for(c ← chars) yield c)(collection.breakOut) 51 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/threadLocal.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | 4 | object threadLocal extends threadLocal 5 | 6 | trait threadLocal { 7 | def create[A](initial: A): ThreadLocal[A] = new ThreadLocal[A] { 8 | override def initialValue: A = initial 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/throwable.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.io.{PrintWriter, StringWriter} 4 | 5 | import pimpathon.any._ 6 | 7 | 8 | object throwable { 9 | implicit class ThrowablePimps(val self: Throwable) extends AnyVal { 10 | def stackTraceAsString(): String = 11 | new StringWriter().tap(sw ⇒ self.printStackTrace(new PrintWriter(sw, true))).toString 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/try.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.{Failure, Success, Try} 4 | 5 | 6 | object pimpTry { 7 | implicit class TryPimps[A](val self: Try[A]) extends AnyVal { 8 | def getMessage: Option[String] = fold(t ⇒ Some(t.getMessage), _ ⇒ None) 9 | def toEither: Either[Throwable, A] = fold(Left(_), Right(_)) 10 | 11 | def fold[B](failure: Throwable ⇒ B, success: A ⇒ B): B = self match { 12 | case Success(a) ⇒ success(a) 13 | case Failure(t) ⇒ failure(t) 14 | } 15 | } 16 | 17 | def failure[A](throwable: Throwable): Try[A] = Failure[A](throwable) 18 | def success[A](a: A): Try[A] = Success[A](a) 19 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/tuple.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.collection.generic.{Growable, Shrinkable} 4 | 5 | 6 | object tuple { 7 | implicit class Tuple2Pimps[A, B](val self: (A, B)) extends AnyVal { 8 | def addTo(as: Growable[A], bs: Growable[B]): (A, B) = tap(a ⇒ b ⇒ {as += a; bs += b }) 9 | def removeFrom(as: Shrinkable[A], bs: Shrinkable[B]): (A, B) = tap(a ⇒ b ⇒ {as -= a; bs -= b }) 10 | def tap[Discarded](actions: (A ⇒ B ⇒ Discarded)*): (A, B) = { actions.foreach(a ⇒ a(self._1)(self._2)); self } 11 | def calc[C](f: (A, B) ⇒ C): C = f(self._1, self._2) 12 | def calcC[C](f: A ⇒ B ⇒ C): C = f(self._1)(self._2) 13 | def to[C](implicit ac: A ⇒ C, bc: B ⇒ C): (C, C) = (ac(self._1), bc(self._2)) 14 | def tmap[C, D](f: A ⇒ C, g: B ⇒ D): (C, D) = (f(self._1), g(self._2)) 15 | def map1[C](f: A ⇒ C): (C, B) = calcC(a ⇒ b ⇒ (f(a), b)) 16 | def map2[C](f: B ⇒ C): (A, C) = calcC(a ⇒ b ⇒ (a, f(b))) 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/scala/pimpathon/vector.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.genTraversableLike.{GenTraversableLikeOfTuple2Mixin, GenTraversableLikeOfEitherPimpsMixin, GTLGT} 4 | 5 | 6 | object vector { 7 | implicit class VectorPimps[A](private val self: Vector[A]) extends genTraversableLike.GenTraversableLikePimpsMixin[A, Vector] { 8 | protected def gtl: GTLGT[A] = self 9 | protected def cc: Vector[A] = self 10 | } 11 | 12 | implicit class VectorOfEitherPimps[L, R]( 13 | private val self: Vector[_ <: Either[L, R]] 14 | ) extends GenTraversableLikeOfEitherPimpsMixin[L, R, Vector] { 15 | 16 | protected def gtl: GTLGT[Either[L, R]] = self 17 | } 18 | 19 | implicit class VectorOfTuple2Pimps[K, V](private val self: Vector[(K, V)]) extends GenTraversableLikeOfTuple2Mixin[K, V] { 20 | protected def gtl: GTLGT[(K, V)] = self 21 | } 22 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/FilterMonadic.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.filterMonadic._ 4 | 5 | 6 | class FilterMonadicSpec extends PSpec { 7 | "toMultiMap" in { 8 | Set.empty[(Int, Int)].toMultiMap[List] ≡ Map() 9 | List.empty[(Int, Int)].toMultiMap[List] ≡ Map() 10 | 11 | Set((1, 10), (1, 11), (2, 20), (2, 21)).toMultiMap[List] ≡ Map(1 → List(10, 11), 2 → List(20, 21)) 12 | List((1, 10), (1, 11), (2, 20), (2, 21)).toMultiMap[List] ≡ Map(1 → List(10, 11), 2 → List(20, 21)) 13 | 14 | Set((1, 10), (1, 11), (2, 20), (2, 21)).toMultiMap[Set] ≡ Map(1 → Set(10, 11), 2 → Set(20, 21)) 15 | List((1, 10), (1, 11), (2, 20), (2, 21)).toMultiMap[Set] ≡ Map(1 → Set(10, 11), 2 → Set(20, 21)) 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/PSpec.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.util.concurrent.atomic.AtomicReference 4 | import org.scalatest.{BeforeAndAfterEach, FreeSpec} 5 | 6 | import scala.util.DynamicVariable 7 | 8 | 9 | trait PSpec extends FreeSpec with BeforeAndAfterEach with pimpathon.util { 10 | class Resetable[A](val get: A, resetIt: A => Unit) { 11 | def reset(): Unit = resetIt(get) 12 | } 13 | 14 | object perTest { 15 | def dynamicVariable[A](initial: => A): DynamicVariable[A] = { 16 | resetBeforeEach(new Resetable[DynamicVariable[A]](new DynamicVariable[A](initial), _.value = initial)) 17 | } 18 | } 19 | 20 | protected def resetBeforeEach[A](result: Resetable[A]): A = { 21 | resetables.getAndUpdate((values: List[Resetable[_]]) => result :: values) 22 | 23 | result.get 24 | } 25 | 26 | override protected def beforeEach(): Unit = { 27 | super.beforeEach() 28 | 29 | resetables.get().foreach(_.reset()) 30 | } 31 | 32 | private val resetables = new AtomicReference[List[Resetable[_]]](Nil) 33 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/RunnableTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.builder._ 4 | 5 | 6 | class RunnableSpec extends PSpec { 7 | "create" in ints().run(is ⇒ run(runnable.create(is += 1))) ≡ List(1) 8 | "fromThunk" in ints().run(is ⇒ run(() ⇒ is += 3)) ≡ List(3) 9 | 10 | private def run(runnable: Runnable): Unit = runnable.run() 11 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/ThreadLocal.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | 4 | class ThreadLocalSpec extends PSpec { 5 | "create" in threadLocal.create(1).get() ≡ 1 6 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/ThrowableTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.throwable._ 4 | 5 | 6 | class ThrowableSpec extends PSpec { 7 | "stackTraceAsString" in boom.stackTraceAsString().lines.toList.take(4) ≡ 8 | (boom.toString :: boom.getStackTrace.toList.take(3).map("\tat " + _)) 9 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/any.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.{Failure, Random, Success} 4 | import org.junit.Assert._ 5 | import pimpathon.any._ 6 | import pimpathon.boolean._ 7 | import pimpathon.builder._ 8 | 9 | 10 | class AnySpec extends PSpec { 11 | "calc" in List("12".calc(_ + "3"), "12" |> (_ + "3")) ≡ List("123", "123") 12 | "calcIf" in on(2, 3, 4).calling(_.calcIf(_ % 2 == 0)(_ + 3)).produces(Some(5), None, Some(7)) 13 | "calcUnless" in on(2, 3, 4).calling(_.calcUnless(_ % 2 != 0)(_ + 3)).produces(Some(5), None, Some(7)) 14 | 15 | "calcPF" in 16 | on(1, 2, 3, 4).calling(_.calcPF(util.partial(2 → "two", 4 → "four"))).produces(None, Some("two"), None, Some("four")) 17 | 18 | "transform" in on(1, 2, 3, 4).calling(_.transform(util.partial(2 → 4, 4 → 8))).produces(1, 4, 3, 8) 19 | 20 | "transformIf" in on(1, 2, 3, 4).calling(n ⇒ n.transformIf(n % 2 == 0)(_ * 2)).produces(1, 4, 3, 8) 21 | 22 | "tap" in ints().run(is ⇒ 1.tap(is += _, is += _)) ≡ List(1, 1) 23 | 24 | "tapIf" in ints().run(is ⇒ List(1, 2, 3).foreach(i ⇒ i.tapIf(_ % 2 == 0)(is += _))) ≡ List(2) 25 | 26 | "tapUnless" in 27 | ints().run(is ⇒ List(1, 2, 3).foreach(i ⇒ i.tapUnless(_ % 2 == 0)(is += _))) ≡ List(1, 3) 28 | 29 | "tapPF" in 30 | ints().run(is ⇒ List(1, 2, 3).foreach(i ⇒ i.tapPF { case j if j % 2 != 0 ⇒ is += j })) ≡ List(1, 3) 31 | 32 | "castTo" in 33 | on("foo", 123).calling(_.castTo[String]).produces(Some("foo"), None) 34 | 35 | "cond" in on("true", "false").calling(_.cond(_ == "true", _ ⇒ "T", _ ⇒ "F")).produces("T", "F") 36 | 37 | "partialMatch" in 38 | on(1, 0).calling(i ⇒ i partialMatch { case 1 ⇒ "Matched" }).produces(Some("Matched"), None) 39 | 40 | "lpair" in (1.lpair(_ * 10) ≡ (10, 1)) 41 | "rpair" in (1.rpair(_ * 10) ≡ (1, 10)) 42 | 43 | "filterSelf" in on(1, 2, 3, 4).calling(_.filterSelf(_ % 2 == 0)).produces(None, Some(2), None, Some(4)) 44 | "ifSelf" in on(1, 2, 3, 4).calling(_.ifSelf(_ % 2 == 0)).produces(None, Some(2), None, Some(4)) 45 | 46 | "filterNotSelf" in 47 | on(1, 2, 3, 4).calling(_.filterNotSelf(_ % 2 == 0)).produces(Some(1), None, Some(3), None) 48 | 49 | "unlessSelf" in on(1, 2, 3, 4).calling(_.unlessSelf(_ % 2 == 0)).produces(Some(1), None, Some(3), None) 50 | 51 | "containedIn" in on(1, 2, 3, 4).calling(_.containedIn(Set(1, 3))).produces(true, false, true, false) 52 | "notContainedIn" in on(1, 2, 3, 4).calling(_.notContainedIn(Set(1, 3))).produces(false, true, false, true) 53 | 54 | "isOneOf" in on(1, 2, 3, 4).calling(_.isOneOf(1, 3)).produces(true, false, true, false) 55 | "isNotOneOf" in on(1, 2, 3, 4).calling(_.isNotOneOf(1, 3)).produces(false, true, false, true) 56 | 57 | "withFinally" in strings().run(ss ⇒ { 58 | ss += "input".withFinally(s ⇒ ss += "finally: " + s)(s ⇒ {ss += "body: " + s; "done"}) 59 | }) ≡ List("body: input", "finally: input", "done") 60 | 61 | "tryFinally" in strings().run(ss ⇒ { 62 | ss += "input".tryFinally(s ⇒ {ss += "body: " + s; "done"})(s ⇒ ss += "finally: " + s) 63 | }) ≡ List("body: input", "finally: input", "done") 64 | 65 | "attempt" in List(1.attempt(_ * 2), 1.attempt(_ ⇒ throw boom)) ≡ List(Success(2), Failure(boom)) 66 | 67 | "addTo" in ints().run(is ⇒ 1.addTo(is)) ≡ List(1) 68 | "removeFrom" in ints(1).tap(is ⇒ 1.removeFrom(is)).toList ≡ Nil 69 | 70 | "unfold" in 64.unfold(i ⇒ (i > 1).option((i, i/2))).toList ≡ List(64, 32, 16, 8, 4, 2) 71 | 72 | "bounded" in { 73 | Stream.fill(10)(Random.nextInt()).foreach(num ⇒ num.bounded(10, 100) ≡ ((10 max num) min 100)) 74 | 75 | Stream.fill(10)(Random.nextDouble()).foreach(num ⇒ { 76 | assertEquals((10.0 max num) min 100.0, num.bounded(10.0, 100.0), 0.01) 77 | }) 78 | } 79 | 80 | "indent" in { 81 | (new Spiel).indent ≡ 82 | """| Pimpathon contains pimps 83 | | for classes in core 84 | | scala & java libraries 85 | | and pimps for external 86 | | libraries""".stripMargin 87 | } 88 | 89 | "passes" - { 90 | "one" in { 91 | on(1, 2, 3, 4).calling(_.passes.one(_ < 2, _ > 3)).produces(Some(1), None, None, Some(4)) 92 | on(1, 2, 3, 4).calling(_.passes.one()).produces(None, None, None, None) 93 | } 94 | 95 | "all" in { 96 | on(1, 2, 3, 4).calling(_.passes.all(_ >= 2, _ <= 3)).produces(None, Some(2), Some(3), None) 97 | on(1, 2, 3, 4).calling(_.passes.all()).produces(Some(1), Some(2), Some(3), Some(4)) 98 | } 99 | 100 | "none" in { 101 | on(1, 2, 3, 4).calling(_.passes.none(_ >= 2, _ <= 3)).produces(Some(1), None, None, Some(4)) 102 | on(1, 2, 3, 4).calling(_.passes.none()).produces(None, None, None, None) 103 | } 104 | 105 | "some" in { 106 | on(1, 2, 3, 4).calling(_.passes.some(_ < 2, _ > 3)).produces(None, Some(2), Some(3), None) 107 | on(1, 2, 3, 4).calling(_.passes.some()).produces(Some(1), Some(2), Some(3), Some(4)) 108 | } 109 | } 110 | 111 | "fails" - { 112 | "one" in { 113 | on(1, 2, 3, 4).calling(_.fails.one(_ < 2, _ > 3)).produces(None, Some(2), Some(3), None) 114 | on(1, 2, 3, 4).calling(_.fails.one()).produces(Some(1), Some(2), Some(3), Some(4)) 115 | } 116 | 117 | "all" in { 118 | on(1, 2, 3, 4).calling(_.fails.all(_ >= 2, _ <= 3)).produces(Some(1), None, None, Some(4)) 119 | on(1, 2, 3, 4).calling(_.fails.all()).produces(None, None, None, None) 120 | } 121 | 122 | "none" in { 123 | on(1, 2, 3, 4).calling(_.fails.none(_ >= 2, _ <= 3)).produces(None, Some(2), Some(3), None) 124 | on(1, 2, 3, 4).calling(_.fails.none()).produces(Some(1), Some(2), Some(3), Some(4)) 125 | } 126 | 127 | "some" in { 128 | on(1, 2, 3, 4).calling(_.fails.some(_ < 2, _ > 3)).produces(Some(1), None, None, Some(4)) 129 | on(1, 2, 3, 4).calling(_.fails.some()).produces(None, None, None, None) 130 | } 131 | } 132 | 133 | private class Spiel { 134 | override def toString: String = 135 | """|Pimpathon contains pimps 136 | | for classes in core 137 | | scala & java libraries 138 | | and pimps for external 139 | |libraries""".stripMargin 140 | } 141 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/argonautTests/CodecJsonTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.argonautTests 2 | 3 | import _root_.argonaut._ 4 | import pimpathon.any._ 5 | import pimpathon.argonaut._ 6 | import pimpathon.throwable._ 7 | import pimpathon.{CodecException, PSpec} 8 | import scalaz.{-\/, \/, \/-} 9 | import sjc.delta.argonaut.json.actualExpected.flat._ 10 | import sjc.delta.matchers.syntax.anyDeltaMatcherOps 11 | 12 | 13 | class CodecJsonSpec extends PSpec with JsonUtil { 14 | "beforeDecode" in codec.beforeDecode(reverse).decodeJson(json) ≡ codec.decodeJson(reverse(json)) 15 | "afterDecode" in codec.afterDecode(_.reverse).decodeJson(json) ≡ reverse(codec.decodeJson(json)) 16 | "beforeEncode" in codec.beforeEncode(_.reverse).encode(list) <=> codec.encode(list.reverse) 17 | "afterEncode" in codec.afterEncode(reverse).encode(list) <=> reverse(codec.encode(list)) 18 | "andThen" in codec.andThen(reverse).encode(list) <=> reverse(codec.encode(list)) 19 | "compose" in codec.compose(reverse).decodeJson(json) ≡ codec.decodeJson(reverse(json)) 20 | 21 | "xmapEntries" in mapCodec.xmapEntries[String, String](reverseEntry)(reverseEntry).calc(reversed ⇒ { 22 | reversed.encode(Map("foo" → "bar")) <=> mapCodec.encode(Map("oof" → "rab")) 23 | reversed.decodeJson(jsonMap("foo" → "bar")) ≡ mapCodec.decodeJson(jsonMap("oof" → "rab")) 24 | }) 25 | 26 | "xmapKeys" in mapCodec.xmapKeys[String](_.reverse)(_.reverse).calc(reversed ⇒ { 27 | reversed.encode(Map("foo" → "bar")) <=> mapCodec.encode(Map("oof" → "bar")) 28 | reversed.decodeJson(jsonMap("foo" → "bar")) ≡ mapCodec.decodeJson(jsonMap("oof" → "bar")) 29 | }) 30 | 31 | "xmapValues" in mapCodec.xmapValues[String](_.reverse)(_.reverse).calc(reversed ⇒ { 32 | reversed.encode(Map("foo" → "bar")) <=> mapCodec.encode(Map("foo" → "rab")) 33 | reversed.decodeJson(jsonMap("foo" → "bar")) ≡ mapCodec.decodeJson(jsonMap("foo" → "rab")) 34 | }) 35 | 36 | "xmapDisjunction" in stringCodec.xmapDisjunction[Int](attempt(_.toInt))(_.toString).calc(intCodec ⇒ { 37 | intCodec.encode(3) <=> Json.jString("3") 38 | intCodec.decodeJson(Json.jString("3")) ≡ DecodeResult.ok(3) 39 | intCodec.decodeJson(Json.jString("a")) ≡ DecodeResult.fail("a", CursorHistory(Nil)) 40 | }) 41 | 42 | "wrapExceptions" in on(Thing(null) -> 4, (null, 3)).calling((interceptEncode _).tupled).produces( 43 | List( 44 | "pimpathon.CodecException: ", 45 | "\tat Encode(thingCodec).(:0)", 46 | "\tat Encode(hcursorCodec).(:0)", 47 | "Caused by: java.lang.NullPointerException" 48 | ), 49 | List( 50 | "pimpathon.CodecException: ", 51 | "\tat Encode(thingCodec).(:0)", 52 | "Caused by: java.lang.NullPointerException" 53 | ) 54 | ) 55 | 56 | private def interceptEncode(thing: Thing, lines: Int): List[String] = 57 | intercept[CodecException](CodecJson.derived[Thing].encode(thing)).stackTraceAsString().lines.toList.take(lines) 58 | 59 | private case class Thing(cursor: HCursor) 60 | 61 | private object Thing { 62 | implicit val thingCodec: CodecJson[Thing] = 63 | CodecJson.derived[HCursor].wrapExceptions("hcursorCodec").xmap[Thing](Thing(_))(_.cursor).wrapExceptions("thingCodec") 64 | } 65 | 66 | // Searching for a better name before making this a pimp (and one producing Either[A, B]) 67 | private def attempt[A, B](f: A ⇒ B)(a: A): A \/ B = a.attempt(f).fold(_ => -\/(a), \/-(_)) 68 | } 69 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/argonautTests/DecodeJsonTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.argonautTests 2 | 3 | import _root_.argonaut._ 4 | import pimpathon.PSpec 5 | import pimpathon.argonaut._ 6 | 7 | 8 | class DecodeJsonSpec extends PSpec with JsonUtil { 9 | "beforeDecode" in decoder.beforeDecode(reverse).decodeJson(json) ≡ decoder.decodeJson(reverse(json)) 10 | "compose" in decoder.compose(reverse).decodeJson(json) ≡ decoder.decodeJson(reverse(json)) 11 | "upcast" in Derived.codec.upcast[Base].decodeJson(derivedEncoded) ≡ DecodeResult.ok(derived) 12 | 13 | "mapEntries" in 14 | mapDecoder.mapEntries(reverseEntry).decodeJson(jsonMap("foo" → "bar")) ≡ mapDecoder.decodeJson(jsonMap("oof" → "rab")) 15 | 16 | "mapKeys" in 17 | mapDecoder.mapKeys(_.reverse).decodeJson(jsonMap("foo" → "bar")) ≡ mapDecoder.decodeJson(jsonMap("oof" → "bar")) 18 | 19 | "mapValues" in 20 | mapDecoder.mapValues(_.reverse).decodeJson(jsonMap("foo" → "bar")) ≡ mapDecoder.decodeJson(jsonMap("foo" → "rab")) 21 | } 22 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/argonautTests/EncodeJsonTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.argonautTests 2 | 3 | import argonaut.StringWrap.StringToStringWrap 4 | import pimpathon.PSpec 5 | import pimpathon.argonaut._ 6 | import sjc.delta.argonaut.json.actualExpected.flat._ 7 | import sjc.delta.matchers.syntax.anyDeltaMatcherOps 8 | 9 | 10 | class EncodeJsonSpec extends PSpec with JsonUtil { 11 | "afterEncode" in encoder.afterEncode(reverse).encode(list) <=> reverse(encoder.encode(list)) 12 | "andThen" in encoder.andThen(reverse).encode(list) <=> reverse(encoder.encode(list)) 13 | "downcast" in Base.encoder.downcast[Derived].encode(derived) <=> derivedEncoded 14 | "add" in encoder.add("length" := _.length).encode(list) <=> encoder.encode(list).addIfMissing("length" := list.length) 15 | 16 | "contramapEntries" in 17 | mapEncoder.contramapEntries[String, String](reverseEntry).encode(Map("foo" → "bar")) <=> mapEncoder.encode(Map("oof" → "rab")) 18 | 19 | "contramapKeys" in 20 | mapEncoder.contramapKeys[String](_.reverse).encode(Map("foo" → "bar")) <=> mapEncoder.encode(Map("oof" → "bar")) 21 | 22 | "contramapValues" in 23 | mapEncoder.contramapValues[String](_.reverse).encode(Map("foo" → "bar")) <=> mapEncoder.encode(Map("foo" → "rab")) 24 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/argonautTests/JsonUtil.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.argonautTests 2 | 3 | import _root_.argonaut.Json._ 4 | import _root_.argonaut._ 5 | import pimpathon.option._ 6 | 7 | import scala.collection.immutable.{Map ⇒ ▶:} 8 | 9 | 10 | trait JsonUtil { 11 | def reverse(json: Json): Json = json.withArray(_.reverse) 12 | def reverse[A](decodeResult: DecodeResult[List[A]]): DecodeResult[List[A]] = decodeResult.map(_.reverse) 13 | 14 | val codec: CodecJson[List[String]] = CodecJson.derived[List[String]] 15 | val mapCodec: CodecJson[String ▶: String] = CodecJson.derived[String ▶: String] 16 | val stringCodec: CodecJson[String] = CodecJson.derived[String] 17 | val (encoder, decoder) = (codec.Encoder, codec.Decoder) 18 | val (mapEncoder, mapDecoder) = (mapCodec.Encoder, mapCodec.Decoder) 19 | 20 | val list = List("food", "foo", "bard", "bar") 21 | val json = Json.jArray(list.map(Json.jString)) 22 | def jsonMap(kvs: (String, String)*) = Json.jObjectAssocList(kvs.map { case (k, v) ⇒ (k, Json.jString(v)) }.toList) 23 | 24 | trait Base 25 | object Base { val encoder = EncodeJson[Base]({ case d: Derived ⇒ Derived.codec.encode(d) }) } 26 | 27 | case class Derived(i: Int) extends Base 28 | object Derived { implicit val codec: CodecJson[Derived] = CodecJson.casecodec1(Derived.apply, Derived.unapply)("i") } 29 | 30 | val derived = Derived(123) 31 | val derivedEncoded = Derived.codec.encode(derived) 32 | 33 | val bananaMan: BananaMan = BananaMan( 34 | name = "Eric", 35 | age = 3, 36 | lying = true, 37 | address = Address(List("29 Acacia Road", "Nuttytown")), 38 | preferences = Preferences(bananas = true), 39 | width = 33.5, 40 | potatoes = Nil, 41 | knownUnknowns = KnownUnknowns(), 42 | awkward = Awkward(`1` = "one") 43 | ) 44 | 45 | val acaciaRoad = bananaMan.address.lines.map(jString) 46 | val bananas = JsonObject.empty + ("bananas", jBool(bananaMan.preferences.bananas)) 47 | val intObj = JsonObject.empty + ("1", jString(bananaMan.awkward.`1`)) 48 | 49 | lazy val fields@List(lying, name, address, age, width, preferences, potatoes, knownUnknowns, awkward) = List( 50 | jBool(bananaMan.lying), jString(bananaMan.name), jArray(acaciaRoad), jNumber(bananaMan.age), jNumberOrNull(bananaMan.width), 51 | jObject(bananas), jArrayElements(), jObjectFields(), jObject(intObj) 52 | ) 53 | 54 | lazy val jobj: Json = jObjectFields( 55 | "name" → name, "age" → age, "lying" → lying, "address" → address, "preferences" → preferences, "width" → width, 56 | "potatoes" → potatoes, "knownUnknowns" → knownUnknowns, "awkward" → awkward 57 | ) 58 | 59 | case class BananaMan( 60 | name: String, 61 | age: Int, 62 | lying: Boolean, 63 | address: Address, 64 | preferences: Preferences, 65 | width: Double, 66 | potatoes: List[String], 67 | knownUnknowns: KnownUnknowns, 68 | awkward: Awkward 69 | ) 70 | 71 | object BananaMan { 72 | implicit val bananaManCodec: CodecJson[BananaMan] = 73 | CodecJson.casecodec9(BananaMan.apply _, BananaMan.unapply _)( 74 | "name", "age", "lying", "address", "preferences", "width", "potatoes", "knownUnknowns", "awkward" 75 | ) 76 | } 77 | 78 | case class Awkward(`1`: String) 79 | 80 | object Awkward { 81 | implicit val awkwardCodec: CodecJson[Awkward] = 82 | CodecJson.casecodec1(Awkward.apply _, Awkward.unapply _)("1") 83 | } 84 | 85 | case class KnownUnknowns() 86 | 87 | object KnownUnknowns { 88 | implicit val KnownUnknownsCodec: CodecJson[KnownUnknowns] = CodecJson.derived[KnownUnknowns]( 89 | EncodeJson[KnownUnknowns](_ ⇒ Json.jEmptyObject), 90 | DecodeJson[KnownUnknowns](cursor ⇒ { 91 | if (cursor.focus == Json.jEmptyObject) { 92 | DecodeResult.ok(KnownUnknowns()) 93 | } else { 94 | DecodeResult.fail("Not an empty object", cursor.history) 95 | } 96 | }) 97 | ) 98 | } 99 | 100 | case class Preferences(bananas: Boolean) 101 | 102 | object Preferences { 103 | implicit val preferencesCodec: CodecJson[Preferences] = 104 | CodecJson.casecodec1(Preferences.apply _, Preferences.unapply _)("bananas") 105 | } 106 | 107 | case class Address(lines: List[String]) { 108 | def reverse: Address = copy(lines.reverse) 109 | } 110 | 111 | object Address { 112 | implicit val addressCodec: CodecJson[Address] = 113 | CodecJson.derived[List[String]].xmap[Address](Address(_))(_.lines) 114 | } 115 | 116 | val redacted = jString("redacted") 117 | 118 | def parse(jsonText: String) = Parse.parseOption(jsonText).getOrThrow("not json") 119 | 120 | def obj(socks: Json.JsonAssoc*): Json = Json.jObjectFields(socks: _*) 121 | 122 | def reverseEntry(key: String, value: String): (String, String) = (key.reverse, value.reverse) 123 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/argonautTests/TraversalFrills.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.argonautTests 2 | 3 | import argonaut.Json._ 4 | import argonaut._ 5 | import monocle.Traversal 6 | import pimpathon.PSpec 7 | import pimpathon.argonaut._ 8 | import pimpathon.map._ 9 | import sjc.delta.argonaut.json.actualExpected.flat._ 10 | import sjc.delta.matchers.syntax.anyDeltaMatcherOps 11 | 12 | 13 | class TraversalFrillsSpec extends PSpec with JsonUtil { 14 | "bool" in { 15 | calling(id.bool.getAll) .partitions(fields).into(lying → List(true), others → nil) 16 | calling(id.bool.modify(_ ⇒ false)).partitions(fields).into(lying → jBool(false), others → unchanged) 17 | } 18 | 19 | "string" in { 20 | calling(id.string.getAll) .partitions(fields).into(name → List("Eric"), others → nil) 21 | calling(id.string.modify(_ + "!")).partitions(fields).into(name → jString("Eric!"), others → unchanged) 22 | } 23 | 24 | "array" in { 25 | calling(id.array.getAll) .partitions(fields) 26 | .into(address → List(acaciaRoad), potatoes → List(Nil), others → nil) 27 | 28 | calling(id.array.modify(_.reverse)).partitions(fields).into(address → jArray(acaciaRoad.reverse), others → unchanged) 29 | 30 | id.array.string.getAll(address) ≡ List(List("29 Acacia Road", "Nuttytown")) 31 | id.array.string.modify("Eric" :: _)(address) <=> jArray(jString("Eric") :: acaciaRoad) 32 | id.array.int.getAll(address) ≡ List(List()) 33 | id.array.int.modify(1 :: _)(address) <=> jArrayElements(jNumber(1)) 34 | } 35 | 36 | "obj" in { 37 | calling(id.obj.getAll).partitions(fields) 38 | .into(preferences → List(bananas), knownUnknowns → List(JsonObject.empty), awkward → List(intObj), others → nil) 39 | 40 | calling(id.obj.modify(_ - "bananas")).partitions(fields).into(preferences → jEmptyObject, others → unchanged) 41 | 42 | id.obj.bool.getAll(preferences) ≡ List(Map("bananas" → true)) 43 | id.obj.bool.modify(_ + ("Selina" → true))(preferences) ≡ (("Selina" → jBool(true)) ->: preferences) 44 | 45 | id.obj.obj.bool.getAll(jObjectFields("prefs" → preferences)) ≡ List(Map("prefs" → Map("bananas" -> true))) 46 | 47 | id.obj.obj.bool.modify(_.mapKeysEagerly(_.capitalize))(jObjectFields("prefs" → preferences)) ≡ jObjectFields( 48 | "Prefs" → preferences 49 | ) 50 | } 51 | 52 | // "double" in { 53 | // calling(id.double.getAll).partitions(fields).into(age → List(3.0), width → List(33.5), others → nil) 54 | // 55 | // calling(id.double.modify(_ * 2.0)).partitions(fields) 56 | // .into(age → jNumberOrNull(6.0), width → jNumberOrNull(67.0), others → unchanged) 57 | // } 58 | 59 | "int" in { 60 | calling(id.int.getAll) .partitions(fields).into(age → List(3), others → nil) 61 | calling(id.int.modify(_ * 2)).partitions(fields).into(age → jNumber(6), others → unchanged) 62 | } 63 | 64 | "descendant" - { 65 | "values" in { 66 | id.descendant("age").getAll(jobj) ≡ List(age) 67 | id.descendant("age").modify(_ ⇒ redacted)(jobj) <=> ("age" → redacted) ->: jobj 68 | 69 | id.descendant("{name, age}").getAll(jobj) ≡ List(name, age) 70 | id.descendant("{name, age}").modify(_ ⇒ redacted)(jobj) <=> ("name" → redacted) ->: ("age" → redacted) ->: jobj 71 | } 72 | 73 | "elements" in { 74 | id.descendant("[0, 2]").getAll(jArray(fields)) ≡ List(lying, address) 75 | 76 | id.descendant("[0, 2]").modify(_ ⇒ redacted)(jArray(fields)) <=> jArrayElements( 77 | redacted, name, redacted, age, width, preferences, potatoes, knownUnknowns, awkward 78 | ) 79 | } 80 | 81 | "all" in { 82 | id.descendant("*").getAll(jobj) ≡ List( 83 | name, age, lying, address, preferences, width, potatoes, knownUnknowns, awkward 84 | ) 85 | 86 | id.descendant("*").modify(_ ⇒ jString("redacted"))(jobj) <=> jObjectFields( 87 | "name" → redacted, "age" → redacted, "lying" → redacted, "address" → redacted, "preferences" → redacted, 88 | "width" → redacted, "potatoes" → redacted, "knownUnknowns" → redacted, "awkward" → redacted 89 | ) 90 | } 91 | 92 | "filter" in { 93 | val repeatingThings = parse(""" 94 | |{ 95 | | "repeatingThings" : [ 96 | | { "country" : "United Kingdom", "summary" : "Summarise this" }, 97 | | { "country" : "France", "summary" : "Summarise that" } 98 | | ] 99 | |}""".stripMargin) 100 | 101 | id.descendant("repeatingThings/*[country='United Kingdom']/summary").string.getAll(repeatingThings) ≡ List( 102 | "Summarise this" 103 | ) 104 | 105 | id.descendant("repeatingThings/*[country='United Kingdom']/summary").string.set("UK")(repeatingThings) ≡ parse(""" 106 | |{ 107 | | "repeatingThings" : [ 108 | | { "country" : "United Kingdom", "summary" : "UK" }, 109 | | { "country" : "France", "summary" : "Summarise that" } 110 | | ] 111 | |}""".stripMargin) 112 | } 113 | } 114 | 115 | private lazy val id: Traversal[Json, Json] = Traversal.id[Json] 116 | } 117 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/array.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.nio.charset.Charset 4 | import pimpathon.array._ 5 | 6 | 7 | class ArraySpec extends PSpec { 8 | "toHex" in Array[Byte](126, 87, -85, 30).toHex ≡ "7e57ab1e" 9 | 10 | "readUpToN" in { 11 | def read(input: String, n: Int, bufferSize: Int): (String, Int) = { 12 | val buffer = new Array[Byte](bufferSize) 13 | val count = buffer.readUpToN(n, createInputStream(input)) 14 | 15 | (new String(buffer).substring(0, count), count) 16 | } 17 | 18 | on(9, 8, 7).calling(read("contents", _, 8)).produces(("contents", 8), ("contents", 8), ("content", 7)) 19 | } 20 | 21 | "copyUpToN" in { 22 | def copy(input: String, n: Int, bufferSize: Int): (String, Int) = { 23 | val buffer = new Array[Byte](bufferSize) 24 | val os = createOutputStream() 25 | val count = buffer.copyUpToN(n, createInputStream(input), os) 26 | 27 | (os.toString, count) 28 | } 29 | 30 | on(9, 8, 7).calling(copy("contents", _, 8)).produces(("contents", 8), ("contents", 8), ("content", 7)) 31 | } 32 | 33 | "copyTo" in 34 | Array(1, 2, 3, 4, 5).copyTo(2, Array(0, 0, 0, 0, 0), 1, 3).toList ≡ List(0, 3, 4, 5, 0) 35 | 36 | "asString" in { 37 | Array('a'.toByte, 'b'.toByte, 'c'.toByte).asString(Charset.forName("UTF-8")) ≡ "abc" 38 | Array('a'.toByte, 'b'.toByte, 'c'.toByte).asString ≡ "abc" 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/boolean.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.boolean._ 4 | import pimpathon.builder._ 5 | import pimpathon.scalaz.std.boolean._ 6 | import _root_.scalaz.{-\/, \/-} 7 | 8 | 9 | class BooleanSpec extends PSpec { 10 | "asInt" in truthTableFor(_.asInt).produces(0, 1) 11 | "option" in truthTableFor(_.option(123)).produces(None, Some(123)) 12 | "cond" in truthTableFor(_.cond(123, 456)).produces(456, 123) 13 | "implies" in truthTableFor(_ implies _).produces(t, t, f, t) 14 | "nor" in truthTableFor(_ nor _).produces(t, f, f, f) 15 | "nand" in truthTableFor(_ nand _).produces(t, t, t, f) 16 | 17 | "tapFalse" in { 18 | strings().run(ss ⇒ false.tapFalse(ss += "false")) ≡ List("false") 19 | strings().run(ss ⇒ true.tapFalse(ss += "false")) ≡ Nil 20 | } 21 | 22 | "tapTrue" in { 23 | strings().run(ss ⇒ false.tapTrue(ss += "true")) ≡ Nil 24 | strings().run(ss ⇒ true.tapTrue(ss += "true")) ≡ List("true") 25 | } 26 | 27 | "either" - { 28 | "or" in truthTableFor(_.either(123).or("456")).produces(Left("456"), Right(123)) 29 | } 30 | 31 | "disjunction" - { 32 | "or" in truthTableFor(_.disjunction(123).or("456")).produces(-\/("456"), \/-(123)) 33 | } 34 | 35 | private def truthTableFor[A](fn: Boolean => A): util.on[Boolean]#calling[A] = 36 | util.on(false, true).calling(fn) 37 | 38 | private def truthTableFor[A](fn: (Boolean, Boolean) ⇒ A)(implicit d: DummyImplicit): util.on[(Boolean, Boolean)]#calling[A] = 39 | util.on((f,f), (f,t), (t,f), (t,t)).calling(fn.tupled) 40 | 41 | private lazy val (t, f) = (true, false) 42 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/builder.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.any._ 4 | import pimpathon.builder._ 5 | 6 | 7 | class BuilderSpec extends PSpec { 8 | "+++=" in (ints() +++= List(List(1, 2), List(3, 4))).result() ≡ List(1, 2, 3, 4) 9 | 10 | "on" in { 11 | val (ib, sb) = ints() rpair (_.on[String](_.toInt) ++= List("1", "2")) 12 | (sb.result(), ib.result()) ≡ (List(1, 2), List(1, 2)) 13 | 14 | sb.clear() 15 | (sb.result(), ib.result()) ≡ (Nil, Nil) 16 | } 17 | 18 | "reset" in { 19 | (ints() |> (ib ⇒ (ib.reset(), ib.result()))) ≡ (Nil, Nil) 20 | (ints(1) |> (ib ⇒ (ib.reset(), ib.result()))) ≡ (List(1), Nil) 21 | } 22 | 23 | "run" in { 24 | ints().run() ≡ Nil 25 | ints().run(_ += 1) ≡ List(1) 26 | ints().run(_ += 1, _ += 2) ≡ List(1, 2) 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/classtag.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | 4 | class ClassTagSpec extends PSpec { 5 | "className" in classTag.className[ClassTagSpec] ≡ "pimpathon.ClassTagSpec" 6 | "simplecCassName" in classTag.simpleClassName[ClassTagSpec] ≡ "ClassTagSpec" 7 | "klassOf" in classTag.klassOf[ClassTagSpec] ≡ classOf[ClassTagSpec] 8 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/dynamicVariable.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.dynamicVariable._ 4 | 5 | import scala.util.DynamicVariable 6 | 7 | 8 | class DynamicVariableSpec extends PSpec { 9 | "modify" in { 10 | dyn.modify(_ * 2) 11 | dyn.value ≡ (123 * 2) 12 | } 13 | 14 | "withModification" in { 15 | dyn.withModification(_ * 2) { 16 | dyn.value ≡ (123 * 2) 17 | "foo" 18 | } ≡ "foo" 19 | 20 | dyn.value ≡ 123 21 | } 22 | 23 | private lazy val dyn: DynamicVariable[Int] = perTest.dynamicVariable(123) 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/either.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.{Failure, Success} 4 | 5 | import pimpathon.builder._ 6 | import pimpathon.either.EitherPimps 7 | import pimpathon.function._ 8 | import pimpathon.tuple._ 9 | 10 | 11 | class EitherSpec extends PSpec { 12 | "leftOr" in on(Left("left"), Right("right")).calling(_.leftOr(_ + " !")).produces("left", "right !") 13 | "rightOr" in on(Left("left"), Right("right")).calling(_.rightOr(_ + " !")).produces("left !", "right") 14 | 15 | "rescue" in on(Right(123), Left("456")).calling(_.rescue(_.toInt)).produces(123, 456) 16 | "valueOr" in on(Right(123), Left("456")).calling(_.valueOr(_.toInt)).produces(123, 456) 17 | 18 | "rescuePF" in on(Right(123), Left("456"), Left("123")) 19 | .calling(_.rescue(util.partial("123" → 123))).produces(Right(123), Left("456"), Right(123)) 20 | 21 | "valueOrPF" in on(Right(123), Left("456"), Left("123")) 22 | .calling(_.valueOr(util.partial("123" → 123))).produces(Right(123), Left("456"), Right(123)) 23 | 24 | "bimap" in on(left(1), right("foo")) 25 | .calling(_.bimap(_.toString, _.length)).produces(Left[String, Int]("1"), Right[String, Int](3)) 26 | 27 | "leftMap" in on(left(1), right("foo")) 28 | .calling(_.leftMap(_.toString)).produces(Left[String, String]("1"), Right[String, String]("foo")) 29 | 30 | "rightMap" in on(left(1), right("foo")) 31 | .calling(_.rightMap(_.length)).produces(Left[Int, Int](1), Right[Int, Int](3)) 32 | 33 | "leftFlatMap" in on(Right(123), Left("456"), Left("123")) 34 | .calling(_.leftFlatMap(partial("123" → 123).toRight)).produces(Right(123), Left("456"), Right(123)) 35 | 36 | "rightFlatMap" in on(Left(123), Right("456"), Right("123")) 37 | .calling(_.rightFlatMap(partial("123" → 123).toLeft)).produces(Left(123), Right("456"), Left(123)) 38 | 39 | "tap" in { 40 | (ints(), strings()).tap(is ⇒ ss ⇒ left(1).tap(is += _, ss += _)).tmap(_.reset(), _.reset()) ≡ (List(1), Nil) 41 | 42 | (ints(), strings()).tap(is ⇒ ss ⇒ right("foo").tap(is += _, ss += _)).tmap(_.reset(), _.reset()) ≡ ( 43 | Nil, List("foo") 44 | ) 45 | } 46 | 47 | "tapLeft" in { 48 | ints().run(is ⇒ left(1).tapLeft(is += _)) ≡ List(1) 49 | ints().run(is ⇒ right("foo").tapLeft(is += _)) ≡ Nil 50 | } 51 | 52 | "tapRight" in { 53 | strings().run(ss ⇒ left(1).tapRight(ss += _)) ≡ Nil 54 | strings().run(ss ⇒ right("foo").tapRight(ss += _)) ≡ List("foo") 55 | } 56 | 57 | "addTo" in { 58 | (ints(), strings()).tap(is ⇒ ss ⇒ left(1).addTo(is, ss)).tmap(_.result(), _.result()) ≡ (List(1), Nil) 59 | (ints(), strings()).tap(is ⇒ ss ⇒ right("foo").addTo(is, ss)).tmap(_.result(), _.result()) ≡ (Nil, List("foo")) 60 | } 61 | 62 | "removeFrom" in { 63 | (ints(1), strings("oo")).tap(is ⇒ ss ⇒ left(1).removeFrom(is, ss)).tmap(_.toList, _.toList) ≡ (Nil, List("oo")) 64 | (ints(1), strings("oo")).tap(is ⇒ ss ⇒ right("oo").removeFrom(is, ss)).tmap(_.toList, _.toList) ≡ (List(1), Nil) 65 | } 66 | 67 | "getMessage" in 68 | on(Left(boom), Right("foo")).calling(_.getMessage).produces(Some(boom.getMessage), None) 69 | 70 | "toTry" in on(Left[Throwable, String](boom), Right[Throwable, String]("foo")) 71 | .calling(_.toTry).produces(Failure[String](boom), Success[String]("foo")) 72 | 73 | "toOption" in on(Left[Throwable, String](boom), Right[Throwable, String]("foo")) 74 | .calling(_.toOption).produces(None, Some("foo")) 75 | 76 | private def left(i: Int): Either[Int, String] = Left(i) 77 | private def right(s: String): Either[Int, String] = Right(s) 78 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/frills/AnyTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.frills.any._ 5 | 6 | import scalaz.syntax.validation._ 7 | 8 | 9 | class AnySpec extends PSpec { 10 | "ensure" in on(1, 2).calling(_.ensure("odd")(isEven)).produces("odd".failure, 2.success) 11 | "ensureNel" in on(1, 2).calling(_.ensureNel("odd")(isEven)).produces("odd".failureNel, 2.successNel) 12 | 13 | private lazy val isEven = (i: Int) ⇒ i % 2 == 0 14 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/frills/ListTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.frills.list._ 5 | import scalaz.{NonEmptyList, \/} 6 | 7 | import scalaz.syntax.either._ 8 | 9 | 10 | class ListSpec extends PSpec { 11 | "toNel" in on(nil[Int], List(1)).calling(_.toNel).produces(None, Some(NonEmptyList(1))) 12 | 13 | "onlyDisjunction" in 14 | on(nil[Int], List(1, 2), List(1)).calling(_.onlyDisjunction).produces(nil[Int].left, List(1, 2).left, 1.right) 15 | 16 | "partitionDisjunctions" in 17 | list(1.left, "abc".right, "def".right, 2.left).partitionDisjunctions[List] ≡ (List(1, 2), List("abc", "def")) 18 | 19 | private def list(elements: (Int \/ String)*): List[Int \/ String] = elements.toList 20 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/frills/StringTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.frills.string._ 5 | import scalaz.NonEmptyList 6 | 7 | class StringTest extends PSpec { 8 | "splitToNel" - { 9 | "no seperators" in { 10 | "no-separators-here-no-siree".splitToNel(",") ≡ NonEmptyList("no-separators-here-no-siree") 11 | } 12 | 13 | "one seperator" in { 14 | "first-thing,second-thing".splitToNel(",") ≡ NonEmptyList("first-thing", "second-thing") 15 | } 16 | 17 | "many seperators" in { 18 | "first-thing,second-thing,another-thing,yet-another-thing".splitToNel(",") ≡ NonEmptyList( 19 | "first-thing", "second-thing", "another-thing", "yet-another-thing" 20 | ) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/frills/TryTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.frills.pimpTry._ 5 | import scalaz.{-\/, \/-} 6 | 7 | import scala.util.{Failure, Success} 8 | 9 | 10 | class TrySpec extends PSpec { 11 | "toDisjunction" in 12 | on(Success("foo"), Failure(boom)).calling(_.toDisjunction).produces(\/-("foo"), -\/(boom)) 13 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/frills/function.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.frills 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.frills.function._ 5 | import scalaz.{-\/, \/, \/-} 6 | 7 | 8 | class PartialFunctionSpec extends PSpec { 9 | "disjunction" in on[Int \/ String](-\/(1), \/-("two"), -\/(3), \/-("four")) 10 | .calling((partial(1 → 11) \/ partial("two" → 22)).lift).produces(Some(11), Some(22), None, None) 11 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/function.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.util.{Failure, Success, Random} 4 | 5 | import org.junit.Assert.{assertTrue, assertFalse} 6 | import pimpathon.any._ 7 | import pimpathon.function._ 8 | 9 | 10 | class FunctionSpec extends PSpec { 11 | "attempt" in { 12 | ((i: Int) ⇒ i).attempt(3) ≡ Success(3) 13 | ((i: Int) ⇒ goBoom).attempt(3) ≡ Failure(boom) 14 | } 15 | 16 | "guardWith" in 17 | on(1, 2, 3, 4).calling((double guardWith isEven).lift).produces(None, Some(4), None, Some(8)) 18 | 19 | "unlift" in f.unlift.calc(pf ⇒ { 20 | on(0, 1, 2, 3).calling(pf.isDefinedAt).produces(true, false, true, false) 21 | on(0, 2).calling(pf.apply).produces(0, 2) 22 | assertThrows[MatchError](messageDoesntMatter)(pf(1)) 23 | pf.lift ≡ f 24 | }) 25 | 26 | "tuple2" in (f.tuple2.apply(1, 2) ≡ (None, Some(2))) 27 | "tuple3" in (f.tuple3.apply(1, 2, 3) ≡ (None, Some(2), None)) 28 | "tuple4" in (f.tuple4.apply(1, 2, 3, 4) ≡ (None, Some(2), None, Some(4))) 29 | "tuple5" in (f.tuple5.apply(1, 2, 3, 4, 5) ≡ (None, Some(2), None, Some(4), None)) 30 | 31 | private lazy val f: (Int) ⇒ Option[Int] = (i: Int) ⇒ i.filterSelf(_ % 2 == 0) 32 | private lazy val isEven: Predicate[Int] = _ % 2 == 0 33 | private lazy val double: (Int ⇒ Int) = _ * 2 34 | } 35 | 36 | class Function2Spec extends PSpec { 37 | "tuple2" in (f.tuple2.apply((1, 2), (10, 20)) ≡ (11, 22)) 38 | "tuple3" in (f.tuple3.apply((1, 2, 3), (10, 20, 30)) ≡ (11, 22, 33)) 39 | "tuple4" in (f.tuple4.apply((1, 2, 3, 4), (10, 20, 30, 40)) ≡ (11, 22, 33, 44)) 40 | "tuple5" in (f.tuple5.apply((1, 2, 3, 4, 5), (10, 20, 30, 40, 50)) ≡ (11, 22, 33, 44, 55)) 41 | 42 | private lazy val f: (Int, Int) ⇒ Int = _ + _ 43 | } 44 | 45 | class CurriedFunction2Spec extends PSpec { 46 | "tupled" in ((i: Int) ⇒ (j: Int) ⇒ i + j).tupled((1, 2)) ≡ 3 47 | } 48 | 49 | class PredicateSpec extends PSpec { 50 | "cond" in List(2, 3, 4, 6).map(isEven.cond("even", "odd")) ≡ List("even", "odd", "even", "even") 51 | 52 | "and" in { 53 | List(2, 3, 4, 6).filter(isEven and (_ > 2)) ≡ List(4, 6) 54 | List(2, 3, 4, 6).filter(function.and(isEven, _ > 2)) ≡ List(4, 6) 55 | assertTrue(function.and[Int]().apply(Random.nextInt())) 56 | } 57 | 58 | "or" in { 59 | List(2, 1, 4, 3, 5).filter(isEven or (_ == 3)) ≡ List(2, 4, 3) 60 | List(2, 1, 4, 3, 5).filter(function.or(isEven, _ == 3)) ≡ List(2, 4, 3) 61 | assertFalse(function.or[Int]().apply(Random.nextInt())) 62 | } 63 | 64 | "not" in List(2, 1, 4, 3, 5).filter(isEven.not) ≡ List(1, 3, 5) 65 | 66 | "exists" in List(Nil, List(2), List(3), List(2, 4), List(2, 4, 3)).filter(isEven.exists) ≡ 67 | List(List(2), List(2, 4), List(2, 4, 3)) 68 | 69 | "forall" in 70 | List(Nil, List(2), List(3), List(2, 4), List(2, 4, 3)).filter(isEven.forall) ≡ List(Nil, List(2), List(2, 4)) 71 | 72 | "ifSome" in List(None, Some(3), Some(4), None, Some(6)).filter(isEven.ifSome) ≡ 73 | List(Some(4), Some(6)) 74 | 75 | "first" in List((1, 2), (2, 3)).filter(isEven.first[Int]) ≡ List((2, 3)) 76 | "second" in List((1, 2), (2, 3)).filter(isEven.second[Int]) ≡ List((1, 2)) 77 | 78 | "guard" in on(1, 2, 3, 4).calling((isEven guard double).lift).produces(None, Some(4), None, Some(8)) 79 | 80 | "nand" in { 81 | List(2, 3, 4, 6).filter(function.nand(isEven, _ > 2)) ≡ List(2, 3) 82 | assertFalse(function.nand[Int]().apply(Random.nextInt())) 83 | } 84 | 85 | "nor" in { 86 | List(2, 1, 4, 3, 5).filter(function.nor(isEven, _ == 3)) ≡ List(1, 5) 87 | assertTrue(function.nor[Int]().apply(Random.nextInt())) 88 | } 89 | 90 | private lazy val isEven: Predicate[Int] = _ % 2 == 0 91 | private lazy val double: (Int ⇒ Int) = _ * 2 92 | } 93 | 94 | class PartialFunctionSpec extends PSpec { 95 | "either" in on(1, 2).calling(util.partial(1 → "2").either).produces(Right("2"), Left(2)) 96 | "toRight" in on(1, 2).calling(util.partial(1 → "2").toRight).produces(Right("2"), Left(2)) 97 | "toLeft" in on(1, 2).calling(util.partial(1 → "2").toLeft).produces(Left("2"), Right(2)) 98 | "unify" in on(1, 2).calling(util.partial(2 → 4).unify).produces(1, 4) 99 | 100 | "isUndefinedAt" in 101 | on("oof", "foo").calling(util.partial("foo" → "bar").isUndefinedAt).produces(true, false) 102 | 103 | "first" in 104 | on(1 → "foo", 2 → "bar").calling(util.partial(1 → 2).first[String].lift).produces(Some(2 → "foo"), None) 105 | 106 | "second" in 107 | on("foo" → 1, "bar" → 2).calling(util.partial(1 → 2).second[String].lift).produces(Some("foo" → 2), None) 108 | 109 | "partition" in 110 | util.partial(1 → "one", 3 → "three").partition[List](List(1, 2, 3, 4)) ≡ ((List(2, 4), List("one", "three"))) 111 | 112 | "***" in on((1, 2), (0, 2), (1, 0)) 113 | .calling((util.partial(1 → 2) *** util.partial(2 → 3)).lift).produces(Some((2, 3)), None, None) 114 | 115 | "&&&" in on(1, 2, 3) 116 | .calling((util.partial(1 → 2, 2 → 3) &&& util.partial(1 → 3, 3 → 4)).lift).produces(Some((2, 3)), None, None) 117 | 118 | "|||" in on(Left(1), Right("two"), Left(3), Right("four")) 119 | .calling((util.partial(1 → 11) ||| util.partial("two" → 22)).lift).produces(Some(11), Some(22), None, None) 120 | 121 | "map" in on(1, 2, 3) 122 | .calling(util.partial(1 → 2, 2 → 3).map(_.toString).lift).produces(Some("2"), Some("3"), None) 123 | 124 | "contramap" in on(10, 20, 30) 125 | .calling(util.partial(1 → 2, 2 → 3).contramap[Int](_ / 10).lift).produces(Some(2), Some(3), None) 126 | 127 | "partialChain" in { 128 | def reverse(prefix: Int)(value: String): String = prefix + value.reverse 129 | def duplicate(prefix: Int)(value: String): String = prefix + value + value 130 | 131 | function.partialChain(reverse, duplicate)(1)("Bolton") ≡ "11notloB1notloB" 132 | } 133 | 134 | "partialChain2" in { 135 | def reverse(prefix: Int, suffix: String)(value: String): String = prefix + value.reverse + suffix 136 | def duplicate(prefix: Int, suffix: String)(value: String): String = prefix + value + value + suffix 137 | 138 | function.partialChain2(reverse, duplicate)(1, "2")("Bolton") ≡ "11notloB21notloB22" 139 | } 140 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/java/io/InputStream.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.io 2 | 3 | import scala.language.reflectiveCalls 4 | 5 | import java.io._ 6 | import java.nio.charset.Charset 7 | import java.util.zip.GZIPOutputStream 8 | import pimpathon.PSpec 9 | import pimpathon.any._ 10 | 11 | import scala.util.{Failure, Success} 12 | 13 | 14 | class InputStreamSpec extends PSpec { 15 | "attemptClose" in { 16 | createInputStream().attemptClose() ≡ Success(()) 17 | new ByteArrayInputStream(Array()) { override def close(): Unit = goBoom }.attemptClose() ≡ Failure(boom) 18 | } 19 | 20 | "closeAfter" in { 21 | val is = createInputStream() 22 | 23 | is.closeAfter(_ ⇒ "result") ≡ "result" 24 | is.assertClosed 25 | } 26 | 27 | "closeIf" in { 28 | createInputStream().closeIf(condition = false).assertOpen 29 | createInputStream().closeIf(condition = true).assertClosed 30 | } 31 | 32 | "closeUnless" in { 33 | createInputStream().closeUnless(condition = true).assertOpen 34 | createInputStream().closeUnless(condition = false).assertClosed 35 | } 36 | 37 | "drain" in { 38 | for { closeIn ← List(false, true); closeOut ← List(false, true); input ← List("Input", "Repeat" * 100) } { 39 | val (is, os) = (createInputStream(input), createOutputStream()) 40 | 41 | is.drain(os, closeIn, closeOut) 42 | 43 | os.toString ≡ input 44 | if (closeIn) is.assertClosed else is.assertOpen 45 | if (closeOut) os.assertClosed else os.assertOpen 46 | } 47 | 48 | ignoreExceptions { // Merely verifying (via compilation) that named parameters works, bit redundant. 49 | val (is, os) = (createInputStream(), createOutputStream()) 50 | 51 | is.drain(os, closeOut = false) 52 | is.drain(os, closeIn = false) 53 | is.drain(os, closeOut = false, closeIn = false) 54 | is.drain(os, closeIn = false, closeOut = false) 55 | } 56 | } 57 | 58 | ">>" in { 59 | val (is, os) = (createInputStream("content"), createOutputStream()) 60 | 61 | is >> os 62 | 63 | os.toString ≡ "content" 64 | is.assertOpen 65 | os.assertOpen 66 | } 67 | 68 | "buffered" in { 69 | val (is, os) = (createInputStream("content"), createOutputStream()) 70 | (is.buffered: BufferedInputStream).drain(os) 71 | 72 | os.toString ≡ "content" 73 | } 74 | 75 | "gunzip" in { 76 | import pimpathon.java.io.outputStream._ 77 | 78 | val os = createOutputStream().tap(os ⇒ new GZIPOutputStream(os).closeAfter(_.write("content".getBytes))) 79 | val result = createOutputStream().tap(rs ⇒ createInputStream(os.toByteArray).gunzip.drain(rs)) 80 | 81 | result.toString ≡ "content" 82 | } 83 | 84 | "readUpToN" in { 85 | def read(text: String, n: Int, bufferSize: Int = inputStream.bufferSize): String = { 86 | val withBufferSize = InputStreamUtils(bufferSize = bufferSize); import withBufferSize._ 87 | val (is, os) = (createInputStream(text), createOutputStream()) 88 | 89 | os.tap(is.readUpToN(_, n), _.close()).toString 90 | } 91 | 92 | read("contents", 0) ≡ "" 93 | read("contents", 4) ≡ "cont" 94 | read("contents", 8) ≡ "contents" 95 | read("contents", 9) ≡ "contents" 96 | read("contents", 7, 2) ≡ "content" 97 | read("content", 8, 2) ≡ "content" 98 | 99 | assertThrows[IllegalArgumentException]("requirement failed: You can't read a negative number of bytes!") { 100 | read("contents", -1) 101 | } 102 | } 103 | 104 | "readN" in { 105 | def read(text: String, n: Int): String = { 106 | val (is, os) = (createInputStream(text), createOutputStream()) 107 | os.tap(is.readN(_, n), _.close()).toString 108 | } 109 | 110 | read("contents", 0) ≡ "" 111 | read("contents", 4) ≡ "cont" 112 | read("contents", 8) ≡ "contents" 113 | 114 | assertThrows[IllegalArgumentException]("requirement failed: You can't read a negative number of bytes!") { 115 | read("contents", -1) 116 | } 117 | 118 | assertThrows[IOException]("Failed to read 9 bytes, only 8 were available")(read("contents", 9)) 119 | } 120 | 121 | "toByteArray" in new String(createInputStream("contents").toByteArray) ≡ "contents" 122 | 123 | "asString" in { 124 | new ByteArrayInputStream("contents".getBytes).asString ≡ "contents" 125 | val UTF8 = Charset.forName("UTF-8") 126 | new ByteArrayInputStream("contents".getBytes(UTF8)).asString(UTF8) ≡ "contents" 127 | } 128 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/java/io/OutputStream.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.io 2 | 3 | import java.io._ 4 | import java.util.zip.GZIPInputStream 5 | import pimpathon.PSpec 6 | import pimpathon.any._ 7 | 8 | import scala.util.{Failure, Success} 9 | 10 | 11 | class OutputStreamSpec extends PSpec { 12 | "attemptClose" in { 13 | createOutputStream().attemptClose() ≡ Success(()) 14 | new ByteArrayOutputStream() { override def close(): Unit = goBoom }.attemptClose() ≡ Failure(boom) 15 | } 16 | 17 | "closeAfter" in { 18 | val os = createOutputStream() 19 | 20 | os.closeAfter(_ ⇒ "result") ≡ "result" 21 | os.assertClosed 22 | } 23 | 24 | "closeIf" in { 25 | createOutputStream().closeIf(condition = false).assertOpen 26 | createOutputStream().closeIf(condition = true).assertClosed 27 | } 28 | 29 | "closeUnless" in { 30 | createOutputStream().closeUnless(condition = true).assertOpen 31 | createOutputStream().closeUnless(condition = false).assertClosed 32 | } 33 | 34 | "drain" in { 35 | for { closeIn ← List(false, true); closeOut ← List(false, true); input ← List("Input", "Repeat" * 100) } { 36 | val (is, os) = (createInputStream(input), createOutputStream()) 37 | 38 | os.drain(is, closeOut, closeIn) 39 | 40 | os.toString ≡ input 41 | if (closeOut) os.assertClosed else os.assertOpen 42 | if (closeIn) is.assertClosed else is.assertOpen 43 | } 44 | 45 | ignoreExceptions { // Merely verifying (via compilation) that named parameters works, bit redundant. 46 | val (is, os) = (createInputStream(), createOutputStream()) 47 | 48 | os.drain(is, closeOut = false) 49 | os.drain(is, closeIn = false) 50 | os.drain(is, closeOut = false, closeIn = false) 51 | os.drain(is, closeIn = false, closeOut = false) 52 | } 53 | } 54 | 55 | "<<" in { 56 | val (is, os) = (createInputStream("content"), createOutputStream()) 57 | 58 | os << is 59 | 60 | os.toString ≡ "content" 61 | os.assertOpen 62 | is.assertOpen 63 | } 64 | 65 | "buffered" in { 66 | val (is, os) = (createInputStream("content"), createOutputStream()) 67 | 68 | os.tap(o ⇒ (o.buffered: BufferedOutputStream).drain(is)).toString ≡ "content" 69 | } 70 | 71 | "gzip" in { 72 | val os = createOutputStream().tap(_.gzip.closeAfter(_.write("content".getBytes))) 73 | val result = createOutputStream().tap(rs ⇒ new GZIPInputStream(createInputStream(os.toByteArray)).drain(rs)) 74 | 75 | result.toString ≡ "content" 76 | } 77 | 78 | "writeUpToN" in { 79 | def write(text: String, n: Int): String = { 80 | val (is, os) = (createInputStream(text), createOutputStream()) 81 | os.tap(_.writeUpToN(is, n), _.close()).toString 82 | } 83 | 84 | write("contents", 4) ≡ "cont" 85 | write("contents", 8) ≡ "contents" 86 | write("contents", 9) ≡ "contents" 87 | write("contents", 0) ≡ "" 88 | 89 | assertThrows[IllegalArgumentException]("requirement failed: You can't read a negative number of bytes!") { 90 | write("contents", -1) 91 | } 92 | } 93 | 94 | "writeN" in { 95 | def write(text: String, n: Int): String = { 96 | val (is, os) = (createInputStream(text), createOutputStream()) 97 | os.tap(_.writeN(is, n), _.close()).toString 98 | } 99 | 100 | write("contents", 4) ≡ "cont" 101 | write("contents", 8) ≡ "contents" 102 | write("contents", 0) ≡ "" 103 | 104 | assertThrows[IllegalArgumentException]("requirement failed: You can't read a negative number of bytes!") { 105 | write("contents", -1) 106 | } 107 | 108 | assertThrows[IOException]("Failed to write 9 only 8 were available")(write("contents", 9)) 109 | } 110 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/java/util/CallableTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util 2 | 3 | import java.util.concurrent.Callable 4 | import pimpathon.PSpec 5 | import pimpathon.java.util.callable._ 6 | 7 | import scala.util.{Failure, Success} 8 | 9 | 10 | class CallableSpec extends PSpec { 11 | "create" in call(callable.create(1)) ≡ 1 12 | 13 | "fromThunk" in call(() ⇒ 1) ≡ 1 14 | 15 | "attempt" in { 16 | on(callable.create(3), callable.create(goBoom)).calling(_.attempt.call).produces(Success(3), Failure(boom)) 17 | 18 | assertThrows[InterruptedException]("fatal") { 19 | callable.create[Int](throw new InterruptedException("fatal")).attempt.call() 20 | } 21 | } 22 | 23 | private def call[A](callable: Callable[A]): A = callable.call() 24 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/java/util/DateTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util 2 | 3 | import java.util.{Calendar, Date} 4 | import pimpathon.PSpec 5 | import pimpathon.any._ 6 | import pimpathon.java.util.date._ 7 | 8 | 9 | class DateSpec extends PSpec { 10 | "addDay" in 11 | on(-1, 1, 7).calling(date(2015, 3, 24).addDay).produces(date(2015, 3, 23), date(2015, 3, 25), date(2015, 4, 1)) 12 | 13 | private def date(year: Int, month: Int, day: Int): Date = 14 | Calendar.getInstance().tap(_.set(year, month, day, 0, 0, 0), _.set(Calendar.MILLISECOND, 0)).getTime 15 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/java/util/concurrent/ThreadFactoryTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util.concurrent 2 | 3 | import java.util.concurrent.ThreadFactory 4 | import pimpathon.PSpec 5 | import pimpathon.any._ 6 | import pimpathon.java.util.concurrent.threadFactory._ 7 | 8 | 9 | class ThreadFactorySpec extends PSpec { 10 | "naming" in basic.naming("name:" + _).calc(factory ⇒ { 11 | factory.newThread(runnable).getName ≡ "name:0" 12 | factory.newThread(runnable).getName ≡ "name:1" 13 | }) 14 | 15 | private lazy val basic = new ThreadFactory { def newThread(r: Runnable): Thread = new Thread(r) } 16 | private lazy val runnable = pimpathon.runnable.create(()) 17 | } 18 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/java/util/concurrent/atomic/AtomicReferenceTest.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.java.util.concurrent.atomic 2 | 3 | import java.util.concurrent.atomic.AtomicReference 4 | import pimpathon.PSpec 5 | import pimpathon.any._ 6 | import pimpathon.java.util.concurrent.atomic.atomicReference._ 7 | 8 | class AtomicReferenceSpec extends PSpec { 9 | "update" in 10 | (new AtomicReference(123) |> (ref => (ref.update(_ * 2), ref.get()))) ≡ (246, 246) 11 | 12 | "updateEither" in { 13 | (new AtomicReference(123) |> (ref => (ref.updateEither(i => Left(i * 2)), ref.get()))) ≡ (Left(246), 123) 14 | (new AtomicReference(123) |> (ref => (ref.updateEither(i => Right(i * 2)), ref.get()))) ≡ (Right(246), 246) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/list.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import org.junit.Assert._ 4 | import pimpathon.builder._ 5 | import pimpathon.list._ 6 | import pimpathon.either._ 7 | 8 | 9 | class ListTest extends PSpec { 10 | "uncons" in on(nil[Int], List(1, 2, 3)) 11 | .calling(_.uncons("empty", l ⇒ s"size: ${l.size}")).produces("empty", "size: 3") 12 | 13 | "unconsC" in on(nil[Int], List(1, 2, 3)) 14 | .calling(_.unconsC("empty", h ⇒ t ⇒ s"head: $h, tail: $t")).produces("empty", "head: 1, tail: List(2, 3)") 15 | 16 | "unsnocC" in on(nil[Int], List(1, 2, 3)) 17 | .calling(_.unsnocC("empty", i ⇒ l ⇒ s"init: $i, last: $l")).produces("empty", "init: List(1, 2), last: 3") 18 | 19 | "emptyTo" in on(nil[Int], List(1, 2, 3)).calling(_.emptyTo(List(1))).produces(List(1), List(1, 2, 3)) 20 | 21 | "calcIfNonEmpty" in on(nil[Int], List(1, 2, 3)) 22 | .calling(_.calcIfNonEmpty(_.reverse)).produces(None, Some(List(3, 2, 1))) 23 | 24 | "mapIfNonEmpty" in on(nil[Int], List(1, 2, 3)) 25 | .calling(_.mapIfNonEmpty(_ * 2)).produces(None, Some(List(2, 4, 6))) 26 | 27 | "zipToMap" in { 28 | nil[Int].zipToMap(nil[Int]) ≡ Map.empty[Int, Int] 29 | List(1).zipToMap(List(2)) ≡ Map(1 → 2) 30 | } 31 | 32 | "zipWith" in List(2, 0).zipWith(List(3))(lr ⇒ lr._1 * lr._2) ≡ List(6) 33 | 34 | "countWithSize" in on(nil[Int], List(0), List(1), List(0, 1)) 35 | .calling(_.countWithSize(_ < 1)).produces(None, Some((1, 1)), Some((0, 1)), Some((1, 2))) 36 | 37 | "sizeGT" in { 38 | assertTrue(nil[Int].sizeGT(-1)) 39 | assertFalse(nil[Int].sizeGT(0)) 40 | assertTrue(List(1, 2).sizeGT(1)) 41 | assertFalse(List(1, 2).sizeGT(2)) 42 | } 43 | 44 | "duplicates" in 45 | List("foo", "bar", "foo", "food", "bar", "foo").duplicates ≡ List("bar", "bar", "foo", "foo", "foo") 46 | 47 | "duplicatesBy" in 48 | List("foo", "bar", "bard", "food", "foody").duplicatesBy(_.length) ≡ List("bard", "food", "foo", "bar") 49 | 50 | "distinctBy" in 51 | List("foo", "bar", "bard", "food", "foody", "bardo").distinctBy(_.length) ≡ List("foo", "bard", "foody") 52 | 53 | "countBy" in List("foo", "bard", "food", "barb", "foody", "barby").countBy(_.length) ≡ 54 | Map(1 → List("foo"), 2 → List("foody", "barby"), 3 → List("bard", "food", "barb")) 55 | 56 | "tailOption" in 57 | on(nil[Int], List(0), List(0, 1)).calling(_.tailOption).produces(None, Some(Nil), Some(List(1))) 58 | 59 | "headTail" in { 60 | on(List(1), List(1, 2), List(1, 2, 3)).calling(_.headTail).produces((1, Nil), (1, List(2)), (1, List(2, 3))) 61 | assertThrows[NoSuchElementException]("headTail of empty list")(Nil.headTail) 62 | } 63 | 64 | "initLast" in { 65 | on(List(1), List(1, 2), List(1, 2, 3)).calling(_.initLast).produces((Nil, 1), (List(1), 2), (List(1, 2), 3)) 66 | assertThrows[NoSuchElementException]("initLast of empty list")(Nil.initLast) 67 | } 68 | 69 | "headTailOption" in on(nil[Int], List(1), List(1, 2), List(1, 2, 3)) 70 | .calling(_.headTailOption).produces(None, Some((1, Nil)), Some((1, List(2))), Some((1, List(2, 3)))) 71 | 72 | "initLastOption" in on(nil[Int], List(1), List(1, 2), List(1, 2, 3)) 73 | .calling(_.initLastOption).produces(None, Some((Nil, 1)), Some((List(1), 2)), Some((List(1, 2), 3))) 74 | 75 | "initOption" in on(nil[Int], List(1), List(1, 2), List(1, 2, 3)) 76 | .calling(_.initOption).produces(None, Some(Nil), Some(List(1)), Some(List(1, 2))) 77 | 78 | "const" in on(nil[Int], List('a', 'b', 'c')).calling(_.const(1)).produces(nil[Int], List(1, 1, 1)) 79 | 80 | "mapC" in 81 | on(nil[(Int, Int)], List((1, 2), (2, 3))).calling(_.mapC(a ⇒ b ⇒ a * b)).produces(nil[Int], List(2, 6)) 82 | 83 | "mapFirst" in 84 | on(nil[(Int, Int)], List((1, 2), (2, 3))).calling(_.mapFirst(_ * 2)).produces(nil[(Int, Int)], List((2, 2), (4, 3))) 85 | 86 | "mapSecond" in 87 | on(nil[(Int, Int)], List((1, 2), (2, 3))).calling(_.mapSecond(_ * 2)).produces(nil[(Int, Int)], List((1, 4), (2, 6))) 88 | 89 | "mapValues" in 90 | on(nil[(Int, Int)], List((1, 2), (2, 3))).calling(_.mapValues(_ * 2)).produces(nil[(Int, Int)], List((1, 4), (2, 6))) 91 | 92 | "flatMapValues" in { 93 | on(nil[(String, Int)], List(("a", 1), ("b", 2))) 94 | .calling(_.flatMapValues(v ⇒ (0 to 2).map(i ⇒ v * i))) 95 | .produces(nil[(String, Int)], List(("a", 0), ("a", 1), ("a", 2), ("b", 0), ("b", 2), ("b", 4))) 96 | on(nil[(String, Int)], List(("a", 1), ("b", 2))) 97 | .calling(_.flatMapValues { case 1 ⇒ Some(10); case _ ⇒ None }) 98 | .produces(nil[(String, Int)], List(("a", 10))) 99 | } 100 | 101 | "keys" in 102 | List((2,1), (4,2), (6,3), (4, 1)).keys ≡ List(2, 4, 6, 4) 103 | 104 | "values" in 105 | List((2,1), (4,2), (6,3), (4, 1)).values ≡ List(1, 2, 3, 1) 106 | 107 | "lpair" in 108 | on(nil[Int], List(1, 2, 3)).calling(_.lpair(_ * 2)).produces(nil[(Int, Int)], List((2,1), (4,2), (6,3))) 109 | 110 | "rpair" in 111 | on(nil[Int], List(1, 2, 3)).calling(_.rpair(_ * 2)).produces(nil[(Int, Int)], List((1,2), (2,4), (3,6))) 112 | 113 | "sharedPrefix" in { 114 | nil[Int].sharedPrefix(Nil) ≡ (Nil, Nil, Nil) 115 | List(1).sharedPrefix(List(1)) ≡ (List(1), Nil, Nil) 116 | List(1, 2, 3, 4).sharedPrefix(List(1, 2, 4, 3)) ≡ (List(1, 2), List(3, 4), List(4, 3)) 117 | } 118 | 119 | "fraction" in { 120 | assertEquals(Double.NaN, nil[Int].fraction(_ ⇒ true), 0.0001) 121 | assertEquals(0.0, List(1).fraction(_ < 1), 0.0001) 122 | assertEquals(1.0, List(0).fraction(_ < 1), 0.0001) 123 | assertEquals(0.5, List(0, 1).fraction(_ < 1), 0.0001) 124 | } 125 | 126 | "batchBy" in { 127 | nil[Int].batchBy(_ ⇒ true) ≡ Nil 128 | 129 | List(1 → 1, 1 → 2, 2 → 1, 1 → 3, 2 → 2, 2 → 3).batchBy(_._1) ≡ 130 | List(List(1 → 1, 1 → 2), List(2 → 1), List(1 → 3), List(2 → 2, 2 → 3)) 131 | } 132 | 133 | "batchWhile" in List("foo", "food", "bar", "bare", "barf") 134 | .batchWhile(_.mkString("").distinct.length <= 4) ≡ List(List("foo", "food"), List("bar", "bare"), List("barf")) 135 | 136 | "prefixPadTo" in List(1, 2, 3).prefixPadTo(6, 0) ≡ List(0, 0, 0, 1, 2, 3) 137 | 138 | "tap" in { 139 | strings().run(ss ⇒ nil[Int].tap(ss += "empty", _ ⇒ ss += "non-empty")) ≡ List("empty") 140 | strings().run(ss ⇒ List(1).tap(ss += "empty", _ ⇒ ss += "non-empty")) ≡ List("non-empty") 141 | } 142 | 143 | "tapEmpty" in { 144 | strings().run(ss ⇒ nil[Int].tapEmpty(ss += "empty")) ≡ List("empty") 145 | strings().run(ss ⇒ List(1).tapEmpty(ss += "empty")) ≡ Nil 146 | } 147 | 148 | "tapNonEmpty" in { 149 | strings().run(ss ⇒ nil[Int].tapNonEmpty(_ ⇒ ss += "non-empty")) ≡ Nil 150 | strings().run(ss ⇒ List(1).tapNonEmpty(_ ⇒ ss += "non-empty")) ≡ List("non-empty") 151 | } 152 | 153 | "amass" in List(1, 2, 3, 4).amass { case i if i % 2 == 0 ⇒ List(i, -i) } ≡ List(2, -2, 4, -4) 154 | 155 | "interleave" in List(1, 2, 3).interleave(List(10, 20)) ≡ List(1, 10, 2, 20, 3) 156 | 157 | "interleaveWith" in 158 | List(1, 2, 3).interleaveWith(List("ten", "twenty"))(_.valueOr(_.toString)) ≡ List("1", "ten", "2", "twenty", "3") 159 | 160 | "cartesianProduct" in List(List(1, 2), List(10, 20), List(100, 200)).cartesianProduct ≡ 161 | (for { a ← List(1, 2); b ← List(10, 20); c ← List(100, 200) } yield List(a, b, c)) 162 | 163 | "onlyOrThrow" in { 164 | on(nil[Int], List(1, 2)).calling(_.onlyOrThrow(exception)).throws("List()", "List(1, 2)") 165 | List(1).onlyOrThrow(_ ⇒ new Exception()) ≡ 1 166 | } 167 | 168 | "onlyEither" in 169 | on(nil[Int], List(1, 2), List(1)).calling(_.onlyEither).produces(Left(Nil), Left(List(1, 2)), Right(1)) 170 | 171 | "onlyOrEither" in 172 | on(nil[Int], List(1, 2), List(1)).calling(_.onlyOrEither(_.size)).produces(Left(0), Left(2), Right(1)) 173 | 174 | "onlyOption" in on(nil[Int], List(1, 2), List(1)).calling(_.onlyOption).produces(None, None, Some(1)) 175 | 176 | "zipExact" in { 177 | Nil.zipExact(Nil) ≡ (Nil, None) 178 | List(1, 2, 3).zipExact(List(4, 5, 6)) ≡ (List((1, 4), (2, 5), (3, 6)), None) 179 | List(1, 2, 3).zipExact(Nil) ≡ (Nil, Some(Left(List(1, 2, 3)))) 180 | Nil.zipExact(List(4, 5, 6)) ≡ (Nil, Some(Right(List(4, 5, 6)))) 181 | List(1, 2, 3).zipExact(List(4)) ≡ (List((1, 4)), Some(Left(List(2, 3)))) 182 | List(1).zipExact(List(4, 5, 6)) ≡ (List((1, 4)), Some(Right(List(5, 6)))) 183 | } 184 | 185 | "zipExactWith" in { 186 | nil[Int].zipExactWith(nil[Int])(_ + _) ≡ (nil[Int], None) 187 | List(1, 2, 3).zipExactWith(List(4, 5, 6))(_ + _) ≡ (List(5, 7, 9), None) 188 | List(1, 2, 3).zipExactWith(nil[Int])(_ + _) ≡ (nil[Int], Some(Left(List(1, 2, 3)))) 189 | nil[Int].zipExactWith(List(4, 5, 6))(_ + _) ≡ (nil[Int], Some(Right(List(4, 5, 6)))) 190 | List(1, 2, 3).zipExactWith(List(4))(_ + _) ≡ (List(5), Some(Left(List(2, 3)))) 191 | List(1).zipExactWith(List(4, 5, 6))(_ + _) ≡ (List(5), Some(Right(List(5, 6)))) 192 | 193 | List(1).zipExactWith(List(4, 5, 6))(_ + _, _ * -10, _ * 10) ≡ List(5, 50, 60) 194 | List(4, 5, 6).zipExactWith(List(1))(_ + _, _ * -10, _ * 10) ≡ List(5, -50, -60) 195 | } 196 | 197 | "partitionEithers" in { 198 | List(Left(1), Right("abc"), Right("def"), Left(2)).partitionEithers[List] ≡ (List(1, 2), List("abc", "def")) 199 | } 200 | 201 | "toMultiMap" in on(List((1, 10), (1, 11), (2, 20), (2, 21))) 202 | .calling(_.toMultiMap[List], _.toMultiMap[Set]) 203 | .produces(Map(1 → List(10, 11), 2 → List(20, 21)), Map(1 → Set(10, 11), 2 → Set(20, 21))) 204 | 205 | "sortPromoting" in 206 | List("red", "green", "black", "purple").sortPromoting("purple", "green") ≡ List("purple", "green", "black", "red") 207 | 208 | "sortDemoting" in 209 | List("black", "red", "purple", "green").sortDemoting("green", "black") ≡ List("purple", "red", "green", "black") 210 | 211 | "shuffle" in List.range(1, 10).shuffle().sorted ≡ List.range(1, 10) 212 | 213 | "updateIf" in { 214 | val isEven = (x: Int) ⇒ x % 2 == 0 215 | val decrement = (x: Int) ⇒ x-1 216 | List.range(1, 6).updateIf(isEven, decrement) ≡ List(1, 1, 3, 3, 5) 217 | } 218 | 219 | "update" in { 220 | List.range(1, 6).update { case x if x % 2 == 0 ⇒ x - 1 } ≡ List(1, 1, 3, 3, 5) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/multiMap.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.builder._ 4 | import pimpathon.multiMap._ 5 | 6 | import scala.collection.generic.CanBuildFrom 7 | import scala.collection.{mutable => M} 8 | 9 | 10 | class MultiMapSpec extends PSpec { 11 | "multiMapCBF" in { 12 | val cbf = MultiMap.build[List, Int, String] 13 | val builder = cbf.apply() 14 | 15 | builder += (1 → "foo") += (1 → "bar") 16 | builder.reset() ≡ Map(1 → List("foo", "bar")) 17 | builder.reset() ≡ Map() 18 | } 19 | 20 | "ignoreFromCBF" in on( 21 | new UnitCanBuildFrom[List[Int], Int], 22 | new UnitCanBuildFrom[List[Int], Int] with IgnoreFromCBF[List[Int], Int, Unit] 23 | ).calling(_.apply(), _.apply(List(1, 2, 3))).produces( 24 | UnitBuilder[Int]("apply()"), UnitBuilder[Int]("apply()"), 25 | UnitBuilder[Int]("apply(List(1, 2, 3))"), UnitBuilder[Int]("apply()") 26 | ) 27 | 28 | "merge" in { 29 | Map(1 → List(1, 2)).merge(MultiMap.empty[List, Int, Int]) ≡ Map(1 → List(1, 2)) 30 | MultiMap.empty[List, Int, Int].merge(Map(1 → List(1, 2))) ≡ Map(1 → List(1, 2)) 31 | Map(1 → List(1)).merge(Map(1 → List(2))) ≡ Map(1 → List(1, 2)) 32 | Map(1 → List(1)).merge(Map(2 → List(2))) ≡ Map(1 → List(1), 2 → List(2)) 33 | Map(1 → Set(1)).merge(Map(1 → Set(2))) ≡ Map(1 → Set(1, 2)) 34 | } 35 | 36 | "select" in Map(1 → List(2), 2 → List(3, 4)).select(_.head) ≡ Map(1 → 2, 2 → 3) 37 | 38 | "append" in { 39 | MultiMap.empty[List, Int, Int].append(1, List(2, 3)) ≡ Map(1 → List(2, 3)) 40 | Map(1 → List(2)).append(1, List(3)) ≡ Map(1 → List(2, 3)) 41 | Map(1 → List(2, 3)).append(1, Nil) ≡ Map(1 → List(2, 3)) 42 | } 43 | 44 | "onlyOption" in on( 45 | Map(1 → Nil, 2 → List(20)), Map(1 → List(10, 11), 2 → List(20)), Map(1 → Nil), MultiMap.empty[List, Int, Int] 46 | ).calling(_.onlyOption).produces(Some(Map(2 → 20)), None, None, None) 47 | 48 | "headTailOption" in on( 49 | Map(1 → List(10, 11), 2 → List(20)), Map(1 → Nil, 2 → List(20)), Map(1 → Nil), MultiMap.empty[List, Int, Int] 50 | ).calling(_.headTailOption).produces( 51 | Some(Map(1 → 10, 2 → 20), Map(1 → List(11))), Some(Map(2 → 20), MultiMap.empty[List, Int, Int]), None, None 52 | ) 53 | 54 | "flatMapValues" in Map(0 → List(1, 2), 1 → List(2, 3)).flatMapValues(v ⇒ List(v, -v)) ≡ 55 | Map(0 → List(1, -1, 2, -2), 1 → List(2, -2, 3, -3)) 56 | 57 | "flatMapValuesU" in { 58 | Map(0 → List(1, 2), 1 → List(2, 3)).flatMapValuesU(v ⇒ Set(v, -v)) ≡ 59 | Map(0 → Set(1, -1, 2, -2), 1 → Set(2, -2, 3, -3)) 60 | 61 | Map(0 → Vector(1, 2), 1 → Vector(2, 3)).flatMapValuesU(v ⇒ List(v, -v)) ≡ 62 | Map(0 → List(1, -1, 2, -2), 1 → List(2, -2, 3, -3)) 63 | } 64 | 65 | "pop" in on(Map(1 → List(2, 3), 2 → List(3))).calling(_.pop(1), _.pop(2), _.pop(3)) 66 | .produces(Map(1 → List(3), 2 → List(3)), Map(1 → List(2, 3)), Map(1 → List(2, 3), 2 → List(3))) 67 | 68 | "sequence" in Map(1 → List(10, 11), 2 → List(20, 21)).sequence ≡ 69 | List(Map(1 → 10, 2 → 20), Map(1 → 11, 2 → 21)) 70 | 71 | "sliding" in Map(1 → List(11, 12, 13), 2 → List(21, 22, 23)).multiMap.sliding(2) ≡ 72 | List(Map(1 → List(11, 12), 2 → List(21, 22)), Map(1 → List(12, 13), 2 → List(22, 23))) 73 | 74 | "getOrEmpty" in { 75 | on(Map(1 → List(2))).calling(_.getOrEmpty(1), _.getOrEmpty(2)).produces(List(2), Nil) 76 | on(Map(1 → Set(2))).calling(_.getOrEmpty(1), _.getOrEmpty(2)).produces(Set(2), Set()) 77 | } 78 | 79 | "multiMap" - { 80 | "head" in on( 81 | Map(1 → List(10, 11), 2 → List(20)), Map(1 → Nil, 2 → List(20)), MultiMap.empty[List, Int, Int] 82 | ).calling(_.multiMap.head).produces(Map(1 → 10, 2 → 20), Map(2 → 20), Map()) 83 | 84 | "tail" in on( 85 | Map(1 → List(10, 11), 2 → List(20)), Map(1 → Nil, 2 → List(20)), Map(1 → Nil), MultiMap.empty[List, Int, Int] 86 | ).calling(_.multiMap.tail).produces(Map(1 → List(11)), Map(), Map(), Map()) 87 | 88 | "values" in { 89 | Map(1 → List(1), 2 → List(2, 3)).multiMap.values ≡ List(1, 2, 3) 90 | Map(1 → Set(1), 2 → Set(2, 3)).multiMap.values ≡ Set(1, 2, 3) 91 | } 92 | 93 | "reverse" in Map(1 → List(2, 3), 2 → List(3, 4)).multiMap.reverse ≡ 94 | Map(2 → List(1), 3 → List(1, 2), 4 → List(2)) 95 | 96 | "mapEntries" in 97 | Map(1 → List(10, 11), 2 → List(20, 21), 3 → List(30, 31)).multiMap.mapEntries(k ⇒ vs ⇒ (k % 2, vs)) ≡ 98 | Map(0 → List(20, 21), 1 → List(10, 11, 30, 31)) 99 | 100 | "mapEntriesU" in 101 | Map(1 → List(10, 11), 2 → List(20, 21), 3 → List(30, 31)).multiMap.mapEntriesU(k ⇒ vs ⇒ (k % 2, vs.toSet)) ≡ 102 | Map(0 → Set(20, 21), 1 → Set(10, 11, 30, 31)) 103 | 104 | "mapValues" in 105 | Map(1 → List(10, 11), 2 → List(20, 21), 3 → List(30, 31)).multiMap.mapValues(v ⇒ v * 2) ≡ 106 | Map(1 → List(20, 22), 2 → List(40, 42), 3 → List(60, 62)) 107 | } 108 | 109 | class UnitCanBuildFrom[From, Elem] extends CanBuildFrom[From, Elem, Unit] { 110 | def apply(): M.Builder[Elem, Unit] = UnitBuilder[Elem]("apply()") 111 | def apply(from: From): M.Builder[Elem, Unit] = UnitBuilder[Elem](s"apply($from)") 112 | } 113 | 114 | case class UnitBuilder[E](from: String) extends M.Builder[E, Unit] { 115 | def +=(elem: E): this.type = this 116 | def clear(): Unit = {} 117 | def result(): Unit = () 118 | override def toString = s"UnitBuilder($from)" 119 | } 120 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/mutableMap.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.mutableMap._ 4 | 5 | import scala.collection.{mutable => M} 6 | 7 | 8 | class MutableMapSpec extends PSpec { 9 | "retainKeys" in { 10 | empty.retainKeys(_ ⇒ false) ≡ empty 11 | nonEmpty.retainKeys(_ ⇒ false) ≡ empty 12 | nonEmpty.retainKeys(_ ⇒ true) ≡ nonEmpty 13 | M.Map(1 → 2, 2 → 3).retainKeys(_ == 1) ≡ nonEmpty 14 | } 15 | 16 | "retainValues" in { 17 | empty.retainValues(_ ⇒ false) ≡ empty 18 | nonEmpty.retainValues(_ ⇒ false) ≡ empty 19 | nonEmpty.retainValues(_ ⇒ true) ≡ nonEmpty 20 | M.Map(1 → 2, 2 → 3).retainValues(_ == 2) ≡ nonEmpty 21 | } 22 | 23 | private def empty = M.Map[Int, Int]() 24 | private def nonEmpty = M.Map[Int, Int](1 → 2) 25 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/nestedMap.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.builder._ 4 | import pimpathon.nestedMap._ 5 | 6 | 7 | class NestedMapSpec extends PSpec { 8 | "nestedMapCBF" in { 9 | val cbf = NestedMap.build[String, Int, String] 10 | val builder = cbf.apply() 11 | 12 | builder += (("one", 1, "foo")) += (("two", 2, "bar")) 13 | builder.reset() ≡ Map("one" → Map(1 → "foo"), "two" → Map(2 → "bar")) 14 | builder.reset() ≡ NestedMap.empty[String, Int, String] 15 | } 16 | 17 | "nestedMap_mapValuesEagerly" in 18 | Map(1 → Map(2 → 3, 3 → 4), 2 → Map(3 → 4, 4 → 5)).nestedMap.mapValuesEagerly(_ * 2) ≡ 19 | Map(1 → Map(2 → 6, 3 → 8), 2 → Map(3 → 8, 4 → 10)) 20 | 21 | "nestedMap_mapKeysEagerly" in 22 | Map(1 → Map(2 → 3, 3 → 4), 2 → Map(3 → 4, 4 → 5)).nestedMap.mapKeysEagerly(_ * 2) ≡ 23 | Map(1 → Map(4 → 3, 6 → 4), 2 → Map(6 → 4, 8 → 5)) 24 | 25 | "nesteMap_mapEntries" in 26 | Map(1 → Map(2 → 3, 3 → 4), 2 → Map(3 → 4, 4 → 5)).nestedMap.mapEntries { 27 | case (outer, inner, value) ⇒ (outer + 1, inner - 1, value * 10) 28 | } ≡ Map(2 → Map(1 → 30, 2 → 40), 3 → Map(2 → 40, 3 → 50)) 29 | 30 | "flipNesting" in Map(10 → Map(2 → 3, 3 → 4), 20 → Map(3 → 4, 4 → 5)).flipNesting ≡ 31 | Map(2 → Map(10 → 3), 3 → Map(10 → 4, 20 → 4), 4 → Map(20 → 5)) 32 | 33 | "append" in { 34 | on(Map(1 → Map(2 → 3))).calling(_.append(1, 2, 4), _.append(1, 3, 4), _.append(2, 3, 4)).produces( 35 | Map(1 → Map(2 → 4)), Map(1 → Map(2 → 3, 3 → 4)), Map(1 → Map(2 → 3), 2 → Map(3 → 4)) 36 | ) 37 | 38 | on(Map(1 → Map(2 → 3))).calling(_ + ((1, 2, 4)), _ + ((1, 3, 4)), _ + ((2, 3, 4))).produces( 39 | Map(1 → Map(2 → 4)), Map(1 → Map(2 → 3, 3 → 4)), Map(1 → Map(2 → 3), 2 → Map(3 → 4)) 40 | ) 41 | } 42 | 43 | "getOrEmpty" in 44 | on(Map(1 → Map(2 → 3))).calling(_.getOrEmpty(1), _.getOrEmpty(2)).produces(Map(2 → 3), Map()) 45 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/numeric.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import org.junit.Assert._ 4 | import pimpathon.numeric._ 5 | 6 | import scala.math.Numeric._ 7 | 8 | 9 | class NumericSpec extends PSpec { 10 | "xmap" in { 11 | val numericString = 12 | implicitly[Numeric[Int]].xmap[String](_.toString, Integer.parseInt) 13 | 14 | numericString.plus("1", "2") ≡ "3" 15 | numericString.times("2", "3") ≡ "6" 16 | numericString.minus("3", "2") ≡ "1" 17 | numericString.negate("4") ≡ "-4" 18 | numericString.toInt("4") ≡ 4 19 | numericString.toLong("4") ≡ 4L 20 | assertEquals(4.0, numericString.toDouble("4"), 0.001) 21 | assertEquals(4.0, numericString.toFloat("4"), 0.001) 22 | numericString.fromInt(4) ≡ "4" 23 | numericString.compare("1", "2") ≡ (1 compare 2) 24 | 25 | val numericListString = 26 | numericString.xmap[List[String]](List(_), _.head) 27 | 28 | numericListString.plus(List("1"), List("2")) ≡ List("3") 29 | numericListString.times(List("2"), List("3")) ≡ List("6") 30 | numericListString.minus(List("3"), List("2")) ≡ List("1") 31 | numericListString.negate(List("4")) ≡ List("-4") 32 | numericListString.toInt(List("4")) ≡ 4 33 | numericListString.toLong(List("4")) ≡ 4L 34 | assertEquals(4.0, numericListString.toDouble(List("4")), 0.001) 35 | assertEquals(4.0, numericListString.toFloat(List("4")), 0.001) 36 | numericListString.fromInt(4) ≡ List("4") 37 | numericListString.compare(List("1"), List("2")) ≡ (1 compare 2) 38 | } 39 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/option.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import org.junit.Test 4 | import scala.util.Success 5 | 6 | import pimpathon.builder._ 7 | import pimpathon.classTag._ 8 | import pimpathon.option._ 9 | 10 | 11 | class OptionSpec extends PSpec { 12 | "tap" in on(none[String], some("some")) 13 | .calling(o ⇒ strings().run(ss ⇒ o.tap(ss += "none", ss += _))).produces(List("none"), List("some")) 14 | 15 | "tapNone" in on(none[String], some("some")) 16 | .calling(o ⇒ strings().run(ss ⇒ o.tapNone(ss += "none"))).produces(List("none"), Nil) 17 | 18 | "tapSome" in on(none[String], some("some")) 19 | .calling(o ⇒ strings().run(ss ⇒ o.tapSome(ss += _))).produces(Nil, List("some")) 20 | 21 | "getOrThrow" in { 22 | Some("present").getOrThrow("missing") ≡ "present" 23 | Some("present").getOrThrow(new Exception("missing")) ≡ "present" 24 | Some("present").getOrThrow(util.goBoom: Exception) ≡ "present" 25 | 26 | assertThrows[NoSuchElementException]("missing")(None.getOrThrow("missing")) 27 | assertThrows[RuntimeException]("missing")(None.getOrThrow(new RuntimeException("missing"))) 28 | } 29 | 30 | "toTry" in { 31 | none[String].toTry.failed.map(_.getClass.getName) ≡ Success[String](className[NoSuchElementException]) 32 | Some("foo").toTry ≡ Success[String]("foo") 33 | } 34 | 35 | "invert" in on(none[Int], some(0)).calling(_.invert(1)).produces(some(1), none[Int]) 36 | 37 | "amass" in on(none[Int], some(1), some(2), some(3)) 38 | .calling(_.amass(util.partial(2 → none, 3 → some("three")))).produces(none, none, none, some("three")) 39 | 40 | "toEither" in{ 41 | none[String].toEither(42, identity[String]) ≡ Left(42) 42 | some("forty two").toEither(42, identity[String]) ≡ Right("forty two") 43 | some("change me").toEither(1, s ⇒ s.dropRight(2) + "you") ≡ Right("change you") 44 | } 45 | 46 | "containedIn" in { 47 | on(some(1), none, some(2)).calling(_.containedIn(Set(0, 1))).produces(true, false, false) 48 | on(some(1), none, some(2)).calling(_.containedIn(0, 1)).produces(true, false, false) 49 | } 50 | 51 | private def none[A]: Option[A] = None 52 | private def some[A](a: A): Option[A] = Some(a) 53 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/ordering.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.list.ListPimps 4 | import pimpathon.ordering._ 5 | 6 | 7 | class OrderingSpec extends PSpec { 8 | "promote" in on(List(1, 2), List(1, 9), List(9, 2), List(9, 10)) 9 | .calling(_.sorted(Ordering.Int.promote(10, 9))).produces(List(1, 2), List(9, 1), List(9, 2), List(10, 9)) 10 | 11 | "demote" in on(List(1, 2), List(1, 9), List(9, 2), List(9, 10)) 12 | .calling(_.sorted(Ordering.Int.demote(2, 1))).produces(List(2, 1), List(9, 1), List(9, 2), List(9, 10)) 13 | 14 | "and" in 15 | List((1, 2), (1, 3), (2, 3)).shuffle().sorted(Ordering.Int && Ordering.Int) ≡ List((1, 2), (1, 3), (2, 3)) 16 | 17 | "or" in 18 | List(4, 2, 1, 3).shuffle().sorted(bit(0) || bit(1)) ≡ List(4, 2, 1, 3) 19 | 20 | "sameAs" in { 21 | List(1, 2, 3, 4, 5).sorted(Ordering.sameAs(4, 2, 1, 3)) ≡ List(4, 2, 1, 3, 5) 22 | } 23 | 24 | private def bit(index: Int): Ordering[Int] = Ordering.Boolean.on[Int](i ⇒ (i & (1 << index)) != 0) 25 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/random.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import org.junit.Assert._ 4 | import pimpathon.random._ 5 | 6 | import scala.util.Random 7 | 8 | 9 | class RandomSpec extends PSpec { 10 | "between" in { 11 | assertTrue(Random.between(20, 80) >= 20 && Random.between(20, 80) < 80) 12 | assertTrue(Random.between('f', 'u') >= 'f' && Random.between('f', 'u') < 'u') 13 | } 14 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/scalaz/either.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz 2 | 3 | import org.junit.Test 4 | import pimpathon.PSpec 5 | 6 | import scala.{PartialFunction => ~>} 7 | import pimpathon.builder._ 8 | import pimpathon.tuple._ 9 | import pimpathon.util._ 10 | import pimpathon.scalaz.either._ 11 | 12 | import scalaz.syntax.std.option._ 13 | import scalaz.{-\/, \/, \/-} 14 | 15 | 16 | class DisjunctionSpec extends PSpec { 17 | "tap" in { 18 | (ints(), strings()).tap(is ⇒ ss ⇒ left(1).tap(is += _, ss += _)).tmap(_.reset(), _.reset()) ≡ (List(1), Nil) 19 | 20 | (ints(), strings()).tap(is ⇒ ss ⇒ right("foo").tap(is += _, ss += _)).tmap(_.reset(), _.reset()) ≡ 21 | (Nil, List("foo")) 22 | } 23 | 24 | "tapLeft" in { 25 | ints().run(is ⇒ left(1).tapLeft(is += _)) ≡ List(1) 26 | ints().run(is ⇒ right("foo").tapLeft(is += _)) ≡ Nil 27 | } 28 | 29 | "tapRight" in { 30 | strings().run(ss ⇒ left(1).tapRight(ss += _)) ≡ Nil 31 | strings().run(ss ⇒ right("foo").tapRight(ss += _)) ≡ List("foo") 32 | } 33 | 34 | "addTo" in { 35 | (ints(), strings()).tap(is ⇒ ss ⇒ left(1).addTo(is, ss)).tmap(_.result(), _.result()) ≡ (List(1), Nil) 36 | (ints(), strings()).tap(is ⇒ ss ⇒ right("foo").addTo(is, ss)).tmap(_.result(), _.result()) ≡ (Nil, List("foo")) 37 | } 38 | 39 | "removeFrom" in { 40 | (ints(1), strings("foo")).tap(is ⇒ ss ⇒ left(1).removeFrom(is, ss)).tmap(_.toList, _.toList) ≡ (Nil, List("foo")) 41 | 42 | (ints(1), strings("foo")).tap(is ⇒ ss ⇒ right("foo").removeFrom(is, ss)).tmap(_.toList, _.toList) ≡ 43 | (List(1), Nil) 44 | } 45 | 46 | "flatten" in { 47 | locally { 48 | def l(i: Int): Int \/ (Int \/ String) = -\/(1) 49 | def m(i: Int): Int \/ (Int \/ String) = \/-(-\/(i)) 50 | def r(s: String): Int \/ (Int \/ String) = \/-(\/-(s)) 51 | 52 | on[Int \/ (Int \/ String)](l(1), m(2), r("s")).calling(_.flatten).produces(-\/(1), -\/(2), \/-("s")) 53 | } 54 | 55 | locally { 56 | def l(i: Int): (Int \/ String) \/ String = -\/(-\/(1)) 57 | def m(s: String): (Int \/ String) \/ String = -\/(\/-(s)) 58 | def r(s: String): (Int \/ String) \/ String = \/-(s) 59 | 60 | on[(Int \/ String) \/ String](l(1), m("s"), r("s")).calling(_.flatten).produces(-\/(1), \/-("s"), \/-("s")) 61 | } 62 | } 63 | 64 | "leftFlatMap" in on[String \/ Int](\/-(123), -\/("456"), -\/("123")) 65 | .calling(_.leftFlatMap(partial("123" → 123).toRightDisjunction)).produces(\/-(123), -\/("456"), \/-(123)) 66 | 67 | private def left(i: Int): Int \/ String = -\/(i) 68 | private def right(s: String): Int \/ String = \/-(s) 69 | 70 | implicit class PartialFunctionFrills[In, Out](private val self: In ~> Out) { 71 | def toRightDisjunction: In ⇒ In \/ Out = (in: In) ⇒ self.lift(in).toRightDisjunction(in) 72 | } 73 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/scalaz/nel.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.multiMap._ 5 | import pimpathon.scalaz.nel._ 6 | import scalaz.{NonEmptyList, \/} 7 | 8 | import scalaz.syntax.either._ 9 | 10 | 11 | class NelSpec extends PSpec { 12 | "unique" in NonEmptyList(1, 2, 1).unique ≡ NonEmptyList(1, 2) 13 | 14 | "uniqueBy" in NonEmptyList("foo", "bar", "bard", "food", "foody", "bardo").uniqueBy(_.length) ≡ 15 | NonEmptyList("foo", "bard", "foody") 16 | 17 | "filter" in 18 | on(NonEmptyList(1), NonEmptyList(1, 2)).calling(_.filter(_ % 2 == 0)).produces(None, Some(NonEmptyList(2))) 19 | 20 | "filterNot" in 21 | on(NonEmptyList(2), NonEmptyList(1, 2)).calling(_.filterNot(_ % 2 == 0)).produces(None, Some(NonEmptyList(1))) 22 | 23 | "max" in NonEmptyList(1, 3, 2).max(scalaz.std.anyVal.intInstance) ≡ 3 24 | "min" in NonEmptyList(3, 1, 2).min(scalaz.std.anyVal.intInstance) ≡ 1 25 | 26 | "partitionDisjunctions" in { 27 | def nel(head: (Int \/ String), tail: (Int \/ String)*): NonEmptyList[Int \/ String] = 28 | NonEmptyList.fromSeq(head, tail) 29 | 30 | nel(1.left, "abc".right, "def".right, 2.left).partitionDisjunctions[List] ≡ (List(1, 2), List("abc", "def")) 31 | } 32 | 33 | "partitionEithers" in { 34 | NonEmptyList[Either[Int, String]](Left(1), Right("abc"), Right("def"), Left(2)).partitionEithers[List] ≡ 35 | (List(1, 2), List("abc", "def")) 36 | } 37 | 38 | "toMultiMap" in on(NonEmptyList((1, 10), (1, 11), (2, 20), (2, 21))) 39 | .calling(_.toMultiMap[List], _.toMultiMap[Set]) 40 | .produces(Map(1 → List(10, 11), 2 → List(20, 21)), Map(1 → Set(10, 11), 2 → Set(20, 21))) 41 | 42 | "asMultiMap_withKeys" in { 43 | on(NonEmptyList(0, 1, 2, 3)) 44 | .calling(_.asMultiMap[List].withKeys(_ % 2)) 45 | .produces(Map(0 → List(0, 2), 1 → List(1, 3))) 46 | 47 | on(NonEmptyList(0, 1, 2, 3)) 48 | .calling(_.asMultiMap[NonEmptyList].withKeys(_ % 2)) 49 | .produces(Map(0 → NonEmptyList(0, 2), 1 → NonEmptyList(1, 3))) 50 | } 51 | 52 | "onlyOption" in on(NonEmptyList(1), NonEmptyList(1, 2)).calling(_.onlyOption).produces(Some(1), None) 53 | 54 | "onlyEither" in 55 | on(NonEmptyList(1, 2), NonEmptyList(1)).calling(_.onlyEither).produces(Left(NonEmptyList(1, 2)), Right(1)) 56 | 57 | "onlyDisjunction" in 58 | on(NonEmptyList(1, 2), NonEmptyList(1)).calling(_.onlyDisjunction).produces(NonEmptyList(1, 2).left, 1.right) 59 | 60 | "onlyOrDisjunction" in 61 | on(NonEmptyList(1, 2), NonEmptyList(1)).calling(_.onlyOrDisjunction(_.size)).produces(2.left, 1.right) 62 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/scalaz/std/option.scala: -------------------------------------------------------------------------------- 1 | package pimpathon.scalaz.std 2 | 3 | import pimpathon.PSpec 4 | import pimpathon.scalaz.std.option._ 5 | import scalaz.{Failure, Success, NonEmptyList => NEL} 6 | 7 | class OptionSpec extends PSpec { 8 | "toSuccessNel" in values.calling(_.toSuccessNel("fail")).produces(Success(1), Failure(NEL("fail"))) 9 | "toFailureNel" in values.calling(it => it.toFailureNel("succeed")).produces(Failure(NEL(1)), Success("succeed")) 10 | 11 | private lazy val values: on[Option[Int]] = on(Some(1), None) 12 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/set.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import org.junit.Assert._ 4 | import pimpathon.set._ 5 | 6 | import scala.collection.{mutable => M} 7 | 8 | 9 | class SetSpec extends PSpec { 10 | "notContains" in { 11 | assertTrue(Set.empty[Int].notContains(3)) 12 | assertFalse(Set(3).notContains(3)) 13 | } 14 | 15 | "powerSet" in { 16 | Set.empty[Int].powerSet ≡ Set(Set.empty[Int]) 17 | Set(1).powerSet ≡ Set(Set.empty[Int], Set(1)) 18 | Set(1, 2).powerSet ≡ Set(Set.empty[Int], Set(1), Set(2), Set(1, 2)) 19 | } 20 | 21 | "sorted" in Set(4, 1, 2).sorted.toList ≡ List(1, 2, 4) 22 | 23 | "mutable" in on(Set(1, 2)).calling(_.mutable, _.toMutable).produces(M.Set(1, 2), M.Set(1, 2)) 24 | 25 | "amass" in Set(1, 2, 3, 4).amass { case i if i % 2 == 0 ⇒ Set(i, -i) } ≡ Set(2, -2, 4, -4) 26 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/stream.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.any._ 4 | import pimpathon.function.Predicate 5 | import pimpathon.stream._ 6 | 7 | import scala.collection.{mutable => M} 8 | 9 | 10 | class StreamSpec extends PSpec { 11 | "cond" in { 12 | stream.cond[Int](cond = false, util.goBoom) ≡ Stream.empty[Int] 13 | stream.cond(cond = true, Stream(1, 2, 3)) ≡ Stream(1, 2, 3) 14 | } 15 | 16 | "continuallyWhile" in { 17 | stream.continuallyWhile(1)(_ ⇒ false) ≡ Stream.empty[Int] 18 | stream.continuallyWhile(1)(_ ⇒ true).take(1000).toList ≡ List.fill(1000)(1) 19 | M.Stack[Int](1, 2, 3).tap(ints ⇒ stream.continuallyWhile(ints.pop())(_ < 3).toList ≡ List(1, 2)) 20 | } 21 | 22 | "uncons" in on(Stream.empty[Int], Stream(1, 2, 3)) 23 | .calling(_.uncons("empty", s ⇒ s"size: ${s.size}")).produces("empty", "size: 3") 24 | 25 | "unconsC" in on(Stream.empty[Int], Stream(1, 2, 3)) 26 | .calling(_.unconsC("empty", h ⇒ t ⇒ s"size: ${1 + t.size}")).produces("empty", "size: 3") 27 | 28 | "tailOption" in on(Stream.empty[Int], Stream(0), Stream(0, 1)).calling(_.tailOption) 29 | .produces(None, Some(Stream.empty[Int]), Some(Stream(1))) 30 | 31 | "lazyScanLeft" in 32 | blockingInts(start = 1, end = 4).lazyScanLeft(0)(_ + _).take(4).toList ≡ List(0, 1, 3, 6) 33 | 34 | "reverseInits" in blockingInts(start = 1, end = 4).reverseInits.take(4).toList.map(_.toList) ≡ 35 | List(Nil, List(1), List(1, 2), List(1, 2, 3)) 36 | 37 | private def blockingInts(start: Int, end: Int): Stream[Int] = blockWhen(Stream.iterate(start)(_ + 1))(_ == end) 38 | private def blockWhen[A](in: Stream[A])(p: Predicate[A]): Stream[A] = in.map(_.tapIf(p)(_ ⇒ block())) 39 | private def block(): Unit = this.synchronized(this.wait(0)) 40 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/string.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import _root_.java.nio.charset.Charset 4 | import org.junit.Assert 5 | import pimpathon.string._ 6 | 7 | 8 | class StringSpec extends PSpec { 9 | "pascal" in { 10 | "CreateImageBuilderStreamingURL".pascal ≡ "CreateImageBuilderStreamingUrl" 11 | } 12 | 13 | "stripAffixes" in 14 | on("(foo)", "(foo", "oof)", "ooo").calling(_.stripAffixes("(", ")")).produces("foo", "foo", "oof", "ooo") 15 | 16 | "affixWith" in 17 | on("(foo)", "(foo", "oof)", "ooo").calling(_.affixWith("(", ")")).produces("(foo)", "(foo)", "(oof)", "(ooo)") 18 | 19 | "quote" in 20 | on("foo", "\"foo", "foo\"", "\"foo\"").calling(_.quote).produces("\"foo\"", "\"\"foo\"", "\"foo\"\"", "\"\"foo\"\"") 21 | 22 | "quoteWith" in 23 | on("foo", "'foo", "foo'", "'foo'").calling(_.quoteWith('\'')).produces("'foo'", "''foo'", "'foo''", "''foo''") 24 | 25 | "unquote" in 26 | on("foo", "\"foo", "foo\"", "\"foo\"").calling(_.unquote).produces("foo", "foo", "foo", "foo") 27 | 28 | "hyphenate" in 29 | on("foo", "fooFood").calling(_.hyphenate).produces("foo", "foo-food") 30 | 31 | "prefixWith" in { 32 | "".prefixWith("") ≡ "" 33 | on("", "-suffix", "prefix").calling(_.prefixWith("prefix")).produces("prefix", "prefix-suffix", "prefix") 34 | } 35 | 36 | "sharedPrefix" in { 37 | "".sharedPrefix("") ≡ ("", "", "") 38 | "1".sharedPrefix("1") ≡ ("1", "", "") 39 | "1234".sharedPrefix("1243") ≡ ("12", "34", "43") 40 | } 41 | 42 | "suffixWith" in { 43 | "".suffixWith("") ≡ "" 44 | on("", "prefix-", "suffix").calling(_.suffixWith("suffix")).produces("suffix", "prefix-suffix", "suffix") 45 | } 46 | 47 | "prefixPadTo" in "-suffix".prefixPadTo(10, 'p') ≡ "ppp-suffix" 48 | 49 | "md5" in "blah".md5 ≡ "6f1ed002ab5595859014ebf0951522d9" 50 | 51 | "emptyTo" in on("", "def").calling(_.emptyTo("abc")).produces("abc", "def") 52 | 53 | "toByteArray" in { 54 | Assert.assertArrayEquals(Array('a'.toByte, 'b'.toByte, 'c'.toByte), "abc".toByteArray(Charset.forName("UTF-8"))) 55 | Assert.assertArrayEquals(Array('d'.toByte, 'e'.toByte, 'f'.toByte), "def".toByteArray) 56 | } 57 | 58 | "wrap" in { 59 | val wrapped = 60 | """|Pimpathon contains pimps 61 | |for classes in core 62 | |scala & java libraries 63 | |and pimps for external 64 | |libraries""".stripMargin 65 | 66 | wrapped.replaceAllLiterally("\n", " ").wrap(24) ≡ wrapped 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/scala/pimpathon/try.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import pimpathon.pimpTry._ 4 | 5 | import scala.util.{Failure, Success} 6 | 7 | 8 | class TrySpec extends PSpec { 9 | "fold" in 10 | on(Success("foo"), Failure(boom)).calling(_.fold(_.getMessage, s ⇒ s)).produces("foo", boom.getMessage) 11 | 12 | "getMessage" in 13 | on(Success("foo"), Failure(boom)).calling(_.getMessage).produces(None, Some(boom.getMessage)) 14 | 15 | "toEither" in 16 | on(Success("foo"), Failure(boom)).calling(_.toEither).produces(Right("foo"), Left(boom)) 17 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/tuple.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.implicitConversions 4 | 5 | import pimpathon.builder._ 6 | import pimpathon.tuple._ 7 | 8 | 9 | class TupleSpec extends PSpec { 10 | "tap" in strings().run(ss ⇒ (1, "foo").tap(i ⇒ s ⇒ ss += (s + i))) ≡ List("foo1") 11 | "calc" in ("123", "abc").calc(_ + _) ≡ "123abc" 12 | "calcC" in ("123", "abc").calcC(a ⇒ b ⇒ a + b) ≡ "123abc" 13 | 14 | "to" in { 15 | implicit def intToString(i: Int): String = i.toString 16 | implicit def doubleToString(d: Double): String = d.toString 17 | 18 | (123, 456.0).to[String] ≡ ("123", "456.0") 19 | } 20 | 21 | "tmap" in (2, "abc").tmap(_ * 3, _.reverse) ≡ (6, "cba") 22 | 23 | "map1" in (2, "abc").map1(_ * 3) ≡ (6, "abc") 24 | 25 | "map2" in (2, "abc").map2(_.reverse) ≡ (2, "cba") 26 | 27 | "addTo" in 28 | (ints(), strings()).tap(is ⇒ ss ⇒ (1, "foo").addTo(is, ss)).tmap(_.result(), _.result()) ≡ (List(1), List("foo")) 29 | 30 | "removeFrom" in 31 | (ints(1), strings("foo")).tap(is ⇒ ss ⇒ (1, "foo").removeFrom(is, ss)).tmap(_.toList, _.toList) ≡ (Nil, Nil) 32 | } -------------------------------------------------------------------------------- /src/test/scala/pimpathon/util.scala: -------------------------------------------------------------------------------- 1 | package pimpathon 2 | 3 | import scala.language.implicitConversions 4 | 5 | import _root_.java.io.{ByteArrayInputStream, ByteArrayOutputStream} 6 | import _root_.java.util.concurrent.atomic.AtomicBoolean 7 | 8 | import scala.{PartialFunction => ~>} 9 | import scala.collection.mutable.ListBuffer 10 | import scala.collection.{mutable => M} 11 | import scala.reflect.ClassTag 12 | import scala.util.{DynamicVariable, Try} 13 | import org.junit.Assert._ 14 | import pimpathon.any._ 15 | import pimpathon.list._ 16 | import pimpathon.tuple._ 17 | import pimpathon.pimpTry._ 18 | 19 | object util extends util 20 | trait util { 21 | implicit class AnyTestPimp[A](val self: A) { 22 | def ≡(expected: A): Unit = assertEquals(expected, self) 23 | } 24 | 25 | def ignore(f: ⇒ Unit): Unit = {} 26 | def ignoreExceptions(f: ⇒ Unit): Unit = Try(f) 27 | 28 | protected val messageDoesntMatter: String = "messageDoesntMatter" 29 | 30 | def assertThrows[T <: Throwable: ClassTag](expectedMessage: String)(f: ⇒ Any): Unit = { 31 | val message = getMessage[T](f).getOrElse(sys.error("Expected exception: " + classTag.className[T])) 32 | 33 | if (!(expectedMessage eq messageDoesntMatter)) { 34 | message ≡ expectedMessage 35 | } 36 | } 37 | 38 | def assertEqualsSet[A](expected: Set[A], actual: Set[A]): Unit = (expected -- actual, actual -- expected).calcC( 39 | missing ⇒ extra ⇒ assertTrue(s"Extra: $extra, Missing: $missing", extra.isEmpty && missing.isEmpty) 40 | ) 41 | 42 | def intercept[T <: Throwable: ClassTag](f: ⇒ Unit): T = 43 | getThrowable[T](f).getOrElse(sys.error("Expected exception: " + classTag.className[T])) 44 | 45 | case class calling[A, B](f: A ⇒ B) { 46 | def partitions(as: A*): partitions = partitions(as.toList) 47 | case class partitions(as: List[A]) { 48 | def into(expectations: (Expectation[A, B])*): Unit = { 49 | val (expectedFailbackFns, abs) = expectations.map(_.value).toList.partitionEithers[List] 50 | 51 | expectedFailbackFns.onlyOption match { 52 | case None ⇒ pairs(as.rpair(f)) ≡ pairs(abs) 53 | case Some(expectedFallbackFn) ⇒ { 54 | as.partition(a ⇒ abs.exists(_._1 == a)).calcC(positive ⇒ remainder ⇒ { 55 | val fallbacks = remainder.rpair(expectedFallbackFn) 56 | 57 | pairs((positive ::: remainder).rpair(f)) ≡ pairs(abs ::: fallbacks) 58 | }) 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | case class on[A](as: A*) { 66 | case class calling[B](fs: (A ⇒ B)*) { 67 | def produces(bs: B*): Unit = (for {f ← fs; a ← as} yield f(a)).toList ≡ bs.toList 68 | def throws(es: String*): Unit = (for {f ← fs; a ← as; m ← Try(f(a)).getMessage} yield m).toList ≡ es.toList 69 | } 70 | } 71 | 72 | implicit class onFnPimps[A, B](private val self: on[A ⇒ B]) { 73 | def maps[C](abs: (A, B)*): Unit = applying(abs.map(_._1): _*).produces(abs.map(_._2): _*) 74 | 75 | def applying(as: A*): on[Unit]#calling[B] = on(()).calling[B]((for { 76 | ab ← self.as 77 | a ← as 78 | } yield (_: Unit) ⇒ ab.apply(a)): _*) 79 | } 80 | 81 | object Expectation { 82 | implicit def tupleAsExpectation[A, B](ab: (A, B)): Expectation[A, B] = Expectation(Right(ab)) 83 | implicit def otherAsExpectation[A, B](ob: (others, B)): Expectation[A, B] = Expectation(Left(_ ⇒ ob._2)) 84 | 85 | implicit def othersUnchangedAsExpectation[A](ou: (others, unchanged)): Expectation[A, A] = 86 | Expectation[A, A](Left((a: A) ⇒ a)) 87 | } 88 | 89 | case class Expectation[A, +B](value: Either[A ⇒ B, (A, B)]) 90 | 91 | private def pairs[A, B](abs: List[(A, B)]): String = abs.mapC(a ⇒ b ⇒ s"$a → $b").mkString(", ") 92 | 93 | def createInputStream(content: String = ""): ByteArrayInputStream with Closeable = 94 | new ByteArrayInputStream(content.getBytes) with Closeable 95 | 96 | def createInputStream(content: Array[Byte]): ByteArrayInputStream with Closeable = 97 | new ByteArrayInputStream(content) with Closeable 98 | 99 | def createOutputStream(): ByteArrayOutputStream with Closeable = 100 | new ByteArrayOutputStream with Closeable 101 | 102 | private def getMessage[T <: Throwable: ClassTag](f: ⇒ Unit): Option[String] = getThrowable[T](f).map(_.getMessage) 103 | 104 | private def getThrowable[T <: Throwable: ClassTag](f: ⇒ Unit): Option[T] = try { f; None } catch { 105 | case t: Throwable ⇒ Some(t.castTo[T].getOrElse(sys.error(s"Invalid exception, expected ${classTag.className[T]}, got: $t"))) 106 | } 107 | 108 | def goBoom: Nothing = throw boom 109 | val boom = new Throwable("Boom !") 110 | def exception[A](message: A): Exception = new Exception(message.toString) 111 | 112 | def currentTime(): Long = dynamicTime.value 113 | def withTime[A](millis: Long)(f: ⇒ A): A = dynamicTime.withValue(millis)(f) 114 | 115 | def partial[A, B](entries: (A, B)*): A ~> B = entries.toMap 116 | 117 | def ints(is: Int*): ListBuffer[Int] = new M.ListBuffer[Int] ++= is 118 | def strings(ss: String*): ListBuffer[String] = new M.ListBuffer[String] ++= ss 119 | 120 | def nil[A]: List[A] = Nil 121 | sealed trait others; case object others extends others 122 | sealed trait unchanged; case object unchanged extends unchanged 123 | 124 | trait Closeable extends _root_.java.io.Closeable { 125 | abstract override def close(): Unit = { closed.set(true); super.close() } 126 | 127 | def assertOpen: Unit = assertFalse(s"expected $kind to be open", closed.get()) 128 | def assertClosed: Unit = assertTrue(s"expected $kind to be closed", closed.get()) 129 | 130 | private val closed = new AtomicBoolean(false) 131 | private def kind = if (this.isInstanceOf[ByteArrayInputStream]) "InputStream" else "OutputStream" 132 | } 133 | 134 | private val dynamicTime = new DynamicVariable[Long](0) 135 | } -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "1.9.3" 2 | --------------------------------------------------------------------------------