fun md5sumOfFile (path : string) : MD5.hash
= let val ins = BinIO.openIn path
val data = BinIO.inputAll ins before BinIO.closeIn ins
in MD5.compute data
end
fun collectFileInfo (fileList : file_info list, auxstatus : aux_status StringMap.map) : aux_status StringMap.map
= let fun go ({ abspath, kind, ... } : file_info, auxstatus) : aux_status ref StringMap.map
= if FSUtil.isFile abspath then
let val (status, auxstatus) = case StringMap.find (auxstatus, abspath) of
NONE => let val s = ref { mtime = NONE, size = NONE, md5sum = NONE }
in (s, StringMap.insert (auxstatus, abspath, s))
end
| SOME status => (status, auxstatus)
in case kind of
INPUT => (case status of
ref (s as { mtime = NONE, ... }) => status := { s where mtime = SOME (OS.FileSys.modTime abspath) }
| _ => ()
)
| AUXILIARY => let val s = !status
val s = case s of
{ mtime = NONE, ... } => { s where mtime = SOME (OS.FileSys.modTime abspath) }
| _ => s
val s = case s of
{ size = NONE, ... } => { s where size = SOME (OS.FileSys.fileSize abspath) }
| _ => s
val s = case s of
{ md5sum = NONE, ... } => { s where md5sum = SOME (md5sumOfFile abspath) }
| _ => s
in status := s
end
| OUTPUT => ()
; auxstatus
end
else
auxstatus
in StringMap.map ! (List.foldl go (StringMap.map ref auxstatus) fileList)
end
fun compareFileInfo (fileList : file_info list, auxstatus : aux_status StringMap.map) : bool * aux_status StringMap.map
= let fun go ([], newauxstatus) = (false, newauxstatus)
| go ({ path = shortPath, abspath, kind } :: fileList, newauxstatus)
= if FSUtil.isFile abspath then
let val (shouldRerun, newauxstatus)
= case kind of
INPUT => (* Input file: User might have modified while running TeX. *)
let val mtime = OS.FileSys.modTime abspath
in case StringMap.find (auxstatus, abspath) of
SOME { mtime = SOME mtime', ... } =>
if Time.< (mtime', mtime) then
(* Input file was updated during execution *)
( Message.info ("Input file '" ^ shortPath ^ "' was modified (by user, or some external commands).")
; (true, StringMap.insert (newauxstatus, abspath, ref { mtime = SOME mtime, size = NONE, md5sum = NONE }))
)
else
(false, newauxstatus)
| _ => (* New input file *)
(false, newauxstatus)
end
| AUXILIARY => (* Auxiliary file: Compare file contents. *)
(case StringMap.find (auxstatus, abspath) of
SOME s =>
let val size = OS.FileSys.fileSize abspath
val sizeIsDifferent = case #size s of
SOME z => z <> size
| NONE => true
val (modifiedBecause, newauxstatus)
= if sizeIsDifferent then
let val previousSize = case #size s of
SOME z => Position.toString z
| NONE => "(N/A)"
in (SOME ("size: " ^ previousSize ^ " -> " ^ Position.toString size), StringMap.insert (newauxstatus, abspath, ref { mtime = NONE, size = SOME size, md5sum = NONE }))
end
else
let val md5sum = md5sumOfFile abspath
val md5sumIsDifferent = case #md5sum s of
SOME h => h <> md5sum
| NONE => true
in if md5sumIsDifferent then
let val previousMd5sum = case #md5sum s of
SOME h => MD5.hashToLowerHexString h
| NONE => "(N/A)"
in (SOME ("md5: " ^ previousMd5sum ^ " -> " ^ MD5.hashToLowerHexString md5sum), StringMap.insert (newauxstatus, abspath, ref { mtime = NONE, size = SOME size, md5sum = SOME md5sum }))
end
else
(NONE, newauxstatus)
end
in case modifiedBecause of
SOME reason => ( Message.info ("File '" ^ shortPath ^ "' was modified (" ^ reason ^ ").")
; (true, newauxstatus)
)
| NONE => ( if Message.getVerbosity () >= 1 then
Message.info ("File '" ^ shortPath ^ "' unmodified (size and md5sum).")
else
()
; (false, newauxstatus)
)
end
| NONE => (* New file *)
let val (shouldRerun, newauxstatus)
= if String.isSuffix ".aux" abspath then
let val size = OS.FileSys.fileSize abspath
in if size = 8 then
let val ins = BinIO.openIn abspath
val contents = BinIO.inputAll ins before BinIO.closeIn ins
val isTrivial = Byte.bytesToString contents = "\\relax \n"
val newauxstatus = StringMap.insert (newauxstatus, abspath, ref { mtime = NONE, size = SOME size, md5sum = SOME (MD5.compute contents) })
in (not isTrivial, newauxstatus)
end
else
let val newauxstatus = StringMap.insert (newauxstatus, abspath, ref { mtime = NONE, size = SOME size, md5sum = NONE })
in (true, newauxstatus)
end
end
else
(true, newauxstatus)
in if shouldRerun then
Message.info ("New auxiliary file '" ^ shortPath ^ "'.")
else if Message.getVerbosity () >= 1 then
Message.info ("Ignoring almost-empty auxiliary file '" ^ shortPath ^ "'.")
else
()
; (shouldRerun, newauxstatus)
end
)
| OUTPUT => (false, newauxstatus)
in if shouldRerun then
(true, newauxstatus)
else
go (fileList, newauxstatus)
end
else
go (fileList, newauxstatus)
val (shouldRerun, auxstatus) = go (fileList, StringMap.empty)
in (shouldRerun, StringMap.map ! auxstatus)
end
(* true if src is newer than dst *)
fun compareFileTime { srcAbs, dst, auxstatus : aux_status StringMap.map }
= if not (FSUtil.isFile dst) then
true
else
case StringMap.find (auxstatus, srcAbs) of
SOME { mtime = SOME mtime, ... } => Time.> (mtime, OS.FileSys.modTime dst)
| _ => false
end;