├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── build.sbt ├── notes ├── 0.1.markdown ├── 0.2.markdown ├── 0.3.1.markdown ├── 0.3.markdown ├── 0.4.1.markdown ├── 0.4.2.markdown ├── 0.4.markdown ├── 0.5.1.markdown ├── 0.5.markdown ├── 0.6.markdown ├── 0.7.1.markdown ├── 0.7.2.markdown ├── 0.7.3.markdown ├── 0.7.4.markdown ├── 0.7.markdown ├── 1.0.1.markdown ├── 1.0.10.markdown ├── 1.0.11.1.markdown ├── 1.0.11.markdown ├── 1.0.12.markdown ├── 1.0.13.markdown ├── 1.0.14.markdown ├── 1.0.2.markdown ├── 1.0.3.markdown ├── 1.0.4.markdown ├── 1.0.5.markdown ├── 1.0.6.markdown ├── 1.0.7.markdown ├── 1.0.8.markdown ├── 1.0.9.markdown ├── 1.0.markdown ├── 1.1.0.markdown ├── 1.1.1.markdown ├── 1.1.2.markdown ├── 1.1.3.markdown ├── 1.1.4.markdown ├── 1.1.5.markdown ├── 1.1.6.markdown ├── 1.2.markdown ├── 1.3.markdown └── about.markdown ├── project ├── build.properties ├── gpg.sbt └── plugins.sbt └── src ├── main ├── pre-scala-2.13 │ └── grizzled │ │ └── ScalaCompat.scala ├── resources │ └── META-INF │ │ └── mime.types ├── scala-2.13 │ └── grizzled │ │ └── ScalaCompat.scala └── scala │ └── grizzled │ ├── binary.scala │ ├── collection │ ├── Implicits.scala │ └── package.scala │ ├── config │ ├── config.scala │ └── package.scala │ ├── datetime │ ├── DateTimeUtil.scala │ └── Implicits.scala │ ├── file │ ├── Implicits.scala │ ├── Includer.scala │ ├── filter │ │ ├── BackslashContinuedLineIterator.scala │ │ └── package.scala │ ├── package.scala │ └── util.scala │ ├── io │ ├── Implicits.scala │ ├── MultiSource.scala │ ├── PartialReader.scala │ ├── SourceReader.scala │ └── package.scala │ ├── math │ ├── package.scala │ ├── stats.scala │ └── util.scala │ ├── net │ ├── Implicits.scala │ ├── URI.scala │ ├── URL.scala │ ├── URLUtil.scala │ ├── inet.scala │ ├── package.scala │ └── udp.scala │ ├── package.scala │ ├── parsing │ ├── Pushback.scala │ ├── SafeIterator.scala │ ├── StringToken.scala │ └── package.scala │ ├── random │ └── RandomUtil.scala │ ├── reflect.scala │ ├── security │ └── MessageDigest.scala │ ├── string │ ├── Implicits.scala │ ├── WordWrapper.scala │ ├── package.scala │ ├── template │ │ ├── package.scala │ │ └── template.scala │ └── util.scala │ ├── sys.scala │ ├── util.scala │ └── zip │ ├── Zipper.scala │ └── package.scala ├── style.css └── test └── scala └── grizzled ├── BaseSpec.scala ├── BinarySpec.scala ├── ConfigSpec.scala ├── ReflectionSpec.scala ├── SecuritySpec.scala ├── SysSpec.scala ├── datetime ├── DateTimeUtilSpec.scala ├── EnrichedDurationSpec.scala └── ImplicitsSpec.scala ├── file ├── BackslashContinuedLineIteratorSpec.scala ├── FileUtilSpec.scala ├── GrizzledFileSpec.scala └── IncluderSpec.scala ├── io ├── GrizzledSourceSpec.scala ├── MultiSourceSpec.scala ├── RichInputStreamSpec.scala ├── RichReaderSpec.scala └── SourceReaderSpec.scala ├── math ├── MathUtilSpec.scala └── StatsSpec.scala ├── net ├── IPAddressSpec.scala ├── URISpec.scala ├── URLSpec.scala └── URLUtilSpec.scala ├── parsing ├── PushbackSpec.scala └── SafeIteratorSpec.scala ├── random └── RandomSpec.scala ├── string ├── GrizzledCharSpec.scala ├── GrizzledStringSpec.scala ├── StringTemplateSpec.scala ├── StringUtilSpec.scala └── WordWrapperSpec.scala ├── util ├── RichTrySpec.scala └── WithResourceSpec.scala └── zip └── ZipperSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .tool-versions 2 | lib_managed 3 | local_lib 4 | project/boot 5 | project/build/target 6 | project/plugins/lib_managed 7 | project/plugins/project 8 | project/plugins/src_managed 9 | project/plugins/target 10 | project/.bloop 11 | .metals 12 | target 13 | .history 14 | *.sublime-* 15 | .ensime* 16 | .idea 17 | *.iml 18 | atlassian*.xml 19 | tmp 20 | .bloop 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI configuration file. 2 | 3 | sudo: false 4 | 5 | # These directories are cached to S3 at the end of the build. 6 | cache: 7 | directories: 8 | - $HOME/.ivy/cache 9 | - $HOME/.sbt/boot 10 | 11 | language: scala 12 | 13 | #script: 14 | # - sbt ++$TRAVIS_SCALA_VERSION -Dfile.encoding=UTF8 -J-XX:ReservedCodeCacheSize=256M test 15 | 16 | before_cache: 17 | # Tricks to avoid unnecessary cache updates 18 | - find $HOME/.sbt -name "*.lock" | xargs rm 19 | - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm 20 | 21 | jdk: 22 | - openjdk8 23 | 24 | scala: 25 | - 2.13.0 26 | - 2.11.12 27 | - 2.12.8 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # AVSL license 2 | 3 | Copyright © 2010-2019 Brian M. Clapper. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grizzled Scala: A general-purpose library of Scala classes and objects 2 | 3 | [![Build Status](https://travis-ci.org/bmc/grizzled-scala.svg?branch=master)](https://travis-ci.org/bmc/grizzled-scala) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.clapper/grizzled-scala_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.clapper/grizzled-scala_2.11) 5 | 6 | ## Introduction 7 | 8 | Grizzled Scala is a library of miscellaneous utility classes and objects. 9 | Basically, whenever I find myself writing something that's general-purpose, 10 | I put it in here, so I can easily use it in multiple projects. 11 | 12 | The home page for the Grizzled Scala Library is 13 | . Please see that page for 14 | complete details, including installation instructions. 15 | 16 | Grizzled Scala is copyright © 2009-2019 [Brian M. Clapper][] and 17 | is released under the [Apache Software License](LICENSE.md). 18 | 19 | [Brian M. Clapper]: mailto:bmc@clapper.org 20 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Basic settings 3 | 4 | name := "grizzled-scala" 5 | version := "4.11.0" 6 | organization := "org.clapper" 7 | licenses := Seq("ASF" -> url("https://www.apache.org/licenses/LICENSE-2.0")) 8 | homepage := Some(url("http://software.clapper.org/grizzled-scala/")) 9 | description := "A general-purpose Scala utility library" 10 | scalaVersion := "2.13.0" 11 | crossScalaVersions := Seq("2.11.12", "2.12.8", "2.13.0") 12 | 13 | unmanagedSourceDirectories in Compile += { 14 | val sourceDir = (sourceDirectory in Compile).value 15 | CrossVersion.partialVersion(scalaVersion.value) match { 16 | case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13" 17 | case _ => sourceDir / "pre-scala-2.13" 18 | } 19 | } 20 | 21 | // --------------------------------------------------------------------------- 22 | // Additional compiler options and plugins 23 | 24 | autoCompilerPlugins := true 25 | scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked") 26 | bintrayPackageLabels := Seq("library", "grizzled", "scala") 27 | 28 | wartremoverErrors in (Compile, compile) ++= Seq( 29 | Wart.ArrayEquals, 30 | // Wart.Any, 31 | Wart.AnyVal, 32 | Wart.AsInstanceOf, 33 | Wart.EitherProjectionPartial, 34 | Wart.Enumeration, 35 | Wart.ExplicitImplicitTypes, 36 | Wart.FinalCaseClass, 37 | Wart.FinalVal, 38 | Wart.IsInstanceOf, 39 | Wart.JavaConversions, 40 | Wart.LeakingSealed, 41 | Wart.MutableDataStructures, 42 | // Wart.NonUnitStatements, 43 | // Wart.Nothing, 44 | Wart.Null, 45 | Wart.Option2Iterable, 46 | Wart.OptionPartial, 47 | Wart.PublicInference, 48 | Wart.Return, 49 | Wart.StringPlusAny, 50 | Wart.Throw, 51 | Wart.TraversableOps, 52 | Wart.TryPartial, 53 | Wart.Var, 54 | Wart.While 55 | ) 56 | 57 | // --------------------------------------------------------------------------- 58 | // Dependencies 59 | 60 | libraryDependencies ++= Seq( 61 | "org.scala-lang.modules" %% "scala-collection-compat" % "2.0.0", 62 | "org.scalatest" %% "scalatest" % "3.0.8" % Test 63 | ) 64 | 65 | parallelExecution in Test := true 66 | 67 | // --------------------------------------------------------------------------- 68 | // Other tasks 69 | 70 | // --------------------------------------------------------------------------- 71 | // Publishing criteria 72 | 73 | // Don't set publishTo. The Bintray plugin does that automatically. 74 | 75 | publishMavenStyle := true 76 | publishArtifact in Test := false 77 | pomIncludeRepository := { _ => false } 78 | pomExtra := 79 | 80 | git@github.com:bmc/grizzled-scala.git/ 81 | scm:git:git@github.com:bmc/grizzled-scala.git 82 | 83 | 84 | 85 | bmc 86 | Brian Clapper 87 | http://www.clapper.org/bmc 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /notes/0.1.markdown: -------------------------------------------------------------------------------- 1 | * Initial release. 2 | -------------------------------------------------------------------------------- /notes/0.2.markdown: -------------------------------------------------------------------------------- 1 | * In `grizzled.cmd`, the default handler for the "help" command now does 2 | tab completion on the existing commands. 3 | * Changed the way `grizzled.readline` exposes completion context. Instead 4 | of exposing a cursor, it exposes a tokenized list, with a special 5 | `Cursor` token. This approach fits better with Scala's pattern matching. 6 | * `grizzled.collection.MultiIterator` is now covariant, not invariant. 7 | * Miscellaneous internal changes where `yield` is used. 8 | * Changed license to New BSD License. 9 | -------------------------------------------------------------------------------- /notes/0.3.1.markdown: -------------------------------------------------------------------------------- 1 | `grizzled.cmd` changes: 2 | 3 | * "history" command now uses the syntax `history [-n] [regex]` 4 | where *n* is the maximum number of entries to show, and *regex* is a 5 | regular expression to filter history entries. 6 | 7 | * Some commands starting with "." were being incorrectly limited to a 8 | single character (e.g., ".r", but not ".read"). 9 | 10 | * General clean-up and bug fixing in the "redo command" handler. 11 | 12 | * `CommandHandler` classes can now exempt themselves from the history. 13 | 14 | * The `RedoCommandHandler` (which handles the "r" and "!" commands) now 15 | exempts itself from the history. 16 | -------------------------------------------------------------------------------- /notes/0.3.markdown: -------------------------------------------------------------------------------- 1 | * Converted to Scala 2.8.0 2 | * Now must be compiled with [SBT][sbt] version 0.7.0 or better. 3 | * Fixed tail call optimization problem in `grizzled.io.RichInputStream`. 4 | Thanks to Claudio Bley (*cbley /at/ av-test.de*) 5 | * Added grizzled.parsing.MarkdownParser, for parsing Markdown documents. 6 | (Currently uses the [Showdown][showdown] Javascript library, via 7 | [Mozilla Rhino][rhino].) 8 | * `grizzled.cmd.HelpHandler` now supports a ".help" alias. 9 | * Added `grizzled.util.withCloseable` control structure. 10 | * The grizzled.readline API now uses the [Java EditLine][javaeditline] 11 | wrapper for the Unix EditLine library, instead of the one in 12 | Java-Readline. implementation, instead of the one in Java-Readline. 13 | Completion handling is more reliable with the Java Editline 14 | implementation. 15 | * grizzled.cmd now tries to load EditLine first. 16 | 17 | [sbt]: http://code.google.com/p/simple-build-tool 18 | [javaeditline]: http://www.clapper.org/software/java/javaeditline/ 19 | [showdown]: http://attacklab.net/showdown/ 20 | [rhino]: http://www.mozilla.org/rhino/ 21 | -------------------------------------------------------------------------------- /notes/0.4.1.markdown: -------------------------------------------------------------------------------- 1 | * Fixed inadvertent bug in `grizzled.cmd` command handling, when commands 2 | span multiple lines. 3 | 4 | [sbt]: http://code.google.com/p/simple-build-tool 5 | -------------------------------------------------------------------------------- /notes/0.4.2.markdown: -------------------------------------------------------------------------------- 1 | * Updated to [SBT][] version 0.7.3. 2 | * Added `withDownloadedFile()` to `grizzled.net.url`, to execute a block on 3 | a downloaded URL. 4 | * The `grizzled.io.implicits` module has been replaced by individual 5 | modules, for more granular scope control (e.g., 6 | `grizzled.io.RichInputStream`, `grizzled.io.RichReader`) 7 | * The `grizzled.io` package has been split into individual source files. 8 | * Added new `grizzled.io.SourceReader` class that wraps a `scala.io.Source` 9 | inside a `java.io.Reader`. 10 | 11 | [SBT]: http://code.google.com/p/simple-build-tool 12 | -------------------------------------------------------------------------------- /notes/0.4.markdown: -------------------------------------------------------------------------------- 1 | * Added `grizzled.collection.GrizzledLinearSeq` and related implicits, as a 2 | place to put additional methods for sequences. 3 | * Added `grizzled.collection.GrizzledLinearSeq.columnarize()`, which takes a 4 | sequence (e.g., a list), converts its contents to strings, and returns a 5 | single string with the sequence's contents arranged in columns. 6 | * Rearranged the locations of various implicit functions, so callers can 7 | have finer-grained control over which ones are in scope. 8 | * `grizzled.editline.EditLine` now shows its completions in columnar format. 9 | * Added `BlockCommandHandler` to `grizzled`.cmd, to handle commands consisting 10 | of blocks of lines between a start and end line. 11 | * Added `HiddenCommandHandler` to `grizzled.cmd`, allowing special commands 12 | that are not displayed in the help. 13 | * Changed EOF handling in `grizzled.cmd` slightly. 14 | * Added `createTemporaryDirectory()` and `withTemporaryDirectory()` to 15 | `grizzled.file.util` module. 16 | * Added `isEmpty` to `grizzled.file.GrizzledFile` (which can be implicitly 17 | converted to and from `java.io.File`). 18 | * Fixed problem with prefix handling in `grizzled.string.WordWrapper`. 19 | * Now uses [SBT][sbt] 0.7.2 to build from source. 20 | 21 | [sbt]: http://code.google.com/p/simple-build-tool 22 | -------------------------------------------------------------------------------- /notes/0.5.1.markdown: -------------------------------------------------------------------------------- 1 | * Updated to `posterous-sbt` plugin version 0.1.5. 2 | * Removed CHANGELOG, because it can now be generated by `posterous-sbt`. 3 | * Added `grizzled.generator`, which can be used to create Python-style 4 | generators. (It borrows shamelessly from 5 | [Rich Dougherty's Stack Overflow post][].) 6 | * Added `listRecursively()` generator function to `grizzled.file.GrizzledFile`. 7 | Via implicits, `GrizzledFile` can be used to extend `java.io.File`. 8 | * The `grizzled.readline.Readline` trait now contains a `cleanup` method, 9 | and the `grizzled.cmd.CommandInterpreter` class now calls it on exit. 10 | This change ensures that the terminal isn't left in a weird state. 11 | 12 | [Rich Dougherty's Stack Overflow post]: http://stackoverflow.com/questions/2201882/implementing-yield-yield-return-using-scala-continuations/2215182#2215182 13 | -------------------------------------------------------------------------------- /notes/0.5.markdown: -------------------------------------------------------------------------------- 1 | * Updated to Scala 2.8.0.RC1. 2 | * Replaced uses of now-deprecated `Math` functions with corresponding functions 3 | from `scala.math`. 4 | * Enhanced `grizzled.config.Configuration`: 5 | - A `forMatchingSections()` method allows simple looping over sections that 6 | match a regular expression. 7 | - A `matchingSections()` methods returns a sequence of sections that match 8 | a regular expression. 9 | - The `options()` method now returns an empty map if the section doesn't 10 | exist, instead of throwing an exception. 11 | - A new `setOption()` method overwrites an option value, if it exists already, 12 | instead of throwing a `DuplicateOptionException`, as `addOption()` does. 13 | - `ConfigurationReader` is deprecated, and the logic to read a configuration 14 | file has been moved into the `Configuration` class, to permit easier 15 | subclassing. 16 | - Now supports a "safe load" mode, where exceptions aren't thrown. 17 | - Added unit tests for the `Configuration` class. 18 | - Added ability to retrieve converted integer and boolean option values. 19 | - The `option()` methods are now deprecated, in favor of new `get()` and 20 | `getOrElse()` methods that are modeled after their `Map` counterparts. 21 | - Added a new `getSection()` method that returns an `Option[Section]`. 22 | -------------------------------------------------------------------------------- /notes/0.6.markdown: -------------------------------------------------------------------------------- 1 | * Added `findReadline()` convenience method to `grizzled.readline.Readline`. 2 | This method attempts to find and load a suitable Readline library. 3 | * Cleaned up `grizzled.file.util.deleteTree` method. 4 | * Added versions of `grizzled.file.util.copyFile`, 5 | `grizzled.file.util.copyTree`, and `grizzled.file.util.deleteTree` that 6 | take `java.io.File` objects. 7 | * Replaced `grizzled.io.useThenClose` with the more flexible 8 | `grizzled.io.withCloseable`. (`useThenClose` is still present, but 9 | it's deprecated.) 10 | * Added `copyTo()` method to `grizzled.file.GrizzledFile`, which can be 11 | implicitly mixed into `java.io.File`. 12 | * Ensured that various supposedly tail-recursive methods are marked with 13 | `@tailrec`, to be sure. 14 | * Maven artifact now includes Scala version (e.g., `grizzled-scala_2.8.0.RC2`, 15 | instead of `grizzled-scala`). 16 | * Updated to build against Scala 2.8.0.RC2, as well as Scala 2.8.0.RC1. 17 | -------------------------------------------------------------------------------- /notes/0.7.1.markdown: -------------------------------------------------------------------------------- 1 | * Bumped to [SBT][] version 0.7.4. 2 | * Added `relativePath` method to `GrizzledFile`. 3 | * Added ability to "parse" (i.e., emit) plain text and HTML/XHTML to the 4 | `grizzled.parsing.markup` package. 5 | * Updated to [Knockoff][] version 0.7.1-12, which corrects some Markdown 6 | translation bugs. 7 | * Fixed `grizzled-scala` artifact publishing bug ([issue #1][]). 8 | * Removed support for Scala 2.8.0.RC2. 9 | * Changed SBT publishing to use an SSH key file, to eliminate the Ivy 10 | Swing prompt. 11 | 12 | [SBT]: http://code.google.com/p/simple-build-tool 13 | [Knockoff]: http://tristanhunt.com/projects/knockoff/ 14 | [issue #1]: http://github.com/bmc/grizzled-scala/issues/issue/1 15 | -------------------------------------------------------------------------------- /notes/0.7.2.markdown: -------------------------------------------------------------------------------- 1 | * Updated to [Knockoff][] version 0.7.2-13, which corrects some Markdown 2 | translation bugs. 3 | * Updated to Scala 2.8.0.RC5. Now builds against RC3 and RC5 only. 4 | 5 | [SBT]: http://code.google.com/p/simple-build-tool 6 | [Knockoff]: http://tristanhunt.com/projects/knockoff/ 7 | -------------------------------------------------------------------------------- /notes/0.7.3.markdown: -------------------------------------------------------------------------------- 1 | * Updated to build with Scala 2.8.0.final *only*. 2 | 3 | [SBT]: http://code.google.com/p/simple-build-tool 4 | [Knockoff]: http://tristanhunt.com/projects/knockoff/ 5 | -------------------------------------------------------------------------------- /notes/0.7.4.markdown: -------------------------------------------------------------------------------- 1 | * Added `grizzled.reflect` module and `grizzled.reflect.isOfType()` method, 2 | which uses `scala.reflect.Manifest` to simplify erasure-proof type tests. 3 | e.g.: 4 | 5 | def test(v: Any) = 6 | { 7 | import grizzled.reflect._ 8 | if (isOfType[List[Int]](v)) 9 | ... 10 | else if (isOfType[List[Char]](v)) 11 | ... 12 | ... 13 | } 14 | 15 | * Moved `grizzled.parsing.markup` to the new, separate [MarkWrap][] 16 | library. Among other things, this move keeps the Grizzled Scala library 17 | more focused and reduces transitive dependencies. 18 | * Removed most explicit matches against `Some` and `None`, making better 19 | use of the Scala API. 20 | * Updated to released 1.2 version of [ScalaTest][]. 21 | * Changed dependency on [ScalaTest][] to be a test-only dependency. 22 | 23 | [ScalaTest]: http://scalatest.org/ 24 | [MarkWrap]: http://bmc.github.com/markwrap/ 25 | -------------------------------------------------------------------------------- /notes/0.7.markdown: -------------------------------------------------------------------------------- 1 | * Added `grizzled.io.GrizzledSource`, which extends `scala.io.Source` with 2 | mixin methods. 3 | * Deprecated `grizzled.string.implicits` and `grizzled.file.implicits` 4 | modules, in favor of more granular imports. See the 5 | `grizzled.file.GrizzledFile`, `grizzled.string.GrizzledString` and 6 | `grizzled.string.GrizzledChar` companion objects for details. 7 | * Deprecated the string-to-boolean implicit function, in favor of the 8 | more explicit `grizzled.string.util.stringToBoolean()` method. 9 | * Changed `GrizzledFile.listRecursively` to take an optional 10 | `topdown` flag, indicating whether directory traversal should be top-down 11 | or bottom-up. 12 | * Deprecated `grizzled.parsing.Markdown` in favor of new 13 | `grizzled.parsing.markup` module. 14 | * Add [Textile][] support to `grizzled.parsing.markup`, via the Eclipse 15 | [WikiText][] library. 16 | * Changed `grizzled.parsing.markup` to use Tristan Juricek's [Knockoff][] 17 | library for [Markdown][], rather than invoking the [Showdown][] 18 | JavaScript parser via [Rhino][]. 19 | * Now compiles under Scala 2.8.0.RC3 and RC2. Dropped support for RC1. 20 | 21 | [Rhino]: http://www.mozilla.org/rhino/ 22 | [Knockoff]: http://tristanhunt.com/projects/knockoff/ 23 | [Showdown]: http://attacklab.net/showdown/ 24 | [Markdown]: http://daringfireball.net/projects/markdown/ 25 | [Textile]: http://textile.thresholdstate.com/ 26 | [WikiText]: http://help.eclipse.org/ganymede/index.jsp?topic=/org.eclipse.mylyn.wikitext.help.ui/help/devguide/WikiText%20Developer%20Guide.html 27 | -------------------------------------------------------------------------------- /notes/1.0.1.markdown: -------------------------------------------------------------------------------- 1 | * Now compiles against [Scala][] 2.8.1 RC1, as well as 2.8.0 2 | 3 | [Scala]: http://www.scala-lang.org/ 4 | -------------------------------------------------------------------------------- /notes/1.0.10.markdown: -------------------------------------------------------------------------------- 1 | * Fixed `grizzled.sys.makeNativePath` and related functions to treat the 2 | Mac platform the same as Posix, instead of throwing an exception. 3 | * Updated to use SBT 0.11.2. 4 | * Now publishes artifacts to `oss.sonatype.org`. Artifacts are signed with 5 | GPG key, as a result. 6 | -------------------------------------------------------------------------------- /notes/1.0.11.1.markdown: -------------------------------------------------------------------------------- 1 | * Cross-compiled for Scala 2.8.2. 2 | -------------------------------------------------------------------------------- /notes/1.0.11.markdown: -------------------------------------------------------------------------------- 1 | * Fixed cross-compilation issues. Grizzled-Scala is, once again, cross- 2 | compiled and cross-published for 2.8.0, 2.8.1, 2.9.0, 2.9.0-1 and 2.9.1. 3 | -------------------------------------------------------------------------------- /notes/1.0.12.markdown: -------------------------------------------------------------------------------- 1 | * Readline implementation now uses [Jline 2][]. 2 | * Cross-compiled for Scala 2.9.1-1. 3 | 4 | [Jline 2]: https://github.com/huynhjl/jline2 5 | -------------------------------------------------------------------------------- /notes/1.0.13.markdown: -------------------------------------------------------------------------------- 1 | * Cross-compiled for Scala 2.9.2. 2 | -------------------------------------------------------------------------------- /notes/1.0.14.markdown: -------------------------------------------------------------------------------- 1 | * Addressed [Issue #4](https://github.com/bmc/grizzled-scala/issues/4): 2 | `stats.range()` broken when passed a single value. 3 | -------------------------------------------------------------------------------- /notes/1.0.2.markdown: -------------------------------------------------------------------------------- 1 | * Added the `grizzled.math.stats` module, which contains some common 2 | statistics functions. 3 | * Scaladoc documentation is now generated with a proper title. 4 | * Fixed problem where `grizzled.cmd` failed to find a Readline library, 5 | because of an inadvertent change of a constant from a `def` to a `val`. 6 | * Now compiles against [Scala][] 2.8.1 RC2, as well as 2.8.0. 7 | 8 | [Scala]: http://www.scala-lang.org/ 9 | [http://www.nmr.mgh.harvard.edu/Neural_Systems_Group/gary/python.html]: http://www.nmr.mgh.harvard.edu/Neural_Systems_Group/gary/python.html 10 | -------------------------------------------------------------------------------- /notes/1.0.3.markdown: -------------------------------------------------------------------------------- 1 | * Now builds against [Scala][] 2.8.1 and 2.8.0. 2 | * Added `range()` function to the `grizzled.math.stats` module. 3 | * Enhanced `grizzled.readline` module to permit the caller-supplied 4 | `Completer` object to specify the delimiters to use when tokenizing an 5 | input line for tab completion. 6 | * Enhanced `grizzled.cmd` module to allow the caller to instantiate the 7 | `CommandInterpreter` class with the set of delimiters to use when tokenizing 8 | an input line for tab completion. 9 | 10 | [Scala]: http://www.scala-lang.org/ 11 | -------------------------------------------------------------------------------- /notes/1.0.4.markdown: -------------------------------------------------------------------------------- 1 | * Fixed some error messages in the `Configuration` class, per an email from 2 | *brian.ewins /at/ gmail.com*. 3 | -------------------------------------------------------------------------------- /notes/1.0.5.markdown: -------------------------------------------------------------------------------- 1 | * Miscellaneous internal cleanup of `Configuration` and `grizzled.readline` 2 | code. 3 | * Updated to version 1.3 of [ScalaTest][]. 4 | 5 | [ScalaTest]: http://www.scalatest.org/ 6 | 7 | -------------------------------------------------------------------------------- /notes/1.0.6.markdown: -------------------------------------------------------------------------------- 1 | * Now builds against Scala 2.9.0, as well as Scala 2.8.0 and 2.8.1. 2 | * Updated to version 1.4.1 of [ScalaTest][] for Scala 2.9.0. (Still uses 3 | ScalaTest 1.3, for Scala 2.8). 4 | * Updated to use [SBT][] 0.7.7. 5 | * Removed various deprecated methods. 6 | * Corrected implementation of `grizzled.reflect.isOfType` for non-primitives. 7 | 8 | [ScalaTest]: http://www.scalatest.org/ 9 | [SBT]: http://code.google.com/p/simple-build-tool/ 10 | 11 | -------------------------------------------------------------------------------- /notes/1.0.7.markdown: -------------------------------------------------------------------------------- 1 | * Now builds against Scala 2.9.0.1, as well as Scala 2.9.0, 2.8.1 and 2.8.0. 2 | * Converted to build with [SBT][] 0.10.1 3 | 4 | [ScalaTest]: http://www.scalatest.org/ 5 | [SBT]: http://code.google.com/p/simple-build-tool/ 6 | 7 | -------------------------------------------------------------------------------- /notes/1.0.8.markdown: -------------------------------------------------------------------------------- 1 | * Fixed an off-by-one error in `grizzled.collection.ListIterator` 2 | * Cleaned up Scaladocs considerably. 3 | * Converted code to confirm with standard Scala coding style. 4 | * Now builds for [Scala][] 2.9.1, as well as 2.9.0-1, 2.9.0, 2.8.1 and 2.8.0. 5 | 6 | [Scala]: http://www.scala-lang.org/ 7 | -------------------------------------------------------------------------------- /notes/1.0.9.markdown: -------------------------------------------------------------------------------- 1 | * Fixed `grizzled.readline` so that a newline in the prompt doesn't return 2 | `None` (for EOF). 3 | * Based on [pull request #3][], by [Dan Sully][], added the following 4 | features to `grizzled.config`: 5 | - Section name regular expression can now be specified to `Configuration` 6 | objects, thus allowing alternate section name forms. 7 | - Comment regular expression can now be specified to `Configuration`, 8 | allowing alternate comment syntaxes. 9 | - `Configuration` now supports at `getAsList()` method, which returns 10 | a value split into a string list. Delimiters may be specified. The 11 | value returned is of type `Option[List[String]]`. 12 | 13 | [pull request #3]: https://github.com/bmc/grizzled-scala/pull/3 14 | [Dan Sully]: https://github.com/dsully 15 | -------------------------------------------------------------------------------- /notes/1.0.markdown: -------------------------------------------------------------------------------- 1 | * Now published to the [Scala Tools Nexus][] repository, so it's no 2 | longer necessary to specify a custom repository to find this artifact. 3 | 4 | [Scala Tools Nexus]: http://nexus.scala-tools.org/content/repositories/releases 5 | -------------------------------------------------------------------------------- /notes/1.1.0.markdown: -------------------------------------------------------------------------------- 1 | * Built for the Scala 2.10.0 series _only_ (2.10.0-M7, initially). **This 2 | version, and later versions, are 2.10-only. 2.9.x and earlier will be 3 | supported via the 1.0.x release branch.** This is due to changes in the 4 | Scala library between 2.9 and 2.10. 5 | * Converted code to use 2.10 reflection API. 6 | * Added `-feature` to `scalac` options, and removed all feature warnings. 7 | In many cases, this simply necessitating importing various `scala.language` 8 | packages, such as `scala.language.reflectiveCalls` and 9 | `scala.language.implicitConversions`. 10 | * Removed use of `val` in `for` comprehensions, as it's now deprecated. 11 | * Upgraded build to SBT 0.12. 12 | * Moved `GrizzledFile.listRecursively()` functionality from `GrizzledFile` 13 | (which is intended to enhance `java.io.File` implicitly) to 14 | `grizzled.file.util` package, where it is more easily invoked directly. 15 | Replaced `GrizzledFile.listRecursively()` with a simple wrapper that 16 | invokes `grizzled.file.util.listRecursively()`. 17 | * Converted use of `scala.collection.JavaConversions.IterableWrapper` (which 18 | is deprecated in 2.10) to `scala.collection.convert.JIterableWrapper`. 19 | -------------------------------------------------------------------------------- /notes/1.1.1.markdown: -------------------------------------------------------------------------------- 1 | * Re-integrated Doug Tangren's (outstanding) [ls](http://ls.implicit.ly/) SBT plugin. 2 | -------------------------------------------------------------------------------- /notes/1.1.2.markdown: -------------------------------------------------------------------------------- 1 | * Cross-compiled and published for Scala 2.10.0-RC1. 2 | * Converted to use ScalaTest 2.0, which changes `expect` to `expectResult`. 3 | -------------------------------------------------------------------------------- /notes/1.1.3.markdown: -------------------------------------------------------------------------------- 1 | * API documentation changes. 2 | * Built for Scala 2.10 release. 3 | * Removed a bunch of deprecated methods. 4 | * Updated ScalaTest version. 5 | * Addressed [Issue #4](https://github.com/bmc/grizzled-scala/issues/4): 6 | `stats.range()` broken when passed a single value. 7 | -------------------------------------------------------------------------------- /notes/1.1.4.markdown: -------------------------------------------------------------------------------- 1 | * Added `grizzled.string.util.bytesToHexString`. 2 | * Added `grizzled.security.MessageDigest`, a simplified interface to the 3 | Java `MessageDigest` capability. 4 | -------------------------------------------------------------------------------- /notes/1.1.5.markdown: -------------------------------------------------------------------------------- 1 | * Added `grizzled.either`, which contains enrichments for the `Either` 2 | class, including `map()` and `flatMap()` methods that map when the 3 | value is `Right` (and permits easier use of `Either` objects in 4 | `for` comprehensions). 5 | * Increased file copying speed in `grizzled.io.RichInputStream` by 6 | adding buffering. (Fix supplied by [Jim Fulton](https://github.com/jimfulton) 7 | * `grizzled.math.stats.range()` of a single value now returns 0, as it should. 8 | Addresses [Issue #4](https://github.com/bmc/grizzled-scala/issues/4) 9 | * Upgraded to latest version of ScalaTest. 10 | -------------------------------------------------------------------------------- /notes/1.1.6.markdown: -------------------------------------------------------------------------------- 1 | * Removed the `grizzled.generator` package, as it relies on the unsupported 2 | and unmaintained Scala continuations plugin. 3 | * Changed `grizzled.file.util.listRecursively()` to use Scala `Stream` objects, 4 | which permit lazy evaluation. It's a better, and simpler, solution than 5 | continuation passing. 6 | * Now cross-compiled for Scala 2.11. 7 | * Published to Bintray. 8 | * Updated to SBT 0.13.2 9 | -------------------------------------------------------------------------------- /notes/1.2.markdown: -------------------------------------------------------------------------------- 1 | Now with _More Immutable_ and _Fewer Exceptionism_... 2 | 3 | * `grizzled.config.Configuration` is now completely immutable. Specifically, 4 | the following changes have been made: 5 | - Explicit conversion methods (e.g., `getInt()`, `getBoolean()`) have 6 | been removed, in favor of new `asOpt[T]()` and `asEither[T]()` methods. 7 | These new methods take an implicit `ValueConverter` object, allowing 8 | callers to specify their own type conversions. Some predefined converters 9 | are available in the `grizzled.config.Configuration.Implicits` package. 10 | - It is now possible to specify a "not found" handler, when constructing 11 | a `Configuration` object, to handle cases where an option was not found. 12 | - The `Configuration` class is now final, because the constructor is now 13 | private. 14 | - All `Configuration` objects must be instantiated through the companion 15 | `Configuration` object. 16 | - All methods throwing exceptions have either been deprecated or removed. 17 | Implication: Non-safe configuration objects (i.e., those that throw 18 | exceptions for variables that cannot be substituted) are not supported. 19 | However, `asEither()` will properly handle that situation. 20 | - Most exceptions have been removed, except those still thrown by deprecated 21 | methods. 22 | * `grizzled.string.util.stringToBoolean()` is deprecated, as it throws an 23 | exception. Use the new `str2Boolean()` instead; it returns an `Either`. 24 | * Removed exceptions from `grizzled.string.StringTemplate`. The `substitute()` 25 | method is now deprecated (because it throws exceptions) in favor of the 26 | `sub()` method (which returns an `Either`). 27 | * `grizzled.url.download()` now returns an `Either`, instead of throwing an 28 | exception on error. 29 | * The functions in `grizzled.file.util` and the methods in 30 | `grizzled.file.GrizzledFile` now return `Either`, instead of throwing 31 | exceptions. 32 | * Implicits in the `grizzled.net.IPAddress` class are now in a special 33 | `grizzled.net.Implicits` object. 34 | -------------------------------------------------------------------------------- /notes/1.3.markdown: -------------------------------------------------------------------------------- 1 | * `grizzled.config.Configuration` wasn't properly handling custom regular 2 | expressions for parsing section names and comments. Fixed by 3 | [Stefan Schlott (@Skyr)](https://github.com/Skyr). 4 | * Added `grizzled.string.util.hexBytesToString()`, to complement the 5 | `bytesToHexString()` function. 6 | -------------------------------------------------------------------------------- /notes/about.markdown: -------------------------------------------------------------------------------- 1 | The [Grizzled Scala Library][grizzled-scala] contains a variety of 2 | miscellaneous utility classes and objects. Basically, whenever I find 3 | myself writing something that's general-purpose, I put it in here, so I can 4 | easily use it in multiple projects. 5 | 6 | [grizzled-scala]: http://bmc.github.com/grizzled-scala/ 7 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | 3 | -------------------------------------------------------------------------------- /project/gpg.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.3") 2 | 3 | addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4") 4 | 5 | addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.2") 6 | -------------------------------------------------------------------------------- /src/main/pre-scala-2.13/grizzled/ScalaCompat.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Compatibility definitions for Scala 2.13+ vs. Scala 2.12 and lesser. 4 | * This object is conceptually similar to `scala.collection.compat`. 5 | * 6 | * - For Scala 2.12 and earlier, it provides a type alias and compatibility 7 | * functions for `LazyList`. For Scala 2.13 and greater, it's empty. Thus, 8 | * all code can use `LazyList` throughout. 9 | * - It also provides the implicit objects `Ordering` objects for floats and 10 | * doubles. For instance, it provides 11 | * `grizzled.ScalaCompat.math.Ordering.Double.IeeeOrdering` and 12 | * `grizzled.ScalaCompat.math.Ordering.Double.IeeeOrdering`. For Scala 2.12 13 | * and earlier, these values are aliases for `scala.math.Ordering.Double`. 14 | * For Scala 2.13 and greater, they map to their 2.13 counterparts (e.g., 15 | * `scala.math.Ordering.Double.IeeeOrdering`). 16 | */ 17 | package object ScalaCompat { 18 | import scala.collection.convert.{DecorateAsJava, DecorateAsScala} 19 | 20 | val CollectionConverters: DecorateAsJava with DecorateAsScala = 21 | scala.collection.JavaConverters 22 | 23 | type LazyList[+T] = Stream[T] 24 | 25 | object LazyList { 26 | def empty[T]: LazyList[T] = Stream.empty[T] 27 | 28 | object #:: { 29 | @SuppressWarnings(Array("org.wartremover.warts.TraversableOps")) 30 | def unapply[T](s: LazyList[T]): Option[(T, LazyList[T])] = 31 | if (s.nonEmpty) Some((s.head, s.tail)) else None 32 | } 33 | } 34 | 35 | object math { 36 | object Ordering { 37 | object Double { 38 | implicit val IeeeOrdering: Ordering[Double] = 39 | scala.math.Ordering.Double 40 | implicit val TotalOrdering: Ordering[Double] = 41 | scala.math.Ordering.Double 42 | } 43 | object Float { 44 | implicit val IeeeOrdering: Ordering[Float] = 45 | scala.math.Ordering.Float 46 | implicit val TotalOrdering: Ordering[Float] = 47 | scala.math.Ordering.Float 48 | } 49 | } 50 | } 51 | 52 | object scalautil { 53 | import scala.util.Try 54 | import scala.util.control.{ControlThrowable, NonFatal} 55 | 56 | // Stolen directly from 57 | // https://github.com/scala/scala/blob/2.13.x/src/library/scala/util/Using.scala 58 | // See that file and the official API docs for details. 59 | // 60 | // Doesn't include everything in the 2.13 Using. Includes enough to make 61 | // 62 | object Using { 63 | trait Releasable[-R] { 64 | def release(resource: R): Unit 65 | } 66 | 67 | object Releasable { 68 | implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] { 69 | def release(resource: AutoCloseable): Unit = resource.close() 70 | } 71 | } 72 | 73 | def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A] = { 74 | Try { 75 | Using.resource(resource)(f) 76 | } 77 | } 78 | @SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", 79 | "org.wartremover.warts.Throw", 80 | "org.wartremover.warts.Var", 81 | "org.wartremover.warts.Null")) 82 | def resource[R, A](resource: R)(body: R => A) 83 | (implicit releaseable: Releasable[R]): A = { 84 | if (resource == null) throw new NullPointerException("null resource") 85 | 86 | var toThrow: Throwable = null 87 | try { 88 | body(resource) 89 | } 90 | catch { 91 | case t: Throwable => 92 | toThrow = t 93 | null.asInstanceOf[A] 94 | } 95 | finally { 96 | if (toThrow eq null) { 97 | releaseable.release(resource) 98 | } 99 | else { 100 | try { 101 | releaseable.release(resource) 102 | } 103 | catch { 104 | case other: Throwable => 105 | toThrow = preferentiallySuppress(toThrow, other) 106 | } 107 | finally { 108 | throw toThrow 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | private def preferentiallySuppress(primary: Throwable, secondary: Throwable): Throwable = { 116 | def score(t: Throwable): Int = t match { 117 | case _: VirtualMachineError => 4 118 | case _: LinkageError => 3 119 | case _: InterruptedException | _: ThreadDeath => 2 120 | case _: ControlThrowable => 0 121 | case e if !NonFatal(e) => 1 // in case this method gets out of sync with NonFatal 122 | case _ => -1 123 | } 124 | @inline def suppress(t: Throwable, suppressed: Throwable): Throwable = { 125 | t.addSuppressed(suppressed) 126 | t 127 | } 128 | 129 | if (score(secondary) > score(primary)) suppress(secondary, primary) 130 | else suppress(primary, secondary) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/mime.types: -------------------------------------------------------------------------------- 1 | # Additional MIME types. 2 | # --------------------------------------------------------------------------- 3 | 4 | # Markdown 5 | text/markdown md markdown 6 | 7 | # Textile 8 | text/textile textile 9 | -------------------------------------------------------------------------------- /src/main/scala-2.13/grizzled/ScalaCompat.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | import scala.collection.convert.{AsJavaExtensions, AsScalaExtensions} 4 | 5 | /** Compatibility definitions for Scala 2.13+ vs. Scala 2.12 and lesser. 6 | * This object is conceptually similar to `scala.collection.compat`. 7 | * 8 | * - For Scala 2.12 and earlier, it provides a type alias and compatibility 9 | * functions for `LazyList`. For Scala 2.13 and greater, it's empty. Thus, 10 | * all code can use `LazyList` throughout. 11 | * - It also provides the implicit objects `Ordering` objects for floats and 12 | * doubles. For instance, it provides 13 | * `grizzled.ScalaCompat.math.Ordering.Double.IeeeOrdering` and 14 | * `grizzled.ScalaCompat.math.Ordering.Double.IeeeOrdering`. For Scala 2.12 15 | * and earlier, these values are aliases for `scala.math.Ordering.Double`. 16 | * For Scala 2.13 and greater, they map to their 2.13 counterparts (e.g., 17 | * `scala.math.Ordering.Double.IeeeOrdering`). 18 | */ 19 | package object ScalaCompat { 20 | import scala.util.Using.Releasable 21 | 22 | val CollectionConverters: AsJavaExtensions with AsScalaExtensions = 23 | scala.jdk.CollectionConverters 24 | 25 | object math { 26 | object Ordering { 27 | object Double { 28 | implicit val IeeeOrdering: Ordering[Double] = 29 | scala.math.Ordering.Double.IeeeOrdering 30 | implicit val TotalOrdering: Ordering[Double] = 31 | scala.math.Ordering.Double.TotalOrdering 32 | } 33 | object Float { 34 | implicit val IeeeOrdering: Ordering[Float] = 35 | scala.math.Ordering.Float.IeeeOrdering 36 | implicit val TotalOrdering: Ordering[Float] = 37 | scala.math.Ordering.Float.TotalOrdering 38 | } 39 | } 40 | } 41 | 42 | object scalautil { 43 | import scala.util.{Using => ScalaUsing, Try} 44 | 45 | object Using { 46 | type Releasable[-R] = scala.util.Using.Releasable[R] 47 | 48 | @inline 49 | def apply[R: scala.util.Using.Releasable, A](resource: R)(f: R => A): Try[A] = { 50 | ScalaUsing.apply(resource)(f) 51 | } 52 | 53 | @inline 54 | def resource[R, A](resource: R)(body: R => A)(implicit rel: Releasable[R]): A = { 55 | ScalaUsing.resource(resource)(body)(rel) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/binary.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Useful binary-related utility functions. 4 | */ 5 | object binary { 6 | 7 | /** Count the number of bits in a numeric (integer or long) value. This 8 | * method is adapted from the Hamming Weight algorithm. It works for 9 | * up to 64 bits. 10 | * 11 | * @param num the numeric value 12 | * 13 | * @return the number of 1 bits in a binary representation of `num` 14 | */ 15 | def bitCount(num: Int): Int = { 16 | val numLong: Long = num.toLong 17 | bitCount(numLong & 0xffffffffL) 18 | } 19 | 20 | /** Count the number of bits in a numeric (integer or long) value. This 21 | * method is adapted from the Hamming Weight algorithm. It works for 22 | * up to 64 bits. 23 | * 24 | * @param num the numeric value 25 | * 26 | * @return the number of 1 bits in a binary representation of `num` 27 | */ 28 | def bitCount(num: Long): Int = { 29 | // Put count of each 2 bits into those 2 bits. 30 | val res1: Long = num - ((num >> 1) & 0x5555555555555555L) 31 | 32 | // Put count of each 4 bits into those 4 bits. 33 | val allThrees = 0x3333333333333333L 34 | val res2 = (res1 & allThrees) + ((res1 >> 2) & allThrees) 35 | 36 | // Put count of each 8 bits into those 8 bits. 37 | val res3 = (res2 + (res2 >> 4)) & 0x0f0f0f0f0f0f0f0fL 38 | 39 | // Left-most bits. 40 | ((res3 * 0x0101010101010101L) >> 56).toInt 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/collection/Implicits.scala: -------------------------------------------------------------------------------- 1 | package grizzled.collection 2 | 3 | import scala.collection.immutable.LinearSeq 4 | import scala.language.implicitConversions 5 | import java.util.{Collection => JCollection, Iterator => JIterator} 6 | 7 | import scala.annotation.tailrec 8 | import scala.collection.compat._ 9 | import scala.collection.mutable.{Builder => MutableBuilder} 10 | import scala.sys.SystemProperties 11 | import scala.language.higherKinds 12 | 13 | /** Enrichment classes for collections. 14 | */ 15 | object Implicits { 16 | 17 | import scala.collection.Iterable 18 | 19 | /** Can be used to add a `mapWhile()` function to a collection type. 20 | * 21 | * @tparam T type of value contained in the collection 22 | * @tparam I the collection, which must be a subclass of `Iterable[T]` 23 | */ 24 | trait MapWhile[+T, I <: Iterable[T]] { 25 | val container: I 26 | 27 | /** Perform a map on an `Iterable` subclass until the predicate function 28 | * returns `false`. Combines `map()` and `takeWhile()` in a more efficient 29 | * manner. The predicate is called ''after'' the mapping operation is 30 | * performed. 31 | * 32 | * @param mapper the mapper function 33 | * @param predicate the predicate. The mapping operation will continue 34 | * until this function returns `false` or the iterable 35 | * is exhausted 36 | * @param cbf the `CanBuildFrom` factory, which allows this function 37 | * to make an instance of the subclass without knowing 38 | * what it is 39 | * @tparam U the type of the returned subclass 40 | * @tparam J the type of the returned `Iterable` subclass 41 | * @return the mapped (and possibly filtered) result 42 | */ 43 | def mapWhile[U, J](mapper: T => U, predicate: U => Boolean) 44 | (implicit cbf: Factory[U, J]): J = { 45 | @tailrec 46 | def loop(xs: List[T], acc: MutableBuilder[U, J]): J = { 47 | xs match { 48 | case Nil => 49 | acc.result 50 | case head :: tail => 51 | val b = mapper(head) 52 | if (!predicate(b)) 53 | acc.result 54 | else { 55 | acc += b 56 | loop(tail, acc) 57 | } 58 | } 59 | } 60 | 61 | loop(container.toList, cbf.newBuilder) 62 | } 63 | } 64 | 65 | /** Adds a `mapWhile()` function to an `Iterable`. 66 | * 67 | * @param i the iterable 68 | * @tparam T the type of the iterable 69 | * 70 | * @return a `MapWhile` object with a `mapWhile()` function that returns a 71 | * new `Iterator[T]` 72 | */ 73 | implicit def iterableMapWhile[T](i: Iterable[T]): MapWhile[T, Iterable[T]] = { 74 | new MapWhile[T, Iterable[T]] { val container = i } 75 | } 76 | 77 | /** Adds a `mapWhile()` function to a `Seq`. 78 | * 79 | * @param seq the sequence 80 | * @tparam T the type of the sequence 81 | * 82 | * @return a `MapWhile` object with a `mapWhile()` function that returns a 83 | * new `Seq[T]` 84 | */ 85 | implicit def seqMapWhile[T](seq: Seq[T]): MapWhile[T, Seq[T]] = { 86 | new MapWhile[T, Seq[T]] { val container = seq } 87 | } 88 | 89 | /** Useful for converting a collection into an object suitable for use with 90 | * Scala's `for` loop. 91 | */ 92 | implicit class CollectionIterator[T](private val self: JIterator[T]) 93 | extends Iterator[T] { 94 | 95 | def this(c: JCollection[T]) = this(c.iterator) 96 | 97 | def hasNext: Boolean = self.hasNext 98 | 99 | def next: T = self.next 100 | } 101 | 102 | /** An enrichment class that decorates a `LinearSeq`. 103 | * 104 | * @param container the underlying `LinearSeq` 105 | * @tparam T the type 106 | */ 107 | implicit class GrizzledLinearSeq[+T](val container: LinearSeq[T]) 108 | extends { 109 | 110 | def realSeq: Seq[T] = container 111 | 112 | /** Create a string containing the contents of this sequence, arranged 113 | * in columns. 114 | * 115 | * @param width total (maximum) columns 116 | * 117 | * @return a possibly multiline string containing the columnar output. 118 | * The string may have embedded newlines, but it will not end 119 | * with a newline. 120 | */ 121 | def columnarize(width: Int): String = { 122 | import scala.collection.mutable.ArrayBuffer 123 | import grizzled.math.util.{max => maxnum} 124 | 125 | val lineSep = (new SystemProperties).getOrElse("line.separator", "\n") 126 | 127 | val buf = new ArrayBuffer[Char] 128 | 129 | // Lay them out in columns. Simple-minded for now. 130 | val strings: Seq[String] = container.map(_.toString) 131 | val colSize = strings match { 132 | case s if s.isEmpty => 133 | 0 134 | case Seq(s) => 135 | s.length 136 | case Seq(head, tail @ _*) => 137 | maxnum(head.length, tail.map(_.length): _*) + 2 138 | } 139 | 140 | val colsPerLine = width / colSize 141 | 142 | strings 143 | .zipWithIndex 144 | .map { case (s: String, i: Int) => 145 | val count = i + 1 146 | val padding = " " * (colSize - s.length) 147 | val sep = if ((count % colsPerLine) == 0) lineSep else "" 148 | s + padding + sep 149 | } 150 | .mkString("") 151 | } 152 | 153 | override def toString = container.toString 154 | } 155 | 156 | /** An `Iterable` enrichment class. 157 | * 158 | * @param container the underlying iterable 159 | * @tparam T the iterable type 160 | */ 161 | implicit class GrizzledIterable[+T](val container: Iterable[T]) 162 | extends Iterable[T] { 163 | def self: GrizzledIterable[T] = this 164 | 165 | def realIterable: Iterable[T] = container 166 | 167 | def iterator: Iterator[T] = container.iterator 168 | 169 | 170 | /** Create a string containing the contents of this iterable, arranged 171 | * in columns. 172 | * 173 | * @param width total (maximum) columns 174 | * 175 | * @return a possibly multiline string containing the columnar output. 176 | * The string may have embedded newlines, but it will not end 177 | * with a newline. 178 | */ 179 | def columnarize(width: Int): String = { 180 | new GrizzledLinearSeq(container.toList).columnarize(width) 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/collection/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Some collection-related helpers. 4 | */ 5 | package object collection { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/config/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** 4 | * Classes and objects to aid in the parsing of INI-style configuration 5 | * files. This package is similar, in concept, to the Python 6 | * `ConfigParser` module (though its implementation and capabilities 7 | * differ quite a bit). 8 | */ 9 | package object config { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/datetime/DateTimeUtil.scala: -------------------------------------------------------------------------------- 1 | package grizzled.datetime 2 | 3 | import java.util.{Calendar, Date} 4 | 5 | /** Some basic date- and time-related helpers. 6 | */ 7 | object DateTimeUtil { 8 | /** Convert a `Date` object to a `java.util.Calendar` object. 9 | * 10 | * @param date the `Date` object 11 | * 12 | * @return the `Calendar` object 13 | */ 14 | def dateToCalendar(date: Date): Calendar = { 15 | val cal = Calendar.getInstance 16 | cal.setTime(date) 17 | cal 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/datetime/Implicits.scala: -------------------------------------------------------------------------------- 1 | package grizzled.datetime 2 | 3 | import java.sql.Timestamp 4 | import java.text.DecimalFormat 5 | import java.util.concurrent.TimeUnit 6 | import java.util.{Calendar, Date} 7 | 8 | import scala.concurrent.duration.Duration 9 | 10 | /** Some enrichments for various Java date- and time-related classes. 11 | */ 12 | object Implicits { 13 | 14 | private val NumFormatter = new DecimalFormat("#,###") 15 | 16 | implicit class EnrichedDuration(val duration: Duration) extends AnyVal { 17 | 18 | /** Return a better-formatted result than `toString`. For instance, 19 | * given this `Duration`: 20 | * 21 | * {{{ 22 | * Duration(13123, "milliseconds") 23 | * }}} 24 | * 25 | * the standard `toString` method will return "13123 milliseconds". 26 | * By contrast, `humanize` will return "13 seconds, 123 milliseconds". 27 | * 28 | * Similarly: 29 | * 30 | * {{{ 31 | * val d = Duration(172801003, "milliseconds") 32 | * 33 | * d.toString // 172801003 milliseconds 34 | * d.humanize // 2 days, 1 second, 3 milliseconds 35 | * }}} 36 | * 37 | * @return something nicer 38 | */ 39 | @SuppressWarnings(Array("org.wartremover.warts.Option2Iterable")) 40 | def humanize: String = { 41 | val days = duration.toDays 42 | val minusDays = duration - Duration(days, TimeUnit.DAYS) 43 | val hours = minusDays.toHours 44 | val minusHours = minusDays - Duration(hours, TimeUnit.HOURS) 45 | val minutes = minusHours.toMinutes 46 | val minusMinutes = minusHours - Duration(minutes, TimeUnit.MINUTES) 47 | val seconds = minusMinutes.toSeconds 48 | val minusSeconds = minusMinutes - Duration(seconds, TimeUnit.SECONDS) 49 | val millis = minusSeconds.toMillis 50 | val minusMillis = minusSeconds - Duration(millis, TimeUnit.MILLISECONDS) 51 | val micros = minusMillis.toMicros 52 | val minusMicros = minusMillis - Duration(micros, TimeUnit.MICROSECONDS) 53 | val nanos = minusMicros.toNanos 54 | 55 | val units = Seq( 56 | (duration.toDays, "day", "days"), 57 | (hours, "hour", "hours"), 58 | (minutes, "minute", "minutes"), 59 | (seconds, "second", "seconds"), 60 | (millis, "millisecond", "milliseconds"), 61 | (micros, "microsecond", "microseconds"), 62 | (nanos, "nanosecond", "nanoseconds") 63 | ) 64 | 65 | val s = units.flatMap { 66 | case (value, _, _) if value == 0 => 67 | None 68 | case (value, singular, _) if value == 1 => 69 | Some(NumFormatter.format(value) + " " + singular) 70 | case (value, _, plural) => 71 | Some(NumFormatter.format(value) + " " + plural) 72 | } 73 | .mkString(", ") 74 | 75 | if (s.isEmpty) "0 milliseconds" else s 76 | } 77 | } 78 | 79 | /** An enriched `java.sql.Timestamp` class. 80 | * 81 | * @param ts the `java.sql.Timestamp` 82 | */ 83 | implicit class EnrichedTimestamp(ts: Timestamp) { 84 | 85 | /** Convert the `Timestamp` object to a `java.util.Calendar` object. 86 | * 87 | * @return the `Calendar` object 88 | */ 89 | def toCalendar: Calendar = DateTimeUtil.dateToCalendar(new Date(ts.getTime)) 90 | 91 | /** Convert the `Timestamp` object to a `java.util.Date` object. 92 | * 93 | * @return the `Date` object 94 | */ 95 | def toDate: Date = new Date(ts.getTime) 96 | } 97 | 98 | /** An enriched `java.util.Date` class. 99 | * 100 | * @param date the `java.util.Date` 101 | */ 102 | implicit class EnrichedDate(date: Date) { 103 | 104 | /** Convert the `Date` object to a `java.util.Calendar` object. 105 | * 106 | * @return the `Calendar` object 107 | */ 108 | def toCalendar: Calendar = DateTimeUtil.dateToCalendar(date) 109 | } 110 | 111 | /** An enriched `java.util.Calendar` class. 112 | * 113 | * @param cal the `Calendar` object 114 | */ 115 | implicit class EnrichedCalendar(cal: Calendar) { 116 | 117 | /** Convert the `Calendar` object to a `java.sql.Timestamp`. 118 | * 119 | * @return the `java.sql.Timestamp` 120 | */ 121 | def toTimestamp: Timestamp = new Timestamp(cal.getTime.getTime) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/file/filter/BackslashContinuedLineIterator.scala: -------------------------------------------------------------------------------- 1 | package grizzled.file.filter 2 | 3 | import grizzled.string.Implicits.String._ 4 | 5 | import scala.io.Source 6 | import scala.collection.mutable.ArrayBuffer 7 | import scala.sys.SystemProperties 8 | 9 | /** Assemble input lines, honoring backslash escapes for line continuation. 10 | * `BackslashContinuedLineIterator` takes an iterator over lines of 11 | * input, looks for lines containing trailing backslashes, and treats them 12 | * as continuation lines, to be concatenated with subsequent lines in the 13 | * input. Thus, when a `BackslashContinuedLineIterator` filters this 14 | * input: 15 | * 16 | * {{{ 17 | * Lorem ipsum dolor sit amet, consectetur \ 18 | * adipiscing elit. 19 | * In congue tincidunt fringilla. \ 20 | * Sed interdum nibh vitae \ 21 | * libero 22 | * fermentum id dictum risus facilisis. 23 | * }}} 24 | * 25 | * it produces these lines: 26 | * 27 | * {{{ 28 | * Lorem ipsum dolor sit amet, consectetur adipiscing elit. 29 | * In congue tincidunt fringilla. Sed interdum nibh vitae libero 30 | * fermentum id dictum risus facilisis. 31 | * }}} 32 | * 33 | * @param source an iterator that produces lines of input. Any trailing 34 | * newlines are stripped. 35 | */ 36 | class BackslashContinuedLineIterator(val source: Iterator[String]) 37 | extends Iterator[String] { 38 | private val lineSep = (new SystemProperties).getOrElse("line.separator", "\n") 39 | 40 | /** Determine whether there's any input remaining. 41 | * 42 | * @return `true` if input remains, `false` if not 43 | */ 44 | def hasNext: Boolean = source.hasNext 45 | 46 | /** Get the next logical line of input, which may represent a concatenation 47 | * of physical input lines. Any trailing newlines are stripped. 48 | * 49 | * @return the next input line 50 | */ 51 | def next: String = { 52 | 53 | import scala.annotation.tailrec 54 | 55 | @tailrec 56 | def readNext(buf: String): String = { 57 | if (! source.hasNext) 58 | buf 59 | else { 60 | source.next match { 61 | case line if line.lastOption == Some('\\') => 62 | readNext(buf + line.dropRight(1)) 63 | case line => 64 | buf + line 65 | } 66 | } 67 | } 68 | 69 | readNext("") 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/file/filter/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled.file 2 | 3 | 4 | /** Contains various file- and I/O-related filter classes. 5 | */ 6 | package object filter { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/file/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** File-related classes and utilities. This package is distinguished from 4 | * the `grizzled.io` package in that this package operates on files and 5 | * paths, not on open streams or sources. 6 | * 7 | * @see [[grizzled.io]] 8 | */ 9 | package object file { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/io/Implicits.scala: -------------------------------------------------------------------------------- 1 | package grizzled.io 2 | 3 | import java.io.{InputStream, OutputStream, Reader, Writer} 4 | 5 | import scala.annotation.tailrec 6 | import scala.io.Source 7 | import scala.util.{Failure, Success, Try} 8 | 9 | /** Implicits that addFile enrichments to `java.io` and `scala.io` classes. 10 | */ 11 | object Implicits { 12 | 13 | /** Provides additional methods, over and above those already present in 14 | * the Java `InputStream` class. The `implicits` object contains implicit 15 | * conversions between `RichInputStream` and `InputStream`. 16 | * 17 | * @param inputStream the input stream to wrap 18 | */ 19 | implicit class RichInputStream(val inputStream: InputStream) 20 | extends PartialReader[Byte] { 21 | 22 | val reader = inputStream 23 | 24 | protected def convert(b: Int) = (b & 0xff).toByte 25 | 26 | /** Copy the input stream to an output stream, stopping on EOF. 27 | * This method does not close either stream. 28 | * 29 | * @param out the output stream 30 | */ 31 | def copyTo(out: OutputStream): Try[Int] = { 32 | val buffer = new Array[Byte](8192) 33 | 34 | @tailrec 35 | def doCopyTo(copiedSoFar: Int): Try[Int] = { 36 | Try { reader.read(buffer) } match { 37 | case Success(n) if n <= 0 => Success(copiedSoFar) 38 | case Success(n) => Try { out.write(buffer, 0, n) } match { 39 | case Failure(ex) => Failure(ex) 40 | case Success(_) => doCopyTo(copiedSoFar + n) 41 | } 42 | } 43 | } 44 | 45 | doCopyTo(0) 46 | } 47 | } 48 | 49 | /** Provides additional methods, over and above those already present in 50 | * the Java `Reader` class. The `implicits` object contains implicit 51 | * conversions between `RichReader` and `Reader`. 52 | * 53 | * @param reader the reader to wrap 54 | */ 55 | implicit class RichReader(val reader: Reader) extends PartialReader[Char] { 56 | protected def convert(b: Int) = (b & 0xff).toChar 57 | 58 | /** Copy the reader to a writer, stopping on EOF. This method does no 59 | * buffering. If you want buffering, make sure you use a 60 | * `java.io.BufferedReader` and a `java.io.BufferedWriter`. This method 61 | * does not close either object. 62 | * 63 | * @param out the output stream 64 | */ 65 | def copyTo(out: Writer): Unit = { 66 | val buffer = new Array[Char](8192) 67 | @tailrec 68 | def doCopyTo(copiedSoFar: Int): Try[Int] = { 69 | Try { reader.read(buffer) } match { 70 | case Success(n) if n <= 0 => Success(copiedSoFar) 71 | case Success(n) => Try { out.write(buffer, 0, n) } match { 72 | case Failure(ex) => Failure(ex) 73 | case Success(_) => doCopyTo(copiedSoFar + n) 74 | } 75 | } 76 | } 77 | 78 | doCopyTo(0) 79 | } 80 | } 81 | 82 | /** A wrapper for `scala.io.Source` that provides additional methods. 83 | * By importing the implicit conversion functions, you can use the methods 84 | * in this class transparently from a `java.io.File` object. 85 | * 86 | * {{{ 87 | * import grizzled.io.Implicits._ 88 | * 89 | * val source = Source.fromFile(new java.io.File("/tmp/foo/bar")) 90 | * source.firstNonblankLine.getOrElse("") 91 | * }}} 92 | */ 93 | implicit class GrizzledSource(val source: Source) { 94 | 95 | /** Find the lines between two marker lines in the source. For instance, 96 | * to get all lines between the next occurrence of "{{{" (on a line by 97 | * itself and "}}}" (or end of file), use: 98 | * 99 | * {{{ 100 | * import grizzled.io.Implicits._ 101 | * import scala.io.Source 102 | * import java.io.File 103 | * 104 | * val path = "/path/to/some/file" 105 | * val lines = Source.fromFile(new File(path)).linesBetween("{{{", "}}}") 106 | * }}} 107 | * 108 | * This method uses `Source.getLines()`, which may or may not start 109 | * at the beginning of the source, depending on the source's state. 110 | * 111 | * @param start the starting line marker 112 | * @param finish the ending line marker 113 | * 114 | * @return a iterator of lines, or an empty iterator if none found 115 | */ 116 | def linesBetween(start: String, finish: String): Iterator[String] = 117 | source.getLines.dropWhile(_ != start).drop(1).takeWhile(_ != finish) 118 | 119 | /** Find and return the first non-blank line (without trailing newline) 120 | * in the source. Uses `Source.getLines()`, which may or may not start 121 | * at the beginning of the source, depending on the source's state. 122 | * 123 | * @return `None` if there is no nonblank line, `Some(line)` if there is. 124 | */ 125 | def firstNonblankLine: Option[String] = { 126 | source.getLines.dropWhile(_.trim.length == 0).take(1).toSeq.headOption 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/io/MultiSource.scala: -------------------------------------------------------------------------------- 1 | package grizzled.io 2 | 3 | import scala.io.Source 4 | import scala.collection.compat._ 5 | 6 | /** A `MultiSource` contains multiple `scala.io.Source` 7 | * objects and satisfies reads from them serially. Once composed, a 8 | * `MultiSource` can be used anywhere a `Source` is used. 9 | * 10 | * @param sources the sources to wrap 11 | */ 12 | class MultiSource(sources: List[Source]) extends Source { 13 | 14 | /** Version of constructor that takes multiple arguments, instead of a list. 15 | * 16 | * @param sources the sources to wrap 17 | */ 18 | def this(sources: Source*) = this(sources.toList) 19 | 20 | /** The actual iterator. 21 | */ 22 | protected val iter: Iterator[Char] = { 23 | sources.map(_.iterator).foldLeft(Iterator[Char]())(_ ++ _) 24 | } 25 | 26 | /** Reset, returning a new source. 27 | */ 28 | override def reset: Source = new MultiSource(sources.map(_.reset())) 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/io/PartialReader.scala: -------------------------------------------------------------------------------- 1 | package grizzled.io 2 | 3 | import scala.annotation.tailrec 4 | 5 | /** Contains methods that can read part of a stream or reader. 6 | */ 7 | trait PartialReader[T] { 8 | type HasRead = {def read(): Int} 9 | val reader: HasRead 10 | 11 | protected def convert(b: Int): T 12 | 13 | /** Read up to `max` items from the reader. 14 | * 15 | * @param max maximum number of items to read 16 | * 17 | * @return a list of the items 18 | */ 19 | def readSome(max: Int): List[T] = { 20 | import scala.language.reflectiveCalls 21 | 22 | @tailrec def doRead(r: HasRead, partialList: List[T], cur: Int): List[T] = { 23 | if (cur >= max) 24 | partialList 25 | 26 | else { 27 | val b = r.read() 28 | if (b == -1) 29 | partialList 30 | else 31 | doRead(r, partialList :+ convert(b), cur + 1) 32 | } 33 | } 34 | 35 | Option(reader).map(r => doRead(r, List.empty[T], 0)).getOrElse(Nil) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/io/SourceReader.scala: -------------------------------------------------------------------------------- 1 | package grizzled.io 2 | 3 | import scala.io.Source 4 | import java.io.{IOException, Reader} 5 | 6 | import scala.annotation.tailrec 7 | 8 | /** Provides a `java.io.Reader` that is backed by a Scala `Source` object. 9 | * 10 | * @param sourceToWrap the source to wrap 11 | */ 12 | class SourceReader(sourceToWrap: Source) extends Reader { 13 | // A var is used only to support reset(). 14 | @SuppressWarnings(Array("org.wartremover.warts.Var")) 15 | private var source: Source = sourceToWrap 16 | 17 | /** Reads characters into a portion of an array. This method will block until 18 | * some input is available, an I/O error occurs, or the end of the 19 | * underlying `Source` is reached. 20 | * 21 | * @param buf the destination character buffer 22 | * @param offset offset at which to start reading into the buffer 23 | * @param length maximum number of characters to read 24 | * @return total number of characters read, or -1 on EOF. 25 | */ 26 | def read(buf: Array[Char], offset: Int, length: Int): Int = { 27 | @tailrec 28 | def readNext(i: Int, readSoFar: Int): Int = { 29 | if (readSoFar >= length) 30 | readSoFar 31 | else if (i >= buf.length) 32 | readSoFar 33 | else { 34 | read() match { 35 | case c if c <= 0 => readSoFar 36 | case c => 37 | buf(i) = c.toChar 38 | readNext(i + 1, readSoFar + 1) 39 | } 40 | } 41 | } 42 | 43 | readNext(offset, 0) 44 | } 45 | 46 | /** Skips characters. This method will block until some characters are 47 | * available, an I/O error occurs, or the end of the underlying `Source` 48 | * is reached. 49 | * 50 | * @param n the number of characters to skip 51 | * @return the number of characters actually skipped 52 | */ 53 | override def skip(n: Long): Long = { 54 | assert(n >= 0) 55 | 56 | @tailrec 57 | def skipNext(skippedSoFar: Long): Long = { 58 | if (skippedSoFar >= n) 59 | skippedSoFar 60 | else { 61 | read() match { 62 | case -1 => skippedSoFar 63 | case c => skipNext(skippedSoFar + 1) 64 | } 65 | } 66 | } 67 | 68 | skipNext(0) 69 | } 70 | 71 | /** Return whether `mark()` is supported. This version always returns `false`. 72 | * 73 | * @return `false` 74 | */ 75 | override def markSupported(): Boolean = false 76 | 77 | /** `mark()` is not supported. This method unconditionally throws 78 | * `IOException`. 79 | * 80 | * @param readAheadLimit the mark limit. Ignored. 81 | */ 82 | @SuppressWarnings(Array("org.wartremover.warts.Throw")) 83 | override def mark(readAheadLimit: Int): Unit = { 84 | throw new IOException("mark() not supported.") 85 | } 86 | 87 | /** Tells whether the `Reader` is ready to be read. The `Reader` APi states 88 | * that this method "returns `true` if the next `read()` is guaranteed not to 89 | * block for input, `false` otherwise. Note that returning `false` does not 90 | * guarantee that the next read will block." 91 | * 92 | * There's no simple mapping of `ready()` to a `Source`, so this method 93 | * always returns `false`. 94 | * 95 | * @return `false`, unconditionally. 96 | */ 97 | override def ready(): Boolean = true 98 | 99 | /** Reads a single character. This method will block until a character is 100 | * available, an I/O error occurs, or the end of the stream is reached. 101 | * 102 | * @return the character read, as an integer in the range 0x00 to 0xffff, 103 | * or -1 if at the end of the underlying `Source`. 104 | */ 105 | override def read(): Int = { 106 | try { 107 | source.next 108 | } 109 | catch { 110 | case e: NoSuchElementException => -1 111 | } 112 | } 113 | 114 | /** Resets the `Reader` by resetting the underlying `Source`. 115 | */ 116 | override def reset(): Unit = { 117 | source = source.reset() 118 | } 119 | 120 | /** Closes the `Reader` by closing the underlying `Source`. 121 | */ 122 | def close(): Unit = source.close() 123 | } 124 | 125 | /** Companion to `SourceReader` class. 126 | */ 127 | object SourceReader { 128 | /** Create a `SourceReader` from a `Source`. The result is compatible 129 | * with the `java.io.Reader` interface. 130 | * 131 | * @param source the `scala.io.Source` 132 | * 133 | * @return the `SourceReader` 134 | */ 135 | def apply(source: Source): SourceReader = new SourceReader(source) 136 | } 137 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/io/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** I/O-related classes and utilities. This package is distinguished from 4 | * the `grizzled.file` package in that this package operates on 5 | * already-open Java `InputStream`, `OutputStream`, `Reader` and `Writer` 6 | * objects, and on Scala `Source` objects. 7 | * 8 | * See [[grizzled.file]] 9 | */ 10 | package object io { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/math/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Miscellaneous math and statistics utilities. 4 | */ 5 | package object math { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/math/util.scala: -------------------------------------------------------------------------------- 1 | package grizzled.math 2 | 3 | /** Useful math-related utility functions. 4 | */ 5 | object util { 6 | 7 | /** A `max` function that will take any number of arguments for which an 8 | * `Ordering` is defined, returning the "largest" one, as defined by the 9 | * `Ordering`. 10 | * 11 | * @param arg the first item 12 | * @param args the remaining items for which to find the maximum 13 | * @tparam T the argument type 14 | * 15 | * @return the maximum value 16 | */ 17 | // reduce() is okay here, since the argument list cannot be empty. 18 | @SuppressWarnings(Array("org.wartremover.warts.TraversableOps")) 19 | def max[T: Ordering](arg: T, args: T*): T = { 20 | (arg +: args).reduce { (a: T, b: T) => 21 | val ev = implicitly[Ordering[T]] 22 | ev.compare(a, b) match { 23 | case i if i < 0 => b 24 | case i if i > 0 => a 25 | case _ => a 26 | } 27 | } 28 | } 29 | 30 | /** A `max` function that will take any number of arguments for which an 31 | * `Ordering` is defined, returning the "largest" one, as defined by the 32 | * `Ordering`. 33 | * 34 | * @param arg the first item 35 | * @param args the remaining items for which to find the maximum 36 | * @tparam T the argument type 37 | * 38 | * @return the maximum value 39 | */ 40 | // reduce() is okay here, since the argument list cannot be empty. 41 | @SuppressWarnings(Array("org.wartremover.warts.TraversableOps")) 42 | def min[T: Ordering](arg: T, args: T*): T = { 43 | (arg +: args).reduce { (a: T, b: T) => 44 | val ev = implicitly[Ordering[T]] 45 | ev.compare(a, b) match { 46 | case i if i < 0 => a 47 | case i if i > 0 => b 48 | case _ => a 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/net/Implicits.scala: -------------------------------------------------------------------------------- 1 | package grizzled.net 2 | 3 | import java.net.InetAddress 4 | import scala.language.implicitConversions 5 | 6 | /** Implicit conversions for network classes and types. 7 | */ 8 | object Implicits { 9 | 10 | /** Implicitly converts a `java.net.InetAddress` to an 11 | * `IPAddress`. 12 | * 13 | * @param addr the `java.net.InetAddress` 14 | * 15 | * @return the `IPAddress` 16 | */ 17 | implicit def inetToIPAddress(addr: InetAddress): IPAddress = IPAddress(addr) 18 | 19 | /** Implicitly converts an `IPAddress` to a 20 | * `java.net.InetAddress`. 21 | * 22 | * @param ip the `IPAddress` 23 | * @return the corresponding `java.net.InetAddress` 24 | */ 25 | implicit def ipToInetAddress(ip: IPAddress): InetAddress = ip.toInetAddress 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/net/URI.scala: -------------------------------------------------------------------------------- 1 | package grizzled.net 2 | 3 | import scala.util.Try 4 | 5 | /** Convenient Scala case-class wrapper for a `java.net.URI`. 6 | * 7 | * @param scheme the scheme, if defined 8 | * @param userInfo the user info, if defined 9 | * @param host the host, if defined 10 | * @param port the port, if defined 11 | * @param path the path, if defined 12 | * @param query the query string, if defined 13 | * @param fragment the fragment, if defined 14 | */ 15 | @SuppressWarnings(Array("org.wartremover.warts.Null")) 16 | final case class URI(scheme: Option[String], 17 | userInfo: Option[String], 18 | host: Option[String], 19 | port: Option[Int], 20 | path: Option[String], 21 | query: Option[String] = None, 22 | fragment: Option[String] = None) { 23 | 24 | /** The underlying `java.net.URI`. 25 | */ 26 | val javaURI: java.net.URI = new java.net.URI( 27 | scheme.orNull, 28 | userInfo.orNull, 29 | host.orNull, 30 | port.getOrElse(-1), 31 | path.orNull, 32 | query.orNull, 33 | fragment.orNull 34 | ) 35 | 36 | /** The coded authority for this URI. 37 | * 38 | * @return the authority, if any 39 | */ 40 | def authority: Option[String] = Option(javaURI.getAuthority) 41 | 42 | /** Resolve the given URI against this URI. 43 | * 44 | * @param uri the other URI 45 | * 46 | * @return `Success(URI)` or `Failure(Exception)` 47 | */ 48 | def resolve(uri: URI): Try[URI] = Try { URI(javaURI.resolve(javaURI)) } 49 | 50 | /** Construct a new URI by parsing the given string and resolving it against 51 | * this URI. 52 | * 53 | * @param str the string 54 | * 55 | * @return `Success(URI)` or `Failure(Exception)` 56 | */ 57 | def resolve(str: String): Try[URI] = Try { URI(javaURI.resolve(str)) } 58 | 59 | /** Relativize another URI against this one. 60 | * 61 | * @param uri the other URI 62 | * 63 | * @return `Success(URI)` or `Failure(Exception)` 64 | */ 65 | def relativize(uri: URI): Try[URI] = Try { 66 | URI(javaURI.relativize(uri.javaURI)) 67 | } 68 | 69 | /** Determine whether this URI is absolute or not. 70 | * 71 | * @return true if absolute, false if not 72 | */ 73 | val isAbsolute: Boolean = javaURI.isAbsolute 74 | 75 | /** Determine whether this URI is opaque or not. 76 | * 77 | * @return true if opaque, false if not 78 | */ 79 | val isOpaque: Boolean = javaURI.isOpaque 80 | 81 | /** Normalize the URI's path, returning a new URI. 82 | * 83 | * @return a possibly normalized URI. 84 | */ 85 | def normalize: URI = URI(javaURI.normalize) 86 | 87 | /** Get the URI string representation of this URI (i.e., the string 88 | * you could paste into a browser). Contrast this function with 89 | * `toString()`, which gets the string representation of the object 90 | * and its fields. 91 | * 92 | * @return the string 93 | */ 94 | def toExternalForm: String = javaURI.toString 95 | 96 | /** Convert to a URL object. 97 | * 98 | * @return `Success(URL)` or `Failure(Exception)` 99 | */ 100 | def toURL: Try[URL] = Try { URL(javaURI.toURL) } 101 | } 102 | 103 | /** Companion object, adding some functions that aren't available in the 104 | * generated one. 105 | */ 106 | object URI { 107 | /** Construct a URI from a `java.net.URI`. 108 | * 109 | * @param uri the `java.net.URI` 110 | */ 111 | def apply(uri: java.net.URI): URI = { 112 | URI(scheme = Option(uri.getScheme).filter(_.length > 0), 113 | userInfo = Option(uri.getUserInfo).filter(_.length > 0), 114 | host = Option(uri.getHost).filter(_.length > 0), 115 | port = if (uri.getPort < 0) None else Some(uri.getPort), 116 | path = Option(uri.getPath).filter(_.length > 0), 117 | query = Option(uri.getQuery).filter(_.length > 0), 118 | fragment = Option(uri.getFragment).filter(_.length > 0)) 119 | } 120 | 121 | /** Construct a URI from a string. 122 | * 123 | * @param uriString the string 124 | * @return `Success(URI)` on success, or `Failure(Exception)` 125 | */ 126 | def apply(uriString: String): Try[URI] = { 127 | Try { 128 | apply(new java.net.URI(uriString)) 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/net/URL.scala: -------------------------------------------------------------------------------- 1 | package grizzled.net 2 | 3 | import java.io.InputStream 4 | import java.net.{URL => JavaURL} 5 | 6 | import scala.util.Try 7 | 8 | /** Convenient Scala case-class wrapper for a `java.net.URL`. This class 9 | * doesn't include all the capabilities. For example, it lacks the equivalent 10 | * of `getContent()`, as that's better handled through other means. 11 | * 12 | * @param protocol the protocol, if defined 13 | * @param host the host, if defined 14 | * @param port the port, if defined 15 | * @param path the path 16 | * @param query the query string, if any 17 | * @param userInfo the URL's user info, if any 18 | * @param fragment the fragment, if any 19 | */ 20 | @SuppressWarnings(Array("org.wartremover.warts.Null")) 21 | final case class URL(protocol: String, 22 | host: Option[String], 23 | port: Option[Int], 24 | path: Option[String], 25 | query: Option[String] = None, 26 | userInfo: Option[String] = None, 27 | fragment: Option[String] = None) { 28 | 29 | /** The underlying `java.net.URL`. 30 | */ 31 | val javaURL: JavaURL = { 32 | // Get around some really odd Java URL issues by simply creating a 33 | // URL, then mapping it to a URL. Except that this doesn't work with 34 | // "jar" URLs. 35 | protocol match { 36 | case "http" | "https" | "file" | "ftp" => 37 | URI(scheme = Some(protocol), 38 | userInfo = userInfo, 39 | host = host, 40 | port = port, 41 | path = path, 42 | query = query, 43 | fragment = fragment).javaURI.toURL 44 | case _ => 45 | new JavaURL(protocol, host.orNull, port.getOrElse(-1), path.orNull) 46 | } 47 | } 48 | 49 | 50 | /** The coded authority for this URI. 51 | * 52 | * @return the authority, if any 53 | */ 54 | def authority: Option[String] = Option(javaURL.getAuthority) 55 | 56 | /** Get the default port for the protocol. 57 | * 58 | * @return the default port, if defined 59 | */ 60 | val defaultPort: Option[Int] = { 61 | val port = javaURL.getDefaultPort 62 | if (port < 0) None else Some(port) 63 | } 64 | 65 | /** Open an input stream to the URL. 66 | * 67 | * @return `Success(stream)` or `Failure(Exception)` 68 | */ 69 | def openStream(): Try[InputStream] = Try { 70 | javaURL.openStream() 71 | } 72 | 73 | /** Get the URL string representation of this URL (i.e., the string 74 | * you could paste into a browser). Contrast this function with 75 | * `toString()`, which gets the string representation of the object 76 | * and its fields. 77 | * 78 | * @return the string 79 | */ 80 | def toExternalForm: String = javaURL.toExternalForm 81 | } 82 | 83 | /** Companion object, adding some functions that aren't available in the 84 | * generated one. 85 | */ 86 | object URL { 87 | /** Construct a URL from a `java.net.URL`. 88 | * 89 | * @param url the `java.net.URL` 90 | */ 91 | def apply(url: java.net.URL): URL = { 92 | URL(protocol = url.getProtocol, 93 | host = Option(url.getHost).filter(_.length > 0), 94 | port = if (url.getPort < 0) None else Some(url.getPort), 95 | path = Option(url.getPath).filter(_.length > 0), 96 | userInfo = Option(url.getUserInfo).filter(_.length > 0), 97 | query = Option(url.getQuery).filter(_.length > 0), 98 | fragment = Option(url.getRef).filter(_.length > 0)) 99 | } 100 | 101 | /** Construct a URL from a string. 102 | * 103 | * @param spec the string specification 104 | * @return `Success(URL)` or `Failure(Exception)` 105 | */ 106 | def apply(spec: String): Try[URL] = Try { URL(new java.net.URL(spec)) } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/net/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Network-related utility methods and classes. 4 | */ 5 | package object net { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/package.scala: -------------------------------------------------------------------------------- 1 | /** The Grizzled Scala Library contains a variety of miscellaneous, 2 | * general purpose utility classes and objects. 3 | * 4 | * The home page for the Grizzled Scala Library is 5 | * [[http://software.clapper.org/grizzled-scala/]]. Please see that page for 6 | * complete details, including installation instructions. 7 | */ 8 | package object grizzled { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/parsing/Pushback.scala: -------------------------------------------------------------------------------- 1 | package grizzled.parsing 2 | 3 | /** The `Pushback` trait can be mixed into an `SafeIterator` to permit 4 | * arbitrary pushback. 5 | * 6 | * NOTE: This trait it not thread-safe. 7 | */ 8 | trait Pushback[T] extends SafeIterator[T] { 9 | 10 | @SuppressWarnings(Array("org.wartremover.warts.Var")) 11 | private var pushbackStack = List.empty[T] 12 | 13 | /** Get the next item from the stream, advancing the cursor, while 14 | * honoring previous calls to `pushback()`. 15 | * 16 | * @return an `Option` containing the next item, or `None` 17 | * if the iterator is exhausted. 18 | */ 19 | override def next: Option[T] = { 20 | 21 | pushbackStack match { 22 | case Nil => super.next 23 | 24 | case head :: tail => 25 | pushbackStack = tail 26 | Some(head) 27 | } 28 | } 29 | 30 | /** Push a single item back onto the stream. 31 | * 32 | * @param item the item 33 | */ 34 | def pushback(item: T): Unit = { 35 | pushbackStack = item :: pushbackStack 36 | } 37 | 38 | /** Push a list of items back onto the stream. The items are pushed 39 | * back in reverse order, so the items in the list should be in the order 40 | * they were retrieved from the stream. For example: 41 | * 42 | * {{{ 43 | * val stream = new SafeIterator[Char]("foobar") with Pushback[Char] 44 | * val list = List(stream.next.get, stream.next.get) 45 | * 46 | * // At this point, the list contains ('f', 'o'), and the stream 47 | * // contains "obar". 48 | * 49 | * stream.pushback(list) // Stream now contains "foobar" 50 | * }}} 51 | * 52 | * @param items the items to push back. 53 | */ 54 | def pushbackMany(items: List[T]): Unit = { 55 | pushbackStack = items ::: pushbackStack 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/parsing/SafeIterator.scala: -------------------------------------------------------------------------------- 1 | package grizzled.parsing 2 | 3 | /** `SafeIterator` places a simple stream on top of an iterator, 4 | * returning `Option`-wrapped instances from the underlying iterator. 5 | * When the stream is exhausted, the `Iterator` stream returns 6 | * `None`. Differences from a plain `Iterator` include: 7 | * 8 | * - An `SafeIterator` will not throw an exception if you try to read 9 | * past the end of it. Instead, it will just keep returning `None`. 10 | * 11 | * Example of use with a string: 12 | * 13 | * {{{ 14 | * import grizzled.parsing.SafeIterator 15 | * 16 | * val s = ... 17 | * val istream = new SafeIterator[Char](s.elements) 18 | * }}} 19 | * 20 | * @param iterator the iterator to wrap 21 | */ 22 | @SuppressWarnings(Array("org.wartremover.warts.Var")) 23 | class SafeIterator[+T](private val iterator: Iterator[T]) { 24 | private var count = 0 25 | 26 | /** Alternate constructor that takes an `Iterable`. 27 | * 28 | * @param iterable the `Iterable` 29 | */ 30 | def this(iterable: Iterable[T]) = this(iterable.iterator) 31 | 32 | /** Get the next item from the stream, advancing the cursor. 33 | * 34 | * @return an `Option` containing the next item, or `None` 35 | * if the iterator is exhausted. 36 | */ 37 | def next: Option[T] = { 38 | if (! iterator.hasNext) 39 | None 40 | 41 | else { 42 | count += 1 43 | Some(iterator.next) 44 | } 45 | } 46 | } 47 | 48 | /** Companion object for `SafeIterator`. 49 | */ 50 | object SafeIterator { 51 | /** Create a new `SafeIterator` from an `Iterable`. 52 | * 53 | * @param iterable the `Iterable`` 54 | * @tparam T the type of the `Iterable` 55 | * 56 | * @return the allocated `SafeIterator` 57 | */ 58 | def apply[T](iterable: Iterable[T]): SafeIterator[T] = 59 | new SafeIterator(iterable) 60 | 61 | /** Create a new `SafeIterator` from an `Iterator`. 62 | * 63 | * @param iterator the `Iterator`` 64 | * @tparam T the type of the `Iterable` 65 | * 66 | * @return the allocated `SafeIterator` 67 | */ 68 | def apply[T](iterator: Iterator[T]): SafeIterator[T] = 69 | new SafeIterator(iterator) 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/parsing/StringToken.scala: -------------------------------------------------------------------------------- 1 | package grizzled.parsing 2 | 3 | /** A simple string token class, consisting of: 4 | * 5 | * - a string token 6 | * - the starting position of the token in the original string from which 7 | * the token was parsed 8 | * 9 | * This class is used by the `toTokens()` method in 10 | * [[grizzled.string.Implicits.String.GrizzledString]]. 11 | * 12 | * @param string the string token 13 | * @param start the start of the token within the original string 14 | */ 15 | final case class StringToken(string: String, start: Int) { 16 | override def toString = string 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/parsing/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Methods and classes useful for parsing various things. 4 | */ 5 | package object parsing { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/random/RandomUtil.scala: -------------------------------------------------------------------------------- 1 | package grizzled.random 2 | 3 | import scala.util.Random 4 | 5 | /** Utility functions for working with random numbers. These functions use 6 | * the default `scala.util.Random` object. To specify your own `Random` 7 | * instance, create an instance of the `RandomUtil` companion class. 8 | */ 9 | object RandomUtil extends RandomUtilFunctions { 10 | val rng = Random 11 | } 12 | 13 | /** Utility functions for working with random numbers. It's more convenient 14 | * to use the companion `RandomUtil` object, unless you need to specify your 15 | * own `scala.util.Random` instance. 16 | * 17 | * @param rng the `scala.util.Random` instance to use. Defaults to the 18 | * `scala.util.Random` object. 19 | */ 20 | class RandomUtil(val rng: Random = Random) extends RandomUtilFunctions 21 | 22 | /** The trait that implements the actual random utility methods. 23 | */ 24 | trait RandomUtilFunctions { 25 | val rng: Random 26 | 27 | val DefaultRandomStringChars = "abcdefghijklmnopqrstuvwxyz" + 28 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + 29 | "0123456789" 30 | 31 | /** Choose a random value from an array of values. 32 | * 33 | * @param a the array 34 | * @tparam T the type of the elements in the sequent 35 | * 36 | * @return a random value from the array 37 | */ 38 | def randomChoice[T](a: Array[T]): T = a(rng.nextInt(a.length)) 39 | 40 | /** Choose a random value from an indexed sequence of values. 41 | * 42 | * @param seq the sequence 43 | * @tparam T the type of the elements in the sequent 44 | * 45 | * @return a random value from the sequence 46 | */ 47 | def randomChoice[T](seq: IndexedSeq[T]): T = seq(rng.nextInt(seq.length)) 48 | 49 | /** Return a random integer between `low` and `high`, inclusive. If `low` 50 | * and `high` are identical, `low` is returned. 51 | * 52 | * @param low the lowest number 53 | * @param high the highest number 54 | * 55 | * @return an integer in the range `[low, high]`. 56 | * 57 | * @throws IllegalArgumentException if `low` is greater than `high`. 58 | */ 59 | def randomIntBetween(low: Int, high: Int): Int = { 60 | require(low <= high) 61 | if (low == high) low else rng.nextInt(high - low) + low 62 | } 63 | 64 | /** Return a random long integer between `low` and `high`, inclusive. If `low` 65 | * and `high` are identical, `low` is returned. 66 | * 67 | * @param low the lowest number 68 | * @param high the highest number 69 | * 70 | * @return a long integer in the range `[low, high]`. 71 | * 72 | * @throws IllegalArgumentException if `low` is greater than `high`. 73 | */ 74 | def randomLongBetween(low: Long, high: Long): Long = { 75 | require(low <= high) 76 | val range = high - low + 1 77 | if (low == high) low else Math.abs(rng.nextLong % range) + low 78 | } 79 | 80 | /** Return a random string. This method is similar to `Random.nextString()`, 81 | * except that it allows you to control the set of characters that are 82 | * allowed to be in the returned string. The set of characters defaults to 83 | * ASCII alphanumerics. 84 | * 85 | * @param length the size of the string to return 86 | * @param chars the set of legal characters 87 | * 88 | * @return a random string, drawn from the supplied characters, of the 89 | * specified length 90 | */ 91 | def randomString(length: Int, 92 | chars: String = DefaultRandomStringChars): String = { 93 | require(chars.length > 0) 94 | if (chars.length == 1) 95 | chars.take(1) * length 96 | else 97 | (1 to length).map { _ => randomChoice(chars) }.mkString 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/reflect.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** Some reflection-related utility methods and classes. 4 | */ 5 | object reflect { 6 | import scala.reflect.{ClassTag, classTag} 7 | 8 | /** Determine whether an object is of a particular type. Example 9 | * of use: 10 | * 11 | * {{{ 12 | * def foo(obj: Any) = { 13 | * // Is this object of type Seq[Int] or just Int? 14 | * if (isOfType[Int](obj)) 15 | * ... 16 | * else if (isOfType[Seq[Int]](obj)) 17 | * ... 18 | * else 19 | * ... 20 | * } 21 | * }}} 22 | * 23 | * @param o the object to test 24 | * @tparam T the type to test against 25 | * 26 | * @return `true` if `o` is of type `T`, `false` if not. 27 | */ 28 | def isOfType[T: ClassTag](o: Any): Boolean = { 29 | val clsT = classTag[T].runtimeClass 30 | 31 | @SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf")) 32 | def isPrimitive[P: ClassTag]: Boolean = 33 | classTag[P].runtimeClass.isAssignableFrom(o.asInstanceOf[AnyRef].getClass) 34 | 35 | @SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf")) 36 | def isClass: Boolean = 37 | clsT.isAssignableFrom(o.asInstanceOf[AnyRef].getClass) 38 | 39 | clsT.toString match { 40 | case "int" => isPrimitive[java.lang.Integer] 41 | case "short" => isPrimitive[java.lang.Short] 42 | case "long" => isPrimitive[java.lang.Long] 43 | case "float" => isPrimitive[java.lang.Float] 44 | case "double" => isPrimitive[java.lang.Double] 45 | case "char" => isPrimitive[java.lang.Character] 46 | case "byte" => isPrimitive[java.lang.Byte] 47 | case "boolean" => isPrimitive[java.lang.Boolean] 48 | case _ => isClass 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/security/MessageDigest.scala: -------------------------------------------------------------------------------- 1 | package grizzled.security 2 | 3 | import java.io.{File, FileInputStream, InputStream} 4 | import java.security.{MessageDigest => JMessageDigest} 5 | 6 | import scala.io.Source 7 | 8 | import grizzled.string.{util => StringUtil} 9 | 10 | /** A message digest producer. 11 | */ 12 | class Digester(dg: JMessageDigest) { 13 | 14 | /** Create a digest of a string, returning the digest bytes. 15 | * 16 | * @param string the input string 17 | * 18 | * @return the digest bytes 19 | */ 20 | def digest(string: String): Array[Byte] = { 21 | dg.synchronized { 22 | dg.reset() 23 | dg.update(string.getBytes) 24 | dg.digest 25 | } 26 | } 27 | 28 | /** Create a digest of a string, returning the digest string. 29 | * 30 | * @param string the input string 31 | * 32 | * @return the digest bytes 33 | */ 34 | def digestString(string: String): String = { 35 | StringUtil.bytesToHexString(digest(string)) 36 | } 37 | 38 | /** Create a digest of a file, returning the digest bytes. 39 | * 40 | * @param file the file 41 | * 42 | * @return the digest bytes 43 | */ 44 | def digest(file: File): Array[Byte] = digest(new FileInputStream(file)) 45 | 46 | /** Create a digest of a string, returning the digest string. 47 | * 48 | * @param file the file 49 | * 50 | * @return the digest bytes 51 | */ 52 | def digestString(file: File): String = { 53 | StringUtil.bytesToHexString(digest(file)) 54 | } 55 | 56 | /** Create a digest of a `Source`. Note that sources are implicitly 57 | * character-based, not byte-based. 58 | * 59 | * @param source the `Source` 60 | * 61 | * @return the digest bytes 62 | */ 63 | def digest(source: Source): Array[Byte] = { 64 | dg.synchronized { 65 | dg.reset() 66 | 67 | for (c <- source) 68 | dg.update(c.toByte) 69 | 70 | dg.digest 71 | } 72 | } 73 | 74 | /** Create a digest of a `Source`, returning the digest string. Note that 75 | * sources are implicitly character-based, not byte-based. 76 | * 77 | * @param source the `Source` 78 | * 79 | * @return the digest string 80 | */ 81 | def digestString(source: Source): String = { 82 | StringUtil.bytesToHexString(digest(source)) 83 | } 84 | 85 | /** Create a digest of an `InputStream`, returning the digest bytes. 86 | * 87 | * @param stream the `InputStream` 88 | * 89 | * @return the digest bytes 90 | */ 91 | @SuppressWarnings(Array("org.wartremover.warts.While", 92 | "org.wartremover.warts.Var")) 93 | def digest(stream: InputStream): Array[Byte] = { 94 | dg.synchronized { 95 | dg.reset() 96 | 97 | val buf = Array.ofDim[Byte](8192) 98 | var readLen = stream.read(buf) 99 | 100 | while (readLen > 0) { 101 | dg.update(buf, 0, readLen) 102 | readLen = stream.read(buf) 103 | } 104 | 105 | dg.digest 106 | } 107 | } 108 | 109 | /** Create a digest of an `InputStream`, returning the digest string. 110 | * 111 | * @param stream the `InputStream` 112 | * 113 | * @return the digest string 114 | */ 115 | def digestString(stream: InputStream): String = { 116 | StringUtil.bytesToHexString(digest(stream)) 117 | } 118 | } 119 | 120 | /** Convenience methods for generating crypto-hash (i.e., message digest) 121 | * strings (e.g., MD5, SHA256, etc.) from byte values. Sample use: 122 | * 123 | * {{{ 124 | * // Generate an MD5 hash-string of a string 125 | * 126 | * val hash = MessageDigest("md5").digestString("foo") 127 | * 128 | * // Generate an SHA256 hash-string of a file's contents. 129 | * 130 | * val hash = MessageDigest("sha256").digestString(new File("/tmp/foo")) 131 | * }}} 132 | */ 133 | object MessageDigest { 134 | 135 | /** Get a `Digester` for the specified algorithm. 136 | * 137 | * @param algorithm Any algorithm string accepted by 138 | * `java.security.MessageDigest.getInstance()` 139 | * 140 | * @return the `Digester` object. 141 | */ 142 | def apply(algorithm: String): Digester = { 143 | new Digester(JMessageDigest.getInstance(algorithm)) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/string/WordWrapper.scala: -------------------------------------------------------------------------------- 1 | package grizzled.string 2 | 3 | import scala.annotation.tailrec 4 | import scala.sys.SystemProperties 5 | 6 | /** Wraps strings on word boundaries to fit within a proscribed output 7 | * width. The wrapped string may have a prefix or not; prefixes are useful 8 | * for error messages, for instance. You tell a `WordWrapper` about 9 | * a prefix by passing a non-empty prefix to the constructor. 10 | * 11 | *

