1structure generateBuildSummary =
2struct
3
4
5
6(* reads the output of bin/build on standard input, and turns it into a
7   mail message *)
8
9infix |>
10fun (x |> f) = f x
11
12fun die s = (TextIO.output(TextIO.stdErr, s^"\n");
13             TextIO.flushOut TextIO.stdErr;
14             OS.Process.exit OS.Process.failure)
15
16
17fun usage() = die ("Usage:\n  "^CommandLine.name()^
18                   " from-address system-description\n")
19
20
21fun datestring () = let
22  (* SML implementations have such broken time/date code that this seems
23     best - now only works on Unixy systems *)
24  val tmp = OS.FileSys.tmpName()
25  val result = OS.Process.system ("date +\"%a, %d %b %Y %H:%M:%S %z\" > "^tmp)
26  val _ = OS.Process.isSuccess result orelse
27          die "Couldn't run date"
28  val istrm = TextIO.openIn tmp
29in
30  TextIO.inputAll istrm before TextIO.closeIn istrm
31end
32
33fun standard_header from subject =
34    "To: hol-builds@lists.sourceforge.net\n\
35    \From: "^from^"\n\
36    \Date: "^datestring()^ (* datestring provides its own newline *)
37    "Subject: "^subject^"\n"^
38    "MIME-Version: 1.0\n\
39    \Content-Type: text/plain; charset=UTF-8\n\
40    \Content-Transfer-Encoding: 8bit\n\n"
41
42val remove_nulls = String.translate (fn #"\000" => "^@" | c => str c)
43
44val lastlines = 80
45
46fun tidy_p2_lines linelist = let
47  val maxlen = List.foldl (fn (l,acc) => Int.max(size l, acc)) 0 linelist
48  val maxlen = Int.max(78, maxlen)
49  fun make_maxlen s =
50    if size s = maxlen then s
51    else let
52        fun findlbrack i =
53            if String.sub(s, i) = #"[" then i else findlbrack (i - 1)
54        val lbrack_i = findlbrack (size s - 1)
55        val pfx = String.extract(s, 0, SOME lbrack_i)
56        val spaces = CharVector.tabulate(maxlen - size s, (fn _ => #" "))
57        val sfx = String.extract(s, lbrack_i, NONE)
58      in
59        pfx ^ spaces ^ sfx
60      end
61in
62  map make_maxlen linelist
63end
64
65fun filter_input instr = let
66  fun phase1 lines =
67      case TextIO.inputLine instr of
68        NONE => (lines, false)
69      | SOME s => if s = "-- Configuration Description Ends --\n" then
70                    ("\n"::s::lines, true)
71                  else phase1 (s::lines)
72  fun phase2 (lines, dirlines, cnt) =
73      case Option.map remove_nulls (TextIO.inputLine instr) of
74        NONE => (if length lines > lastlines then List.take(lines, lastlines)
75                 else lines, false)
76      | SOME s => if s = "Hol built successfully.\n" then
77                      (dirlines, true)
78                  else let
79                      val dirlines =
80                          if String.isPrefix "Building directory" s then
81                            s :: dirlines
82                          else dirlines
83                      val (lines, cnt) =
84                          if cnt = lastlines * 2 then
85                            (s :: List.take(lines, lastlines - 1), lastlines)
86                          else
87                            (s::lines, cnt + 1)
88                    in
89                      phase2 (lines, dirlines, cnt)
90                    end
91  val (p1_lines, p1_ok) = phase1 []
92in
93  if not p1_ok then (String.concat (List.rev p1_lines), false)
94  else let
95      val (p2_lines, p2_ok) = phase2 ([], [], 0)
96      val p2_lines = if p2_ok then tidy_p2_lines p2_lines else p2_lines
97      val all_lines = String.concat (List.rev p1_lines @ List.rev p2_lines)
98    in
99      (all_lines, p2_ok)
100    end
101end
102
103
104fun main() = let
105  open TextIO
106  val (from, sysdesc) = case CommandLine.arguments() of
107               [f,s] => (f,s)
108             | _ => usage()
109  val (outputthis, succeeded) = filter_input stdIn
110  val header =
111      standard_header from
112                      ((if succeeded then "SUCCESS: " else "FAILURE: ")^sysdesc)
113in
114  output(stdOut, header);
115  output(stdOut, outputthis)
116end handle e => die ("Unexpected exception: "^exnMessage e)
117
118end
119