annotate src/demo.sml @ 1739:c414850f206f

Add support for -boot flag, which allows in-tree execution of Ur/Web The boot flag rewrites most hardcoded paths to point to the build directory, and also forces static compilation. This is convenient for developing Ur/Web, or if you cannot 'sudo make install' Ur/Web. The following changes were made: * Header files were moved to include/urweb instead of include; this lets FFI users point their C_INCLUDE_PATH at this directory at write <urweb/urweb.h>. For internal Ur/Web executables, we simply pass -I$PATH/include/urweb as normal. * Differentiate between LIB and SRCLIB; SRCLIB is Ur and JavaScript source files, while LIB is compiled products from libtool. For in-tree compilation these live in different places. * No longer reference Config for paths; instead use Settings; these settings can be changed dynamically by Compiler.enableBoot () (TODO: add a disableBoot function.) * config.h is now generated directly in include/urweb/config.h, for consistency's sake (especially since it gets installed along with the rest of the headers!) * All of the autotools build products got updated. * The linkStatic field in protocols now only contains the name of the build product, and not the absolute path. Future users have to be careful not to reference the Settings files to early, lest they get an old version (this was the source of two bugs during development of this patch.)
author Edward Z. Yang <ezyang@mit.edu>
date Wed, 02 May 2012 17:17:57 -0400
parents 7b9775d4a8ce
children f8ddaa296115
rev   line source
adamc@1151 1 (* Copyright (c) 2008-2010, Adam Chlipala
adamc@380 2 * All rights reserved.
adamc@380 3 *
adamc@380 4 * Redistribution and use in source and binary forms, with or without
adamc@380 5 * modification, are permitted provided that the following conditions are met:
adamc@380 6 *
adamc@380 7 * - Redistributions of source code must retain the above copyright notice,
adamc@380 8 * this list of conditions and the following disclaimer.
adamc@380 9 * - Redistributions in binary form must reproduce the above copyright notice,
adamc@380 10 * this list of conditions and the following disclaimer in the documentation
adamc@380 11 * and/or other materials provided with the distribution.
adamc@380 12 * - The names of contributors may not be used to endorse or promote products
adamc@380 13 * derived from this software without specific prior written permission.
adamc@380 14 *
adamc@380 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
adamc@380 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
adamc@380 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
adamc@380 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
adamc@380 19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
adamc@380 20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
adamc@380 21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
adamc@380 22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
adamc@380 23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
adamc@380 24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
adamc@380 25 * POSSIBILITY OF SUCH DAMAGE.
adamc@380 26 *)
adamc@380 27
adamc@380 28 structure Demo :> DEMO = struct
adamc@380 29
adam@1301 30 val noEmacs = ref false
adam@1301 31
adamc@1079 32 fun make' {prefix, dirname, guided} =
adamc@380 33 let
adamc@380 34 val prose = OS.Path.joinDirFile {dir = dirname,
adamc@380 35 file = "prose"}
adamc@380 36 val inf = TextIO.openIn prose
adamc@380 37
adamc@380 38 val outDir = OS.Path.concat (dirname, "out")
adamc@380 39
adamc@380 40 val () = if OS.FileSys.access (outDir, []) then
adamc@380 41 ()
adamc@380 42 else
adamc@380 43 OS.FileSys.mkDir outDir
adamc@380 44
adamc@380 45 val fname = OS.Path.joinDirFile {dir = outDir,
adamc@380 46 file = "index.html"}
adamc@380 47
adamc@380 48 val out = TextIO.openOut fname
adamc@773 49 val () = (TextIO.output (out, "<frameset cols=\"15%,85%\">\n");
adamc@380 50 TextIO.output (out, "<frame src=\"demos.html\">\n");
adamc@380 51 TextIO.output (out, "<frame src=\"intro.html\" name=\"staging\">\n");
adamc@380 52 TextIO.output (out, "</frameset>\n");
adamc@380 53 TextIO.closeOut out)
adamc@380 54
adamc@380 55 val fname = OS.Path.joinDirFile {dir = outDir,
adamc@380 56 file = "demos.html"}
adamc@380 57
adamc@380 58 val demosOut = TextIO.openOut fname
adamc@381 59 val () = (TextIO.output (demosOut, "<html><body>\n\n");
adamc@380 60 TextIO.output (demosOut, "<li> <a target=\"staging\" href=\"intro.html\">Intro</a></li>\n\n"))
adamc@380 61
adamc@384 62 val fname = OS.Path.joinDirFile {dir = dirname,
adamc@384 63 file = "demo.urs"}
adamc@384 64 val ursOut = TextIO.openOut fname
adamc@384 65 val () = (TextIO.output (ursOut, "val main : unit -> transaction page\n");
adamc@384 66 TextIO.closeOut ursOut)
adamc@384 67
adamc@384 68 val fname = OS.Path.joinDirFile {dir = dirname,
adamc@384 69 file = "demo.ur"}
adamc@384 70 val urOut = TextIO.openOut fname
adamc@384 71 val () = TextIO.output (urOut, "fun main () = return <xml><body>\n")
adamc@384 72
adamc@380 73 fun mergeWith f (o1, o2) =
adamc@380 74 case (o1, o2) of
adamc@380 75 (NONE, _) => o2
adamc@380 76 | (_, NONE) => o1
adamc@380 77 | (SOME v1, SOME v2) => SOME (f (v1, v2))
adamc@380 78
adamc@380 79 fun combiner (combined : Compiler.job, urp : Compiler.job) = {
adamc@385 80 prefix = prefix,
adamc@380 81 database = mergeWith (fn (v1, v2) =>
adamc@380 82 if v1 = v2 then
adamc@380 83 v1
adamc@380 84 else
adamc@380 85 raise Fail "Different demos want to use different database strings")
adamc@380 86 (#database combined, #database urp),
adamc@380 87 sources = foldl (fn (file, files) =>
adamc@380 88 if List.exists (fn x => x = file) files then
adamc@380 89 files
adamc@380 90 else
adamc@380 91 files @ [file])
adamc@380 92 (#sources combined) (#sources urp),
adamc@891 93 exe = case Settings.getExe () of
adamc@891 94 NONE => OS.Path.joinDirFile {dir = dirname,
adamc@891 95 file = "demo.exe"}
adamc@891 96 | SOME s => s,
adamc@891 97 sql = SOME (case Settings.getSql () of
adamc@891 98 NONE => OS.Path.joinDirFile {dir = dirname,
adamc@891 99 file = "demo.sql"}
adamc@891 100 | SOME s => s),
adamc@863 101 debug = Settings.getDebug (),
adamc@673 102 timeout = Int.max (#timeout combined, #timeout urp),
adamc@764 103 profile = false,
adamc@764 104 ffi = [],
adamc@764 105 link = [],
adam@1725 106 linker = NONE,
adamc@765 107 headers = [],
adamc@766 108 scripts = [],
adamc@765 109 clientToServer = [],
adamc@765 110 effectful = [],
adamc@1171 111 benignEffectful = [],
adamc@765 112 clientOnly = [],
adamc@765 113 serverOnly = [],
adamc@768 114 jsFuncs = [],
adamc@774 115 rewrites = #rewrites combined @ #rewrites urp,
adamc@769 116 filterUrl = #filterUrl combined @ #filterUrl urp,
adamc@866 117 filterMime = #filterMime combined @ #filterMime urp,
adam@1465 118 filterRequest = #filterRequest combined @ #filterRequest urp,
adam@1465 119 filterResponse = #filterResponse combined @ #filterResponse urp,
adamc@866 120 protocol = mergeWith #2 (#protocol combined, #protocol urp),
adamc@1164 121 dbms = mergeWith #2 (#dbms combined, #dbms urp),
adamc@1183 122 sigFile = mergeWith #2 (#sigFile combined, #sigFile urp),
adam@1614 123 safeGets = #safeGets combined @ #safeGets urp,
adam@1332 124 onError = NONE,
adam@1332 125 minHeap = 0
adamc@380 126 }
adamc@380 127
adamc@380 128 val parse = Compiler.run (Compiler.transform Compiler.parseUrp "Demo parseUrp")
adamc@380 129
adamc@380 130 fun capitalize "" = ""
adamc@380 131 | capitalize s = str (Char.toUpper (String.sub (s, 0)))
adamc@380 132 ^ String.extract (s, 1, NONE)
adamc@380 133
adamc@380 134 fun startUrp urp =
adamc@380 135 let
adamc@380 136 val base = OS.Path.base urp
adamc@380 137 val name = capitalize base
adamc@380 138
adamc@380 139 val () = (TextIO.output (demosOut, "<li> <a target=\"staging\" href=\"");
adamc@380 140 TextIO.output (demosOut, base);
adamc@380 141 TextIO.output (demosOut, ".html\">");
adamc@380 142 TextIO.output (demosOut, name);
adamc@380 143 TextIO.output (demosOut, "</a></li>\n"))
adamc@380 144
adamc@384 145 val () = (TextIO.output (urOut, " <li> <a link={");
adamc@384 146 TextIO.output (urOut, name);
adamc@384 147 TextIO.output (urOut, ".main ()}>");
adamc@384 148 TextIO.output (urOut, name);
adamc@384 149 TextIO.output (urOut, "</a></li>\n"))
adamc@384 150
adamc@380 151 val urp_file = OS.Path.joinDirFile {dir = dirname,
adamc@380 152 file = urp}
adamc@380 153
adamc@380 154 val out = OS.Path.joinBaseExt {base = base,
adamc@380 155 ext = SOME "html"}
adamc@380 156 val out = OS.Path.joinDirFile {dir = outDir,
adamc@380 157 file = out}
adamc@380 158 val out = TextIO.openOut out
adamc@380 159
adamc@501 160 val () = (TextIO.output (out, "<frameset rows=\"");
adamc@501 161 TextIO.output (out, if guided then
adamc@501 162 "*,100"
adamc@501 163 else
adamc@501 164 "50%,*");
adamc@501 165 TextIO.output (out, "\">\n");
adamc@380 166 TextIO.output (out, "<frame src=\"");
adamc@380 167 TextIO.output (out, prefix);
adamc@380 168 TextIO.output (out, "/");
adamc@380 169 TextIO.output (out, name);
adamc@380 170 TextIO.output (out, "/main\" name=\"showcase\">\n");
adamc@380 171 TextIO.output (out, "<frame src=\"");
adamc@380 172 TextIO.output (out, base);
adamc@380 173 TextIO.output (out, ".desc.html\">\n");
adamc@380 174 TextIO.output (out, "</frameset>\n");
adamc@380 175 TextIO.closeOut out)
adamc@380 176 val () = TextIO.closeOut out
adamc@380 177
adamc@380 178 val out = OS.Path.joinBaseExt {base = base,
adamc@380 179 ext = SOME "desc"}
adamc@380 180 val out = OS.Path.joinBaseExt {base = out,
adamc@380 181 ext = SOME "html"}
adamc@380 182 val out = TextIO.openOut (OS.Path.joinDirFile {dir = outDir,
adamc@380 183 file = out})
adamc@380 184 in
adamc@380 185 case parse (OS.Path.base urp_file) of
adamc@380 186 NONE => raise Fail ("Can't parse " ^ urp_file)
adamc@380 187 | SOME urpData =>
adamc@380 188 (TextIO.output (out, "<html><head>\n<title>");
adamc@380 189 TextIO.output (out, name);
adamc@380 190 TextIO.output (out, "</title>\n</head><body>\n\n<h1>");
adamc@380 191 TextIO.output (out, name);
adamc@380 192 TextIO.output (out, "</h1>\n\n<center>[ <a target=\"showcase\" href=\"");
adamc@382 193 TextIO.output (out, prefix);
adamc@382 194 TextIO.output (out, "/");
adamc@382 195 TextIO.output (out, name);
adamc@382 196 TextIO.output (out, "/main\">Application</a>");
adamc@382 197 TextIO.output (out, " | <a target=\"showcase\" href=\"");
adamc@380 198 TextIO.output (out, urp);
adamc@380 199 TextIO.output (out, ".html\"><tt>");
adamc@380 200 TextIO.output (out, urp);
adamc@380 201 TextIO.output (out, "</tt></a>");
adamc@380 202 app (fn file =>
adamc@380 203 let
adamc@380 204 fun ifEx s =
adamc@380 205 let
adamc@380 206 val src = OS.Path.joinBaseExt {base = file,
adamc@380 207 ext = SOME s}
adamc@380 208 val src' = OS.Path.file src
adamc@380 209 in
adamc@1151 210 if String.isPrefix (OS.Path.mkAbsolute {path = dirname,
adamc@1151 211 relativeTo = OS.FileSys.getDir ()}) src
adamc@943 212 andalso OS.FileSys.access (src, []) then
adamc@380 213 (TextIO.output (out, " | <a target=\"showcase\" href=\"");
adamc@380 214 TextIO.output (out, src');
adamc@380 215 TextIO.output (out, ".html\"><tt>");
adamc@380 216 TextIO.output (out, src');
adamc@380 217 TextIO.output (out, "</tt></a>"))
adamc@380 218 else
adamc@1156 219 ()
adamc@380 220 end
adamc@380 221 in
adamc@380 222 ifEx "urs";
adamc@380 223 ifEx "ur"
adamc@380 224 end) (#sources urpData);
adamc@380 225 TextIO.output (out, " ]</center>\n\n");
adamc@380 226
adamc@380 227 (urpData, out))
adamc@380 228 end
adamc@380 229
adamc@380 230 fun endUrp out =
adamc@380 231 (TextIO.output (out, "\n</body></html>\n");
adamc@380 232 TextIO.closeOut out)
adamc@380 233
adamc@380 234 fun readUrp (combined, out) =
adamc@380 235 let
adamc@380 236 fun finished () = endUrp out
adamc@380 237
adamc@380 238 fun readUrp' () =
adamc@380 239 case TextIO.inputLine inf of
adamc@384 240 NONE => (finished ();
adamc@384 241 combined)
adamc@380 242 | SOME line =>
adamc@380 243 if String.isSuffix ".urp\n" line then
adamc@380 244 let
adamc@380 245 val urp = String.substring (line, 0, size line - 1)
adamc@380 246 val (urpData, out) = startUrp urp
adamc@380 247 in
adamc@380 248 finished ();
adamc@380 249
adamc@380 250 readUrp (combiner (combined, urpData),
adamc@380 251 out)
adamc@380 252 end
adamc@380 253 else
adamc@380 254 (TextIO.output (out, line);
adamc@380 255 readUrp' ())
adamc@380 256 in
adamc@380 257 readUrp' ()
adamc@380 258 end
adamc@380 259
adamc@380 260 val indexFile = OS.Path.joinDirFile {dir = outDir,
adamc@380 261 file = "intro.html"}
adamc@380 262
adamc@380 263 val out = TextIO.openOut indexFile
adamc@380 264 val () = TextIO.output (out, "<html><head>\n<title>Ur/Web Demo</title>\n</head><body>\n\n")
adamc@380 265
adamc@380 266 fun readIndex () =
adamc@380 267 let
adamc@380 268 fun finished () = (TextIO.output (out, "\n</body></html>\n");
adamc@380 269 TextIO.closeOut out)
adamc@380 270 in
adamc@380 271 case TextIO.inputLine inf of
adamc@384 272 NONE => (finished ();
adamc@384 273 NONE)
adamc@380 274 | SOME line =>
adamc@380 275 if String.isSuffix ".urp\n" line then
adamc@380 276 let
adamc@380 277 val urp = String.substring (line, 0, size line - 1)
adamc@380 278 val (urpData, out) = startUrp urp
adamc@380 279 in
adamc@380 280 finished ();
adamc@380 281
adamc@384 282 SOME (readUrp (urpData,
adamc@384 283 out))
adamc@380 284 end
adamc@380 285 else
adamc@380 286 (TextIO.output (out, line);
adamc@380 287 readIndex ())
adamc@380 288 end
adamc@381 289
adamc@381 290 fun prettyPrint () =
adamc@381 291 let
adamc@381 292 val dir = Posix.FileSys.opendir dirname
adamc@381 293
adamc@381 294 fun loop () =
adamc@381 295 case Posix.FileSys.readdir dir of
adamc@381 296 NONE => Posix.FileSys.closedir dir
adamc@381 297 | SOME file =>
adamc@381 298 let
adamc@381 299 fun doit f =
adamc@381 300 f (OS.Path.joinDirFile {dir = dirname,
adamc@381 301 file = file},
adamc@382 302 OS.Path.mkAbsolute
adamc@382 303 {relativeTo = OS.FileSys.getDir (),
adamc@382 304 path = OS.Path.joinDirFile {dir = outDir,
adamc@382 305 file = OS.Path.joinBaseExt {base = file,
adamc@382 306 ext = SOME "html"}}})
adamc@382 307
adamc@382 308 fun highlight () =
adamc@382 309 doit (fn (src, html) =>
adamc@382 310 let
adamc@410 311 val dirty =
adamc@410 312 let
adamc@410 313 val srcSt = Posix.FileSys.stat src
adamc@410 314 val htmlSt = Posix.FileSys.stat html
adamc@410 315 in
adamc@410 316 Time.> (Posix.FileSys.ST.mtime srcSt,
adamc@410 317 Posix.FileSys.ST.mtime htmlSt)
adamc@410 318 end handle OS.SysErr _ => true
adamc@410 319
adamc@382 320 val cmd = "emacs --eval \"(progn "
adamc@382 321 ^ "(global-font-lock-mode t) "
adamc@382 322 ^ "(add-to-list 'load-path \\\""
ezyang@1739 323 ^ !Settings.configSitelisp
adamc@382 324 ^ "/\\\") "
adamc@382 325 ^ "(load \\\"urweb-mode-startup\\\") "
adamc@382 326 ^ "(urweb-mode) "
adamc@382 327 ^ "(find-file \\\""
adamc@382 328 ^ src
adamc@382 329 ^ "\\\") "
adamc@382 330 ^ "(switch-to-buffer (htmlize-buffer)) "
adamc@382 331 ^ "(write-file \\\""
adamc@382 332 ^ html
adamc@382 333 ^ "\\\") "
adamc@382 334 ^ "(kill-emacs))\""
adamc@382 335 in
adamc@410 336 if dirty then
adamc@410 337 (print (">>> " ^ cmd ^ "\n");
adamc@410 338 ignore (OS.Process.system cmd))
adamc@410 339 else
adamc@410 340 ()
adamc@382 341 end)
adam@1301 342
adam@1301 343 val highlight = fn () => if !noEmacs then () else highlight ()
adamc@381 344 in
adamc@384 345 if OS.Path.base file = "demo" then
adamc@384 346 ()
adamc@384 347 else case OS.Path.ext file of
adamc@384 348 SOME "urp" =>
adamc@384 349 doit (fn (src, html) =>
adamc@384 350 let
adamc@384 351 val inf = TextIO.openIn src
adamc@384 352 val out = TextIO.openOut html
adamc@381 353
adamc@384 354 fun loop () =
adamc@384 355 case TextIO.inputLine inf of
adamc@384 356 NONE => ()
adamc@384 357 | SOME line => (TextIO.output (out, line);
adamc@384 358 loop ())
adamc@384 359 in
adamc@384 360 TextIO.output (out, "<html><body>\n\n<pre>");
adamc@384 361 loop ();
adamc@384 362 TextIO.output (out, "</pre>\n\n</body></html>");
adamc@381 363
adamc@384 364 TextIO.closeIn inf;
adamc@384 365 TextIO.closeOut out
adamc@384 366 end)
adamc@384 367 | SOME "urs" => highlight ()
adamc@384 368 | SOME "ur" => highlight ()
adamc@384 369 | _ => ();
adamc@381 370 loop ()
adamc@381 371 end
adamc@381 372 in
adamc@381 373 loop ()
adamc@381 374 end
adamc@380 375 in
adamc@384 376 case readIndex () of
adamc@384 377 NONE => raise Fail "No demo applications!"
adamc@384 378 | SOME combined =>
adamc@384 379 let
adamc@384 380 val () = (TextIO.output (urOut, "</body></xml>\n");
adamc@384 381 TextIO.closeOut urOut)
adamc@384 382
adamc@384 383 val fname = OS.Path.joinDirFile {dir = dirname,
adamc@384 384 file = "demo.urp"}
adamc@384 385 val outf = TextIO.openOut fname
adamc@776 386
adamc@776 387 fun filters kind =
adamc@776 388 app (fn rule : Settings.rule =>
adamc@776 389 (TextIO.output (outf, case #action rule of
adamc@776 390 Settings.Allow => "allow"
adamc@776 391 | Settings.Deny => "deny");
adamc@776 392 TextIO.output (outf, " ");
adamc@776 393 TextIO.output (outf, kind);
adamc@776 394 TextIO.output (outf, " ");
adamc@776 395 TextIO.output (outf, #pattern rule);
adamc@776 396 case #kind rule of
adamc@776 397 Settings.Exact => ()
adamc@776 398 | Settings.Prefix => TextIO.output (outf, "*");
adamc@776 399 TextIO.output (outf, "\n")))
adamc@384 400 in
adamc@384 401 Option.app (fn db => (TextIO.output (outf, "database ");
adamc@384 402 TextIO.output (outf, db);
adamc@384 403 TextIO.output (outf, "\n")))
adamc@384 404 (#database combined);
adamc@384 405 TextIO.output (outf, "sql demo.sql\n");
adamc@385 406 TextIO.output (outf, "prefix ");
adamc@385 407 TextIO.output (outf, prefix);
adamc@385 408 TextIO.output (outf, "\n");
adamc@774 409 app (fn rule =>
adamc@774 410 (TextIO.output (outf, "rewrite ");
adamc@774 411 TextIO.output (outf, case #pkind rule of
adamc@774 412 Settings.Any => "any"
adamc@774 413 | Settings.Url => "url"
adamc@774 414 | Settings.Table => "table"
adamc@774 415 | Settings.Sequence => "sequence"
adamc@774 416 | Settings.View => "view"
adamc@774 417 | Settings.Relation => "relation"
adamc@774 418 | Settings.Cookie => "cookie"
adamc@774 419 | Settings.Style => "style");
adamc@774 420 TextIO.output (outf, " ");
adamc@774 421 TextIO.output (outf, #from rule);
adamc@774 422 case #kind rule of
adamc@774 423 Settings.Exact => ()
adamc@774 424 | Settings.Prefix => TextIO.output (outf, "*");
adamc@774 425 TextIO.output (outf, " ");
adamc@774 426 TextIO.output (outf, #to rule);
adamc@774 427 TextIO.output (outf, "\n"))) (#rewrites combined);
adamc@776 428 filters "url" (#filterUrl combined);
adamc@776 429 filters "mime" (#filterMime combined);
adam@1614 430 app (fn path =>
adam@1614 431 (TextIO.output (outf, "safeGet ");
adam@1614 432 TextIO.output (outf, path);
adam@1614 433 TextIO.output (outf, "\n"))) (#safeGets combined);
adamc@384 434 TextIO.output (outf, "\n");
adamc@384 435
adamc@384 436 app (fn s =>
adamc@384 437 let
adamc@384 438 val s = OS.Path.mkAbsolute {relativeTo = OS.FileSys.getDir (),
adamc@384 439 path = s}
adamc@384 440 in
adamc@384 441 TextIO.output (outf, s);
adamc@384 442 TextIO.output (outf, "\n")
adamc@384 443 end)
adamc@384 444 (#sources combined);
adamc@384 445 TextIO.output (outf, "\n");
adamc@384 446 TextIO.output (outf, "demo\n");
adamc@384 447
adamc@384 448 TextIO.closeOut outf;
adamc@384 449
adamc@1079 450 let
adamc@1079 451 val b = Compiler.compile (OS.Path.base fname)
adamc@1079 452 in
adamc@1079 453 TextIO.output (demosOut, "\n</body></html>\n");
adamc@1079 454 TextIO.closeOut demosOut;
adamc@1079 455 if b then
adamc@1079 456 prettyPrint ()
adamc@1079 457 else
adamc@1079 458 ();
adamc@1079 459 b
adamc@1079 460 end
adamc@1079 461 end
adamc@380 462 end
adamc@380 463
adamc@1079 464 fun make args = if make' args then
adamc@1079 465 ()
adamc@1079 466 else
adamc@1079 467 OS.Process.exit OS.Process.failure
adamc@1079 468
adamc@380 469 end