[ all classes ]
[ lzwpack ]
Coverage Summary for Class: Application (lzwpack)
| Class | Method, % | Line, % |
|---|---|---|
| Application$ | 0% (0/ 13) | 0% (0/ 22) |
| Application$$anon$1 | 0% (0/ 4) | 0% (0/ 10) |
| Application$OperationMode$ | 0% (0/ 1) | 0% (0/ 1) |
| Application$OperationMode$Compress$ | 0% (0/ 1) | 0% (0/ 1) |
| Application$OperationMode$Decompress$ | 0% (0/ 1) | 0% (0/ 1) |
| Application$Options | 0% (0/ 1) | 0% (0/ 1) |
| Application$Options$ | 0% (0/ 1) | 0% (0/ 1) |
| total | 0% (0/ 22) | 0% (0/ 37) |
1 package lzwpack
2
3 import java.nio.file.{Path, Paths}
4
5 import cats.effect.{IO, Sync}
6 import fs2.{Sink, Stream}
7
8 object Application {
9 sealed trait OperationMode
10 object OperationMode {
11 case object Compress extends OperationMode
12 case object Decompress extends OperationMode
13 }
14
15 case class Options(path: Path = Paths.get("."), mode: OperationMode = OperationMode.Compress, maxCodeSize: Int = MaxCodeSize)
16
17 def options = new scopt.OptionParser[Options]("lzwpack") {
18 head("lzwpack", "LZW compression utility")
19
20 opt[Unit]('d', "decompress") action { (_, opts) =>
21 opts.copy(mode = OperationMode.Decompress)
22 } text "decompress input file instead of compression"
23
24 opt[Int]('b', "bits") action { (n, opts) => opts.copy(maxCodeSize = n) } validate { n =>
25 if (n >= 9 && n <= 16) success
26 else failure("Code size must be between 9 and 16")
27 } text "the maximum number of bits to use for each code"
28
29 arg[String]("path") action { (path, opts) => opts.copy(path = Paths.get(path)) }
30
31 help("help").text("prints this usage text")
32 }
33
34 // Use compress(1) compatible alphabet by default
35 import Alphabet.Compress
36
37 def inputStream(path: Path): Stream[IO, Byte] = fs2.io.file.readAll[IO](path, 32768)
38
39 def runCompress(path: Path, maxCodeSize: Int, sink: Sink[IO, Byte] = fs2.io.stdout): IO[Unit] = {
40 inputStream(path)
41 .through(compressAdaptive)
42 .through(CompressHeader.encode)
43 .to(sink)
44 .compile
45 .drain
46 }
47
48 def runDecompress(path: Path, maxCodeSize: Int, sink: Sink[IO, Byte] = fs2.io.stdout): IO[Unit] = {
49 inputStream(path)
50 .through(CompressHeader.decode)
51 .through(decompressAdaptive)
52 .to(sink)
53 .compile
54 .drain
55 }
56
57 def parseOptions(args: Seq[String]): IO[Option[Options]] = IO {
58 options.parse(args, Options())
59 }
60
61 /**
62 * Parses program options and evaluates the corresponding action.
63 * This function does not actually perform side effects; it just returns a IO instance that can then
64 * be evaluated "at the end of the universe" – the program's [[main]] method.
65 */
66 def program(args: Seq[String]): IO[Unit] = parseOptions(args) flatMap {
67 case Some(Options(path, OperationMode.Compress, maxCodeSize)) => runCompress(path, maxCodeSize)
68 case Some(Options(path, OperationMode.Decompress, maxCodeSize)) => runDecompress(path, maxCodeSize)
69 case None => IO.unit
70 }
71
72 def main(args: Array[String]): Unit = program(args).unsafeRunSync()
73 }