functor HandleOptions (val showMessageAndFail : string -> 'a
                      val showUsage : unit -> 'a
                      val showVersion : unit -> 'a
                     ) : sig
           val parse : AppOptions.initial_options * string list -> AppOptions.initial_options * string list
       end = struct
open AppOptions
datatype 'a option_action = SIMPLE of 'a
                         | WITH_ARG of string -> 'a
                         | WITH_OPTIONAL_ARG of { default : string, action : string -> 'a }
datatype option_desc = SHORT of string
                    | LONG of string
fun testOption (_, []) = NONE
 | testOption ((SHORT s, SIMPLE v), arg :: args) = if arg = s then
                                                       SOME (v, args)
                                                   else
                                                       NONE
 | testOption ((SHORT s, WITH_ARG f), arg :: args)
   = if arg = s then
         case args of
             [] => raise Fail ("argument missing after " ^ s)
           | arg' :: args' => SOME (f arg', args') (* -x foo *)
     else if String.isPrefix s arg then (* -xfoo *)
         let val arg' = String.extract (arg, String.size s, NONE)
         in SOME (f arg', args)
         end
     else
         NONE
 | testOption ((SHORT s, WITH_OPTIONAL_ARG { default, action }), arg :: args)
   = if arg = s then
         SOME (action default, args)
     else if String.isPrefix s arg then (* -xfoo *)
         let val arg' = String.extract (arg, String.size s, NONE)
         in SOME (action arg', args)
         end
     else
         NONE
 | testOption ((LONG s, SIMPLE v), arg :: args) = if arg = s then
                                                      SOME (v, args)
                                                  else
                                                      NONE
 | testOption ((LONG s, WITH_ARG f), arg :: args)
   = if arg = s then
         case args of
             [] => raise Fail ("argument missing after " ^ s)
           | arg' :: args' => SOME (f arg', args') (* -option foo *)
     else if String.isPrefix (s ^ "=") arg then (* -option=foo *)
         let val arg' = String.extract (arg, String.size s + 1, NONE)
         in SOME (f arg', args)
         end
     else
         NONE
 | testOption ((LONG s, WITH_OPTIONAL_ARG { default, action }), arg :: args)
   = if arg = s then
         SOME (action default, args)
     else if String.isPrefix (s ^ "=") arg then (* -option=foo *)
         let val arg' = String.extract (arg, String.size s + 1, NONE)
         in SOME (action arg', args)
         end
     else
         NONE
fun parseOption (descs, []) = NONE
 | parseOption (descs, args) = let fun go [] = NONE
                                     | go (desc :: descs) = case testOption (desc, args) of
                                                                SOME r => SOME r
                                                              | NONE => go descs
                               in go descs
                               end
datatype option = OPT_ENGINE of string (* -e,--engine=ENGINE *)
               | OPT_ENGINE_EXECUTABLE of string (* --engine-executable=EXECUTABLE *)
               | OPT_OUTPUT of string (* -o,--output=OUTPUT *)
               | OPT_FRESH (* --fresh *)
               | OPT_MAX_ITERATIONS of string (* --max-iterations=N *)
               | OPT_START_WITH_DRAFT (* --start-with-draft *)
               | OPT_CHANGE_DIRECTORY of bool (* --change-directory,--no-change-directory *)
               | OPT_WATCH of string (* --watch[=auto] *)
               | OPT_HELP (* -h,-help,--help *)
               | OPT_VERSION
               | OPT_VERBOSE
               | OPT_COLOR of string (* --color[=always] *)
               | OPT_INCLUDEONLY of string
               | OPT_MAKE_DEPENDS of string
               | OPT_PRINT_OUTPUT_DIRECTORY
               | OPT_PACKAGE_SUPPORT of string
               | OPT_CHECK_DRIVER of string
               | OPT_SOURCE_DATE_EPOCH of string
               | OPT_SYNCTEX of string
               | OPT_FILE_LINE_ERROR of bool
               | OPT_INTERACTION of string
               | OPT_HALT_ON_ERROR of bool
               | OPT_SHELL_ESCAPE of ShellEscape.shell_escape
               | OPT_JOBNAME of string
               | OPT_FMT of string
               | OPT_OUTPUT_DIRECTORY of string
               | OPT_OUTPUT_FORMAT of string
               | OPT_TEX_OPTION of string
               | OPT_TEX_OPTIONS of string
               | OPT_DVIPDFMX_OPTION of string
               | OPT_DVIPDFMX_OPTIONS of string
               | OPT_MAKEINDEX of string
               | OPT_BIBTEX of string
               | OPT_BIBER of string
               | OPT_MAKEGLOSSARIES of string
               | OPT_CONFIG_FILE of string
val optionDescs = [(SHORT "-e", WITH_ARG OPT_ENGINE)
                 ,(LONG "--engine", WITH_ARG OPT_ENGINE)
                 ,(LONG "--engine-executable", WITH_ARG OPT_ENGINE_EXECUTABLE)
                 ,(SHORT "-o", WITH_ARG OPT_OUTPUT)
                 ,(LONG "--output", WITH_ARG OPT_OUTPUT)
                 ,(LONG "--fresh", SIMPLE OPT_FRESH)
                 ,(LONG "--max-iterations", WITH_ARG OPT_MAX_ITERATIONS)
                 ,(LONG "--start-with-draft", SIMPLE OPT_START_WITH_DRAFT)
                 ,(LONG "--change-directory", SIMPLE (OPT_CHANGE_DIRECTORY true))
                 ,(LONG "--no-change-directory", SIMPLE (OPT_CHANGE_DIRECTORY false))
                 ,(LONG "--watch", WITH_OPTIONAL_ARG { action = OPT_WATCH, default = "auto" })
                 ,(SHORT "-h", SIMPLE OPT_HELP)
                 ,(LONG "-help", SIMPLE OPT_HELP)
                 ,(LONG "--help", SIMPLE OPT_HELP)
                 ,(SHORT "-v", SIMPLE OPT_VERSION)
                 ,(LONG "--version", SIMPLE OPT_VERSION)
                 ,(SHORT "-V", SIMPLE OPT_VERBOSE)
                 ,(LONG "--verbose", SIMPLE OPT_VERBOSE)
                 ,(LONG "--color", WITH_OPTIONAL_ARG { action = OPT_COLOR, default = "always" })
                 ,(LONG "--includeonly", WITH_ARG OPT_INCLUDEONLY)
                 ,(LONG "--make-depends", WITH_ARG OPT_MAKE_DEPENDS)
                 ,(LONG "--print-output-directory", SIMPLE OPT_PRINT_OUTPUT_DIRECTORY)
                 ,(LONG "--package-support", WITH_ARG OPT_PACKAGE_SUPPORT)
                 ,(LONG "--check-driver", WITH_ARG OPT_CHECK_DRIVER)
                 ,(LONG "--source-date-epoch", WITH_ARG OPT_SOURCE_DATE_EPOCH)
                 ,(LONG "-synctex", WITH_ARG OPT_SYNCTEX)
                 ,(LONG "--synctex", WITH_ARG OPT_SYNCTEX)
                 ,(LONG "-file-line-error", SIMPLE (OPT_FILE_LINE_ERROR true))
                 ,(LONG "--file-line-error", SIMPLE (OPT_FILE_LINE_ERROR true))
                 ,(LONG "-no-file-line-error", SIMPLE (OPT_FILE_LINE_ERROR false))
                 ,(LONG "--no-file-line-error", SIMPLE (OPT_FILE_LINE_ERROR false))
                 ,(LONG "-interaction", WITH_ARG OPT_INTERACTION)
                 ,(LONG "--interaction", WITH_ARG OPT_INTERACTION)
                 ,(LONG "-halt-on-error", SIMPLE (OPT_HALT_ON_ERROR true))
                 ,(LONG "--halt-on-error", SIMPLE (OPT_HALT_ON_ERROR true))
                 ,(LONG "-no-halt-on-error", SIMPLE (OPT_HALT_ON_ERROR false))
                 ,(LONG "--no-halt-on-error", SIMPLE (OPT_HALT_ON_ERROR false))
                 ,(LONG "-shell-escape", SIMPLE (OPT_SHELL_ESCAPE ShellEscape.ALLOWED))
                 ,(LONG "--shell-escape", SIMPLE (OPT_SHELL_ESCAPE ShellEscape.ALLOWED))
                 ,(LONG "-no-shell-escape", SIMPLE (OPT_SHELL_ESCAPE ShellEscape.FORBIDDEN))
                 ,(LONG "--no-shell-escape", SIMPLE (OPT_SHELL_ESCAPE ShellEscape.FORBIDDEN))
                 ,(LONG "-shell-restricted", SIMPLE (OPT_SHELL_ESCAPE ShellEscape.RESTRICTED))
                 ,(LONG "--shell-restricted", SIMPLE (OPT_SHELL_ESCAPE ShellEscape.RESTRICTED))
                 ,(LONG "-jobname", WITH_ARG OPT_JOBNAME)
                 ,(LONG "--jobname", WITH_ARG OPT_JOBNAME)
                 ,(LONG "-fmt", WITH_ARG OPT_FMT)
                 ,(LONG "--fmt", WITH_ARG OPT_FMT)
                 ,(LONG "-output-directory", WITH_ARG OPT_OUTPUT_DIRECTORY)
                 ,(LONG "--output-directory", WITH_ARG OPT_OUTPUT_DIRECTORY)
                 ,(LONG "-output-format", WITH_ARG OPT_OUTPUT_FORMAT)
                 ,(LONG "--output-format", WITH_ARG OPT_OUTPUT_FORMAT)
                 ,(LONG "--tex-option", WITH_ARG OPT_TEX_OPTION)
                 ,(LONG "--tex-options", WITH_ARG OPT_TEX_OPTIONS)
                 ,(LONG "--dvipdfmx-option", WITH_ARG OPT_DVIPDFMX_OPTION)
                 ,(LONG "--dvipdfmx-options", WITH_ARG OPT_DVIPDFMX_OPTIONS)
                 ,(LONG "--makeindex", WITH_ARG OPT_MAKEINDEX)
                 ,(LONG "--bibtex", WITH_ARG OPT_BIBTEX)
                 ,(LONG "--biber", WITH_OPTIONAL_ARG { action = OPT_BIBER, default = "biber" })
                 ,(LONG "--makeglossaries", WITH_OPTIONAL_ARG { action = OPT_MAKEGLOSSARIES, default = "makeglossaries" })
                 ,(LONG "--config-file", WITH_ARG OPT_CONFIG_FILE)
                 ]
fun parse (opts : initial_options, args)
   = case parseOption (optionDescs, args) of
         SOME (OPT_ENGINE engine, args) => (case #engine opts of
                                                NONE => parse ({ opts where engine = SOME engine }, args)
                                              | SOME _ => showMessageAndFail "multiple --engine options"
                                           )
       | SOME (OPT_ENGINE_EXECUTABLE executable, args) => (case #engine_executable opts of
                                                               NONE => parse ({ opts where engine_executable = SOME executable }, args)
                                                             | SOME _ => showMessageAndFail "multiple --engine-executable options"
                                                          )
       | SOME (OPT_OUTPUT output, args) => (case #output opts of
                                                NONE => parse ({ opts where output = SOME output }, args)
                                              | SOME _ => showMessageAndFail "multiple --output options"
                                           )
       | SOME (OPT_FRESH, args) => (case #fresh opts of
                                        false => parse ({ opts where fresh = true }, args)
                                      | true => showMessageAndFail "multiple --fresh options"
                                   )
       | SOME (OPT_MAX_ITERATIONS n, args) => (case #max_iterations opts of
                                                   NONE => (case Int.fromString n of
                                                                SOME n => parse ({ opts where max_iterations = SOME n }, args)
                                                              | NONE => showMessageAndFail "invalid value for --max-iterations option"
                                                           )
                                                 | SOME _ => showMessageAndFail "multiple --max-iterations options"
                                              )
       | SOME (OPT_START_WITH_DRAFT, args) => (case #start_with_draft opts of
                                                   false => parse ({ opts where start_with_draft = true }, args)
                                                 | true => showMessageAndFail "multiple --start-with-draft options"
                                              )
       | SOME (OPT_WATCH engine, args) => (case #watch opts of
                                               NONE => (case WatchEngine.fromString engine of
                                                            SOME engine => parse ({ opts where watch = SOME engine }, args)
                                                          | NONE => showMessageAndFail "invalid value for --watch option"
                                                       )
                                           | SOME _ => showMessageAndFail "multiple --watch options"
                                        )
       | SOME (OPT_HELP, args) => showUsage ()
       | SOME (OPT_VERSION, args) => showVersion ()
       | SOME (OPT_VERBOSE, args) => ( Message.beMoreVerbose ()
                                     ; parse (opts, args)
                                     )
       | SOME (OPT_COLOR mode, args) => (case #color opts of
                                             NONE => (case ColorMode.fromString mode of
                                                          SOME mode => ( Message.setColors mode
                                                                       ; parse ({ opts where color = SOME mode }, args)
                                                                       )
                                                        | NONE => showMessageAndFail "invalid value for --color option"
                                                     )
                                           | SOME _ => showMessageAndFail "multiple --color options"
                                        )
       | SOME (OPT_CHANGE_DIRECTORY x, args) => (case #change_directory opts of
                                                     NONE => parse ({ opts where change_directory = SOME x }, args)
                                                   | SOME _ => showMessageAndFail "multiple --change-directory options"
                                                )
       | SOME (OPT_INCLUDEONLY x, args) => (case #includeonly opts of
                                                NONE => parse ({ opts where includeonly = SOME x }, args)
                                              | SOME _ => showMessageAndFail "multiple --includeonly options"
                                           )
       | SOME (OPT_MAKE_DEPENDS x, args) => (case #make_depends opts of
                                                 NONE => parse ({ opts where make_depends = SOME x }, args)
                                               | SOME _ => showMessageAndFail "multiple --make-depends options"
                                            )
       | SOME (OPT_PRINT_OUTPUT_DIRECTORY, args) => (case #print_output_directory opts of
                                                         false => parse ({ opts where print_output_directory = true }, args)
                                                       | true => showMessageAndFail "multiple --print-output-directory options"
                                                    )
       | SOME (OPT_PACKAGE_SUPPORT s, args) => let val packages = String.tokens (fn c => c = #"," orelse Char.isSpace c) s
                                                   val ps = List.foldl (fn ("minted", ps) => { ps where minted = true }
                                                                       | ("epstopdf", ps) => { ps where epstopdf = true }
                                                                       | ("pdfx", ps) => { ps where pdfx = true }
                                                                       | (pkg, ps) => ( if Message.getVerbosity () >= 1 then
                                                                                            Message.warn ("ClutTeX provides no special support for '" ^ pkg ^ "'.")
                                                                                        else
                                                                                            ()
                                                                                      ; ps
                                                                                      )
                                                                       ) (#package_support opts) packages
                                               in parse ({ opts where package_support = ps }, args)
                                               end
       | SOME (OPT_CHECK_DRIVER driver, args) => (case #check_driver opts of
                                                      NONE => (case DviDriver.fromString driver of
                                                                   SOME driver => parse ({ opts where check_driver = SOME driver }, args)
                                                                 | NONE => showMessageAndFail "invalid value for --check-driver option"
                                                              )
                                                    | SOME _ => showMessageAndFail "multiple --check-driver options"
                                                 )
       | SOME (OPT_SOURCE_DATE_EPOCH time, args) => (case #source_date_epoch opts of
                                                         NONE => (case SourceDateEpoch.fromString time of
                                                                      SOME time => parse ({ opts where source_date_epoch = SOME time }, args)
                                                                    | NONE => showMessageAndFail "invalid value for --source-date-epoch option"
                                                                 )
                                                       | SOME _ => showMessageAndFail "multiple --source-date-epoch options"
                                                    )
       | SOME (OPT_SYNCTEX x, args) => (case #synctex opts of
                                            NONE => parse ({ opts where synctex = SOME x }, args)
                                          | SOME _ => showMessageAndFail "multiple --synctex options"
                                       )
       | SOME (OPT_FILE_LINE_ERROR x, args) => parse ({ opts where file_line_error = x }, args)
       | SOME (OPT_INTERACTION x, args) => (case #interaction opts of
                                                NONE => (case InteractionMode.fromString x of
                                                             SOME interaction => parse ({ opts where interaction = SOME interaction }, args)
                                                           | NONE => showMessageAndFail "invalid argument for --interaction"
                                                        )
                                              | SOME _ => showMessageAndFail "multiple --interaction options"
                                           )
       | SOME (OPT_HALT_ON_ERROR x, args) => parse ({ opts where halt_on_error = x }, args)
       | SOME (OPT_SHELL_ESCAPE se, args) => (case #shell_escape opts of
                                                  NONE => parse ({ opts where shell_escape = SOME se }, args)
                                                | SOME _ => showMessageAndFail "multiple --(no-)shell-escape / --shell-restricted options"
                                             )
       | SOME (OPT_JOBNAME x, args) => (case #jobname opts of
                                            NONE => parse ({ opts where jobname = SOME x }, args)
                                          | SOME _ => showMessageAndFail "multiple --jobname options"
                                       )
       | SOME (OPT_FMT x, args) => (case #fmt opts of
                                        NONE => parse ({ opts where fmt = SOME x }, args)
                                      | SOME _ => showMessageAndFail "multiple --fmt options"
                                   )
       | SOME (OPT_OUTPUT_DIRECTORY x, args) => (case #output_directory opts of
                                                     NONE => parse ({ opts where output_directory = SOME x }, args)
                                                   | SOME _ => showMessageAndFail "multiple --output-directory options"
                                                )
       | SOME (OPT_OUTPUT_FORMAT format, args) => (case #output_format opts of
                                                       NONE => (case OutputFormat.fromString format of
                                                                    SOME format => parse ({ opts where output_format = SOME format }, args)
                                                                  | NONE => showMessageAndFail "invalid value for --output-format option"
                                                               )
                                                     | SOME _ => showMessageAndFail "multiple --output-format options"
                                                  )
       | SOME (OPT_TEX_OPTION x, args) => let val x = ShellUtil.escape x
                                          in parse ({ opts where tex_extraoptions = x :: #tex_extraoptions opts }, args)
                                          end
       | SOME (OPT_TEX_OPTIONS x, args) => parse ({ opts where tex_extraoptions = x :: #tex_extraoptions opts }, args)
       | SOME (OPT_DVIPDFMX_OPTION x, args) => let val x = ShellUtil.escape x
                                               in parse ({ opts where dvipdfmx_extraoptions = x :: #dvipdfmx_extraoptions opts }, args)
                                               end
       | SOME (OPT_DVIPDFMX_OPTIONS x, args) => parse ({ opts where tex_extraoptions = x :: #dvipdfmx_extraoptions opts }, args)
       | SOME (OPT_MAKEINDEX x, args) => (case #makeindex opts of
                                              NONE => parse ({ opts where makeindex = SOME x }, args)
                                            | SOME _ => showMessageAndFail "multiple --makeindex options"
                                         )
       | SOME (OPT_BIBTEX x, args) => (case #bibtex_or_biber opts of
                                           NONE => parse ({ opts where bibtex_or_biber = SOME (BIBTEX x) }, args)
                                         | SOME _ => showMessageAndFail "multiple --bibtex / --biber options"
                                      )
       | SOME (OPT_BIBER x, args) => (case #bibtex_or_biber opts of
                                          NONE => parse ({ opts where bibtex_or_biber = SOME (BIBER x) }, args)
                                        | SOME _ => showMessageAndFail "multiple --bibtex / --biber options"
                                     )
       | SOME (OPT_MAKEGLOSSARIES x, args) => (case #makeglossaries opts of
                                                   NONE => parse ({ opts where makeglossaries = SOME x }, args)
                                                 | SOME _ => showMessageAndFail "multiple --makeglossaries options"
                                              )
       | SOME (OPT_CONFIG_FILE x, args) => (case #config_file opts of
                                                NONE => parse ({ opts where config_file = SOME x }, args)
                                              | SOME _ => showMessageAndFail "multiple --config-file options"
                                           )
       | NONE => (case args of
                      "--" :: args => (opts, args)
                    | arg :: args' =>
                      if String.isPrefix "-" arg then
                          showMessageAndFail ("Unrecognized option: " ^ arg ^ ".\n")
                      else
                          (opts, args)
                    | [] => showUsage () (* showMessageAndFail "No input given. Try --help.\n" *)
                 )
end;