structure AuxFile:
sig
 val createMissingDirectories:
   {auxfile: string, outdir: string, seen: StringSet.set}
   -> bool * StringSet.set
 val extractBibTeXLines: {auxfile: string, outdir: string} -> string list
end =
struct
 fun stripPrefix prefix s =
   if String.isPrefix prefix s then
     SOME (Substring.extract (s, String.size prefix, NONE))
   else
     NONE
 fun isInput (line, outdir) =
   case stripPrefix "\\@input{" line of
     NONE => NONE
   | SOME rest =>
       let
         val subauxfile = Substring.string
           (Substring.takel (fn c => c <> #"}") rest)
         val subauxfile_abs =
           PathUtil.abspath {path = subauxfile, cwd = SOME outdir}
       in
         SOME {subauxfile = subauxfile, subauxfile_abs = subauxfile_abs}
       end
 fun createMissingDirectories {auxfile, outdir, seen} =
   let
     val ins = TextIO.openIn auxfile
     val seen = StringSet.add (seen, auxfile)
     fun go (did, seen) =
       case TextIO.inputLine ins of
         NONE => (TextIO.closeIn ins; (did, seen))
       | SOME line =>
           case isInput (line, outdir) of
             NONE => go (did, seen)
           | SOME {subauxfile, subauxfile_abs} =>
               if FSUtil.isFile subauxfile_abs then
                 let
                   val (did', seen) =
                     createMissingDirectories
                       {auxfile = subauxfile_abs, outdir = outdir, seen = seen}
                 in
                   go (did orelse did', seen)
                 end
               else
                 let
                   val dir =
                     PathUtil.join2 (outdir, PathUtil.dirname subauxfile)
                 in
                   if FSUtil.isDirectory dir then go (did, seen)
                   else (FSUtil.mkDirRec dir; go (true, seen))
                 end
   in
     go (false, seen)
   end

 (* \citation, \bibdata, \bibstyle *)
 fun extractBibTeXLines' {auxfile, outdir, revLines} =
   let
     val ins = TextIO.openIn auxfile
     fun go revLines =
       case TextIO.inputLine ins of
         NONE => (TextIO.closeIn ins; revLines)
       | SOME line =>
           case isInput (line, outdir) of
             SOME {subauxfile, subauxfile_abs} =>
               if FSUtil.isFile subauxfile_abs then
                 let
                   val revLines =
                     extractBibTeXLines'
                       { auxfile = subauxfile_abs
                       , outdir = outdir
                       , revLines = revLines
                       }
                 in
                   go revLines
                 end
               else
                 go revLines
           | NONE =>
               let
                 val isBibTeXLine =
                   case stripPrefix "\\" line of
                     SOME s =>
                       (case
                          Substring.string
                            (Substring.takel
                               (fn c => Char.isAlpha c orelse c = #"@") s)
                        of
                          "citation" => true
                        | "bibdata" => true
                        | "bibstyle" => true
                        | _ => false)
                   | NONE => false
               in
                 if isBibTeXLine then
                   ( if Message.getVerbosity () >= 2 then
                       Message.info
                         ("BibTeX line: "
                          ^
                          Substring.string
                            (Substring.dropr Char.isSpace (Substring.full line)))
                     else
                       ()
                   ; go (line :: revLines)
                   )
                 else
                   go revLines
               end
   in
     go revLines
   end
 fun extractBibTeXLines {auxfile, outdir} =
   List.rev
     (extractBibTeXLines' {auxfile = auxfile, outdir = outdir, revLines = []})
end;