Examples:

12 | * 13 | * {{{Unable to open file /usr/local/etc/wombat: No such file or directory}}} 14 | * 15 | * might appear like this without a prefix: 16 | * 17 | * {{{ 18 | * Unable to open file /usr/local/etc/wombat: No such file or 19 | * directory 20 | * }}} 21 | * 22 | * and like this if the prefix is "myprog:" 23 | * 24 | * {{{ 25 | * myprog: Unable to open file /usr/local/etc/wombat: No such 26 | * file or directory 27 | * }}} 28 | * 29 | * Alternatively, if the output width is shortened, the same message 30 | * can be made to wrap something like this: 31 | * 32 | * {{{ 33 | * myprog: Unable to open file 34 | * /usr/local/etc/wombat: 35 | * No such file or 36 | * directory 37 | * }}} 38 | * 39 | * Note how the wrapping logic will "tab" past the prefix on wrapped 40 | * lines. 41 | * 42 | * This method also supports the notion of an indentation level, which is 43 | * independent of the prefix. A non-zero indentation level causes each line, 44 | * including the first line, to be indented that many characters. Thus, 45 | * initializing a `WordWrapper` object with an indentation value of 4 46 | * will cause each output line to be preceded by 4 blanks. (It's also 47 | * possible to change the indentation character from a blank to any other 48 | * character. 49 | * 50 | *

Notes

51 | * 52 | * - The class does not do any special processing of tab characters. 53 | * Embedded tab characters can have surprising (and unwanted) effects 54 | * on the rendered output. 55 | * - Wrapping an already wrapped string is an invitation to trouble. 56 | * 57 | * @param wrapWidth the number of characters after which to wrap each line 58 | * @param indentation how many characters to indent 59 | * @param prefix the prefix to use, or "" for none. Cannot be null. 60 | * @param ignore set of characters to ignore when calculating wrapping. 61 | * This feature can be useful when certain characters 62 | * represent escape characters, and you intend to 63 | * post-process the wrapped string. 64 | * @param indentChar the indentation character to use. 65 | */ 66 | final case class WordWrapper(wrapWidth: Int = 79, 67 | indentation: Int = 0, 68 | prefix: String = "", 69 | ignore: Set[Char] = Set.empty[Char], 70 | indentChar: Char = ' ') { 71 | require(Option(prefix).isDefined) // null check 72 | 73 | private val prefixLength = wordLen(prefix) 74 | private val lineSep = (new SystemProperties).getOrElse("line.separator", "\n") 75 | 76 | /** Wrap a string, using the wrap width, prefix, indentation and indentation 77 | * character that were specified to the `WordWrapper` constructor. 78 | * The resulting string may have embedded newlines in it. 79 | * 80 | * @param s the string to wrap 81 | * @return the wrapped string 82 | */ 83 | def wrap(s: String): String = { 84 | import scala.collection.mutable.ArrayBuffer 85 | import grizzled.string.Implicits.String._ 86 | 87 | val indentString = indentChar.toString 88 | val prefixIndentChars = indentString * prefixLength 89 | val indentChars = indentString * indentation 90 | val buf = new ArrayBuffer[String] 91 | 92 | def assembleLine(prefix: String, buf: Vector[String]): String = 93 | prefix + indentChars + buf.mkString(" ") 94 | 95 | def wrapOneLine(line: String, prefix: String): String = { 96 | 97 | @tailrec 98 | def wrapNext(words: List[String], 99 | curLine: Vector[String], 100 | curPrefix: String, 101 | lines: Vector[String]): (Vector[String], String) = { 102 | words match { 103 | case Nil if curLine.isEmpty => (lines, curPrefix) 104 | case Nil => (lines :+ assembleLine(curPrefix, curLine), curPrefix) 105 | case word :: rest => 106 | val wordLength = word.length 107 | val prefixLength = wordLen(curPrefix) 108 | 109 | // Total number of blanks between words in this line = number of 110 | // words - 1 (since we don't put a blank at the end of the line). 111 | val totalBlanks = scala.math.max(curLine.length - 1, 0) 112 | 113 | // Combined length of all words in the current line. 114 | val wordLengths = curLine.map(wordLen).sum 115 | 116 | // The length of the line being assembled is the length of each 117 | // word in the curLine buffer, plus a single blank between them, 118 | // plus any prefix and indentation. to map the words to their 119 | // lengths, and a fold-left operation to sum them up. 120 | val currentLength = totalBlanks + wordLengths + 121 | curPrefix.length + indentation 122 | if ((wordLength + currentLength + 1) > wrapWidth) { 123 | // Adding this word to the current line would exceed the wrap 124 | // width. Put the line together, save it, and start a new one. 125 | val line = assembleLine(curPrefix, curLine) 126 | wrapNext(rest, Vector(word), prefixIndentChars, lines :+ line) 127 | } 128 | else { 129 | // It's safe to put this word in the current line. 130 | wrapNext(rest, curLine :+ word, curPrefix, lines) 131 | } 132 | } 133 | } 134 | 135 | val (lineOut, curPrefix) = wrapNext(line.split("""\s""").toList, 136 | Vector.empty[String], 137 | prefix, 138 | Vector.empty[String]) 139 | if (lineOut.nonEmpty) 140 | lineOut.mkString(lineSep).rtrim 141 | else 142 | "" 143 | } 144 | 145 | val lines = s.split(lineSep) 146 | buf += wrapOneLine(lines(0), prefix) 147 | for (line <- lines.drop(1)) 148 | buf += wrapOneLine(line, prefixIndentChars) 149 | 150 | buf mkString lineSep 151 | } 152 | 153 | private def wordLen(word: String) = word.filter(! ignore.contains(_)).length 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/string/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** String- and text-related classes. 4 | */ 5 | package object string { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/string/template/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled.string 2 | 3 | /** Scala classes that provide for variable substitution within strings, 4 | * akin to the Python `StringTemplate` library. Several syntaxes are 5 | * supported. 6 | */ 7 | package object template { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/string/util.scala: -------------------------------------------------------------------------------- 1 | package grizzled.string 2 | 3 | import scala.annotation.tailrec 4 | import scala.util.Try 5 | 6 | /** Useful string-related utility functions. 7 | */ 8 | object util { 9 | 10 | private val BooleanStrings = Map( 11 | "true" -> true, 12 | "t" -> true, 13 | "yes" -> true, 14 | "y" -> true, 15 | "1" -> true, 16 | "on" -> true, 17 | "false" -> false, 18 | "f" -> false, 19 | "no" -> false, 20 | "n" -> false, 21 | "0" -> false, 22 | "off" -> false 23 | ) 24 | 25 | /** Convert a string to a boolean. 26 | * 27 | * This method currently understands the following strings (in any mixture 28 | * of upper and lower case). It is currently English-specific. 29 | * 30 | * {{{ 31 | * true, t, yes, y, 1 32 | * false, f, no, n, 0 33 | * }}} 34 | * 35 | * @param s the string to convert 36 | * 37 | * @return `Right(boolean)` on success, `Left(error)` on failure 38 | */ 39 | def strToBoolean(s: String): Either[String, Boolean] = { 40 | BooleanStrings.get(s.trim.toLowerCase) 41 | .map(Right(_)) 42 | .getOrElse(Left(s"Cannot convert '$s' to boolean")) 43 | } 44 | 45 | /** Convert an array of bytes to a hexadecimal string. 46 | * 47 | * @param bytes the array of bytes 48 | * 49 | * @return the hexadecimal string, with lower-case hex digits and no 50 | * separators. 51 | */ 52 | def bytesToHexString(bytes: Array[Byte]): String = { 53 | bytes.map("%02x" format _).mkString 54 | } 55 | 56 | // Presumably faster than Integer.parseInt() 57 | private val HexDigits = Map( 58 | '0' -> 0, '1' -> 1, '2' -> 2, '3' -> 3, 59 | '4' -> 4, '5' -> 5, '6' -> 6, '7' -> 7, 60 | '8' -> 8, '9' -> 9, 'a' -> 10, 'b' -> 11, 61 | 'c' -> 12, 'd' -> 13, 'e' -> 14, 'f' -> 15 62 | ) 63 | 64 | /** Convert a hex string to bytes. 65 | * 66 | * @param hexString the hex string 67 | * 68 | * @return `Some(bytes)` if the string was succesfully parsed; 69 | * `None` if the string could not be parsed. 70 | */ 71 | def hexStringToBytes(hexString: String): Option[Array[Byte]] = { 72 | 73 | @SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf")) 74 | def parse(chars: Seq[Char], accum: Array[Byte]): Option[Array[Byte]] = { 75 | chars match { 76 | case upper :: lower :: rest => { 77 | val res = for { u <- HexDigits.get(upper) 78 | l <- HexDigits.get(lower) } 79 | yield ((u << 4) | l).asInstanceOf[Byte] 80 | 81 | res map { byte => parse(rest, accum :+ byte) } getOrElse { None } 82 | } 83 | case Nil => Some(accum) 84 | } 85 | } 86 | 87 | if ((hexString.length % 2) == 0) 88 | parse(hexString.toLowerCase.toList, Array.empty[Byte]) 89 | else 90 | None 91 | } 92 | 93 | private lazy val QUOTED_REGEX = """(["'])(?:\\?+.)*?\1""".r 94 | private lazy val WHITE_SPACE_REGEX = """\s+""".r 95 | private lazy val QUOTE_SET = Set('\'', '"') 96 | 97 | /** Tokenize a string the way a command line shell would, honoring quoted 98 | * strings and embedded escaped quotes. Single quoted strings must start 99 | * and end with single quotes. Double quoted strings must start and end 100 | * with double quotes. Within quoted strings, the quotes themselves may 101 | * be backslash-escaped. Quoted and non-quoted tokens may be mixed in 102 | * the string; quotes are stripped. 103 | * 104 | * Examples: 105 | * 106 | * {{{ 107 | * val s = """one two "three four" """ 108 | * for (t <- tokenizeWithQuotes(s)) println("|" + t + "|") 109 | * // Prints: 110 | * // |one| 111 | * // |two| 112 | * // |three four| 113 | * 114 | * val s = """one two 'three "four'""" 115 | * for (t <- tokenizeWithQuotes(s)) println("|" + t + "|") 116 | * // Prints: 117 | * // |one| 118 | * // |two| 119 | * // |three "four| 120 | * 121 | * val s = """one two 'three \'four ' fiv"e""" 122 | * for (t <- tokenizeWithQuotes(s)) println("|" + t + "|") 123 | * // Prints: 124 | * // |one| 125 | * // |two| 126 | * // |three 'four | 127 | * // |fiv"e| 128 | * }}} 129 | * 130 | * @param s the string to tokenize 131 | * 132 | * @return the tokens, as a list of strings 133 | */ 134 | def tokenizeWithQuotes(s: String): List[String] = { 135 | def fixedQuotedString(qs: String): String = { 136 | val stripped = qs.substring(1, qs.length - 1) 137 | if (qs(0) == '"') 138 | stripped.replace("\\\"", "\"") 139 | else 140 | stripped.replace("\\'", "'") 141 | } 142 | 143 | val trimmed = s.trim() 144 | 145 | if (trimmed.length == 0) 146 | Nil 147 | 148 | else if (QUOTE_SET.contains(trimmed(0))) { 149 | val mOpt = QUOTED_REGEX.findFirstMatchIn(trimmed) 150 | mOpt.map { matched => 151 | val matchedString = matched.toString 152 | val token = fixedQuotedString(matchedString) 153 | val past = trimmed.substring(matched.end) 154 | List(token) ++ tokenizeWithQuotes(past) 155 | } 156 | .getOrElse( // to EOL 157 | List(trimmed) 158 | ) 159 | } 160 | 161 | else { 162 | val mOpt = WHITE_SPACE_REGEX.findFirstMatchIn(trimmed) 163 | mOpt.map { matched => 164 | val token = trimmed.substring(0, matched.start) 165 | val past = trimmed.substring(matched.end) 166 | List(token) ++ tokenizeWithQuotes(past) 167 | } 168 | .getOrElse( // to EOL 169 | List(trimmed) 170 | ) 171 | } 172 | } 173 | 174 | /** Given a sequence of strings, find the longest common prefix. 175 | * 176 | * @param strings the strings to compare 177 | * 178 | * @return the longest common prefix, which might be "" 179 | */ 180 | def longestCommonPrefix(strings: Seq[String]): String = { 181 | 182 | def isCommonPrefix(len: Int): Boolean = { 183 | strings match { 184 | case Seq(head, _*) => 185 | val prefix = head.slice(0, len) 186 | // If there's a single string in the list that doesn't start with this 187 | // prefix, the first N characters of strings(0) is not a common prefix. 188 | strings.forall { _.startsWith(prefix) } 189 | 190 | case _ => 191 | false 192 | } 193 | } 194 | 195 | @tailrec 196 | def search(low: Int, high: Int): (Int, Int) = { 197 | if (low > high) 198 | (low, high) 199 | else { 200 | val middle = (low + high) / 2 201 | if (isCommonPrefix(middle)) 202 | search(low = middle + 1, high = high) 203 | else 204 | search(low = low, high = middle - 1) 205 | } 206 | 207 | } 208 | 209 | strings match { 210 | case Seq(head, _*) => 211 | // Use foldLeft(), instead of min, because min bails with an exception 212 | // on an empty sequence. 213 | val minLen = strings 214 | .map(_.length) 215 | .foldLeft(Int.MaxValue) { (a, b) => if (a < b) a else b } 216 | val (low, high) = search(1, minLen) 217 | head.slice(0, (low + high) / 2) 218 | 219 | case _ => "" 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/sys.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** System-related utility functions and definitions. 4 | */ 5 | object sys { 6 | 7 | /** Indicator of current operating system. 8 | * 9 | * - VMS - OpenVMS 10 | * - Windows - Microsoft Windows, other than Windows CE 11 | * - WindowsCE - Microsoft Windows CE 12 | * - OS2 - OS2 13 | * - NetWare - NetWare 14 | * - Mac - Mac OS, prior to Mac OS X 15 | * - Posix - Anything Unix-like, including Mac OS X 16 | */ 17 | sealed abstract class OperatingSystem(val name: String) 18 | object OperatingSystem { // For backward compatibility 19 | case object Posix extends OperatingSystem("Posix") 20 | case object Mac extends OperatingSystem("Mac OS") 21 | case object Windows extends OperatingSystem("Windows") 22 | case object WindowsCE extends OperatingSystem("Windows CE") 23 | case object OS2 extends OperatingSystem("OS/2") 24 | case object NetWare extends OperatingSystem("NetWare") 25 | case object VMS extends OperatingSystem("VMS") 26 | } 27 | 28 | import OperatingSystem._ 29 | 30 | /** The current operating system, a value of the `OperatingSystem` 31 | * enumeration. 32 | */ 33 | val os: OperatingSystem = getOS(System.getProperty("os.name")) 34 | 35 | /** Version of the `os` function that takes an operating system name 36 | * and returns the `OperatingSystem` enumerated value. 37 | */ 38 | private val WindowsNameMatch = "^(windows)(.*)$".r 39 | def getOS(name: String): OperatingSystem = { 40 | val lcName = name.toLowerCase 41 | val firstToken = lcName.split("""\s""")(0) 42 | firstToken match { 43 | case "windows" if lcName == "windows ce" => WindowsCE 44 | case "windows" if lcName != "windows ce" => Windows 45 | case "mac" => Mac 46 | case "os/2" => OS2 47 | case "netware" => NetWare 48 | case "openvms" => VMS 49 | case _ => Posix 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/util.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | import grizzled.ScalaCompat.scalautil.Using 4 | 5 | import scala.annotation.implicitNotFound 6 | import scala.language.implicitConversions 7 | import scala.language.reflectiveCalls 8 | import scala.concurrent.Future 9 | import scala.util.{Failure, Success, Try} 10 | 11 | /** Miscellaneous utility functions and methods not otherwise categorized. 12 | */ 13 | package object util { 14 | object Implicits { 15 | /** Enriched `Try` class, containing some helper methods. 16 | * 17 | * @param t the underlying `Try` 18 | */ 19 | implicit class RichTry[T](t: Try[T]) { 20 | 21 | /** Replacement for `scala.concurrent.Future.fromTry()`, which can't be 22 | * used here, because we still compile on Scala 2.10, and 2.10 doesn't 23 | * have that function. Converts a `Try` to a `Future`. A successful 24 | * `Try` (a `Success`) becomes a completed and successful `Future`. 25 | * A failed `Try` (a `Failure`) becomes a completed and failed `Future`. 26 | * 27 | * @return the corresponding `Future`. 28 | */ 29 | def toFuture: Future[T] = t match { 30 | case Success(value) => Future.successful(value) 31 | case Failure(ex) => Future.failed(ex) 32 | } 33 | } 34 | } 35 | 36 | /** `withResource()` needs an implicit evidence parameter of this type 37 | * to know how to release what's passed to it. Note that on Scala 2.13, 38 | * this type is just a type alias for `scala.util.Using.Releasable`. 39 | * 40 | * @tparam T the type (which must be contravariant to allow, for instance, 41 | * a `T` of `Closeable` to apply to subclasses like `InputStream`). 42 | */ 43 | @implicitNotFound("Can't find a CanReleaseSource[${T}] for withResource/tryWithResource") 44 | type CanReleaseResource[-T] = Using.Releasable[T] 45 | 46 | /** Companion object for `CanReleaseResource`. 47 | */ 48 | object CanReleaseResource { 49 | 50 | /** 51 | * Note: Do _not_ import `grizzled.util.CanReleaseResource._` in Scala 52 | * 2.12.x. Doing so will cause compiler errors if you then attempt to use 53 | * `withResource` on a `scala.io.Source`, since `Source` is also a 54 | * `Closeable` in 2.12, and this object provides implicit evidence objects 55 | * for both. Use explicit imports for the evidence objects you need. 56 | */ 57 | object Implicits { 58 | import java.io.Closeable 59 | import scala.io.Source 60 | 61 | /** Evidence for type `Closeable`. 62 | */ 63 | implicit object CanReleaseCloseable 64 | extends CanReleaseResource[Closeable] { 65 | 66 | def release(c: Closeable) = c.close() 67 | } 68 | 69 | /** Evidence for type `Source`. Note that, in Scala 2.12.0, 70 | * `Source` is also `Closeable`. 71 | */ 72 | implicit object CanReleaseSource extends CanReleaseResource[Source] { 73 | def release(s: Source) = s.close() 74 | } 75 | 76 | /** Evidence for type `AutoCloseable`, which allows the use of 77 | * `withResource` and `tryWithResource` with types such as 78 | * `java.sql.Connection`. 79 | */ 80 | implicit object CanReleaseAutoCloseable 81 | extends CanReleaseResource[AutoCloseable] { 82 | 83 | def release(c: AutoCloseable) = c.close() 84 | } 85 | } 86 | } 87 | 88 | /** Ensure that a closeable object is closed. Note that this function 89 | * requires an implicit evidence parameter of type `CanClose` to determine 90 | * how to close the object. You can implement your own, though common 91 | * ones are provided automatically. 92 | * 93 | * Sample use: 94 | * 95 | * {{{ 96 | * withResource(new java.io.FileInputStream("/path/to/file")) { in => 97 | * ... 98 | * } 99 | * }}} 100 | * 101 | * In Scala 2.13, you can use `scala.util.Using.resource` to accomplish the 102 | * the same thing: 103 | * 104 | * {{{ 105 | * import scala.util.Using 106 | * 107 | * Using.resource(new java.io.FileInputStream("/path/to/file")) { in => 108 | * ... 109 | * } 110 | * }}} 111 | * 112 | * On Scala 2.13, `withResource` is implemented in terms of `Using.resource`. 113 | * On previous versions, it is implemented in terms of a `Using` 114 | * compatibility layer. 115 | * 116 | * '''Note''': If the block throws an exception, `withResource` propagates 117 | * the exception. If you want to capture the exception, instead, use 118 | * [[grizzled.util.tryWithResource]]. 119 | * 120 | * @param resource the object that holds a resource to be released 121 | * @param code the code block to execute with the resource 122 | * @param mgr the resource manager that can release the resource 123 | * 124 | * @tparam T the type of the resource 125 | * @tparam R the return type of the code block 126 | * 127 | * @return whatever the block returns 128 | */ 129 | @inline 130 | final def withResource[T, R](resource: T) 131 | (code: T => R) 132 | (implicit mgr: CanReleaseResource[T]): R = { 133 | import ScalaCompat.scalautil.Using 134 | Using.resource(resource)(code) 135 | } 136 | 137 | /** A version of [[grizzled.util.withResource]] that captures any thrown 138 | * exception, instead of propagating it. 139 | * 140 | * Example: 141 | * 142 | * {{{ 143 | * val t: Try[Unit] = tryWithResource(new java.io.FileInputStream("...")) { in => 144 | * } 145 | * }}} 146 | * 147 | * In Scala 2.13, you can use `scala.util.Using.apply` to accomplish the 148 | * the same thing: 149 | * 150 | * {{{ 151 | * import scala.util.Using 152 | * 153 | * val t: Try[Unit] = Using(new java.io.FileInputStream("/path/to/file")) { in => 154 | * ... 155 | * } 156 | * }}} 157 | * 158 | * On Scala 2.13, `tryWithResource` is implemented in terms of `Using.apply`. 159 | * On previous versions, it is implemented in terms of a `Using` 160 | * compatibility layer. 161 | * 162 | * @param open the by-name parameter (code block) to open the resource. 163 | * This parameter is a by-name parameter so that this 164 | * function can capture any exceptions it throws. 165 | * @param code the code block to execute with the resource 166 | * @param mgr the resource manager that can release the resource 167 | * 168 | * @tparam T the type of the resource 169 | * @tparam R the return type of the code block 170 | * 171 | * @return A `Success` containing the result of the code block, or a 172 | * `Failure` with any thrown exception. 173 | */ 174 | @inline 175 | final def tryWithResource[T, R](open: => T) 176 | (code: T => R) 177 | (implicit mgr: CanReleaseResource[T]): Try[R] = { 178 | Try { 179 | withResource(open)(code)(mgr) 180 | } 181 | } 182 | } 183 | 184 | -------------------------------------------------------------------------------- /src/main/scala/grizzled/zip/package.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | /** The `grizzled.zip` package contains classes and functions to make it easier 4 | * to operate on zip and jar files. 5 | */ 6 | package object zip 7 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /* Scaladoc style sheet */ 2 | 3 | a:link { 4 | color: #0000ee; 5 | } 6 | 7 | a:visited { 8 | color: #551a8b; 9 | } 10 | 11 | a:active { 12 | color: #0000ee; 13 | } 14 | 15 | body { 16 | background-color: #ffffff; 17 | } 18 | 19 | div.entity { 20 | margin: 18px 0px 18px 0px; 21 | font-size: x-large; 22 | font-weight: bold; 23 | } 24 | 25 | div.doctitle { 26 | font-weight: bold; 27 | font-style: italic; 28 | } 29 | 30 | div.doctitle-larger { 31 | margin: 0px 0px 10px 0px; 32 | font-size: larger; 33 | font-weight: bold; 34 | } 35 | 36 | div.kinds { 37 | margin: 0.6em 0 0 0; /* top right bottom left */ 38 | font-weight: bold; 39 | } 40 | 41 | div.page-title { 42 | margin: 15px 0px 15px 0px; 43 | font-size: x-large; 44 | font-weight: bold; 45 | text-align: center; 46 | } 47 | 48 | div.source { 49 | font-size: smaller; 50 | color: gray; 51 | } 52 | 53 | span.entity { 54 | color: #ff6666; 55 | } 56 | 57 | table.member { 58 | margin: 0 0 1.2em 0; /* top rigth bottom left */ 59 | border-collapse: collapse; 60 | border: 2px inset #888888; 61 | width: 100%; 62 | } 63 | 64 | table.member td.title { 65 | border: 2px inset #888888; 66 | background-color: #ccccff; 67 | font-size: x-large; 68 | font-weight: bold; 69 | } 70 | 71 | table.inherited { 72 | margin: 0 0 1.2em 0; /* top rigth bottom left */ 73 | border-collapse: collapse; 74 | border: 2px inset #888888; 75 | width: 100%; 76 | } 77 | 78 | table.inherited td.title { 79 | background-color: #eeeeff; 80 | font-weight: bold; 81 | } 82 | 83 | table.member-detail { 84 | margin: 10px 0px 0px 0px; 85 | border-collapse: collapse; 86 | border: 2px inset #888888; 87 | background-color: #ffffff; 88 | width: 100%; 89 | } 90 | 91 | table.member-detail td.title { 92 | border: 2px inset #888888; 93 | background-color: #ccccff; 94 | font-size: x-large; 95 | font-weight: bold; 96 | } 97 | 98 | table.navigation { 99 | border-collapse: collapse; 100 | width: 100%; 101 | font-family: Arial,Helvetica,Sans-Serif; 102 | } 103 | 104 | td.inherited-members { 105 | border-top: 2px inset #888888; 106 | border-right: 0px; 107 | } 108 | 109 | td.inherited-owner { 110 | background-color: #eeeeff; 111 | font-weight: bold; 112 | } 113 | 114 | td.modifiers { 115 | border-top: 2px inset #888888; 116 | border-right: 2px inset #888888; 117 | width: 50px; 118 | text-align: right; 119 | } 120 | 121 | td.navigation-enabled { 122 | font-weight: bold; 123 | color: #000000; 124 | background-color: #eeeeff; 125 | } 126 | 127 | td.navigation-links { 128 | width: 100%; 129 | background-color: #eeeeff; 130 | } 131 | 132 | td.navigation-selected { 133 | font-weight: bold; 134 | color: #ffffff; 135 | background-color: #00008b; 136 | } 137 | 138 | td.signature { 139 | border-top: 2px inset #888888; 140 | width: 90%; 141 | } 142 | 143 | ul.list { 144 | margin: 0; 145 | padding: 0; 146 | list-style: none; 147 | } 148 | 149 | .code { 150 | font-family: Courier New, monospace; 151 | } 152 | 153 | table.list { 154 | margin-left: 5em; 155 | margin-top: 1em; 156 | margin-bottom: 1em; 157 | } 158 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/BaseSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | import java.io.{File, FileWriter} 4 | 5 | import org.scalatest.{FlatSpec, Matchers} 6 | 7 | import scala.sys.SystemProperties 8 | 9 | /** Base spec for tests. 10 | */ 11 | class BaseSpec extends FlatSpec with Matchers { 12 | import grizzled.file.util.joinPath 13 | import grizzled.util.withResource 14 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseAutoCloseable 15 | 16 | val lineSep = (new SystemProperties).getOrElse("line.separator", "\n") 17 | 18 | def createTextFile(dir: File, filename: String, contents: String): File = { 19 | val file = new File(joinPath(dir.getAbsolutePath, filename)) 20 | withResource(new FileWriter(file)) { _.write(contents) } 21 | file 22 | } 23 | 24 | def createTextFile(dir: File, filename: String, contents: Array[String]): File = { 25 | createTextFile(dir, filename, contents.mkString(lineSep)) 26 | } 27 | 28 | def makeEmptyFiles(directory: String, files: Seq[String]): Seq[String] = { 29 | for (fname <- files) yield { 30 | val path = joinPath(directory, fname) 31 | new File(path).createNewFile() 32 | path 33 | } 34 | } 35 | 36 | def makeDirectories(directory: String, subdirs: Seq[String]): Seq[String] = { 37 | for (dname <- subdirs) yield { 38 | val path = joinPath(directory, dname) 39 | new File(path).mkdirs() 40 | path 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/BinarySpec.scala: -------------------------------------------------------------------------------- 1 | 2 | package grizzled 3 | 4 | import grizzled.binary._ 5 | 6 | /** 7 | * Tests the grizzled.binary functions. 8 | */ 9 | class BinarySpec extends BaseSpec { 10 | "bitCount" should "properly count bits" in { 11 | val intData = Map[Int, Int]( 12 | 0 -> 0, 13 | 1 -> 1, 14 | 2 -> 1, 15 | 3 -> 2, 16 | 0x44444444 -> 8, 17 | 0xeeeeeeee -> 24, 18 | 0xffffffff -> 32, 19 | 0x7fffffff -> 31 20 | ) 21 | 22 | val longData = Map[Long, Int]( 23 | 0L -> 0, 24 | 1L -> 1, 25 | 2L -> 1, 26 | 3L -> 2, 27 | 0x444444444L -> 9, 28 | 0xeeeeeeeeeL -> 27, 29 | 0xffffffffL -> 32, 30 | 0x7fffffffL -> 31, 31 | 0xffffffffffffL -> 48 32 | ) 33 | 34 | for((n, expected) <- intData) { 35 | bitCount(n) shouldBe expected 36 | } 37 | 38 | for((n, expected) <- longData) { 39 | bitCount(n) shouldBe expected 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/ReflectionSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | import grizzled.reflect._ 4 | import scala.reflect.{ClassTag, classTag} 5 | 6 | /** 7 | * Tests the grizzled.file functions. 8 | */ 9 | class ReflectionSpec extends BaseSpec { 10 | private def isOfTypeTest[T: ClassTag](expected: Boolean, v: Any): Unit = { 11 | isOfType[T](v) shouldBe expected 12 | } 13 | 14 | "isOfType primitives" should "work" in { 15 | isOfTypeTest [Int] (true, 10) 16 | isOfTypeTest [Int] (false, 10L) 17 | 18 | isOfTypeTest [Long] (true, 10L) 19 | isOfTypeTest [Long] (false, 10) 20 | 21 | isOfTypeTest [Short] (true, 10.asInstanceOf[Short] ) 22 | isOfTypeTest [Short] (false, 10) 23 | 24 | isOfTypeTest [Float] (true, 10.0f) 25 | isOfTypeTest [Float] (false, 10) 26 | isOfTypeTest [Float] (false, 10.0) 27 | 28 | isOfTypeTest [Double] (true, 10.0) 29 | isOfTypeTest [Double] (false, 10.0f) 30 | isOfTypeTest [Double] (false, 10) 31 | 32 | isOfTypeTest [Byte] (true, 127.asInstanceOf[Byte] ) 33 | isOfTypeTest [Byte] (false, 127) 34 | isOfTypeTest [Byte] (false, 10L) 35 | isOfTypeTest [Byte] (false, 'c') 36 | 37 | isOfTypeTest [Char] (true, 'c') 38 | isOfTypeTest [Char] (false, 65) 39 | isOfTypeTest [Char] (false, 65.asInstanceOf[Byte]) 40 | } 41 | 42 | "isOfType non-primitives" should "work" in { 43 | class Foo 44 | class Bar extends Foo 45 | 46 | isOfTypeTest [List[Char]] (true, List('a', 'b')) 47 | isOfTypeTest [Seq[Char]] (true, List('a', 'b')) 48 | isOfTypeTest [Char] (false, List('a', 'b')) 49 | isOfTypeTest [Foo] (false, new Object) 50 | isOfTypeTest [Foo] (true, new Foo) 51 | isOfTypeTest [Foo] (true, new Bar) 52 | isOfTypeTest [Bar] (false, new Foo) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/SecuritySpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | import grizzled.security._ 4 | import scala.io.Source 5 | import java.io.ByteArrayInputStream 6 | 7 | /** 8 | * Tests the grizzled.security functions. 9 | */ 10 | class SecuritySpec extends BaseSpec { 11 | val Data = Array( 12 | ("sha-256", "foo") -> "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", 13 | ("md5", "foo") -> "acbd18db4cc2f85cedef654fccc4a4d8", 14 | ("md5", "ab") -> "187ef4436122d1cc2f40dc2b92f0eba0", 15 | ("md5", "a\u0000b") -> "70350f6027bce3713f6b76473084309b" 16 | ) 17 | 18 | "MessageDigest" should "work with string inputs" in { 19 | 20 | for (((algorithm, str), expected) <- Data) { 21 | MessageDigest(algorithm).digestString(str) shouldBe expected 22 | } 23 | } 24 | 25 | it should "work with Source inputs" in { 26 | for (((algorithm, str), expected) <- Data) { 27 | MessageDigest(algorithm).digestString(Source.fromString(str)) shouldBe expected 28 | } 29 | } 30 | 31 | it should "work with InputStream inputs" in { 32 | for (((algorithm, str), expected) <- Data) { 33 | val stream = new ByteArrayInputStream(str.getBytes) 34 | MessageDigest(algorithm).digestString(stream) shouldBe expected 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/SysSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled 2 | 3 | import grizzled.sys._ 4 | 5 | /** 6 | * Tests the grizzled.file functions. 7 | */ 8 | class SysSpec extends BaseSpec { 9 | 10 | "operating system name" should "be correct" in { 11 | import OperatingSystem._ 12 | 13 | val data = Map("mac" -> Mac, 14 | "windows ce" -> WindowsCE, 15 | "windows" -> Windows, 16 | "windows xp" -> Windows, 17 | "os/2" -> OS2, 18 | "netware" -> NetWare, 19 | "openvms" -> VMS, 20 | "linux" -> Posix, 21 | "foo" -> Posix) 22 | 23 | for ((osName, osType) <- data; 24 | name <- List(osName.capitalize, osName.toUpperCase, osName)) { 25 | getOS(osName) shouldBe osType 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/datetime/DateTimeUtilSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.datetime 2 | 3 | import java.util.Date 4 | 5 | import grizzled.BaseSpec 6 | 7 | /** DateTimeUtil tester 8 | */ 9 | class DateTimeUtilSpec extends BaseSpec { 10 | "dateToCalendar" should "return a valid Calendar object" in { 11 | val date = new Date(System.currentTimeMillis - 1000000) 12 | val cal = DateTimeUtil.dateToCalendar(date) 13 | val date2 = cal.getTime 14 | 15 | date shouldBe date2 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/datetime/EnrichedDurationSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.datetime 2 | 3 | import grizzled.BaseSpec 4 | 5 | import scala.concurrent.duration.Duration 6 | 7 | class EnrichedDurationSpec extends BaseSpec { 8 | import grizzled.datetime.Implicits.EnrichedDuration 9 | 10 | "humanize" should "produce valid human-readable strings" in { 11 | val Data = Seq( 12 | (Duration(1, "second"), "1 second"), 13 | (Duration(100, "seconds"), "1 minute, 40 seconds"), 14 | (ms(days = 2, seconds = 1, millis = 3), "2 days, 1 second, 3 milliseconds"), 15 | (ms(days = 4, hours = 1, minutes = 3), "4 days, 1 hour, 3 minutes"), 16 | (Duration("1.2 µs"), "1 microsecond, 200 nanoseconds") 17 | ) 18 | 19 | for ((duration, expected) <- Data) 20 | duration.humanize shouldBe expected 21 | } 22 | 23 | private def ms(days: Int = 0, 24 | hours: Int = 0, 25 | seconds: Int = 0, 26 | minutes: Int = 0, 27 | millis: Int = 0): Duration = { 28 | Duration( 29 | (days * 24 * 60 * 60 * 1000) + 30 | (hours * 60 * 60 * 1000) + 31 | (minutes * 60 * 1000) + 32 | (seconds * 1000) + 33 | millis, 34 | "milliseconds" 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/datetime/ImplicitsSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.datetime 2 | 3 | import java.sql.Timestamp 4 | import java.util.Date 5 | 6 | import grizzled.BaseSpec 7 | 8 | /** 9 | */ 10 | class ImplicitsSpec extends BaseSpec { 11 | 12 | val Deltas = Array(10000000L, -238947233L, 10L, -2987234987L) 13 | 14 | "EnrichedTimestamp" should "convert a Timestamp to a Calendar" in { 15 | import Implicits.EnrichedTimestamp 16 | 17 | for (delta <- Deltas) { 18 | val date = new Date(System.currentTimeMillis + delta) 19 | val ts = new Timestamp(date.getTime) 20 | val cal = ts.toCalendar 21 | date shouldBe cal.getTime 22 | } 23 | } 24 | 25 | it should "convert a Timestamp to a Date" in { 26 | import Implicits.EnrichedTimestamp 27 | 28 | for (delta <- Deltas) { 29 | val date = new Date(System.currentTimeMillis + delta) 30 | val ts = new Timestamp(System.currentTimeMillis + delta) 31 | ts.toDate shouldBe date 32 | } 33 | } 34 | 35 | "EnrichedDate" should "convert a Date to a Calendar" in { 36 | import Implicits.EnrichedDate 37 | 38 | for (delta <- Deltas) { 39 | val date = new Date(System.currentTimeMillis + delta) 40 | val cal = date.toCalendar 41 | 42 | date shouldBe cal.getTime 43 | } 44 | } 45 | 46 | "EnrichedCalendar" should "convert a Calendar to a Timestamp" in { 47 | import Implicits._ 48 | 49 | for (delta <- Deltas) { 50 | val ts = new Timestamp(System.currentTimeMillis + delta) 51 | val cal = ts.toCalendar 52 | cal.toTimestamp shouldBe ts 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/file/BackslashContinuedLineIteratorSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.file 2 | 3 | import grizzled.BaseSpec 4 | import grizzled.file.filter._ 5 | 6 | /** Tests the grizzled.file.filter functions. 7 | */ 8 | class BackslashContinuedLineIteratorSpec extends BaseSpec { 9 | "BackslashContinuedLineIterator" should "properly join lines" in { 10 | val data = List[(List[String], List[String])]( 11 | (List("Lorem ipsum dolor sit amet, consectetur \\", 12 | "adipiscing elit.", 13 | "In congue tincidunt fringilla. \\", 14 | "Sed interdum nibh vitae \\", 15 | "libero", 16 | "fermentum id dictum risus facilisis."), 17 | 18 | List("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 19 | "In congue tincidunt fringilla. Sed interdum nibh vitae libero", 20 | "fermentum id dictum risus facilisis.")) 21 | ) 22 | 23 | for((input, expected) <- data) { 24 | val iterator = input.iterator 25 | val result = new BackslashContinuedLineIterator(iterator).toList 26 | result shouldBe expected 27 | } 28 | } 29 | 30 | it should "handle a blank line at the end of the file" in { 31 | val data = Array("One", "Two", "Three \\", "continued", "Four", " ") 32 | val expected = Array("One", "Two", "Three continued", "Four", " ") 33 | 34 | val result = new BackslashContinuedLineIterator(data.iterator).toArray 35 | result shouldBe expected 36 | } 37 | 38 | it should "handle an empty line at the end of the file" in { 39 | val data = Array("One", "Two", "Three \\", "continued", "Four", "") 40 | val expected = Array("One", "Two", "Three continued", "Four", "") 41 | 42 | val result = new BackslashContinuedLineIterator(data.iterator).toArray 43 | result shouldBe expected 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/file/GrizzledFileSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.file 2 | 3 | import grizzled.BaseSpec 4 | import Implicits.GrizzledFile 5 | import grizzled.file.util.withTemporaryDirectory 6 | import grizzled.file.{util => fileutil} 7 | import grizzled.util.withResource 8 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseAutoCloseable 9 | import java.io.{File, FileWriter} 10 | 11 | class GrizzledFileSpec extends BaseSpec { 12 | 13 | "touch" should "create a file if it does not exist" in { 14 | withTemporaryDirectory("GrizzledFile") { dir => 15 | val path = new File(fileutil.joinPath(dir.getAbsolutePath, "foobar.txt")) 16 | path should not (exist) 17 | path.touch() shouldBe Symbol("success") 18 | path should exist 19 | } 20 | } 21 | 22 | it should "update the timestamp of a file that exists" in { 23 | withTemporaryDirectory("GrizzledFile") { dir => 24 | val path = new File(fileutil.joinPath(dir.getAbsolutePath, "foo.txt")) 25 | withResource(new FileWriter(path)) { w => 26 | w.write("Some content\n") 27 | } 28 | path should exist 29 | val creationTime = path.lastModified 30 | val newTime = creationTime - 86400 31 | path.touch(newTime) 32 | 33 | // lastModified is only guaranteed to be within second granularity. 34 | def seconds(millis: Long) = millis / 1000 * 1000 35 | seconds(path.lastModified) shouldBe seconds(newTime) 36 | } 37 | } 38 | 39 | it should "not create intermediate directories" in { 40 | withTemporaryDirectory("GrizzledFile") { dir => 41 | val absDir = dir.getAbsolutePath 42 | val f = new File(fileutil.joinPath(absDir, "foo", "bar", "baz.txt")) 43 | f.touch() shouldBe Symbol("failure") 44 | } 45 | } 46 | 47 | "pathExists" should "return Success for an existing file" in { 48 | withTemporaryDirectory("GrizzedFile") { dir => 49 | val path = new File(fileutil.joinPath(dir.getAbsolutePath, "foo.txt")) 50 | path.touch() shouldBe Symbol("success") 51 | path.pathExists shouldBe Symbol("success") 52 | } 53 | } 54 | 55 | it should "return Failure for a nonexistent file" in { 56 | withTemporaryDirectory("GrizzedFile") { dir => 57 | val path = new File(fileutil.joinPath(dir.getAbsolutePath, "foo.txt")) 58 | path.pathExists shouldBe Symbol("failure") 59 | } 60 | } 61 | 62 | "isEmpty" should "return true for an empty directory" in { 63 | withTemporaryDirectory("GrizzledFile") { dir => 64 | dir.isEmpty shouldBe true 65 | } 66 | } 67 | 68 | it should "return false for a non-empty directory" in { 69 | withTemporaryDirectory("GrizzledFile") { dir => 70 | val f = new File(fileutil.joinPath(dir.getAbsolutePath, "foo.txt")) 71 | f.touch() shouldBe Symbol("success") 72 | dir.isEmpty shouldBe false 73 | } 74 | } 75 | 76 | it should "fail with an assertion error for a non-directory" in { 77 | withTemporaryDirectory("GrizzledFile") { dir => 78 | val f = new File(fileutil.joinPath(dir.getAbsolutePath, "foo.txt")) 79 | f.touch() shouldBe Symbol("success") 80 | 81 | an [AssertionError] should be thrownBy { 82 | f.isEmpty 83 | } 84 | } 85 | } 86 | 87 | "deleteRecursively" should "delete an entire tree" in { 88 | withTemporaryDirectory("GrizzledFile") { dir => 89 | val absDir = dir.getAbsolutePath 90 | val topDir = new File(fileutil.joinPath(absDir, "foo")) 91 | val parentDir = new File(fileutil.joinPath(topDir.getPath, "bar", "baz")) 92 | val file = new File(fileutil.joinPath(parentDir.getPath, "quux.txt")) 93 | 94 | parentDir.mkdirs() should be (true) 95 | parentDir should exist 96 | file.touch() shouldBe Symbol("success") 97 | 98 | val t = topDir.deleteRecursively() 99 | t shouldBe Symbol("success") 100 | t.get shouldEqual 1 101 | topDir should not (exist) 102 | new File(absDir) should exist 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/file/IncluderSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.file 2 | 3 | import java.io.{File, FileWriter, PrintWriter} 4 | 5 | import grizzled.file.{util => fileutil} 6 | import fileutil.withTemporaryDirectory 7 | import grizzled.BaseSpec 8 | import grizzled.util.withResource 9 | 10 | import scala.util.Success 11 | 12 | class IncluderSpec extends BaseSpec { 13 | 14 | "Includer" should "handle a file including another file" in { 15 | withTemporaryDirectory("incl") { dir => 16 | val input = createTextFile(dir, "outer.txt", 17 | Array("This is a normal line.", 18 | """%include "inner.txt"""", 19 | "This is another normal line.") 20 | ) 21 | createTextFile(dir, "inner.txt", 22 | Array("Inner file line 1.", 23 | "Inner file line 2.") 24 | ) 25 | Includer(input.getPath).map(_.toVector) shouldBe 26 | Success(Vector("This is a normal line.", 27 | "Inner file line 1.", 28 | "Inner file line 2.", 29 | "This is another normal line.")) 30 | } 31 | } 32 | 33 | it should "handle a file including another file including another file" in { 34 | withTemporaryDirectory("incl") { dir => 35 | val input = createTextFile(dir, "main.txt", 36 | Array("main line 1", 37 | """%include "inner1.txt"""", 38 | "main line 3")) 39 | createTextFile(dir, "inner1.txt", 40 | Array("inner 1.1", 41 | """%include "inner2.txt"""", 42 | "inner 1.3") 43 | ) 44 | createTextFile(dir, "inner2.txt", 45 | Array("inner 2.1", 46 | "inner 2.2") 47 | ) 48 | Includer(input.getPath).map(_.toVector) shouldBe 49 | Success(Vector("main line 1", 50 | "inner 1.1", 51 | "inner 2.1", 52 | "inner 2.2", 53 | "inner 1.3", 54 | "main line 3")) 55 | } 56 | } 57 | 58 | it should "handle an include from a URL" in { 59 | withTemporaryDirectory("incl") { dir => 60 | val inner = createTextFile(dir, "inner1.txt", 61 | Array("inner 1", 62 | "inner 2") 63 | ) 64 | 65 | val input = createTextFile(dir, "main.txt", 66 | Array("main line 1", 67 | s"""%include "${inner.toURI.toURL}"""", 68 | "main line 3") 69 | ) 70 | 71 | Includer(input.getPath).map(_.toVector) shouldBe 72 | Success(Vector("main line 1", 73 | "inner 1", 74 | "inner 2", 75 | "main line 3")) 76 | } 77 | } 78 | 79 | it should "abort with an exception on a direct recursive include" in { 80 | withTemporaryDirectory("incl") { dir => 81 | val input = createTextFile(dir, "main.txt", Array("""%include "main.txt"""")) 82 | 83 | intercept[IllegalStateException] { 84 | Includer(input.getPath).map(_.toVector).get 85 | } 86 | } 87 | } 88 | 89 | it should "abort with an exception on an indirect recursive include" in { 90 | withTemporaryDirectory("incl") { dir => 91 | val input = createTextFile(dir, "main.txt", Array("""%include "inner.txt"""")) 92 | createTextFile(dir, "inner.txt", Array("""%include "main.txt"""")) 93 | 94 | intercept[IllegalStateException] { 95 | Includer(input.getPath).map(_.toVector).get 96 | } 97 | } 98 | } 99 | 100 | it should "support an alternate include syntax" in { 101 | withTemporaryDirectory("incl") { dir => 102 | val input = createTextFile(dir, "main.txt", 103 | Array("line 1", "#include 'foo.txt'", "# include 'bar.txt'", "line 2") 104 | ) 105 | val foo = createTextFile(dir, "foo.txt", Array("foo")) 106 | val bar = createTextFile(dir, "bar.txt", Array("bar")) 107 | 108 | val i = Includer(input, """^#\s*include\s*'(.*)'\s*$""".r) 109 | i.map(_.toVector) shouldBe Success(Vector("line 1", "foo", "bar", "line 2")) 110 | } 111 | } 112 | 113 | it should "support an alternate nesting level" in { 114 | withTemporaryDirectory("incl") { dir => 115 | val input = createTextFile(dir, "main.txt", 116 | Array("line 1", """%include "foo.txt"""", "line 2") 117 | ) 118 | val foo = createTextFile(dir, "foo.txt", Array("""%include "bar.txt"""")) 119 | val bar = createTextFile(dir, "bar.txt", Array("bar")) 120 | 121 | // The default should work. 122 | Includer(input).map(_.toVector) shouldBe 123 | Success(Vector("line 1", "bar", "line 2")) 124 | 125 | intercept[IllegalStateException] { 126 | Includer(input, 1).map(_.toVector).get 127 | } 128 | } 129 | } 130 | 131 | // -------------------------------------------------------------------------- 132 | // Helpers 133 | // -------------------------------------------------------------------------- 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/io/GrizzledSourceSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright © 2009-2018, Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Grizzled Scala Library", nor the 20 | names of its contributors may be used to endorse or promote products 21 | derived from this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | package grizzled.io 38 | 39 | import grizzled.BaseSpec 40 | import scala.io.Source 41 | 42 | /** 43 | * Tests the grizzled.file.GrizzledSource functions. 44 | */ 45 | class GrizzledSourceSpec extends BaseSpec { 46 | "First nonblank line" should "skip blank lines" in { 47 | import grizzled.io.Implicits._ 48 | 49 | val data = List(("\n\n\n\nfoo\n\n\n", Some("foo")), 50 | ("\n\n\n\n\n\n\n\n", None), 51 | ("", None), 52 | ("foobar", Some("foobar")) ) 53 | 54 | for((input, expected) <- data) { 55 | val source = Source.fromString(input) 56 | source.firstNonblankLine shouldBe expected 57 | } 58 | } 59 | 60 | "linesBetween" should "properly throw away lines outside the range" in { 61 | import grizzled.io.Implicits._ 62 | 63 | val data = List( 64 | ("{{\na\n}}\n", "{{", "}}", List("a")), 65 | ("*\nfoo\nbar\nbaz\n*\n", "*", "*", List("foo", "bar", "baz")), 66 | ("{{\n}}\n", "{{", "}}", Nil), 67 | ("{{\n\n}}\n", "{{", "}}", List("")), 68 | ("{{\n", "{{", "}}", Nil), 69 | ("\n\n\n", "{{", "}}", Nil), 70 | ("\n\n\n}}", "{{", "}}", Nil) 71 | ) 72 | 73 | for((input, start, end, expected) <- data) { 74 | val source = Source.fromString(input) 75 | source.linesBetween(start, end).toList shouldBe expected 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/io/MultiSourceSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.io 2 | 3 | import grizzled.BaseSpec 4 | 5 | import scala.io.Source 6 | 7 | class MultiSourceSpec extends BaseSpec { 8 | 9 | val lorem = 10 | """|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 11 | |eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 12 | |minim veniam, quis nostrud exercitation ullamco laboris nisi ut 13 | |aliquip ex ea commodo consequat. Duis aute irure dolor in 14 | |reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 15 | |pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 16 | |culpa qui officia deserunt mollit anim id est laborum. 17 | |""".stripMargin 18 | 19 | val rng = new scala.util.Random 20 | 21 | "MultiSource" should "handle just one wrapped Source" in { 22 | val src = Source.fromString(lorem) 23 | val ms = new MultiSource(src) 24 | ms.mkString shouldBe lorem 25 | } 26 | 27 | it should "handle two wrapped sources" in { 28 | val ms = new MultiSource(Source.fromString(lorem), 29 | Source.fromString(lorem.reverse)) 30 | ms.mkString shouldBe (lorem + lorem.reverse) 31 | } 32 | 33 | it should "handle N wrapped sources" in { 34 | val strings = randomStrings(100, 100, 1024) 35 | val ms = new MultiSource(strings.map(Source.fromString): _*) 36 | ms.mkString shouldBe strings.mkString 37 | } 38 | 39 | it should "allow resetting of resettable Sources" in { 40 | val strings = randomStrings(50, 100, 2000) 41 | val ms = new MultiSource(strings.map(Source.fromString): _*) 42 | ms.mkString shouldBe strings.mkString 43 | val ms2 = ms.reset() 44 | ms2.mkString shouldBe strings.mkString 45 | } 46 | 47 | def randomStrings(totalStrings: Int, lower: Int, upper: Int): List[String] = { 48 | (1 to totalStrings).map { i => 49 | val total = rng.nextInt(upper - lower + 1) + lower 50 | (1 to total).map(_ => rng.nextPrintableChar()).mkString 51 | } 52 | .toList 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/io/RichInputStreamSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright © 2009-2018, Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Grizzled Scala Library", nor the 20 | names of its contributors may be used to endorse or promote products 21 | derived from this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | package grizzled.io 38 | 39 | import java.io.ByteArrayInputStream 40 | 41 | import grizzled.BaseSpec 42 | 43 | class RichInputStreamSpec extends BaseSpec { 44 | import grizzled.io.Implicits.RichInputStream 45 | 46 | "readSome" should "stop reading when it hits the max" in { 47 | val input = Array[Byte]( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 48 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) 49 | val data = List( 50 | (10, input.slice(0, 10)), 51 | (20, input), 52 | (30, input) 53 | ) 54 | 55 | for((max, expected) <- data) { 56 | val is = new ByteArrayInputStream(input) 57 | is.readSome(max) shouldBe expected 58 | } 59 | } 60 | 61 | it should "handle a max that's larger than the input" in { 62 | val input = Array[Byte](1, 2, 3, 4) 63 | val is = new ByteArrayInputStream(input) 64 | is.readSome(1000) shouldBe input 65 | } 66 | 67 | it should "handle an empty input" in { 68 | val input = Array.empty[Byte] 69 | val is = new ByteArrayInputStream(input) 70 | is.readSome(1000) shouldBe input 71 | } 72 | 73 | "copyTo" should "copy bytes from an InputStream to an OutputStream" in { 74 | import java.io.{ByteArrayInputStream, ByteArrayOutputStream} 75 | 76 | val input = List[Byte]( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 77 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) 78 | val data = List(input.slice(0, 10), 79 | input, 80 | input.slice(0, 1)) 81 | 82 | for(bytes <- data) { 83 | val is = new ByteArrayInputStream(bytes.toArray) 84 | val os = new ByteArrayOutputStream 85 | is.copyTo(os) 86 | os.toByteArray.toList shouldBe bytes 87 | } 88 | } 89 | 90 | it should "work fine with with big input" in { 91 | // will fail with java.lang.StackOverflowError if copyTo was 92 | // not tail-call optimized 93 | 94 | import java.io.{InputStream, OutputStream} 95 | import java.util.Random 96 | 97 | val rnd = new Random() 98 | val inp = new InputStream { 99 | var countDown = 5000 100 | 101 | override def read(): Int = { 102 | if (countDown > 0) { 103 | countDown -= 1 104 | rnd.nextInt(256) 105 | } 106 | else 107 | -1 108 | } 109 | } 110 | 111 | val nul = new OutputStream { 112 | override def write(b: Int) = {} 113 | } 114 | 115 | try { 116 | inp.copyTo(nul) 117 | } 118 | catch { 119 | case e: StackOverflowError => 120 | fail("StackOverflowError - copyTo not tail-call optimized?") 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/io/RichReaderSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | This software is released under a BSD license, adapted from 4 | http://opensource.org/licenses/bsd-license.php 5 | 6 | Copyright © 2009-2018, Brian M. Clapper. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are 10 | met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, 13 | this list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright 16 | notice, this list of conditions and the following disclaimer in the 17 | documentation and/or other materials provided with the distribution. 18 | 19 | * Neither the names "clapper.org", "Grizzled Scala Library", nor the 20 | names of its contributors may be used to endorse or promote products 21 | derived from this software without specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | --------------------------------------------------------------------------- 35 | */ 36 | 37 | package grizzled.io 38 | 39 | import java.io.StringReader 40 | 41 | import grizzled.BaseSpec 42 | 43 | class RichReaderSpec extends BaseSpec { 44 | 45 | "readSome" should "stop reading when it hits the max" in { 46 | import grizzled.io.Implicits.RichReader 47 | 48 | val data = List( 49 | ("12345678901234567890", 10, "1234567890"), 50 | ("12345678901234567890", 30, "12345678901234567890"), 51 | ("12345678901234567890", 20, "12345678901234567890") 52 | ) 53 | 54 | for((input, max, expected) <- data) { 55 | val r = new StringReader(input) 56 | r.readSome(max).mkString shouldBe expected 57 | } 58 | } 59 | 60 | it should "handle a max that's larger than the input" in { 61 | import grizzled.io.Implicits.RichReader 62 | 63 | val s = "1234" 64 | val r = new StringReader(s) 65 | r.readSome(1000).mkString shouldBe s 66 | } 67 | 68 | it should "handle an empty input" in { 69 | import grizzled.io.Implicits.RichReader 70 | 71 | val r = new StringReader("") 72 | r.readSome(1000).mkString shouldBe "" 73 | } 74 | 75 | "copyTo" should "copy chars from a reader to a writer" in { 76 | import java.io.{StringReader, StringWriter} 77 | import grizzled.io.Implicits.RichReader 78 | 79 | val data = List("12345678901234567890", 80 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 81 | "a", 82 | "") 83 | 84 | for(s <- data) { 85 | val r = new StringReader(s) 86 | val w = new StringWriter 87 | r.copyTo(w) 88 | w.toString shouldBe s 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/io/SourceReaderSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.io 2 | 3 | import java.io.{FileWriter, IOException} 4 | 5 | import grizzled.BaseSpec 6 | import grizzled.file.util.{joinPath, withTemporaryDirectory} 7 | import grizzled.util.withResource 8 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseAutoCloseable 9 | 10 | import scala.io.Source 11 | import scala.util.Random 12 | 13 | class SourceReaderSpec extends BaseSpec { 14 | "read()" should "behave like Reader.read()" in { 15 | val sr = SourceReader(Source.fromString("abc")) 16 | sr.read() shouldBe 'a' 17 | sr.read() shouldBe 'b' 18 | sr.read() shouldBe 'c' 19 | sr.read() shouldBe -1 20 | sr.read() shouldBe -1 21 | } 22 | 23 | it should "fill a buffer, if the buffer is smaller than the remaining data" in { 24 | val s = (1 to 64).map { _ => Random.nextPrintableChar() }.mkString 25 | val sr = SourceReader(Source.fromString(s)) 26 | val buf = new Array[Char](s.length / 2) 27 | sr.read(buf, 0, buf.length) shouldBe buf.length 28 | buf.mkString shouldBe s.take(s.length / 2) 29 | } 30 | 31 | it should "partially fill a buffer, if the buffer is too large" in { 32 | val s = (1 to 64).map { _ => Random.nextPrintableChar() }.mkString 33 | val sr = new SourceReader(Source.fromString(s)) 34 | val buf = new Array[Char](s.length * 2) 35 | val n = sr.read(buf, 0, buf.length) 36 | n shouldBe s.length 37 | buf.take(n).mkString shouldBe s 38 | } 39 | 40 | it should "fill at an offset in the buffer" in { 41 | val s = "0123456789" 42 | val bufContents = "abcdefghijklmnopqrstuvwxyz" 43 | val buf: Array[Char] = bufContents.toArray 44 | val sr = SourceReader(Source.fromString(s)) 45 | val offset = 10 46 | val total = 3 47 | val n = sr.read(buf, offset, total) 48 | val expected = bufContents.take(offset) + 49 | s.take(total) + 50 | bufContents.drop(offset + total) 51 | buf.mkString shouldBe expected 52 | } 53 | 54 | "skip()" should "skip 0 characters" in { 55 | val sr = SourceReader(Source.fromString("abc")) 56 | sr.skip(0) shouldBe 0 57 | sr.read() shouldBe 'a' 58 | } 59 | 60 | it should "skip only as many characters as there are in the Source" in { 61 | val sr = SourceReader(Source.fromString("abc")) 62 | sr.skip(4) shouldBe 3 63 | sr.read() shouldBe -1 64 | } 65 | 66 | it should "skip the right number of characters" in { 67 | val sr = SourceReader(Source.fromString("abcdefghijklmnopqrstuvwxyz")) 68 | sr.skip(10) shouldBe 10 69 | sr.read() shouldBe 'k' 70 | } 71 | 72 | "reset()" should "work on a string Source" in { 73 | val s = "abcdefghijklmnopqrstuvwxyz" 74 | val sr = new SourceReader(Source.fromString(s)) 75 | val buf = new Array[Char](s.length) 76 | sr.read(buf, 0, buf.length) shouldBe buf.length 77 | sr.reset() 78 | sr.read(buf, 0, buf.length) shouldBe buf.length 79 | } 80 | 81 | it should "work on a file Source" in { 82 | withTemporaryDirectory("SourceReader") { dir => 83 | val absDir = dir.getAbsolutePath 84 | val file = joinPath(absDir, "foo.txt") 85 | val s = "abcdefghijklmnopqrstuvwxyz" 86 | withResource(new FileWriter(file)) { w => 87 | w.write(s) 88 | } 89 | val sr = SourceReader(Source.fromFile(file)) 90 | val buf = new Array[Char](s.length) 91 | sr.read(buf, 0, buf.length) shouldBe buf.length 92 | sr.reset() 93 | sr.read(buf, 0, buf.length) shouldBe buf.length 94 | } 95 | } 96 | 97 | "mark()" should "throw an unconditional IOException" in { 98 | val s = "abcdefghijklmnopqrstuvwxyz" 99 | val sr = SourceReader(Source.fromString(s)) 100 | an [IOException] should be thrownBy { sr.mark(10) } 101 | } 102 | 103 | "markSupported()" should "unconditionally return false" in { 104 | SourceReader(Source.fromFile("build.sbt")).markSupported shouldBe false 105 | SourceReader(Source.fromString("abc")).markSupported shouldBe false 106 | } 107 | 108 | "close()" should "close the underlying Source" in { 109 | withTemporaryDirectory("SourceReader") { dir => 110 | val absDir = dir.getAbsolutePath 111 | val file = joinPath(absDir, "foo.txt") 112 | val s = "abcdefghijklmnopqrstuvwxyz" 113 | withResource(new FileWriter(file)) { w => 114 | w.write(s) 115 | } 116 | 117 | val src = SourceReader(Source.fromFile(file)) 118 | src.close() 119 | an [IOException] should be thrownBy { src.read() } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/math/MathUtilSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.math 2 | 3 | import grizzled.BaseSpec 4 | import grizzled.math.util.{max => maximum, min => minimum} 5 | 6 | import scala.util.Random 7 | 8 | class MathUtilSpec extends BaseSpec { 9 | "max" should "work on floats" in { 10 | import grizzled.ScalaCompat.math.Ordering.Float.IeeeOrdering 11 | val nums = (1 to 100).map(_ => Random.nextFloat * 1000) 12 | val biggest = nums.sortWith { _ > _ }.head 13 | maximum(nums.head, nums.tail: _*) shouldBe biggest 14 | } 15 | 16 | it should "work on integers" in { 17 | val nums = (1 to 100).map(_ => Random.nextInt(1000)) 18 | val biggest = nums.sortWith { _ > _ }.head 19 | maximum(nums.head, nums.tail: _*) shouldBe biggest 20 | } 21 | 22 | it should "work on bytes" in { 23 | val bytes = new Array[Byte](1000) 24 | Random.nextBytes(bytes) 25 | val biggest = bytes.sortWith { _ > _ }.head 26 | maximum(bytes.head, bytes.tail.toSeq: _*) shouldBe biggest 27 | } 28 | 29 | it should "work on doubles" in { 30 | import grizzled.ScalaCompat.math.Ordering.Double.IeeeOrdering 31 | val nums = (1 to 100).map(_ => Random.nextDouble * 100000000) 32 | val biggest = nums.sortWith { _ > _ }.head 33 | maximum(nums.head, nums.tail: _*) shouldBe biggest 34 | } 35 | 36 | it should "work on BigInts" in { 37 | val nums = (1 to 100).map(_ => BigInt(Random.nextLong)) 38 | val biggest = nums.sortWith { _ > _ }.head 39 | maximum(nums.head, nums.tail: _*) shouldBe biggest 40 | } 41 | 42 | it should "work on BigDecimals" in { 43 | val nums = (1 to 100).map(_ => BigDecimal(Random.nextDouble * 100000000)) 44 | val biggest = nums.sortWith { _ > _ }.head 45 | maximum(nums.head, nums.tail: _*) shouldBe biggest 46 | } 47 | 48 | it should "work on Strings" in { 49 | val strings = Array( 50 | "klasdfj", 51 | "8haklshdfasdjh asdkjhasdfh", 52 | "jklasdf lkjasdf", 53 | "wertyuijkl" 54 | ) 55 | val biggest = strings.sortWith { _ > _ }.head 56 | maximum(strings.head, strings.tail.toSeq: _*) shouldBe biggest 57 | } 58 | 59 | "min" should "work on floats" in { 60 | import grizzled.ScalaCompat.math.Ordering.Float.IeeeOrdering 61 | val nums = (1 to 100).map(_ => Random.nextFloat * 1000) 62 | val biggest = nums.sortWith { _ < _ }.head 63 | minimum(nums.head, nums.tail: _*) shouldBe biggest 64 | } 65 | 66 | it should "work on integers" in { 67 | val nums = (1 to 100).map(_ => Random.nextInt(1000)) 68 | val biggest = nums.sortWith { _ < _ }.head 69 | minimum(nums.head, nums.tail: _*) shouldBe biggest 70 | } 71 | 72 | it should "work on bytes" in { 73 | val bytes = new Array[Byte](1000) 74 | Random.nextBytes(bytes) 75 | val biggest = bytes.sortWith { _ < _ }.head 76 | minimum(bytes.head, bytes.tail.toSeq: _*) shouldBe biggest 77 | } 78 | 79 | it should "work on doubles" in { 80 | import grizzled.ScalaCompat.math.Ordering.Double.IeeeOrdering 81 | val nums = (1 to 100).map(_ => Random.nextDouble * 100000000) 82 | val biggest = nums.sortWith { _ < _ }.head 83 | minimum(nums.head, nums.tail: _*) shouldBe biggest 84 | } 85 | 86 | it should "work on BigInts" in { 87 | val nums = (1 to 100).map(_ => BigInt(Random.nextLong)) 88 | val biggest = nums.sortWith { _ < _ }.head 89 | minimum(nums.head, nums.tail: _*) shouldBe biggest 90 | } 91 | 92 | it should "work on BigDecimals" in { 93 | val nums = (1 to 100).map(_ => BigDecimal(Random.nextDouble * 100000000)) 94 | val biggest = nums.sortWith { _ < _ }.head 95 | minimum(nums.head, nums.tail: _*) shouldBe biggest 96 | } 97 | 98 | it should "work on Strings" in { 99 | val strings = Seq( 100 | "klasdfj", 101 | "8haklshdfasdjh asdkjhasdfh", 102 | "jklasdf lkjasdf", 103 | "wertyuijkl" 104 | ) 105 | val biggest = strings.sortWith { _ < _ }.head 106 | minimum(strings.head, strings.tail: _*) shouldBe biggest 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/math/StatsSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.math 2 | 3 | import grizzled.math.stats._ 4 | 5 | import Numeric._ 6 | import java.lang.Math.sqrt 7 | 8 | import grizzled.BaseSpec 9 | 10 | /** 11 | * Tests the grizzled.file functions. 12 | */ 13 | class StatsSpec extends BaseSpec { 14 | private def dList[T](l: T*)(implicit x: Numeric[T]): List[Double] = 15 | l.map (x.toDouble).toList 16 | 17 | "geometric mean" should "produce proper values" in { 18 | val Data = List( 19 | (8.15193109605923, dList(1, 10, 30, 10, 12)), 20 | (5.78182338862232, dList(1.0, 10.0, 30.0, 10.0, 12.0, 2.0, 3.0)), 21 | (12.04449703813164, dList(1 to 30: _*)), 22 | (100.0, List(100.0)) 23 | ) 24 | 25 | for ((expected, values) <- Data) { 26 | geometricMean(values.head, values.tail: _*) shouldBe expected 27 | } 28 | } 29 | 30 | "harmonic mean" should "produce proper values" in { 31 | val Data = List( 32 | (3.797468354430379, dList(1, 10, 30, 10, 12)), 33 | (3.2558139534883717, dList(1.0, 10.0, 30.0, 10.0, 12.0, 2.0, 3.0)), 34 | (7.509410923456069, dList(1 to 30: _*)), 35 | (100.0, List(100.0)) 36 | ) 37 | 38 | for ((expected, values) <- Data) { 39 | harmonicMean(values.head, values.tail: _*) shouldBe expected 40 | } 41 | } 42 | 43 | "arithmetic mean" should "produce proper values" in { 44 | val Data = List( 45 | (12.6, dList(1, 10, 30, 10, 12)), 46 | (9.714285714285714, dList(1.0, 10.0, 30.0, 10.0, 12.0, 2.0, 3.0)), 47 | (15.5, dList(1 to 30: _*)), 48 | (100.0, dList(100, 150, 50)), 49 | (100.0, List(100.0)) 50 | ) 51 | 52 | for ((expected, values) <- Data) { 53 | mean(values.head, values.tail: _*) shouldBe expected 54 | } 55 | } 56 | 57 | "median" should "produce proper values" in { 58 | val Data = List( 59 | (10.0, dList(1, 10, 30, 10, 12)), 60 | (10.0, dList(1.0, 10.0, 30.0, 10.0, 12.0, 2.0, 3.0)), 61 | (15.5, dList(1 to 30: _*)), 62 | (100.0, dList(100, 150, 50)), 63 | (2.0, dList(1, 1, 1, 2, 10, 30, 1000)), 64 | (16.0, dList(2, 2, 2, 2, 2, 30, 30, 30, 30, 30)) 65 | 66 | ) 67 | 68 | for ((expected, values) <- Data) { 69 | median(values.head, values.tail: _*) shouldBe expected 70 | } 71 | } 72 | 73 | "mode" should "produce proper values" in { 74 | val Data = List( 75 | (dList(10), dList(1, 10, 30, 10, 12)), 76 | (dList(1), dList(1, 10, 3, 1, 100)), 77 | (dList(1, 3), dList(1, 2, 3, 1, 3)), 78 | (dList(1, 3, 1000), dList(1, 2, 3, 1, 3, 1000, 1000, 9)), 79 | (dList(1), dList(1)) 80 | ) 81 | 82 | for ((expected, values) <- Data) { 83 | mode(values.head, values.tail: _*).sortWith(_ < _) shouldBe expected 84 | } 85 | } 86 | 87 | "sample variance" should "produce proper values" in { 88 | val Data = List( 89 | (50.0, dList(10, 20)), 90 | (1866.5, dList(1, 10, 3, 1, 100)), 91 | (1.0, dList(1, 2, 3, 1, 3)), 92 | (100.0, dList(10.5, 20.5, 30.5)), 93 | (212937.125, dList(1, 2, 3, 1, 3, 1000, 1000, 9)) 94 | ) 95 | 96 | for ((expected, values) <- Data) { 97 | sampleVariance(values.head, values.tail: _*) shouldBe expected 98 | } 99 | } 100 | 101 | private val SampleStddevData = List( 102 | (7.0710678118654755, dList(10, 20)), 103 | (43.2030091544559, dList(1, 10, 3, 1, 100)), 104 | (1.0, dList(1, 2, 3, 1, 3)), 105 | (461.45110791935474, dList(1, 2, 3, 1, 3, 1000, 1000, 9)) 106 | ) 107 | 108 | private val PopulationVarianceData = List( 109 | (25.0, dList(10, 20)), 110 | (25.0, dList(10.5, 20.5)), 111 | (1493.2, dList(1, 10, 3, 1, 100)), 112 | (0.8, dList(1, 2, 3, 1, 3)), 113 | (186319.984375, dList(1, 2, 3, 1, 3, 1000, 1000, 9)) 114 | ) 115 | 116 | private val PopulationStddevData = List( 117 | (5, dList(10, 20)), 118 | (5.0, dList(10.5, 20.5)), 119 | (38.64194612076364, dList(1, 10, 3, 1, 100)), 120 | (0.8944271909999159, dList(1, 2, 3, 1, 3)), 121 | (431.64798664536823, dList(1, 2, 3, 1, 3, 1000, 1000, 9)) 122 | ) 123 | 124 | "sample standard deviation" should "produce proper values" in { 125 | for ((expected, values) <- SampleStddevData) { 126 | sampleStandardDeviation(values.head, values.tail: _*) shouldBe expected 127 | } 128 | } 129 | 130 | it should "be the square root of the sample variance" in { 131 | for ((expected, values) <- SampleStddevData) { 132 | val variance = sampleVariance(values.head, values.tail: _*) 133 | val stddev = sampleStandardDeviation(values.head, values.tail: _*) 134 | 135 | sqrt(variance) shouldBe stddev 136 | } 137 | } 138 | 139 | "population variance" should "produce proper values" in { 140 | for ((expected, values) <- PopulationVarianceData) { 141 | populationVariance(values.head, values.tail: _*) shouldBe expected 142 | } 143 | } 144 | 145 | "population standard deviation" should "produce proper values" in { 146 | for ((expected, values) <- PopulationStddevData) { 147 | populationStandardDeviation(values.head, values.tail: _*) shouldBe expected 148 | } 149 | } 150 | 151 | it should "be the square root of the population variance" in { 152 | for ((expected, values) <- PopulationStddevData) { 153 | val variance = populationVariance(values.head, values.tail: _*) 154 | val stddev = populationStandardDeviation(values.head, values.tail: _*) 155 | 156 | sqrt(variance) shouldBe stddev 157 | } 158 | } 159 | 160 | "range" should "produce proper values" in { 161 | // Must be all the same type 162 | val Data1 = List[(Double, List[Double])]( 163 | (29.0, dList(1 to 30: _*)), 164 | (100.0, dList(1, 100, 30, 28.8, 101)), 165 | (999.0, dList(1 to 1000: _*)) 166 | ) 167 | val Data2 = List[(Int, List[Int])]( 168 | (29, (1 to 30).toList), 169 | (100, List(1, 100, 30, 28, 101)), 170 | (999, (1 to 1000).toList) 171 | ) 172 | 173 | for ((expected, values) <- Data1) { 174 | range(values.head, values.tail: _*) shouldBe expected 175 | } 176 | 177 | for ((expected, values) <- Data2) { 178 | range(values.head, values.tail: _*) shouldBe expected 179 | } 180 | } 181 | 182 | it should "work with a single value" in { 183 | val Data = List[(Int, Int)]( 184 | (0, 1), 185 | (0, 100) 186 | ) 187 | 188 | for ((expected, value) <- Data) { 189 | range(value) shouldBe expected 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/net/URLSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.net 2 | 3 | import java.io.{File, FileWriter} 4 | import java.net.{MalformedURLException, URISyntaxException} 5 | 6 | import grizzled.BaseSpec 7 | import grizzled.util.withResource 8 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseAutoCloseable 9 | import grizzled.file.util.withTemporaryDirectory 10 | import grizzled.file.{util => fileutil} 11 | 12 | import scala.io.Source 13 | 14 | class URLSpec extends BaseSpec { 15 | "URL" should "properly parse a simple HTTP URL string" in { 16 | val r = URL("http://localhost/foo/bar") 17 | r shouldBe Symbol("success") 18 | val u = r.get 19 | u.host shouldBe Some("localhost") 20 | u.protocol shouldBe "http" 21 | u.path shouldBe Some("/foo/bar") 22 | u.port shouldBe None 23 | u.query shouldBe None 24 | u.fragment shouldBe None 25 | } 26 | 27 | it should "return 80 as the default HTTP port" in { 28 | val r = URL("http://localhost/foo/bar") 29 | r shouldBe Symbol("success") 30 | val u = r.get 31 | u.defaultPort shouldBe Some(80) 32 | } 33 | 34 | it should "properly parse a query string from an HTTP URL" in { 35 | val r = URL("http://localhost/foo.html?q=hello&lang=en_US") 36 | r shouldBe Symbol("success") 37 | val u = r.get 38 | u.query shouldBe Some("q=hello&lang=en_US") 39 | } 40 | 41 | it should "properly parse a fragment from an HTTP URL" in { 42 | val r = URL("http://localhost/foo.html#section1") 43 | r shouldBe Symbol("success") 44 | val u = r.get 45 | u.fragment shouldBe Some("section1") 46 | } 47 | 48 | it should "properly parse a port from an HTTP URL" in { 49 | val r = URL("http://localhost:9988/foo.html#section1") 50 | r shouldBe Symbol("success") 51 | val u = r.get 52 | u.port shouldBe Some(9988) 53 | } 54 | 55 | it should "properly parse user info from an HTTP URL" in { 56 | val r = URL("http://user@localhost/foo.html#section1") 57 | r shouldBe Symbol("success") 58 | val u = r.get 59 | u.userInfo shouldBe Some("user") 60 | } 61 | 62 | it should "properly parse user and password info from an HTTP URL" in { 63 | val r = URL("http://user:mypass@localhost/foo.html#section1") 64 | r shouldBe Symbol("success") 65 | val u = r.get 66 | u.userInfo shouldBe Some("user:mypass") 67 | } 68 | 69 | it should "properly handle an HTTPS URL" in { 70 | val us = "https://user:mypass@localhost/foo.zip" 71 | val r = URL(us) 72 | r shouldBe Symbol("success") 73 | val u = r.get 74 | u.userInfo shouldBe Some("user:mypass") 75 | u.protocol shouldBe "https" 76 | u.host shouldBe Some("localhost") 77 | u.port shouldBe None 78 | u.path shouldBe Some("/foo.zip") 79 | u.defaultPort shouldBe Some(443) 80 | u.toExternalForm shouldBe us 81 | } 82 | 83 | it should "properly handle an FTP URL" in { 84 | val us = "ftp://user:mypass@localhost/foo.zip" 85 | val r = URL(us) 86 | r shouldBe Symbol("success") 87 | val u = r.get 88 | u.userInfo shouldBe Some("user:mypass") 89 | u.protocol shouldBe "ftp" 90 | u.host shouldBe Some("localhost") 91 | u.port shouldBe None 92 | u.path shouldBe Some("/foo.zip") 93 | u.defaultPort shouldBe Some(21) 94 | u.toExternalForm shouldBe us 95 | } 96 | 97 | it should "properly handle a file URL" in { 98 | val r = URL("file:///this/is/a/path") 99 | r shouldBe Symbol("success") 100 | val u = r.get 101 | u.userInfo shouldBe None 102 | u.protocol shouldBe "file" 103 | u.host shouldBe None 104 | u.port shouldBe None 105 | u.path shouldBe Some("/this/is/a/path") 106 | u.defaultPort shouldBe None 107 | u.toExternalForm shouldBe "file:/this/is/a/path" 108 | } 109 | 110 | it should "properly handle a jar URL" in { 111 | val us = "jar:file:///this/is/a/path.jar!/foo/bar.class" 112 | val r = URL(us) 113 | r shouldBe Symbol("success") 114 | val u = r.get 115 | u.userInfo shouldBe None 116 | u.protocol shouldBe "jar" 117 | u.host shouldBe None 118 | u.port shouldBe None 119 | u.path shouldBe Some("file:///this/is/a/path.jar!/foo/bar.class") 120 | u.defaultPort shouldBe None 121 | u.toExternalForm shouldBe us 122 | } 123 | 124 | it should "abort on a bad protocol" in { 125 | intercept[MalformedURLException] { 126 | URL("argh:/foo/bar").get 127 | } 128 | } 129 | 130 | it should "abort on a bad port" in { 131 | intercept[MalformedURLException] { 132 | URL("http://localhost:hello/foo").get 133 | } 134 | } 135 | 136 | it should "abort if there's no path in a file URL" in { 137 | intercept[URISyntaxException] { 138 | URL("file:").get 139 | } 140 | } 141 | 142 | it should "abort if there's no host in an HTTP URL" in { 143 | intercept[URISyntaxException] { 144 | URL("http://").get 145 | } 146 | } 147 | 148 | "URL.openStream" should "open a readable InputStream for contents" in { 149 | withTemporaryDirectory("URL") { dir => 150 | val abs = dir.getAbsolutePath 151 | val file = new File(fileutil.joinPath(abs, "foo.txt")) 152 | val contents = Array("foo", "bar").mkString(lineSep) 153 | withResource(new FileWriter(file)) { w => 154 | w.write(contents) 155 | } 156 | val url = file.toURI.toURL 157 | val t = URL(url).openStream() 158 | t shouldBe Symbol("success") 159 | withResource(t.get) { is => 160 | val source = Source.fromInputStream(t.get) 161 | source.mkString shouldBe contents 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/net/URLUtilSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.net 2 | 3 | import java.io.{File, FileWriter} 4 | import java.net.{URL => JavaURL} 5 | 6 | import grizzled.file.{util => fileutil} 7 | import fileutil.withTemporaryDirectory 8 | import grizzled.BaseSpec 9 | import grizzled.util.withResource 10 | 11 | import scala.concurrent.Await 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | import scala.concurrent.duration._ 14 | import scala.io.Source 15 | 16 | class URLUtilSpec extends BaseSpec { 17 | 18 | val WebURL = "https://raw.githubusercontent.com/bmc/grizzled-scala/master/README.md" 19 | 20 | val Contents = 21 | """|Lorem ipsum dolor sit amet, consectetur adipiscing 22 | |elit, sed do eiusmod tempor incididunt ut labore et 23 | |dolore magna aliqua. Ut enim ad minim veniam, quis 24 | |nostrud exercitation ullamco laboris nisi ut aliquip ex 25 | |ea commodo consequat. Duis aute irure dolor in 26 | |reprehenderit in voluptate velit esse cillum dolore eu 27 | |fugiat nulla pariatur. Excepteur sint occaecat 28 | |cupidatat non proident, sunt in culpa qui officia 29 | |deserunt mollit anim id est laborum." 30 | | 31 | |Sed ut perspiciatis unde omnis iste natus error sit 32 | |voluptatem accusantium doloremque laudantium, totam rem 33 | |aperiam, eaque ipsa quae ab illo inventore veritatis et 34 | |quasi architecto beatae vitae dicta sunt explicabo. 35 | |Nemo enim ipsam voluptatem quia voluptas sit aspernatur 36 | |aut odit aut fugit, sed quia consequuntur magni dolores 37 | |eos qui ratione voluptatem sequi nesciunt. Neque porro 38 | |quisquam est, qui dolorem ipsum quia dolor sit amet, 39 | |consectetur, adipisci velit, sed quia non numquam eius 40 | |modi tempora incidunt ut labore et dolore magnam 41 | |aliquam quaerat voluptatem. Ut enim ad minima veniam, 42 | |quis nostrum exercitationem ullam corporis suscipit 43 | |laboriosam, nisi ut aliquid ex ea commodi consequatur? 44 | |Quis autem vel eum iure reprehenderit qui in ea 45 | |voluptate velit esse quam nihil molestiae consequatur, 46 | |vel illum qui dolorem eum fugiat quo voluptas nulla 47 | |pariatur? 48 | |""".stripMargin 49 | 50 | 51 | def urlForContents(dir: File, name: String): JavaURL = { 52 | createTextFile(dir, name, Contents).toURI.toURL 53 | } 54 | 55 | "download" should "download from a file URL object" in { 56 | withTemporaryDirectory("URLUtil") { dir => 57 | val url = URL(urlForContents(dir, "foo.txt")) 58 | val fut = URLUtil.download(url) 59 | val result = Await.result(fut, 10.seconds) 60 | Source.fromFile(result).mkString shouldBe Contents 61 | } 62 | } 63 | 64 | it should "download from a web server" in { 65 | withTemporaryDirectory("URLUtil") { dir => 66 | val url = URL(WebURL).get 67 | val fut = URLUtil.download(url) 68 | val result = Await.result(fut, 10.seconds) 69 | val contents = Source.fromFile(result).mkString 70 | contents.length should be > 0 71 | } 72 | } 73 | 74 | it should "download from a string URL" in { 75 | withTemporaryDirectory("URLUtil") { dir => 76 | val urlString = urlForContents(dir, "bar.txt").toExternalForm 77 | val fut = URLUtil.download(urlString) 78 | val result = Await.result(fut, 10.seconds) 79 | Source.fromFile(result).mkString shouldBe Contents 80 | } 81 | } 82 | 83 | it should "download to a file of my choosing" in { 84 | withTemporaryDirectory("download") { dir => 85 | val url = urlForContents(dir, "foobar.txt") 86 | val file = fileutil.joinPath(dir.getAbsolutePath, "lorem.txt") 87 | val fut = URLUtil.download(url, file) 88 | Await.result(fut, 10.seconds) 89 | Source.fromFile(file).mkString shouldBe Contents 90 | } 91 | } 92 | 93 | "withDownloadedFile" should "download synchronously from a file" in { 94 | import URLUtil._ 95 | withTemporaryDirectory("download") { dir => 96 | val url = urlForContents(dir, "foobar.txt") 97 | val t = withDownloadedFile(url, 10.seconds) { f => 98 | f.exists shouldBe true 99 | Source.fromFile(f).mkString 100 | } 101 | 102 | t shouldBe Symbol("success") 103 | t.get shouldBe Contents 104 | } 105 | } 106 | 107 | "withDownloadedFile" should "download synchronously from a web server" in { 108 | import URLUtil._ 109 | val url = URL(WebURL).get 110 | val t = withDownloadedFile(url, 10.seconds) { f => 111 | f.exists shouldBe true 112 | Source.fromFile(f).mkString 113 | } 114 | 115 | t shouldBe Symbol("success") 116 | t.get.length should be > 0 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/parsing/PushbackSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.parsing 2 | 3 | import grizzled.BaseSpec 4 | 5 | class PushbackSpec extends BaseSpec { 6 | 7 | "Pushback" should "push back a single item" in { 8 | val p = new SafeIterator[Int](1 to 10) with Pushback[Int] 9 | p.next shouldBe Some(1) 10 | p.pushback(1) 11 | p.next shouldBe Some(1) 12 | p.next shouldBe Some(2) 13 | } 14 | 15 | it should "push back multiple items" in { 16 | val p = new SafeIterator[String]((1 to 10).map(_.toString)) with Pushback[String] 17 | p.next shouldBe Some("1") 18 | p.next shouldBe Some("2") 19 | p.pushbackMany(List("1", "2")) 20 | p.next shouldBe Some("1") 21 | p.next shouldBe Some("2") 22 | } 23 | 24 | it should "push back items that weren't there originally" in { 25 | val p = new SafeIterator[Int](1 to 10) with Pushback[Int] 26 | p.next shouldBe Some(1) 27 | p.pushback(100) 28 | p.next shouldBe Some(100) 29 | p.next shouldBe Some(2) 30 | } 31 | 32 | it should "allow arbitrarily large pushback" in { 33 | val p = new SafeIterator[Int](50 to 60) with Pushback[Int] 34 | p.pushbackMany((1 to 49).toList) 35 | for (i <- 1 to 60) 36 | p.next shouldBe Some(i) 37 | 38 | p.next shouldBe None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/parsing/SafeIteratorSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.parsing 2 | 3 | import grizzled.BaseSpec 4 | 5 | import scala.collection.compat._ 6 | 7 | class SafeIteratorSpec extends BaseSpec { 8 | "SafeIterator" should "iterate properly over a wrapped iterator" in { 9 | val i = SafeIterator((1 to 10).iterator) 10 | for (j <- 1 to 10) 11 | i.next shouldBe Some(j) 12 | } 13 | 14 | it should "iterate properly over a wrapped iterable" in { 15 | val i = SafeIterator((1 to 10).map(_.toString)) 16 | for (j <- 1 to 10) 17 | i.next shouldBe Some(j.toString) 18 | } 19 | 20 | it should "repeatedly return None when the underlying iterator is exhausted" in { 21 | val i = SafeIterator(Array(1)) 22 | i.next shouldBe Some(1) 23 | i.next shouldBe None 24 | i.next shouldBe None 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/random/RandomSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.random 2 | 3 | import grizzled.BaseSpec 4 | 5 | import scala.collection.BitSet 6 | import scala.util.Random 7 | 8 | /** For testing random helpers. 9 | */ 10 | class RandomSpec extends BaseSpec { 11 | val Iterations = 100 12 | 13 | "randomChoice" should "return a random value from an array of integers" in { 14 | val a = Random.shuffle(1 to 200).toArray 15 | for (_ <- 1 to Iterations) { 16 | val n = RandomUtil.randomChoice(a) 17 | a should contain (n) 18 | } 19 | } 20 | 21 | it should "return a random value from an array of strings" in { 22 | val a = (1 to 200).map { _ => Random.nextString(20) }.toArray 23 | for (_ <- 1 to Iterations) { 24 | val s = RandomUtil.randomChoice(a) 25 | a should contain (s) 26 | } 27 | } 28 | 29 | it should "work on a Vector" in { 30 | val a = (1 to 200).map { _ => Random.nextString(20) }.toVector 31 | for (_ <- 1 to Iterations) { 32 | val s = RandomUtil.randomChoice(a) 33 | a should contain (s) 34 | } 35 | } 36 | 37 | it should "return the same elements if a constant seed is used" in { 38 | val seed = Random.nextInt 39 | val ru1 = new RandomUtil(new Random(seed)) 40 | val ru2 = new RandomUtil(new Random(seed)) 41 | val a = Random.shuffle(1 to 10000).toArray 42 | 43 | val seq1 = (1 to 20).map { _ => ru1.randomChoice(a) } 44 | val seq2 = (1 to 20).map { _ => ru2.randomChoice(a) } 45 | 46 | seq1 shouldBe seq2 47 | } 48 | 49 | "randomIntBetween" should "always return an integer between lower and upper" in { 50 | val lower = 1 51 | val upper = 100 52 | val nums = BitSet(lower to upper: _*) 53 | 54 | for (i <- 1 to Iterations) { 55 | val n = RandomUtil.randomIntBetween(lower, upper) 56 | nums should contain (n) 57 | } 58 | } 59 | 60 | it should "abort if lower is less than upper" in { 61 | an [IllegalArgumentException] should be thrownBy 62 | RandomUtil.randomIntBetween(100, 1) 63 | } 64 | 65 | it should "always return the same number if low == high" in { 66 | for (i <- 1 to Iterations) { 67 | val n = RandomUtil.randomIntBetween(100, 100) 68 | n shouldBe 100 69 | } 70 | } 71 | 72 | it should "return the same elements if a constant seed is used" in { 73 | val seed = Random.nextInt 74 | val low = 1 75 | val high = 10000 76 | val ru1 = new RandomUtil(new Random(seed)) 77 | val ru2 = new RandomUtil(new Random(seed)) 78 | 79 | val seq1 = (1 to 200).map { _ => ru1.randomIntBetween(low, high) } 80 | val seq2 = (1 to 200).map { _ => ru2.randomIntBetween(low, high) } 81 | 82 | seq1 shouldBe seq2 83 | } 84 | 85 | "randomLongBetween" should "always return an integer between lower and upper" in { 86 | val lower = 1000L 87 | val upper = 100000000L 88 | 89 | for (i <- 1 to Iterations) { 90 | val n = RandomUtil.randomLongBetween(lower, upper) 91 | n should (be >= lower and be <= upper) 92 | } 93 | } 94 | 95 | it should "abort if lower is less than upper" in { 96 | an [IllegalArgumentException] should be thrownBy 97 | RandomUtil.randomLongBetween(10000000L, 50000L) 98 | } 99 | 100 | it should "always return the same number if low == high" in { 101 | for (i <- 1 to Iterations) { 102 | val param = Random.nextLong 103 | val n = RandomUtil.randomLongBetween(param, param) 104 | n shouldBe param 105 | } 106 | } 107 | 108 | it should "return the same elements if a constant seed is used" in { 109 | val seed = Random.nextInt 110 | val low = 1 111 | val high = 10000 112 | val ru1 = new RandomUtil(new Random(seed)) 113 | val ru2 = new RandomUtil(new Random(seed)) 114 | 115 | val seq1 = (1 to 200).map { _ => ru1.randomIntBetween(low, high) } 116 | val seq2 = (1 to 200).map { _ => ru2.randomIntBetween(low, high) } 117 | 118 | seq1 shouldBe seq2 119 | } 120 | 121 | "randomString" should "return a random alphanumeric of the right length" in { 122 | val chars = RandomUtil.DefaultRandomStringChars.toSet 123 | for (len <- 1 to 100) { 124 | for (_ <- 1 to Iterations) { 125 | val s = RandomUtil.randomString(len) 126 | s.length shouldBe len 127 | (s.toSet | chars) shouldBe chars 128 | } 129 | } 130 | } 131 | 132 | it should "return a string composed of specific characters" in { 133 | val chars = Random.nextString(30) 134 | val charSet = chars.toSet 135 | for (len <- 1 to 100) { 136 | for (_ <- 1 to Iterations) { 137 | val s = RandomUtil.randomString(len, chars) 138 | s.length shouldBe len 139 | (s.toSet | charSet) shouldBe charSet 140 | } 141 | } 142 | } 143 | 144 | it should "return the same sequence of strings if a constant seed is used" in { 145 | val seed = Random.nextInt 146 | val ru1 = new RandomUtil(new Random(seed)) 147 | val ru2 = new RandomUtil(new Random(seed)) 148 | val len = 100 149 | 150 | val seq1 = (1 to 200).map { _ => ru1.randomString(len) } 151 | val seq2 = (1 to 200).map { _ => ru2.randomString(len) } 152 | 153 | seq1 shouldBe seq2 154 | } 155 | 156 | it should "fail if the string of legal characters is empty" in { 157 | an [IllegalArgumentException] should be thrownBy 158 | RandomUtil.randomString(10, "") 159 | } 160 | 161 | it should "always return the same string if only one legal character is used" in { 162 | for (len <- 10 to 30) { 163 | val expected = "A" * len 164 | val set = (1 to 30).map { _ => RandomUtil.randomString(len, "A") }.toSet 165 | set.size shouldBe 1 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/string/GrizzledCharSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | Copyright © 2009-2018, Brian M. Clapper. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the names "clapper.org", "Grizzled Scala Library", nor the 17 | names of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | --------------------------------------------------------------------------- 32 | */ 33 | 34 | package grizzled.string 35 | 36 | import grizzled.BaseSpec 37 | 38 | /** 39 | * Tests the GrizzledChar class. 40 | */ 41 | class GrizzledCharSpec extends BaseSpec { 42 | import grizzled.string.Implicits.Char._ 43 | 44 | "isHexDigit" should "detect valid hex digits and reject invalid ones" in { 45 | val data = Map('0' -> true, 46 | '1' -> true, 47 | '2' -> true, 48 | '3' -> true, 49 | '4' -> true, 50 | '5' -> true, 51 | '6' -> true, 52 | '7' -> true, 53 | '8' -> true, 54 | '9' -> true, 55 | 'a' -> true, 56 | 'A' -> true, 57 | 'b' -> true, 58 | 'B' -> true, 59 | 'c' -> true, 60 | 'C' -> true, 61 | 'd' -> true, 62 | 'D' -> true, 63 | 'e' -> true, 64 | 'E' -> true, 65 | 'f' -> true, 66 | 'F' -> true, 67 | 'g' -> false, 68 | 'G' -> false, 69 | '!' -> false, 70 | ':' -> false, 71 | '+' -> false, 72 | '-' -> false, 73 | '.' -> false) 74 | 75 | for((c, expected) <- data) { 76 | c.isHexDigit shouldBe expected 77 | } 78 | } 79 | 80 | val AsciiFirstPrintable = 0x20 81 | val AsciiLastPrintable = 0x7e 82 | 83 | "isPrintable" should "return true for ASCII printables" in { 84 | for (b <- AsciiFirstPrintable to AsciiLastPrintable) 85 | b.toChar.isPrintable should be (true) 86 | } 87 | 88 | it should "return false for ASCII non-printables" in { 89 | for (b <- 0 until AsciiFirstPrintable) 90 | b.toChar.isPrintable should be (false) 91 | 92 | 0x7f.toChar.isPrintable should be (false) 93 | } 94 | 95 | it should "return false for ISO control characters" in { 96 | val iso = (0 to 0xff).map(_.toChar).filter { c => Character.isISOControl(c) } 97 | iso should not be empty 98 | 99 | for (c <- iso) 100 | c.isPrintable should be (false) 101 | } 102 | 103 | it should "return true for select non-ASCII Unicode printables" in { 104 | val tm = '\u2122' // ™ 105 | val copy = '\u00a9' // © 106 | val p = '\u2117' // ℗ 107 | 108 | for (c <- Seq(tm, p, copy)) 109 | c.isPrintable should be (true) 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/string/GrizzledStringSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | Copyright © 2009-2018, Brian M. Clapper. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the names "clapper.org", "Grizzled Scala Library", nor the 17 | names of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | --------------------------------------------------------------------------- 32 | */ 33 | 34 | package grizzled.string 35 | 36 | import grizzled.BaseSpec 37 | /** 38 | * Tests the GrizzledString class. 39 | */ 40 | class GrizzledStringSpec extends BaseSpec { 41 | import grizzled.string.Implicits.String._ 42 | 43 | "ltrim" should "properly trim from the beginning of a string" in { 44 | val data = Map( 45 | "a b c" -> "a b c", 46 | " a" -> "a", 47 | " a " -> "a ", 48 | " " -> "", 49 | "" -> "" 50 | ) 51 | 52 | for((input, expected) <- data) { 53 | input.ltrim shouldBe expected 54 | } 55 | } 56 | 57 | "rtrim" should "properly trim from the end of a string" in { 58 | 59 | val data = Map( 60 | "a b c" -> "a b c", 61 | "a " -> "a", 62 | " a " -> " a", 63 | " " -> "", 64 | "" -> "" 65 | ) 66 | 67 | for((input, expected) <- data) { 68 | input.rtrim shouldBe expected 69 | } 70 | } 71 | 72 | "tokenize" should "properly break a line into tokens" in { 73 | val data = Map( 74 | "" -> Nil, 75 | " " -> Nil, 76 | " " -> Nil, 77 | "\t " -> Nil, 78 | " a b c" -> List("a", "b", "c"), 79 | "one two three four " -> List("one", "two", "three", "four") 80 | ) 81 | 82 | for((input, expected) <- data) { 83 | input.tokenize shouldBe expected 84 | } 85 | } 86 | 87 | "translateMetachars" should "translate metacharacter sequences into chars" in { 88 | val data = Array( 89 | "a b c" -> "a b c", 90 | "\\t\\n\\afooness" -> "\t\n\\afooness", 91 | "\\u2122" -> "\u2122", 92 | "\\u212a" -> "\u212a", 93 | "\\u212x" -> "\\u212x", 94 | "\\\\t" -> "\\t" 95 | ) 96 | 97 | for ((input, expected) <- data) { 98 | input.translateMetachars shouldBe expected 99 | } 100 | } 101 | 102 | it should "handle embedded metacharacter sequences" in { 103 | val data = Array( 104 | "\\u0160ablonas" -> "\u0160ablonas", 105 | "\\u015eablon tart\\u0131\\u015fma" -> "\u015eablon tart\u0131\u015fma", 106 | "Tart\\u0131\\u015fma" -> "Tart\u0131\u015fma", 107 | "Tart\\u0131\\u015fma\\nabc" -> "Tart\u0131\u015fma\nabc" 108 | ) 109 | 110 | for ((input, expected) <- data) { 111 | input.translateMetachars shouldBe expected 112 | } 113 | } 114 | 115 | "escapeNonPrintables" should "return an entirely printable string as is" in { 116 | val s = "\u2122This is a \u00a9 string" 117 | s.escapeNonPrintables should be (s) 118 | } 119 | 120 | it should "escape ISO non-printables properly" in { 121 | val iso = (0x7f to 0xff).map(_.toChar).filter{c => Character.isISOControl(c)} 122 | iso should not be empty 123 | 124 | iso.mkString("").escapeNonPrintables should be ( 125 | "\\u007f\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087\\u0088" + 126 | "\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f\\u0090\\u0091\\u0092" + 127 | "\\u0093\\u0094\\u0095\\u0096\\u0097\\u0098\\u0099\\u009a\\u009b\\u009c" + 128 | "\\u009d\\u009e\\u009f" 129 | ) 130 | } 131 | 132 | it should "handle special metacharacters" in { 133 | "\n\r\t\f".escapeNonPrintables should be ("""\n\r\t\f""") 134 | } 135 | 136 | "replaceFirstChar" should "replace properly, using a replacment char " in { 137 | "abcdefghij".replaceFirstChar('a', 'X') shouldBe "Xbcdefghij" 138 | } 139 | 140 | it should "replace properly, using a replacement string" in { 141 | "asdlkfj".replaceFirstChar('a', "ZZZ") shouldBe "ZZZsdlkfj" 142 | } 143 | 144 | it should "only replace the first instance" in { 145 | "aaaaaaaaaa".replaceFirstChar('a', 'A') shouldBe "Aaaaaaaaaa" 146 | } 147 | 148 | it should "ignore any regular expression characters" in { 149 | val source = "a.b" 150 | source.replaceFirst(".", "X") shouldBe "X.b" 151 | source.replaceFirstChar('.', 'X') shouldBe "aXb" 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/string/StringTemplateSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------- 3 | Copyright © 2009-2018, Brian M. Clapper. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the names "clapper.org", "Grizzled Scala Library", nor the 17 | names of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | --------------------------------------------------------------------------- 32 | */ 33 | 34 | package grizzled.string 35 | 36 | import grizzled.BaseSpec 37 | import grizzled.string.template._ 38 | 39 | import scala.util.Success 40 | 41 | /** 42 | * Tests the grizzled.string.StringTemplate functions. 43 | */ 44 | class StringTemplateSpec extends BaseSpec { 45 | "UnixShellStringTemplate" should 46 | "substitute empty strings for bad vars in safe mode" in { 47 | 48 | val data = Map( 49 | 50 | ("$foo bar $bar ${baz} $", 51 | Map("foo" -> "FOO", 52 | "bar" -> "BARSKI", 53 | "baz" -> "YAWN")) -> Success("FOO bar BARSKI YAWN $"), 54 | 55 | ("$foo bar $bar ${baz} $_", 56 | Map("foo" -> "FOO", 57 | "bar" -> "BARSKI", 58 | "baz" -> "YAWN")) -> Success("FOO bar BARSKI YAWN "), 59 | 60 | ("$foo bar $bar ${baz} $frodo$", 61 | Map("foo" -> "FOO", 62 | "bar" -> "$foo", 63 | "baz" -> "YAWN")) -> Success("FOO bar FOO YAWN $"), 64 | 65 | ("""$foo bar $bar ${baz} \$""", 66 | Map("foo" -> "FOO", 67 | "bar" -> "BARSKI", 68 | "baz" -> "YAWN")) -> Success("FOO bar BARSKI YAWN $"), 69 | 70 | ("""$foo ${foobar?blitz}""", 71 | Map("foo" -> "FOO", 72 | "bar" -> "BARSKI", 73 | "baz" -> "YAWN")) -> Success("FOO blitz") 74 | ) 75 | 76 | for {(input, expected) <- data 77 | (str, vars) = (input._1, input._2)} { 78 | val template = new UnixShellStringTemplate(vars.get, true) 79 | template.sub(str) shouldBe expected 80 | } 81 | } 82 | 83 | it should "abort on bad vars in unsafe mode" in { 84 | val data = Map( 85 | 86 | ("$foo bar $bar ${baz} $$ $x", 87 | Map("foo" -> "FOO", 88 | "bar" -> "BARSKI", 89 | "baz" -> "YAWN")) -> "FOO bar BARSKI YAWN $ ", 90 | 91 | ("$foo bar $bar ${baz} $_", 92 | Map("foo" -> "FOO", 93 | "bar" -> "BARSKI", 94 | "baz" -> "YAWN")) -> "FOO bar BARSKI YAWN ", 95 | 96 | ("$foo bar $bar ${baz} $frodo$", 97 | Map("foo" -> "FOO", 98 | "bar" -> "$foo", 99 | "baz" -> "YAWN")) -> "FOO FOO BARSKI YAWN $", 100 | 101 | ("$foo bar $bar ${baz} $y", 102 | Map("foo" -> "FOO", 103 | "bar" -> "BARSKI", 104 | "baz" -> "YAWN")) -> "FOO bar BARSKI YAWN $$" 105 | ) 106 | 107 | for {(input, expected) <- data 108 | (str, vars) = (input._1, input._2)} { 109 | val template = new UnixShellStringTemplate(vars.get, false) 110 | template.sub(str).isSuccess shouldBe false 111 | } 112 | } 113 | 114 | "WindowsCmdStringTemplate" should 115 | "substitute empty strings for bad vars in safe mode" in { 116 | 117 | val data = Map( 118 | 119 | ("%foo% bar %bar% %baz% %", 120 | Map("foo" -> "FOO", 121 | "bar" -> "BARSKI", 122 | "baz" -> "YAWN")) -> Success("FOO bar BARSKI YAWN %"), 123 | 124 | ("%foo% bar %bar% %baz% %_%", 125 | Map("foo" -> "FOO", 126 | "bar" -> "BARSKI", 127 | "baz" -> "YAWN")) -> Success("FOO bar BARSKI YAWN "), 128 | 129 | ("%foo% bar %bar% %baz% %frodo%x", 130 | Map("foo" -> "FOO", 131 | "bar" -> "%foo%", 132 | "baz" -> "YAWN")) -> Success("FOO bar FOO YAWN x"), 133 | 134 | ("%foo% bar %bar% %baz% %%", 135 | Map("foo" -> "FOO", 136 | "bar" -> "BARSKI", 137 | "baz" -> "YAWN")) -> Success("FOO bar BARSKI YAWN %") 138 | ) 139 | 140 | for {(input, expected) <- data 141 | (str, vars) = (input._1, input._2)} { 142 | val template = new WindowsCmdStringTemplate(vars.get, true) 143 | template.sub(str) shouldBe expected 144 | } 145 | } 146 | 147 | it should "abort on bad vars in unsafe mode" in { 148 | val data = Map( 149 | 150 | ("%foo% bar %bar% ${baz} %% %x%", 151 | Map("foo" -> "FOO", 152 | "bar" -> "BARSKI", 153 | "baz" -> "YAWN")) -> "FOO bar BARSKI YAWN %", 154 | 155 | ("%foo% bar %bar% %baz% %_%", 156 | Map("foo" -> "FOO", 157 | "bar" -> "BARSKI", 158 | "baz" -> "YAWN")) -> "FOO bar BARSKI YAWN ", 159 | 160 | ("%foo% bar %bar% ${baz} %frodo%x", 161 | Map("foo" -> "FOO", 162 | "bar" -> "%foo%", 163 | "baz" -> "YAWN")) -> "FOO FOO BARSKI YAWN x", 164 | 165 | ("%foo% bar %bar% %baz% %y%", 166 | Map("foo" -> "FOO", 167 | "bar" -> "BARSKI", 168 | "baz" -> "YAWN")) -> "FOO bar BARSKI YAWN " 169 | ) 170 | 171 | for {(input, expected) <- data 172 | (str, vars) = (input._1, input._2)} { 173 | val template = new WindowsCmdStringTemplate(vars.get, false) 174 | template.sub(str).isSuccess shouldBe false 175 | } 176 | } 177 | } 178 | 179 | 180 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/string/StringUtilSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.string 2 | 3 | import grizzled.BaseSpec 4 | import grizzled.string._ 5 | import grizzled.string.util._ 6 | 7 | /** 8 | * Tests the grizzled.string functions. 9 | */ 10 | class StringUtilSpec extends BaseSpec { 11 | "strToBoolean" should "succeed on valid input" in { 12 | // Type annotations on Map are for IntelliJ, which gets confused... 13 | val data = Map[String, Either[String, Boolean]]( 14 | "true" -> Right(true), 15 | "t" -> Right(true), 16 | "yes" -> Right(true), 17 | "y" -> Right(true), 18 | "1" -> Right(true), 19 | 20 | "false" -> Right(false), 21 | "f" -> Right(false), 22 | "no" -> Right(false), 23 | "n" -> Right(false), 24 | "0" -> Right(false) 25 | ) 26 | 27 | for {(input: String, expected: Either[String, Boolean]) <- data 28 | permutations = List(input, 29 | input.capitalize, 30 | input.toUpperCase, 31 | " " + input, 32 | " " + input + " ", 33 | input + " ") 34 | s <- permutations} { 35 | 36 | util.strToBoolean(s) shouldBe expected 37 | } 38 | } 39 | 40 | it should "fail on invalid input" in { 41 | val data = List("tru", "tr", "z", "truee", "xtrue", 42 | "000", "00", "111", "1a", "0z", 43 | "fa", "fal", "fals", "falsee") 44 | 45 | for {input <- data 46 | permutations = List(input, input.capitalize, input.toUpperCase) 47 | s <- permutations} { 48 | util.strToBoolean(s).isLeft shouldBe true 49 | } 50 | } 51 | 52 | "tokenizeWithQuotes" should "handle quoted strings" in { 53 | val data = Map( 54 | "a b c" -> List("a", "b", "c"), 55 | "aa bb cc" -> List("aa", "bb", "cc"), 56 | "\"aa\\\"a\" 'b'" -> List("aa\"a", "b"), 57 | "one two '3\" four'" -> List("one", "two", "3\" four"), 58 | "\"a'b c'\" 'b\\'c d' a\"" -> List("a'b c'", "b'c d", "a\"") 59 | ) 60 | 61 | for((input, expected) <- data) { 62 | tokenizeWithQuotes(input) shouldBe expected 63 | } 64 | } 65 | 66 | "bytesToHexString" should "produce proper hex strings" in { 67 | val Data = Seq( 68 | byteArray(Array(0x10, 0x13, 0x99, 0xff)) -> "101399ff" 69 | ) 70 | 71 | for ((bytes, expected) <- Data) { 72 | 73 | bytesToHexString(bytes) shouldBe expected 74 | } 75 | } 76 | 77 | "hexStringToBytes" should "properly decode valid hex strings" in { 78 | val Data = Seq( 79 | "101399ff" -> Some(byteArray(Array(0x10, 0x13, 0x99, 0xff))), 80 | "fail" -> None, 81 | "FFBC9D" -> Some(byteArray(Array(0xff, 0xbc, 0x9d))) 82 | ) 83 | 84 | def eqByteArray(b1: Array[Byte], b2: Array[Byte]): Boolean = { 85 | val s1 = b1.toSet 86 | val s2 = b2.toSet 87 | s2 == s1 88 | } 89 | 90 | def eqOpt(o1: Option[Array[Byte]], o2: Option[Array[Byte]]): Boolean = { 91 | o1.map { b1 => o2.isDefined && eqByteArray(b1, o2.get) } 92 | .getOrElse( o2.isEmpty ) 93 | } 94 | 95 | for ((s, byteOpt) <- Data) { 96 | eqOpt(byteOpt, hexStringToBytes(s)) shouldBe true 97 | } 98 | } 99 | 100 | "longestCommonPrefix" should "properly find a common prefix" in { 101 | longestCommonPrefix(Seq("abc", "abcdef", "abcdefg")) shouldBe "abc" 102 | longestCommonPrefix(Seq("a", "abcdef", "abcdefg")) shouldBe "a" 103 | longestCommonPrefix(Seq("ab", "abcdef", "abcdefg")) shouldBe "ab" 104 | } 105 | 106 | it should "properly handle an array of length 1" in { 107 | longestCommonPrefix(Seq("a")) shouldBe "a" 108 | } 109 | 110 | it should "properly handle an array of length 0" in { 111 | longestCommonPrefix(Seq.empty[String]) shouldBe "" 112 | } 113 | 114 | it should "properly handle an array containing N of the same string" in { 115 | val a = (1 to 20).map(_ => "xxx") 116 | longestCommonPrefix(a) shouldBe "xxx" 117 | } 118 | 119 | it should "properly handle an array with an empty string" in { 120 | longestCommonPrefix(Seq("abc", "abcdef", "abcdefg", "")) shouldBe "" 121 | } 122 | 123 | it should "properly handle an array with no common substring" in { 124 | longestCommonPrefix(Seq("abc", "abcdef", "abcdefg", "xyz")) shouldBe "" 125 | } 126 | 127 | private def byteArray(b: Array[Int]) = b.map { _.asInstanceOf[Byte] } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/string/WordWrapperSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.string 2 | 3 | import grizzled.BaseSpec 4 | 5 | class WordWrapperSpec extends BaseSpec { 6 | 7 | "WordWrapper" should "wrap strings to 79 columns by default" in { 8 | val w = new WordWrapper() 9 | 10 | w.wrap("This is a long string that should wrap at a 79-column boundary " + 11 | "when passed through WordWrapper") shouldBe 12 | """This is a long string that should wrap at a 79-column boundary when passed 13 | |through WordWrapper""".stripMargin 14 | } 15 | 16 | it should "wrap strings to a specified column boundary" in { 17 | val Column = 40 18 | val w = new WordWrapper(wrapWidth = Column) 19 | 20 | w.wrap(s"This is a long string that will wrap at column $Column, when " + 21 | "passed to an appropriately configured WordWrapper.") shouldBe 22 | s"""This is a long string that will wrap at 23 | |column $Column, when passed to an 24 | |appropriately configured WordWrapper.""".stripMargin 25 | } 26 | 27 | it should "handle indentation" in { 28 | val Column = 50 29 | val Indent = 4 30 | val w = new WordWrapper(wrapWidth = Column, indentation = Indent) 31 | w.wrap(s"This is a string that will wrap at column $Column and will be " + 32 | s"indented $Indent characters.") shouldBe 33 | s""" This is a string that will wrap at column 50 34 | | and will be indented 4 characters.""".stripMargin 35 | } 36 | 37 | it should "allow a different indentation character" in { 38 | val Column = 50 39 | val Indent = 4 40 | val w = new WordWrapper(wrapWidth = Column, indentation = Indent, 41 | indentChar = '-') 42 | w.wrap(s"This is a string that will wrap at column $Column and will be " + 43 | s"indented $Indent characters.") shouldBe 44 | s"""----This is a string that will wrap at column 50 45 | |----and will be indented 4 characters.""".stripMargin 46 | } 47 | 48 | it should "handle indenting properly past a prefix" in { 49 | val Column = 60 50 | val w = new WordWrapper(wrapWidth = Column, prefix = "error: ") 51 | w.wrap(s"This is a string that will wrap at column $Column and be " + 52 | "indented past a prefix string. Each line that wraps should " + 53 | "be indented properly.") shouldBe 54 | s"""error: This is a string that will wrap at column 60 and be 55 | | indented past a prefix string. Each line that wraps 56 | | should be indented properly.""".stripMargin 57 | } 58 | 59 | it should "ignore specified characters when calculating wrapping" in { 60 | val Column = 35 61 | val Ignore = Set('@', '_', '/') 62 | val w = new WordWrapper(wrapWidth = Column, ignore = Ignore) 63 | val w2 = new WordWrapper(wrapWidth = Column) 64 | val s = s"This line should be wrapped at column $Column, but so that " + 65 | "@it@ ignores the escapes and matches /a line/ that doesn't " + 66 | "_contain_ those escapes." 67 | val r = s"""[${Ignore.mkString}]""".r 68 | val expected = r.replaceAllIn(s, "") 69 | val postProcessed = r.replaceAllIn(w.wrap(s), "") 70 | postProcessed shouldBe w2.wrap(expected) 71 | } 72 | 73 | it should "wrap words appropriately on column boundaries" in { 74 | val s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + 75 | "In congue tincidunt fringilla. Sed interdum nibh vitae " + 76 | "libero fermentum id dictum risus facilisis. Pellentesque " + 77 | "habitant morbi tristique senectus et netus et malesuada " + 78 | "fames ac turpis egestas. Sed ante nisi, pharetra ut " + 79 | "eleifend vitae, congue ut quam. Vestibulum ante ipsum " + 80 | "primis in." 81 | 82 | val data = Map( 83 | (s, 79, 0, "", ' ') -> 84 | """Lorem ipsum dolor sit amet, consectetur adipiscing elit. In congue tincidunt 85 | fringilla. Sed interdum nibh vitae libero fermentum id dictum risus facilisis. 86 | Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac 87 | turpis egestas. Sed ante nisi, pharetra ut eleifend vitae, congue ut quam. 88 | Vestibulum ante ipsum primis in.""", 89 | 90 | (s, 40, 0, "", ' ') -> 91 | """Lorem ipsum dolor sit amet, consectetur 92 | adipiscing elit. In congue tincidunt 93 | fringilla. Sed interdum nibh vitae 94 | libero fermentum id dictum risus 95 | facilisis. Pellentesque habitant morbi 96 | tristique senectus et netus et malesuada 97 | fames ac turpis egestas. Sed ante nisi, 98 | pharetra ut eleifend vitae, congue ut 99 | quam. Vestibulum ante ipsum primis in.""", 100 | 101 | (s, 40, 5, "", ' ') -> 102 | """ Lorem ipsum dolor sit amet, 103 | consectetur adipiscing elit. In 104 | congue tincidunt fringilla. Sed 105 | interdum nibh vitae libero 106 | fermentum id dictum risus 107 | facilisis. Pellentesque habitant 108 | morbi tristique senectus et netus 109 | et malesuada fames ac turpis 110 | egestas. Sed ante nisi, pharetra ut 111 | eleifend vitae, congue ut quam. 112 | Vestibulum ante ipsum primis in.""", 113 | 114 | (s, 60, 0, "foobar: ", ' ') -> 115 | """foobar: Lorem ipsum dolor sit amet, consectetur adipiscing 116 | elit. In congue tincidunt fringilla. Sed interdum 117 | nibh vitae libero fermentum id dictum risus 118 | facilisis. Pellentesque habitant morbi tristique 119 | senectus et netus et malesuada fames ac turpis 120 | egestas. Sed ante nisi, pharetra ut eleifend vitae, 121 | congue ut quam. Vestibulum ante ipsum primis in.""", 122 | 123 | (s, 60, 0, "foobar: ", '.') -> 124 | """foobar: Lorem ipsum dolor sit amet, consectetur adipiscing 125 | ........elit. In congue tincidunt fringilla. Sed interdum 126 | ........nibh vitae libero fermentum id dictum risus 127 | ........facilisis. Pellentesque habitant morbi tristique 128 | ........senectus et netus et malesuada fames ac turpis 129 | ........egestas. Sed ante nisi, pharetra ut eleifend vitae, 130 | ........congue ut quam. Vestibulum ante ipsum primis in.""" 131 | 132 | ) 133 | 134 | for((input, expected) <- data) { 135 | val (string, width, indent, prefix, indentChar) = input 136 | val wrapper = WordWrapper(wrapWidth = width, 137 | indentation = indent, 138 | prefix = prefix, 139 | indentChar = indentChar) 140 | wrapper.wrap(string) shouldBe expected 141 | } 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/util/RichTrySpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.util 2 | 3 | import java.io.IOException 4 | 5 | import grizzled.BaseSpec 6 | 7 | import scala.util.{Failure, Success} 8 | 9 | class RichTrySpec extends BaseSpec { 10 | import grizzled.util.Implicits.RichTry 11 | 12 | "toFuture" should "convert a successful Try into a succeeded Future" in { 13 | val fut = Success(10).toFuture 14 | fut.isCompleted shouldBe true 15 | fut.value shouldBe Some(Success(10)) 16 | } 17 | 18 | it should "convert a failed Try into a failed Future" in { 19 | val fut = Failure(new IOException("Failed")).toFuture 20 | fut.isCompleted shouldBe true 21 | fut.value shouldBe defined 22 | intercept[IOException] { fut.value.get.get } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/grizzled/util/WithResourceSpec.scala: -------------------------------------------------------------------------------- 1 | package grizzled.util 2 | 3 | import grizzled.BaseSpec 4 | 5 | import scala.util.Try 6 | 7 | class WithResourceSpec extends BaseSpec { 8 | private class TestCloseable extends java.io.Closeable with AutoCloseable { 9 | var isClosed = false 10 | def close(): Unit = isClosed = true 11 | } 12 | 13 | "withResource" should "close a java.io.Closeable on success" in { 14 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 15 | 16 | val c = new TestCloseable 17 | withResource(c) { _ => } 18 | c.isClosed shouldBe true 19 | } 20 | 21 | it should "close a java.io.Closeable on failure" in { 22 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 23 | 24 | val c = new TestCloseable 25 | Try { 26 | withResource(c) { _ => throw new Exception("abort") } 27 | } 28 | 29 | c.isClosed shouldBe true 30 | } 31 | 32 | it should "propagate a thrown exception" in { 33 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 34 | 35 | intercept[java.io.IOException] { 36 | withResource(new TestCloseable) { _ => throw new java.io.IOException("") } 37 | } 38 | } 39 | 40 | "tryWithResource" should "return a Success on success" in { 41 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 42 | 43 | val t = tryWithResource(new TestCloseable) { _ => 1 } 44 | t shouldBe Symbol("success") 45 | t.get shouldBe 1 46 | } 47 | 48 | it should "close a java.io.Closeable on success" in { 49 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 50 | 51 | val c = new TestCloseable 52 | val t = tryWithResource(c) { _ => 1 } 53 | t shouldBe Symbol("success") 54 | c.isClosed shouldBe true 55 | } 56 | 57 | it should "close a java.lang.AutoCloseable on success" in { 58 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseAutoCloseable 59 | 60 | val c = new TestCloseable 61 | val t = tryWithResource(c) { _ => true } 62 | 63 | t shouldBe Symbol("success") 64 | c.isClosed shouldBe true 65 | } 66 | 67 | it should "return a Failure when an exception is thrown" in { 68 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 69 | 70 | val c = new TestCloseable 71 | val f = tryWithResource(c) { _ => 72 | throw new java.io.IOException("oops") 73 | } 74 | 75 | f shouldBe Symbol("failure") 76 | 77 | val msg = f.recover { 78 | case e: Exception => e.getMessage 79 | } 80 | msg shouldBe Symbol("success") 81 | msg.get shouldBe "oops" 82 | } 83 | 84 | it should "close a java.io.Closeable on failure" in { 85 | import grizzled.util.CanReleaseResource.Implicits.CanReleaseCloseable 86 | 87 | val c = new TestCloseable 88 | tryWithResource(c) { _ => throw new java.io.IOException("") } 89 | c.isClosed shouldBe true 90 | } 91 | } 92 | --------------------------------------------------------------------------------