1 /* Title: Pure/System/options.scala
4 System options with external string representation.
10 import java.util.Calendar
15 type Spec = (String, Option[String])
17 val empty: Options = new Options()
22 sealed abstract class Type
24 def print: String = Library.lowercase(toString)
26 case object Bool extends Type
27 case object Int extends Type
28 case object Real extends Type
29 case object String extends Type
30 case object Unknown extends Type
32 case class Opt(public: Boolean, name: String, typ: Type, value: String, default_value: String,
33 description: String, section: String)
35 private def print(default: Boolean): String =
37 val x = if (default) default_value else value
38 "option " + name + " : " + typ.print + " = " +
39 (if (typ == Options.String) quote(x) else x) +
40 (if (description == "") "" else "\n -- " + quote(description))
43 def print: String = print(false)
44 def print_default: String = print(true)
46 def title(strip: String): String =
48 val words = space_explode('_', name)
51 case word :: rest if word == strip => rest
54 words1.map(Library.capitalize).mkString(" ")
57 def unknown: Boolean = typ == Unknown
63 private val SECTION = "section"
64 private val PUBLIC = "public"
65 private val OPTION = "option"
66 private val OPTIONS = Path.explode("etc/options")
67 private val PREFS_DIR = Path.explode("$ISABELLE_HOME_USER/etc")
68 private val PREFS = PREFS_DIR + Path.basic("preferences")
69 private val PREFS_BACKUP = PREFS_DIR + Path.basic("preferences~")
71 lazy val options_syntax =
72 Outer_Syntax.init() + ":" + "=" + "--" +
73 (SECTION, Keyword.THY_HEADING2) + (PUBLIC, Keyword.THY_DECL) + (OPTION, Keyword.THY_DECL)
75 lazy val prefs_syntax = Outer_Syntax.init() + "="
77 object Parser extends Parse.Parser
79 val option_name = atom("option name", _.is_xname)
80 val option_type = atom("option type", _.is_ident)
82 opt(token("-", tok => tok.is_sym_ident && tok.content == "-")) ~ atom("nat", _.is_nat) ^^
83 { case s ~ n => if (s.isDefined) "-" + n else n } |
84 atom("option value", tok => tok.is_name || tok.is_float)
86 val option_entry: Parser[Options => Options] =
88 command(SECTION) ~! text ^^
89 { case _ ~ a => (options: Options) => options.set_section(a) } |
90 opt(command(PUBLIC)) ~ command(OPTION) ~! (option_name ~ keyword(":") ~ option_type ~
91 keyword("=") ~ option_value ~ (keyword("--") ~! text ^^ { case _ ~ x => x } | success(""))) ^^
92 { case a ~ _ ~ (b ~ _ ~ c ~ _ ~ d ~ e) =>
93 (options: Options) => options.declare(a.isDefined, b, c, d, e) }
96 val prefs_entry: Parser[Options => Options] =
98 option_name ~ (keyword("=") ~! option_value) ^^
99 { case a ~ (_ ~ b) => (options: Options) => options.add_permissive(a, b) }
102 def parse_file(syntax: Outer_Syntax, parser: Parser[Options => Options],
103 options: Options, file: Path): Options =
105 val toks = syntax.scan(File.read(file))
107 parse_all(rep(parser), Token.reader(toks, file.implode)) match {
108 case Success(result, _) => result
109 case bad => error(bad.toString)
111 try { (options.set_section("") /: ops) { case (opts, op) => op(opts) } }
112 catch { case ERROR(msg) => error(msg + Position.here(file.position)) }
116 def init_defaults(): Options =
120 dir <- Isabelle_System.components()
121 file = dir + OPTIONS if file.is_file
122 } { options = Parser.parse_file(options_syntax, Parser.option_entry, options, file) }
126 def init(): Options = init_defaults().load_prefs()
131 val encode: XML.Encode.T[Options] = (options => options.encode)
134 /* command line entry point */
136 def main(args: Array[String])
140 case get_option :: export_file :: more_options =>
141 val options = (Options.init() /: more_options)(_ + _)
143 if (get_option != "")
144 java.lang.System.out.println(options.check_name(get_option).value)
145 else if (export_file != "")
146 File.write(Path.explode(export_file), YXML.string_of_body(options.encode))
147 else java.lang.System.out.println(options.print)
150 case _ => error("Bad arguments:\n" + cat_lines(args))
157 final class Options private(
158 val options: Map[String, Options.Opt] = Map.empty,
159 val section: String = "")
161 override def toString: String = options.iterator.mkString("Options (", ",", ")")
163 def print: String = cat_lines(options.toList.sortBy(_._1).map(p => p._2.print))
165 def description(name: String): String = check_name(name).description
170 def check_name(name: String): Options.Opt =
171 options.get(name) match {
172 case Some(opt) if !opt.unknown => opt
173 case _ => error("Unknown option " + quote(name))
176 private def check_type(name: String, typ: Options.Type): Options.Opt =
178 val opt = check_name(name)
179 if (opt.typ == typ) opt
180 else error("Ill-typed option " + quote(name) + " : " + opt.typ.print + " vs. " + typ.print)
184 /* basic operations */
186 private def put[A](name: String, typ: Options.Type, value: String): Options =
188 val opt = check_type(name, typ)
189 new Options(options + (name -> opt.copy(value = value)), section)
192 private def get[A](name: String, typ: Options.Type, parse: String => Option[A]): A =
194 val opt = check_type(name, typ)
195 parse(opt.value) match {
198 error("Malformed value for option " + quote(name) +
199 " : " + typ.print + " =\n" + quote(opt.value))
204 /* internal lookup and update */
208 def apply(name: String): Boolean = get(name, Options.Bool, Properties.Value.Boolean.unapply)
209 def update(name: String, x: Boolean): Options =
210 put(name, Options.Bool, Properties.Value.Boolean(x))
212 val bool = new Bool_Access
216 def apply(name: String): Int = get(name, Options.Int, Properties.Value.Int.unapply)
217 def update(name: String, x: Int): Options =
218 put(name, Options.Int, Properties.Value.Int(x))
220 val int = new Int_Access
224 def apply(name: String): Double = get(name, Options.Real, Properties.Value.Double.unapply)
225 def update(name: String, x: Double): Options =
226 put(name, Options.Real, Properties.Value.Double(x))
228 val real = new Real_Access
232 def apply(name: String): String = get(name, Options.String, s => Some(s))
233 def update(name: String, x: String): Options = put(name, Options.String, x)
235 val string = new String_Access
239 def apply(name: String): Time = Time.seconds(real(name))
241 val seconds = new Seconds_Access
244 /* external updates */
246 private def check_value(name: String): Options =
248 val opt = check_name(name)
250 case Options.Bool => bool(name); this
251 case Options.Int => int(name); this
252 case Options.Real => real(name); this
253 case Options.String => string(name); this
254 case Options.Unknown => this
258 def declare(public: Boolean, name: String, typ_name: String, value: String, description: String)
261 options.get(name) match {
262 case Some(_) => error("Duplicate declaration of option " + quote(name))
266 case "bool" => Options.Bool
267 case "int" => Options.Int
268 case "real" => Options.Real
269 case "string" => Options.String
270 case _ => error("Unknown type for option " + quote(name) + " : " + quote(typ_name))
272 val opt = Options.Opt(public, name, typ, value, value, description, section)
273 (new Options(options + (name -> opt), section)).check_value(name)
277 def add_permissive(name: String, value: String): Options =
279 if (options.isDefinedAt(name)) this + (name, value)
283 (name -> Options.Opt(false, name, Options.Unknown, value, value, "", "")), section)
286 def + (name: String, value: String): Options =
288 val opt = check_name(name)
289 (new Options(options + (name -> opt.copy(value = value)), section)).check_value(name)
292 def + (name: String, opt_value: Option[String]): Options =
294 val opt = check_name(name)
296 case Some(value) => this + (name, value)
297 case None if opt.typ == Options.Bool => this + (name, "true")
298 case None => error("Missing value for option " + quote(name) + " : " + opt.typ.print)
302 def + (str: String): Options =
304 str.indexOf('=') match {
305 case -1 => this + (str, None)
306 case i => this + (str.substring(0, i), str.substring(i + 1))
310 def ++ (specs: List[Options.Spec]): Options =
311 (this /: specs)({ case (x, (y, z)) => x + (y, z) })
316 def set_section(new_section: String): Options =
317 new Options(options, new_section)
319 def sections: List[(String, List[Options.Opt])] =
320 options.groupBy(_._2.section).toList.map({ case (a, opts) => (a, opts.toList.map(_._2)) })
325 def encode: XML.Body =
328 for ((name, opt) <- options.toList; if !opt.unknown)
329 yield (name, opt.typ.print, opt.value)
331 import XML.Encode.{string => str, _}
332 list(triple(str, str, str))(opts)
336 /* user preferences */
338 def load_prefs(): Options =
339 if (Options.PREFS.is_file)
340 Options.Parser.parse_file(
341 Options.prefs_syntax, Options.Parser.prefs_entry, this, Options.PREFS)
346 val defaults = Options.init_defaults()
349 (name, opt2) <- options.iterator
350 opt1 = defaults.options.get(name)
351 if (opt1.isEmpty || opt1.get.value != opt2.value)
352 } yield (name, opt2.value, if (opt1.isEmpty) " (* unknown *)" else "")).toList
356 .map({ case (x, y, z) => x + " = " + Outer_Syntax.quote_string(y) + z + "\n" }).mkString
358 Isabelle_System.mkdirs(Options.PREFS_DIR)
359 Options.PREFS.file renameTo Options.PREFS_BACKUP.file
360 File.write(Options.PREFS,
361 "(* generated by Isabelle " + Calendar.getInstance.getTime + " *)\n\n" + prefs)
366 class Options_Variable
368 private var options = Options.empty
370 def value: Options = synchronized { options }
371 def update(new_options: Options): Unit = synchronized { options = new_options }
373 def + (name: String, x: String): Unit = synchronized { options = options + (name, x) }
377 def apply(name: String): Boolean = synchronized { options.bool(name) }
378 def update(name: String, x: Boolean): Unit =
379 synchronized { options = options.bool.update(name, x) }
381 val bool = new Bool_Access
385 def apply(name: String): Int = synchronized { options.int(name) }
386 def update(name: String, x: Int): Unit =
387 synchronized { options = options.int.update(name, x) }
389 val int = new Int_Access
393 def apply(name: String): Double = synchronized { options.real(name) }
394 def update(name: String, x: Double): Unit =
395 synchronized { options = options.real.update(name, x) }
397 val real = new Real_Access
401 def apply(name: String): String = synchronized { options.string(name) }
402 def update(name: String, x: String): Unit =
403 synchronized { options = options.string.update(name, x) }
405 val string = new String_Access
409 def apply(name: String): Time = synchronized { options.seconds(name) }
411 val seconds = new Seconds_Access