adamc@1151: (* Copyright (c) 2008-2010, Adam Chlipala
adamc@380: * All rights reserved.
adamc@380: *
adamc@380: * Redistribution and use in source and binary forms, with or without
adamc@380: * modification, are permitted provided that the following conditions are met:
adamc@380: *
adamc@380: * - Redistributions of source code must retain the above copyright notice,
adamc@380: * this list of conditions and the following disclaimer.
adamc@380: * - Redistributions in binary form must reproduce the above copyright notice,
adamc@380: * this list of conditions and the following disclaimer in the documentation
adamc@380: * and/or other materials provided with the distribution.
adamc@380: * - The names of contributors may not be used to endorse or promote products
adamc@380: * derived from this software without specific prior written permission.
adamc@380: *
adamc@380: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
adamc@380: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
adamc@380: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
adamc@380: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
adamc@380: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
adamc@380: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
adamc@380: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
adamc@380: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
adamc@380: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
adamc@380: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
adamc@380: * POSSIBILITY OF SUCH DAMAGE.
adamc@380: *)
adamc@380:
adamc@380: structure Demo :> DEMO = struct
adamc@380:
adam@1301: val noEmacs = ref false
adam@1301:
adamc@1079: fun make' {prefix, dirname, guided} =
adamc@380: let
adamc@380: val prose = OS.Path.joinDirFile {dir = dirname,
adamc@380: file = "prose"}
adamc@380: val inf = TextIO.openIn prose
adamc@380:
adamc@380: val outDir = OS.Path.concat (dirname, "out")
adamc@380:
adamc@380: val () = if OS.FileSys.access (outDir, []) then
adamc@380: ()
adamc@380: else
adamc@380: OS.FileSys.mkDir outDir
adamc@380:
adamc@380: val fname = OS.Path.joinDirFile {dir = outDir,
adamc@380: file = "index.html"}
adamc@380:
adamc@380: val out = TextIO.openOut fname
adamc@773: val () = (TextIO.output (out, "
\n");
adamc@380: TextIO.closeOut out)
adamc@380:
adamc@380: val fname = OS.Path.joinDirFile {dir = outDir,
adamc@380: file = "demos.html"}
adamc@380:
adamc@380: val demosOut = TextIO.openOut fname
adamc@381: val () = (TextIO.output (demosOut, "\n\n");
adamc@380: TextIO.output (demosOut, " Intro\n\n"))
adamc@380:
adamc@384: val fname = OS.Path.joinDirFile {dir = dirname,
adamc@384: file = "demo.urs"}
adamc@384: val ursOut = TextIO.openOut fname
adamc@384: val () = (TextIO.output (ursOut, "val main : unit -> transaction page\n");
adamc@384: TextIO.closeOut ursOut)
adamc@384:
adamc@384: val fname = OS.Path.joinDirFile {dir = dirname,
adamc@384: file = "demo.ur"}
adamc@384: val urOut = TextIO.openOut fname
adamc@384: val () = TextIO.output (urOut, "fun main () = return \n")
adamc@384:
adamc@380: fun mergeWith f (o1, o2) =
adamc@380: case (o1, o2) of
adamc@380: (NONE, _) => o2
adamc@380: | (_, NONE) => o1
adamc@380: | (SOME v1, SOME v2) => SOME (f (v1, v2))
adamc@380:
adamc@380: fun combiner (combined : Compiler.job, urp : Compiler.job) = {
adamc@385: prefix = prefix,
adamc@380: database = mergeWith (fn (v1, v2) =>
adamc@380: if v1 = v2 then
adamc@380: v1
adamc@380: else
adamc@380: raise Fail "Different demos want to use different database strings")
adamc@380: (#database combined, #database urp),
adamc@380: sources = foldl (fn (file, files) =>
adamc@380: if List.exists (fn x => x = file) files then
adamc@380: files
adamc@380: else
adamc@380: files @ [file])
adamc@380: (#sources combined) (#sources urp),
adamc@891: exe = case Settings.getExe () of
adamc@891: NONE => OS.Path.joinDirFile {dir = dirname,
adamc@891: file = "demo.exe"}
adamc@891: | SOME s => s,
adamc@891: sql = SOME (case Settings.getSql () of
adamc@891: NONE => OS.Path.joinDirFile {dir = dirname,
adamc@891: file = "demo.sql"}
adamc@891: | SOME s => s),
adamc@863: debug = Settings.getDebug (),
adamc@673: timeout = Int.max (#timeout combined, #timeout urp),
adamc@764: profile = false,
adamc@764: ffi = [],
adamc@764: link = [],
adam@1725: linker = NONE,
adamc@765: headers = [],
adamc@766: scripts = [],
adamc@765: clientToServer = [],
adamc@765: effectful = [],
adamc@1171: benignEffectful = [],
adamc@765: clientOnly = [],
adamc@765: serverOnly = [],
adamc@768: jsFuncs = [],
adamc@774: rewrites = #rewrites combined @ #rewrites urp,
adamc@769: filterUrl = #filterUrl combined @ #filterUrl urp,
adamc@866: filterMime = #filterMime combined @ #filterMime urp,
adam@1465: filterRequest = #filterRequest combined @ #filterRequest urp,
adam@1465: filterResponse = #filterResponse combined @ #filterResponse urp,
adamc@866: protocol = mergeWith #2 (#protocol combined, #protocol urp),
adamc@1164: dbms = mergeWith #2 (#dbms combined, #dbms urp),
adamc@1183: sigFile = mergeWith #2 (#sigFile combined, #sigFile urp),
adam@1614: safeGets = #safeGets combined @ #safeGets urp,
adam@1332: onError = NONE,
adam@1332: minHeap = 0
adamc@380: }
adamc@380:
adamc@380: val parse = Compiler.run (Compiler.transform Compiler.parseUrp "Demo parseUrp")
adamc@380:
adamc@380: fun capitalize "" = ""
adamc@380: | capitalize s = str (Char.toUpper (String.sub (s, 0)))
adamc@380: ^ String.extract (s, 1, NONE)
adamc@380:
adamc@380: fun startUrp urp =
adamc@380: let
adamc@380: val base = OS.Path.base urp
adamc@380: val name = capitalize base
adamc@380:
adamc@380: val () = (TextIO.output (demosOut, " ");
adamc@380: TextIO.output (demosOut, name);
adamc@380: TextIO.output (demosOut, "\n"))
adamc@380:
adamc@384: val () = (TextIO.output (urOut, " ");
adamc@384: TextIO.output (urOut, name);
adamc@384: TextIO.output (urOut, "\n"))
adamc@384:
adamc@380: val urp_file = OS.Path.joinDirFile {dir = dirname,
adamc@380: file = urp}
adamc@380:
adamc@380: val out = OS.Path.joinBaseExt {base = base,
adamc@380: ext = SOME "html"}
adamc@380: val out = OS.Path.joinDirFile {dir = outDir,
adamc@380: file = out}
adamc@380: val out = TextIO.openOut out
adamc@380:
adamc@501: val () = (TextIO.output (out, "\n");
adamc@380: TextIO.closeOut out)
adamc@380: val () = TextIO.closeOut out
adamc@380:
adamc@380: val out = OS.Path.joinBaseExt {base = base,
adamc@380: ext = SOME "desc"}
adamc@380: val out = OS.Path.joinBaseExt {base = out,
adamc@380: ext = SOME "html"}
adamc@380: val out = TextIO.openOut (OS.Path.joinDirFile {dir = outDir,
adamc@380: file = out})
adamc@380: in
adamc@380: case parse (OS.Path.base urp_file) of
adamc@380: NONE => raise Fail ("Can't parse " ^ urp_file)
adamc@380: | SOME urpData =>
adamc@380: (TextIO.output (out, "\n");
adamc@380: TextIO.output (out, name);
adamc@380: TextIO.output (out, "\n\n\n");
adamc@380: TextIO.output (out, name);
adamc@380: TextIO.output (out, "
\n\n[ Application");
adamc@382: TextIO.output (out, " | ");
adamc@380: TextIO.output (out, urp);
adamc@380: TextIO.output (out, "");
adamc@380: app (fn file =>
adamc@380: let
adamc@380: fun ifEx s =
adamc@380: let
adamc@380: val src = OS.Path.joinBaseExt {base = file,
adamc@380: ext = SOME s}
adamc@380: val src' = OS.Path.file src
adamc@380: in
adamc@1151: if String.isPrefix (OS.Path.mkAbsolute {path = dirname,
adamc@1151: relativeTo = OS.FileSys.getDir ()}) src
adamc@943: andalso OS.FileSys.access (src, []) then
adamc@380: (TextIO.output (out, " | ");
adamc@380: TextIO.output (out, src');
adamc@380: TextIO.output (out, ""))
adamc@380: else
adamc@1156: ()
adamc@380: end
adamc@380: in
adamc@380: ifEx "urs";
adamc@380: ifEx "ur"
adamc@380: end) (#sources urpData);
adamc@380: TextIO.output (out, " ]\n\n");
adamc@380:
adamc@380: (urpData, out))
adamc@380: end
adamc@380:
adamc@380: fun endUrp out =
adamc@380: (TextIO.output (out, "\n\n");
adamc@380: TextIO.closeOut out)
adamc@380:
adamc@380: fun readUrp (combined, out) =
adamc@380: let
adamc@380: fun finished () = endUrp out
adamc@380:
adamc@380: fun readUrp' () =
adamc@380: case TextIO.inputLine inf of
adamc@384: NONE => (finished ();
adamc@384: combined)
adamc@380: | SOME line =>
adamc@380: if String.isSuffix ".urp\n" line then
adamc@380: let
adamc@380: val urp = String.substring (line, 0, size line - 1)
adamc@380: val (urpData, out) = startUrp urp
adamc@380: in
adamc@380: finished ();
adamc@380:
adamc@380: readUrp (combiner (combined, urpData),
adamc@380: out)
adamc@380: end
adamc@380: else
adamc@380: (TextIO.output (out, line);
adamc@380: readUrp' ())
adamc@380: in
adamc@380: readUrp' ()
adamc@380: end
adamc@380:
adamc@380: val indexFile = OS.Path.joinDirFile {dir = outDir,
adamc@380: file = "intro.html"}
adamc@380:
adamc@380: val out = TextIO.openOut indexFile
adamc@380: val () = TextIO.output (out, "\nUr/Web Demo\n\n\n")
adamc@380:
adamc@380: fun readIndex () =
adamc@380: let
adamc@380: fun finished () = (TextIO.output (out, "\n\n");
adamc@380: TextIO.closeOut out)
adamc@380: in
adamc@380: case TextIO.inputLine inf of
adamc@384: NONE => (finished ();
adamc@384: NONE)
adamc@380: | SOME line =>
adamc@380: if String.isSuffix ".urp\n" line then
adamc@380: let
adamc@380: val urp = String.substring (line, 0, size line - 1)
adamc@380: val (urpData, out) = startUrp urp
adamc@380: in
adamc@380: finished ();
adamc@380:
adamc@384: SOME (readUrp (urpData,
adamc@384: out))
adamc@380: end
adamc@380: else
adamc@380: (TextIO.output (out, line);
adamc@380: readIndex ())
adamc@380: end
adamc@381:
adamc@381: fun prettyPrint () =
adamc@381: let
adamc@381: val dir = Posix.FileSys.opendir dirname
adamc@381:
adamc@381: fun loop () =
adamc@381: case Posix.FileSys.readdir dir of
adamc@381: NONE => Posix.FileSys.closedir dir
adamc@381: | SOME file =>
adamc@381: let
adamc@381: fun doit f =
adamc@381: f (OS.Path.joinDirFile {dir = dirname,
adamc@381: file = file},
adamc@382: OS.Path.mkAbsolute
adamc@382: {relativeTo = OS.FileSys.getDir (),
adamc@382: path = OS.Path.joinDirFile {dir = outDir,
adamc@382: file = OS.Path.joinBaseExt {base = file,
adamc@382: ext = SOME "html"}}})
adamc@382:
adamc@382: fun highlight () =
adamc@382: doit (fn (src, html) =>
adamc@382: let
adamc@410: val dirty =
adamc@410: let
adamc@410: val srcSt = Posix.FileSys.stat src
adamc@410: val htmlSt = Posix.FileSys.stat html
adamc@410: in
adamc@410: Time.> (Posix.FileSys.ST.mtime srcSt,
adamc@410: Posix.FileSys.ST.mtime htmlSt)
adamc@410: end handle OS.SysErr _ => true
adamc@410:
adamc@382: val cmd = "emacs --eval \"(progn "
adamc@382: ^ "(global-font-lock-mode t) "
adamc@382: ^ "(add-to-list 'load-path \\\""
adamc@382: ^ Config.sitelisp
adamc@382: ^ "/\\\") "
adamc@382: ^ "(load \\\"urweb-mode-startup\\\") "
adamc@382: ^ "(urweb-mode) "
adamc@382: ^ "(find-file \\\""
adamc@382: ^ src
adamc@382: ^ "\\\") "
adamc@382: ^ "(switch-to-buffer (htmlize-buffer)) "
adamc@382: ^ "(write-file \\\""
adamc@382: ^ html
adamc@382: ^ "\\\") "
adamc@382: ^ "(kill-emacs))\""
adamc@382: in
adamc@410: if dirty then
adamc@410: (print (">>> " ^ cmd ^ "\n");
adamc@410: ignore (OS.Process.system cmd))
adamc@410: else
adamc@410: ()
adamc@382: end)
adam@1301:
adam@1301: val highlight = fn () => if !noEmacs then () else highlight ()
adamc@381: in
adamc@384: if OS.Path.base file = "demo" then
adamc@384: ()
adamc@384: else case OS.Path.ext file of
adamc@384: SOME "urp" =>
adamc@384: doit (fn (src, html) =>
adamc@384: let
adamc@384: val inf = TextIO.openIn src
adamc@384: val out = TextIO.openOut html
adamc@381:
adamc@384: fun loop () =
adamc@384: case TextIO.inputLine inf of
adamc@384: NONE => ()
adamc@384: | SOME line => (TextIO.output (out, line);
adamc@384: loop ())
adamc@384: in
adamc@384: TextIO.output (out, "\n\n");
adamc@384: loop ();
adamc@384: TextIO.output (out, "
\n\n");
adamc@381:
adamc@384: TextIO.closeIn inf;
adamc@384: TextIO.closeOut out
adamc@384: end)
adamc@384: | SOME "urs" => highlight ()
adamc@384: | SOME "ur" => highlight ()
adamc@384: | _ => ();
adamc@381: loop ()
adamc@381: end
adamc@381: in
adamc@381: loop ()
adamc@381: end
adamc@380: in
adamc@384: case readIndex () of
adamc@384: NONE => raise Fail "No demo applications!"
adamc@384: | SOME combined =>
adamc@384: let
adamc@384: val () = (TextIO.output (urOut, "\n");
adamc@384: TextIO.closeOut urOut)
adamc@384:
adamc@384: val fname = OS.Path.joinDirFile {dir = dirname,
adamc@384: file = "demo.urp"}
adamc@384: val outf = TextIO.openOut fname
adamc@776:
adamc@776: fun filters kind =
adamc@776: app (fn rule : Settings.rule =>
adamc@776: (TextIO.output (outf, case #action rule of
adamc@776: Settings.Allow => "allow"
adamc@776: | Settings.Deny => "deny");
adamc@776: TextIO.output (outf, " ");
adamc@776: TextIO.output (outf, kind);
adamc@776: TextIO.output (outf, " ");
adamc@776: TextIO.output (outf, #pattern rule);
adamc@776: case #kind rule of
adamc@776: Settings.Exact => ()
adamc@776: | Settings.Prefix => TextIO.output (outf, "*");
adamc@776: TextIO.output (outf, "\n")))
adamc@384: in
adamc@384: Option.app (fn db => (TextIO.output (outf, "database ");
adamc@384: TextIO.output (outf, db);
adamc@384: TextIO.output (outf, "\n")))
adamc@384: (#database combined);
adamc@384: TextIO.output (outf, "sql demo.sql\n");
adamc@385: TextIO.output (outf, "prefix ");
adamc@385: TextIO.output (outf, prefix);
adamc@385: TextIO.output (outf, "\n");
adamc@774: app (fn rule =>
adamc@774: (TextIO.output (outf, "rewrite ");
adamc@774: TextIO.output (outf, case #pkind rule of
adamc@774: Settings.Any => "any"
adamc@774: | Settings.Url => "url"
adamc@774: | Settings.Table => "table"
adamc@774: | Settings.Sequence => "sequence"
adamc@774: | Settings.View => "view"
adamc@774: | Settings.Relation => "relation"
adamc@774: | Settings.Cookie => "cookie"
adamc@774: | Settings.Style => "style");
adamc@774: TextIO.output (outf, " ");
adamc@774: TextIO.output (outf, #from rule);
adamc@774: case #kind rule of
adamc@774: Settings.Exact => ()
adamc@774: | Settings.Prefix => TextIO.output (outf, "*");
adamc@774: TextIO.output (outf, " ");
adamc@774: TextIO.output (outf, #to rule);
adamc@774: TextIO.output (outf, "\n"))) (#rewrites combined);
adamc@776: filters "url" (#filterUrl combined);
adamc@776: filters "mime" (#filterMime combined);
adam@1614: app (fn path =>
adam@1614: (TextIO.output (outf, "safeGet ");
adam@1614: TextIO.output (outf, path);
adam@1614: TextIO.output (outf, "\n"))) (#safeGets combined);
adamc@384: TextIO.output (outf, "\n");
adamc@384:
adamc@384: app (fn s =>
adamc@384: let
adamc@384: val s = OS.Path.mkAbsolute {relativeTo = OS.FileSys.getDir (),
adamc@384: path = s}
adamc@384: in
adamc@384: TextIO.output (outf, s);
adamc@384: TextIO.output (outf, "\n")
adamc@384: end)
adamc@384: (#sources combined);
adamc@384: TextIO.output (outf, "\n");
adamc@384: TextIO.output (outf, "demo\n");
adamc@384:
adamc@384: TextIO.closeOut outf;
adamc@384:
adamc@1079: let
adamc@1079: val b = Compiler.compile (OS.Path.base fname)
adamc@1079: in
adamc@1079: TextIO.output (demosOut, "\n\n");
adamc@1079: TextIO.closeOut demosOut;
adamc@1079: if b then
adamc@1079: prettyPrint ()
adamc@1079: else
adamc@1079: ();
adamc@1079: b
adamc@1079: end
adamc@1079: end
adamc@380: end
adamc@380:
adamc@1079: fun make args = if make' args then
adamc@1079: ()
adamc@1079: else
adamc@1079: OS.Process.exit OS.Process.failure
adamc@1079:
adamc@380: end