structure Recovery:
sig
 val createMissingDirectories:
   {execlog: string, auxfile: string, outdir: string}
   -> bool
 val runEpstopdf:
   { options: AppOptions.options
   , execlog: string
   , originalWorkingDirectory: string
   }
   -> bool
 val tryRecovery:
   { options: AppOptions.options
   , execlog: string
   , auxfile: string
   , originalWorkingDirectory: string
   }
   -> bool
end =
struct
 fun createMissingDirectories {execlog, auxfile, outdir} =
   if String.isSubstring "I can't write on file" execlog then
     (* There is a possibility that there are some subfiles under subdirectories.
      * Directories for sub-auxfiles are not created automatically, so we need to provide them. *)
     let
       val (madeNewDirectory, _) =
         AuxFile.createMissingDirectories
           {auxfile = auxfile, outdir = outdir, seen = StringSet.empty}
     in
       if madeNewDirectory andalso Message.getVerbosity () >= 1 then
         Message.info "Created missing directories."
       else
         ();
       madeNewDirectory
     end
   else
     false

 fun runEpstopdf
   { options: AppOptions.options
   , execlog: string
   , originalWorkingDirectory: string
   } =
   if #shell_escape options <> SOME ShellEscape.FORBIDDEN then (* (possibly restricted) \write18 enabled *)
     let
       val lines =
         Substring.tokens (fn c => c = #"\n") (Substring.full execlog)
       fun doLine (line, run) =
         case List.map Substring.string (Substring.tokens Char.isSpace line) of
           ["(epstopdf)", "Command:", command, outfile', infile'] =>
             if
               command = "<epstopdf"
               orelse
               command = "<repstopdf"
               andalso String.isPrefix "--outfile=" outfile'
               andalso String.isSuffix ">" infile'
             then
               let
                 val outfile = String.extract (outfile', 10, NONE)
                 val infile = String.substring
                   (infile', 0, String.size infile' - 1)
                 val infileAbs =
                   PathUtil.abspath
                     {path = infile, cwd = SOME originalWorkingDirectory}
               in
                 if FSUtil.isFile infileAbs then (* input file exists *)
                   let
                     val outfileAbs = PathUtil.abspath
                       {path = outfile, cwd = SOME (#output_directory options)}
                     val () =
                       if Message.getVerbosity () >= 1 then
                         Message.info ("Running epstopdf on " ^ infile ^ ".")
                       else
                         ()
                     val outdir = PathUtil.dirname outfileAbs
                     val () =
                       if not (FSUtil.isDirectory outdir) then
                         FSUtil.mkDirRec outdir
                       else
                         ()
                     val command =
                       "epstopdf --outfile=" ^ ShellUtil.escape outfileAbs
                       ^ " " ^ ShellUtil.escape infileAbs
                     val () = Message.exec command
                     val success =
                       OS.Process.isSuccess (OS.Process.system command)
                   in
                     run orelse success
                   end
                 else
                   run
               end
             else
               run
         | _ => run
     in
       List.foldl doLine false lines
     end
   else
     false

 (* The next time we will able to set \PassOptionsToPackage{outputdir=}{minted} *)
 fun checkMinted {execlog} =
   String.isSubstring
     "Package minted Error: Missing Pygments output; \\inputminted was" execlog

 fun tryRecovery {options, execlog, auxfile, originalWorkingDirectory} =
   let
     val recovered =
       createMissingDirectories
         { execlog = execlog
         , auxfile = auxfile
         , outdir = #output_directory options
         }
     val recovered =
       runEpstopdf
         { options = options
         , execlog = execlog
         , originalWorkingDirectory = originalWorkingDirectory
         } orelse recovered
     val recovered = recovered orelse checkMinted {execlog = execlog}
   in
     recovered
   end
end;