├── .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 | [](https://travis-ci.org/bmc/grizzled-scala)
4 | [](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 |
--------------------------------------------------------------------------------