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