JsyApplication.scala (6611B)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | package jsy.util import java.io.{ByteArrayOutputStream, File} import java.nio.file.Files trait JsyApplication { var debug = false /* set to false to disable debugging output */ var keepGoing = true /* set to true to keep going after exceptions */ var maxSteps: Option[Int] = None /* set to a number to bound the number of execution steps */ /*** Implement this method to process a file. ***/ def processFile(file: File): Unit var anonOption = ("input", { case filename :: Nil => new File(filename) }: PartialFunction[List[String], File], "A file containing a JavaScripty program or a directory with .jsy files.") var flagOptions = List( ("debug", options.SetBool(b => debug = b, Some(b => debug == b)), "debugging"), ("keep-going", options.SetBool(b => keepGoing = b, Some(b => keepGoing == b)), "keep going after exceptions"), ("bound-steps", options.SetInt( { i => maxSteps = i }, Some({ case true => maxSteps map { i => ": %d".format(i) } case false => if (maxSteps == None) Some("") else None })), "bound for maximum number of execution steps before aborting") ) def handle[T](default: T)(e: => T): T = if (!keepGoing) e else try e catch { case exn: Throwable => println(exn.toString); default } def testJsy(file: File)(k: (File, File, => (Boolean, String)) => Unit): Unit = { val jsyext = """[.]jsy$""".r val ans: File = new File(jsyext.replaceAllIn(file.getPath, ".ans")) k(file, ans, { if (!ans.exists()) { (false, s"Expected output file ${ans} does not exist.") } else { val outstream = new ByteArrayOutputStream() Console.withOut(outstream)(handle(()){ processFile(file)}) val encoding = java.nio.charset.StandardCharsets.UTF_8 val ansstring = new String(Files.readAllBytes(ans.toPath), encoding) outstream.flush() val outstring = new String(outstream.toString(encoding.toString)) outstream.close() (ansstring == outstring, s"Computed output does not match expected output.\nComputed:\n${outstring}\nExpected:\n${ansstring}") } }) } def isJsy(file: File): Boolean = { val jsyext = """[.]jsy$""".r jsyext findFirstIn file.getName match { case Some(_) => true case None => false } } def doFile(doit: File => Unit, file: File) = { def loop(file: File): Unit = { if (file.isFile) { doit(file) } else if (file.isDirectory) { file.listFiles filter { f => f.isDirectory || isJsy(f) } foreach loop } else { throw new IllegalArgumentException("File %s does not exist.".format(file)) } } loop(file) } def test(fileordir: File)(k: (File, File, => (Boolean, String)) => Unit): Unit = { doFile({ f => testJsy(f)(k) }, fileordir) } def main(args: Array[String]): Unit = { val opts = new options.Options("jsy", flagOptions, anonOption) val file: File = opts.process(args) doFile(processFile, file) } } object options { sealed abstract class Spec case class SetBool(setter: Boolean => Unit, default: Option[Boolean => Boolean]) extends Spec case class SetInt(setter: Option[Int] => Unit, default: Option[Boolean => Option[String]]) extends Spec class Options[T](program: String, specs: List[(String, Spec, String)], anon: (String, PartialFunction[List[String], T], String)) { val opts: Map[String, List[String] => Option[List[String]]] = specs.foldLeft[Map[String, List[String] => Option[List[String]]]](Map.empty)((acc, spec) => spec match { case (name, SetBool(setter, _), _) => acc + (("--" + name) -> { (t: List[String]) => setter(true); Some(t) }) + (("--no-" + name) -> { (t: List[String]) => setter(false); Some(t) }) case (name, SetInt(setter, _), _) => acc + (("--" + name) -> { (t: List[String]) => t match { case arg :: t => val argp: Int = try arg.toInt catch { case _: Throwable => usageErr() } setter(Some(argp)); Some(t) case _ => usageErr() } }) + (("--no-" + name) -> { (t: List[String]) => setter(None); Some(t) }) }) val (anonName, anonDo, anonDesc) = anon val nameWidth: Int = opts.foldLeft(anonName.length){ case (acc, (n, _)) => acc max (n.length + 5) } def padRight(s: String, w: Int): String = { val nspaces = (w - s.length) max 0 s + (" " * nspaces) } def optline(name: String, text: String): String = { "%-2s".format("") + padRight(name, nameWidth) + " %s%n".format(text) } val descriptions: String = { (specs foldRight "")((spec, acc) => spec match { case (name, SetBool(_, default), desc) => { def defaultStr(b: Boolean): String = default map (f => if (f(b)) " (default)" else "") getOrElse("") optline("--" + name, "turn on %s".format(desc) + defaultStr(true)) + optline("--no-" + name, "turn off %s".format(desc) + defaultStr(false)) + acc } case (name, SetInt(_, default), desc) => { def defaultStr(b: Boolean): String = { val opt = for { f <- default s <- f(b) } yield " (default%s)".format(s) opt.getOrElse("") } optline("--" + name + " <int>", "set %s".format(desc) + defaultStr(true)) + optline("--no-" + name, "unset %s".format(desc) + defaultStr(false)) + acc } }) } val header = """ Usage: %s [options] %s """.format(program, anonName) + optline(anonName, anonDesc) + """ Options: """ private var currentArgs: List[String] = List() def usageErr(): Nothing = { val unprocessed = currentArgs match { case Nil => "" case _ => """ Unprocessed Arguments: """ + currentArgs.reduceRight({ _ + " " + _ }) + "%n".format() } print(header + descriptions + unprocessed) sys.exit(1) } def process(args: Array[String]): T = { def loop(l: List[String]): List[String] = { currentArgs = l l match { case Nil => Nil case h :: t => opts.get(h) match { case None => l case Some(doit) => { val tp = doit(t).getOrElse(t); loop(tp) } } } } val err: PartialFunction[List[String], T] = { case _ => usageErr() } (anonDo orElse err)(loop(args.toList)) } } } |