# HG changeset patch # User wenzelm # Date 1344970250 -7200 # Node ID fde8c3d84ff539d354c76933469e9f21da3d3722 # Parent 559c1d80e129b1d90dde5991a902549222f4d771 some support for persistent user preferences; diff -r 559c1d80e129 -r fde8c3d84ff5 src/Pure/System/build.scala --- a/src/Pure/System/build.scala Tue Aug 14 16:18:15 2012 +0200 +++ b/src/Pure/System/build.scala Tue Aug 14 20:50:50 2012 +0200 @@ -543,7 +543,7 @@ verbose: Boolean = false, sessions: List[String] = Nil): Int = { - val options = (Options.init() /: build_options)(_.define_simple(_)) + val options = (Options.init() /: build_options)(_ + _) val (descendants, tree) = find_sessions(options, more_dirs).required(all_sessions, session_groups, sessions) val deps = dependencies(verbose, tree) diff -r 559c1d80e129 -r fde8c3d84ff5 src/Pure/System/options.scala --- a/src/Pure/System/options.scala Tue Aug 14 16:18:15 2012 +0200 +++ b/src/Pure/System/options.scala Tue Aug 14 20:50:50 2012 +0200 @@ -7,6 +7,9 @@ package isabelle +import java.util.Calendar + + object Options { type Spec = (String, Option[String]) @@ -31,53 +34,61 @@ /* parsing */ private val OPTION = "option" + private val OPTIONS = Path.explode("etc/options") + private val PREFS = Path.explode("$ISABELLE_HOME_USER/etc/preferences") + private val PREFS_BACKUP = Path.explode("$ISABELLE_HOME_USER/etc/preferences~") - lazy val options_syntax = - Outer_Syntax.init() + ":" + "=" + "--" + (OPTION, Keyword.THY_DECL) + lazy val options_syntax = Outer_Syntax.init() + ":" + "=" + "--" + (OPTION, Keyword.THY_DECL) + lazy val prefs_syntax = Outer_Syntax.init() + "=" - private object Parser extends Parse.Parser + object Parser extends Parse.Parser { - val entry: Parser[Options => Options] = + val option_name = atom("option name", _.is_xname) + val option_type = atom("option type", _.is_ident) + val option_value = + opt(token("-", tok => tok.is_sym_ident && tok.content == "-")) ~ atom("nat", _.is_nat) ^^ + { case s ~ n => if (s.isDefined) "-" + n else n } | + atom("option value", tok => tok.is_name || tok.is_float) + + val option_entry: Parser[Options => Options] = { - val option_name = atom("option name", _.is_xname) - val option_type = atom("option type", _.is_ident) - - val option_value = - opt(token("-", tok => tok.is_sym_ident && tok.content == "-")) ~ atom("nat", _.is_nat) ^^ - { case s ~ n => if (s.isDefined) "-" + n else n } | - atom("option value", tok => tok.is_name || tok.is_float) - command(OPTION) ~! (option_name ~ keyword(":") ~ option_type ~ keyword("=") ~ option_value ~ (keyword("--") ~! text ^^ { case _ ~ x => x } | success(""))) ^^ { case _ ~ (a ~ _ ~ b ~ _ ~ c ~ d) => (options: Options) => options.declare(a, b, c, d) } } - def parse_entries(file: Path): List[Options => Options] = + val prefs_entry: Parser[Options => Options] = + { + option_name ~ (keyword("=") ~! option_value) ^^ + { case a ~ (_ ~ b) => (options: Options) => options + (a, b) } + } + + def parse_file(syntax: Outer_Syntax, parser: Parser[Options => Options], + options: Options, file: Path): Options = { - val toks = options_syntax.scan(File.read(file)) - parse_all(rep(entry), Token.reader(toks, file.implode)) match { - case Success(result, _) => result - case bad => error(bad.toString) - } + val toks = syntax.scan(File.read(file)) + val ops = + parse_all(rep(parser), Token.reader(toks, file.implode)) match { + case Success(result, _) => result + case bad => error(bad.toString) + } + try { (options /: ops) { case (opts, op) => op(opts) } } + catch { case ERROR(msg) => error(msg + Position.str_of(file.position)) } } } - private val OPTIONS = Path.explode("etc/options") - - def init(): Options = + def init_defaults(): Options = { var options = empty for { dir <- Isabelle_System.components() file = dir + OPTIONS if file.is_file - entry <- Parser.parse_entries(file) - } { - try { options = entry(options) } - catch { case ERROR(msg) => error(msg + Position.str_of(file.position)) } - } + } { options = Parser.parse_file(options_syntax, Parser.option_entry, options, file) } options } + def init(): Options = init_defaults().load_prefs() + /* encode */ @@ -91,7 +102,7 @@ Command_Line.tool { args.toList match { case export_file :: more_options => - val options = (Options.init() /: more_options)(_.define_simple(_)) + val options = (Options.init() /: more_options)(_ + _) if (export_file == "") java.lang.System.out.println(options.print) else File.write(Path.explode(export_file), YXML.string_of_body(options.encode)) @@ -104,7 +115,7 @@ } -final class Options private(options: Map[String, Options.Opt] = Map.empty) +final class Options private(protected val options: Map[String, Options.Opt] = Map.empty) { override def toString: String = options.iterator.mkString("Options (", ",", ")") @@ -181,7 +192,7 @@ } - /* external declare and define */ + /* external updates */ private def check_value(name: String): Options = { @@ -207,37 +218,38 @@ case "string" => Options.String case _ => error("Unknown type for option " + quote(name) + " : " + quote(typ_name)) } - (new Options(options + (name -> Options.Opt(typ, value, description)))).check_value(name) + val opt = Options.Opt(typ, value, description) + (new Options(options + (name -> opt))).check_value(name) } } - def define(name: String, value: String): Options = + def + (name: String, value: String): Options = { val opt = check_name(name) (new Options(options + (name -> opt.copy(value = value)))).check_value(name) } - def define(name: String, opt_value: Option[String]): Options = + def + (name: String, opt_value: Option[String]): Options = { val opt = check_name(name) opt_value match { - case Some(value) => define(name, value) - case None if opt.typ == Options.Bool => define(name, "true") + case Some(value) => this + (name, value) + case None if opt.typ == Options.Bool => this + (name, "true") case None => error("Missing value for option " + quote(name) + " : " + opt.typ.print) } } - def ++ (specs: List[Options.Spec]): Options = - (this /: specs)({ case (x, (y, z)) => x.define(y, z) }) - - def define_simple(str: String): Options = + def + (str: String): Options = { str.indexOf('=') match { - case -1 => define(str, None) - case i => define(str.substring(0, i), str.substring(i + 1)) + case -1 => this + (str, None) + case i => this + (str.substring(0, i), str.substring(i + 1)) } } + def ++ (specs: List[Options.Spec]): Options = + (this /: specs)({ case (x, (y, z)) => x + (y, z) }) + /* encode */ @@ -247,4 +259,31 @@ list(triple(str, str, str))( options.toList.map({ case (name, opt) => (name, opt.typ.print, opt.value) })) } + + + /* user preferences */ + + def load_prefs(): Options = + if (Options.PREFS.is_file) + Options.Parser.parse_file( + Options.prefs_syntax, Options.Parser.prefs_entry, this, Options.PREFS) + else this + + def save_prefs() + { + val current_defaults = Options.init_defaults() + val changed = + (for { + (name, opt1) <- current_defaults.options.iterator + opt2 <- options.get(name) + if (opt1.value != opt2.value) + } yield (name, opt2.value)).toList + val prefs = + changed.sortBy(_._1) + .map({ case (x, y) => x + " = " + Outer_Syntax.quote_string(y) + "\n" }).mkString + + Options.PREFS.file renameTo Options.PREFS_BACKUP.file + File.write(Options.PREFS, + "(* generated by Isabelle " + Calendar.getInstance.getTime + " *)\n\n" + prefs) + } }