[MLton] replaced tabs by spaces in source files

Stephen Weeks MLton@mlton.org
Sat, 20 Aug 2005 14:31:23 -0700


Files with the following suffixes were changed.

	c, cm, el, fun, grm, h, lex, mlb, sig, sml, tex, txt

I think it makes sense for whatever automated "no tabs" enforcement we
put in place to continue to enforce the rule for these suffixes.

Some unsuffixed files were also changed, mostly READMEs and shell
scripts.  I'm not sure what to do about automated enforcement for
unsuffixed files, but it's not a huge deal to me.

Some files with tabs were not changed, in particular, regression
output, .st files (Matthew, what about these?), Makefiles, patches
that include Makefiles require tabs.  Here's a complete list of
(ASCII) files that still have tabs in them.

regression/textio.ok
regression/array.ok
regression/word8array.ok
regression/timer.ok
regression/substring.ok
regression/string.ok
regression/check_arrays.ok
regression/pack-real.ok
regression/stringcvt.ok
regression/Makefile
regression/filesys.ok
regression/word8vector.ok
regression/time.ok
regression/bytechar.ok
regression/listpair.ok
regression/math.ok
regression/unixpath.ok
regression/date.ok
regression/word-all.ok
regression/kitsimple.ok
regression/listsort.ok
regression/real.ok
regression/list.ok
regression/int.ok
mlnlffigen/Makefile
mllex/Makefile
Makefile
include/Makefile
runtime/Makefile
mlton/front-end/Makefile
mlton/Makefile
benchmark/tests/Makefile
benchmark/Makefile
bytecode/Makefile
mlprof/Makefile
ide/enscript/sml_verbose.st
ide/enscript/sml_fancy.st
ide/enscript/sml_all.st
ide/enscript/sml_gaudy.st
basis-library/libs/basis-2002/top-level/Makefile
basis-library/Makefile
doc/library-guide/Makefile
doc/hacker-guide/structure.fig
doc/hacker-guide/Makefile
doc/mlb-formal/Makefile
doc/style-guide/Makefile
doc/examples/finalizable/Makefile
doc/examples/save-world/Makefile
doc/examples/ffi/Makefile
doc/examples/profiling/Makefile
doc/examples/Makefile
lib/basis-stubs/Makefile
lib/opengl/Makefile
lib/mlton-stubs/Makefile
lib/mlton/basic/Makefile
lib/mlton/Makefile
lib/mlton-stubs-in-smlnj/Makefile
lib/smlnj-lib/Makefile
lib/smlnj-lib/smlnj-lib.patch
lib/Makefile
lib/ckit-lib/ckit.patch
lib/ckit-lib/Makefile
bin/Makefile
mlyacc/doc/Makefile
mlyacc/examples/pascal/test/c1.p
mlyacc/examples/pascal/test/t1.p
mlyacc/Makefile
man/Makefile
package/debian/mlton.postinst
package/debian/rules
package/debian/mlton.prerm
package/freebsd/pkg-descr
package/freebsd/Makefile
package/rpm/mlton.spec

Finally, for the record, here's the script I used to make the changes.

----------------------------------------------------------------------
structure List =
   struct
      open List

      fun exists (l, f) = List.exists f l
      fun map (l, f) = List.map f l
   end

structure String =
   struct
      open String

      fun hasSuffix (s, {suffix}) =
         Int.>= (size s, size suffix)
         andalso suffix = extract (s, size s - size suffix, NONE)
   end

val name = #file (OS.Path.splitDirFile (CommandLine.name ()))

fun die s =
   (TextIO.output (TextIO.stdErr, s)
    ; let open OS.Process in exit failure end)
   
val root =
   case CommandLine.arguments () of
      [dir] => dir
    | _ => die (concat ["usage: ", name, " <dir>"])

val numFiles = ref 0
val numFilesWithTabs = ref 0
val numTabs = ref 0

fun ++ x = x := 1 + !x

fun msg ss =
   if true then () else
   TextIO.output (TextIO.stdErr, concat [concat ss, "\n"])

val tab = CharVector.tabulate (8, fn _ => #" ")
   
fun replaceTabs f =
   let
      val mode = Posix.FileSys.ST.mode (Posix.FileSys.stat f)
      val ins = TextIO.openIn f
      val tmp = "/tmp/z.tab"
      val out = TextIO.openOut tmp
      fun loop () =
	 case TextIO.input1 ins of
	    NONE => ()
	  | SOME c =>
	       (if c = #"\t"
		   then TextIO.output (out, tab)
		else TextIO.output1 (out, c)
		; loop ())
      val () = loop ()
      val () = TextIO.closeIn ins
      val () = TextIO.closeOut out
      val () = OS.FileSys.rename {new = f, old = tmp}
      val () = Posix.FileSys.chmod (f, mode)
   in
      ()
   end

val replaceSuffixes =
   List.map
   (["c", "cm", "doc", "el", "fun", "grm", "h", "lex", "mlb", "sig", "sml",
    "tex", "txt"],
    fn s => concat [".", s])

fun shouldReplace (dir, f) =
   (dir = "./bin" andalso f <> "Makefile")
   orelse List.exists (["changelog", "README", "README.Debian", "README.kit"],
                       fn f' => f = f')
   orelse List.exists (replaceSuffixes, fn s =>
                       String.hasSuffix (f, {suffix = s}))

fun handleFile (dir, f) =
   let
      val () = ++numFiles
      val isAscii = ref true
      val hasTab = ref false
      val numTabsInFile = ref 0
      val ins = TextIO.openIn f
      fun loop (filePos) =
	 case TextIO.input1 ins of
	    NONE => ()
	  | SOME c =>
	       if not (Char.isAscii c)
		  then isAscii := false
	       else
		  let
		     val () =
			if c = #"\t" then (++numTabsInFile; hasTab := true)
			else ()
		  in
		     loop (filePos + 1)
		  end
      val () = loop 0
      val () = TextIO.closeIn ins
      val () =
	 if not (!isAscii) then ()
	 else if !hasTab then
            (++numFilesWithTabs
             ; (if shouldReplace (dir, f) then replaceTabs f
                else (print (concat ["skipping ",
                                     OS.Path.joinDirFile {dir = dir, file = f},
                                     "\n"]))))
	 else ()
   in
      ()
   end

fun loop (dir, path) =
   let
      val () = msg ["entering ", dir]
      open OS.FileSys
      val saved = getDir ()
      val () = chDir dir
      val ds = openDir "."
      val path = OS.Path.joinDirFile {dir = path, file = dir}
      fun loop' () =
	 case readDir ds of
	    NONE => ()
	  | SOME s =>
	       let
		  val () =
		     case s of
			"." => ()
		      | ".." => ()
                      | ".svn" => ()
		      | _ =>
			   if isDir s then loop (s, path)
			   else if isLink s then ()
			   else handleFile (path, s)
	       in
		  loop' ()
	       end
      val () = loop' ()
      val () = closeDir ds
      val () = chDir saved
      val () = msg ["leaving ", dir]
   in
      ()
   end

val () = loop (root, "")

val () =
   List.app
   (fn (s, r) =>
    print (concat [s, " = ", Int.toString (!r), "\n"]))
   [("numFiles", numFiles),
    ("numFilesWithTabs", numFilesWithTabs),
    ("numTabs", numTabs)]