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;
