structure ConfigFile:
sig
 type config =
   { temporary_directory: string option
   , color: { type_: ANSIStyle.style option
            , execute: ANSIStyle.style option
            , error: ANSIStyle.style option
            , warning: ANSIStyle.style option
            , diagnostic: ANSIStyle.style option
            , information: ANSIStyle.style option
            }
   }
 val defaultConfig: config
 val loadConfig: string -> config
end =
struct

 type config =
   { temporary_directory: string option
   , color: { type_: ANSIStyle.style option
            , execute: ANSIStyle.style option
            , error: ANSIStyle.style option
            , warning: ANSIStyle.style option
            , diagnostic: ANSIStyle.style option
            , information: ANSIStyle.style option
            }
   }

 val defaultConfig: config =
   { temporary_directory = NONE
   , color =
       { type_ = NONE
       , execute = NONE
       , error = NONE
       , warning = NONE
       , diagnostic = NONE
       , information = NONE
       }
   }

 fun get (table: TomlValue.table, key) =
   case List.find (fn (key', _) => key' = key) table of
     NONE => NONE
   | SOME (_, value) => SOME value

 fun weakGet (SOME table : TomlValue.table option, key) =
       (case List.find (fn (key', _) => key' = key) table of
          NONE => NONE
        | SOME (_, value) => SOME value)
   | weakGet (NONE, _) = NONE

 infix ?|> ??
 (* Option.mapPartial, flipped *)
 fun (SOME x) ?|> f = f x
   | NONE ?|> _ = NONE

 val op?? = Option.getOpt

 (*: val checkBool : string -> TomlValue.value -> bool option *)
 fun checkBool _ (TomlValue.BOOL x) = SOME x
   | checkBool path _ =
       (Message.warn ("Config entry " ^ path ^ " should be a boolean."); NONE)

 (*: val checkString : string -> TomlValue.value -> string option *)
 fun checkString _ (TomlValue.STRING x) = SOME x
   | checkString path _ =
       (Message.warn ("Config entry " ^ path ^ " should be a string."); NONE)

 (*: val checkTable : string -> TomlValue.value -> TomlValue.table option *)
 fun checkTable _ (TomlValue.TABLE x) = SOME x
   | checkTable path _ =
       (Message.warn ("Config entry " ^ path ^ " should be a table."); NONE)

 (*: val checkColor : string -> TomlValue.value -> ANSIColor.color option *)
 fun checkColor path (TomlValue.STRING x) =
       (case ANSIColor.fromString x of
          SOME c => SOME c
        | NONE =>
            ( Message.warn
                ("Config entry " ^ path ^ " should be a valid color.")
            ; NONE
            ))
   | checkColor path _ =
       (Message.warn ("Config entry " ^ path ^ " should be a string."); NONE)

 (*: val parseStyle : string -> TomlValue.value -> ANSIStyle.style option *)
 fun parseStyle path (TomlValue.TABLE t) =
       SOME
         { foreground = get (t, "fore") ?|> checkColor (path ^ ".fore")
         , background = get (t, "back") ?|> checkColor (path ^ ".back")
         , bold = get (t, "bold") ?|> checkBool (path ^ ".bold") ?? false
         , dim = get (t, "dim") ?|> checkBool (path ^ ".dim") ?? false
         , underline =
             get (t, "underline") ?|> checkBool (path ^ ".underline") ?? false
         , blink = get (t, "blink") ?|> checkBool (path ^ ".blink") ?? false
         , reverse =
             get (t, "reverse") ?|> checkBool (path ^ ".reverse") ?? false
         , italic = get (t, "italic") ?|> checkBool (path ^ ".italic") ?? false
         , strike = get (t, "strike") ?|> checkBool (path ^ ".strike") ?? false
         }
   | parseStyle path _ =
       (Message.warn ("Config entry " ^ path ^ " should be a table."); NONE)

 fun loadConfig path =
   let
     val ins = TextIO.openIn path
     val ins' = ValidateUtf8.mkValidatingStream (TextIO.getInstream ins)
     val table =
       ParseToml.parse (ValidateUtf8.validatingReader TextIO.StreamIO.input1)
         ins'
   in
     { temporary_directory =
         get (table, "temporary-directory")
         ?|> checkString "temporary-directory"
     , color =
         let
           val color = get (table, "color") ?|> checkTable "color"
         in
           { type_ = weakGet (color, "type") ?|> parseStyle "color.type"
           , execute =
               weakGet (color, "execute") ?|> parseStyle "color.execute"
           , error = weakGet (color, "error") ?|> parseStyle "color.error"
           , warning =
               weakGet (color, "warning") ?|> parseStyle "color.warning"
           , diagnostic =
               weakGet (color, "diagnostic") ?|> parseStyle "color.diagnostic"
           , information =
               weakGet (color, "information")
               ?|> parseStyle "color.information"
           }
         end
     }
   end
end;