adam@1493: (* Copyright (c) 2011, Adam Chlipala adam@1493: * All rights reserved. adam@1493: * adam@1493: * Redistribution and use in source and binary forms, with or without adam@1493: * modification, are permitted provided that the following conditions are met: adam@1493: * adam@1493: * - Redistributions of source code must retain the above copyright notice, adam@1493: * this list of conditions and the following disclaimer. adam@1493: * - Redistributions in binary form must reproduce the above copyright notice, adam@1493: * this list of conditions and the following disclaimer in the documentation adam@1493: * and/or other materials provided with the distribution. adam@1493: * - The names of contributors may not be used to endorse or promote products adam@1493: * derived from this software without specific prior written permission. adam@1493: * adam@1493: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" adam@1493: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE adam@1493: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE adam@1493: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE adam@1493: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR adam@1493: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF adam@1493: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS adam@1493: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN adam@1493: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) adam@1493: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE adam@1493: * POSSIBILITY OF SUCH DAMAGE. adam@1493: *) adam@1493: adam@1493: structure Tutorial :> TUTORIAL = struct adam@1493: adam@1494: fun readAll inf = adam@1493: let adam@1493: fun loop acc = adam@1493: case TextIO.inputLine inf of adam@1493: NONE => Substring.full (String.concat (rev acc)) adam@1493: | SOME line => loop (line :: acc) adam@1493: in adam@1493: loop [] adam@1493: before TextIO.closeIn inf adam@1493: end adam@1493: adam@1494: val readAllFile = readAll o TextIO.openIn adam@1494: adam@1494: fun fixupFile (fname, title) = adam@1494: let adam@1494: val source = readAllFile "/tmp/final.html" adam@1494: val outf = TextIO.openOut (OS.Path.mkAbsolute {relativeTo = OS.FileSys.getDir (), adam@1494: path = OS.Path.joinBaseExt {base = OS.Path.base fname, ext = SOME "html"}}) adam@1494: adam@1494: val (befor, after) = Substring.position "" source adam@1494: adam@1494: fun loop source = adam@1494: let adam@1494: val (befor, after) = Substring.position "<span class=\"comment-delimiter\">(* </span><span class=\"comment\">" source adam@1494: in adam@1494: if Substring.isEmpty after then adam@1494: TextIO.outputSubstr (outf, source) adam@1494: else adam@1494: let adam@1494: val (befor', after) = Substring.position " </span><span class=\"comment-delimiter\">*)</span>" adam@1494: (Substring.slice (after, 64, NONE)) adam@1494: in adam@1494: if Substring.isEmpty after then adam@1494: TextIO.outputSubstr (outf, source) adam@1494: else adam@1494: (TextIO.outputSubstr (outf, befor); adam@1494: TextIO.output (outf, "<div class=\"prose\">"); adam@1494: TextIO.outputSubstr (outf, befor'); adam@1494: TextIO.output (outf, "</div>"); adam@1494: loop (Substring.slice (after, 49, NONE))) adam@1494: end adam@1494: end adam@1494: in adam@1494: if Substring.isEmpty after then adam@1494: raise Fail ("Missing <title> for " ^ title) adam@1494: else adam@1494: (TextIO.outputSubstr (outf, befor); adam@1494: TextIO.output (outf, "<style type=\"text/css\">\n"); adam@1494: TextIO.output (outf, "<!--\n"); adam@1494: TextIO.output (outf, "\tdiv.prose {\n"); adam@1494: TextIO.output (outf, "\t\tfont-family: Arial;\n"); adam@1494: TextIO.output (outf, "\t\tbackground-color: #CCFFCC;\n"); adam@1494: TextIO.output (outf, "\t\tborder-style: solid;\n"); adam@1494: TextIO.output (outf, "\t\tpadding: 5px;\n"); adam@1494: TextIO.output (outf, "\t\tfont-size: larger;\n"); adam@1494: TextIO.output (outf, "\t}\n"); adam@1494: TextIO.output (outf, "-->\n"); adam@1494: TextIO.output (outf, "</style>\n"); adam@1494: TextIO.output (outf, "<title>"); adam@1494: TextIO.output (outf, title); adam@1494: let adam@1494: val (befor, after) = Substring.position "" after adam@1494: in adam@1494: if Substring.isEmpty after then adam@1494: raise Fail ("Missing for " ^ title) adam@1494: else adam@1494: let adam@1494: val (befor, after) = Substring.position "" after adam@1494: in adam@1494: if Substring.isEmpty after then adam@1494: raise Fail ("Missing for " ^ title) adam@1494: else adam@1494: (TextIO.outputSubstr (outf, befor); adam@1494: TextIO.output (outf, "

"); adam@1494: TextIO.output (outf, title); adam@1494: TextIO.output (outf, "

"); adam@1494: loop (Substring.slice (after, 6, NONE))) adam@1494: end adam@1494: end; adam@1494: TextIO.closeOut outf) adam@1494: end adam@1493: adam@1493: fun doUr fname = adam@1493: let adam@1494: val inf = TextIO.openIn fname adam@1494: adam@1494: val title = case TextIO.inputLine inf of adam@1494: NONE => raise Fail ("No title comment at start of " ^ fname) adam@1494: | SOME title => title adam@1494: adam@1494: val title = String.substring (title, 3, size title - 7) adam@1494: adam@1493: val eval = TextIO.openOut "/tmp/eval.ur" adam@1493: val gen = TextIO.openOut "/tmp/gen.ur" adam@1493: adam@1493: fun untilEnd source = adam@1493: let adam@1493: val (befor, after) = Substring.position "(* end *)" source adam@1493: in adam@1493: if Substring.isEmpty after then adam@1493: (source, Substring.full "") adam@1493: else adam@1493: (befor, Substring.slice (after, 9, NONE)) adam@1493: end adam@1493: adam@1493: fun doDirectives (count, source) = adam@1493: let adam@1493: val safe = String.translate (fn #"<" => "<" adam@1493: | #"&" => "&" adam@1493: | #"{" => "{" adam@1493: | #"(" => "(" adam@1493: | #"\n" => "(*NL*)\n" adam@1493: | ch => str ch) o Substring.string adam@1493: adam@1493: val (befor, after) = Substring.position "(* begin " source adam@1493: adam@1493: fun default () = (TextIO.outputSubstr (eval, source); adam@1493: TextIO.output (gen, safe source)) adam@1493: in adam@1493: if Substring.isEmpty after then adam@1493: default () adam@1493: else adam@1493: let adam@1493: val (command, after) = Substring.splitl (not o Char.isSpace) (Substring.slice (after, 9, NONE)) adam@1493: in adam@1493: if Substring.isEmpty after then adam@1493: default () adam@1493: else adam@1493: let adam@1493: val (_, rest) = Substring.position "*)" after adam@1493: in adam@1493: if Substring.isEmpty rest then adam@1493: default () adam@1493: else adam@1493: let adam@1493: val (arg, source) = untilEnd (Substring.slice (rest, 3, NONE)) adam@1493: val () = (TextIO.outputSubstr (eval, befor); adam@1493: TextIO.output (gen, safe befor)) adam@1493: val (count, skip) = adam@1493: case Substring.string command of adam@1493: "hide" => (TextIO.outputSubstr (eval, arg); adam@1493: (count, true)) adam@1493: | "eval" => (TextIO.output (eval, "val _eval"); adam@1493: TextIO.output (eval, Int.toString count); adam@1493: TextIO.output (eval, " = "); adam@1493: TextIO.outputSubstr (eval, arg); adam@1493: TextIO.output (eval, "\n\n"); adam@1493: adam@1493: TextIO.output (gen, safe arg); adam@1493: TextIO.output (gen, "== {[_eval"); adam@1493: TextIO.output (gen, Int.toString count); adam@1493: TextIO.output (gen, "]}"); adam@1493: adam@1493: (count + 1, false)) adam@1493: | s => raise Fail ("Unknown tutorial directive: " ^ s) adam@1493: in adam@1493: doDirectives (count, if skip then adam@1493: #2 (Substring.splitl Char.isSpace source) adam@1493: else adam@1493: source) adam@1493: end adam@1493: end adam@1493: end adam@1493: end adam@1493: in adam@1494: doDirectives (0, readAll inf); adam@1493: TextIO.closeOut gen; adam@1493: adam@1493: TextIO.output (eval, "\n\nfun main () : transaction page =\nreturn "); adam@1494: TextIO.outputSubstr (eval, readAllFile "/tmp/gen.ur"); adam@1493: TextIO.output (eval, ""); adam@1493: TextIO.closeOut eval; adam@1493: adam@1493: if Compiler.compile "/tmp/eval" then adam@1493: let adam@1493: val proc = Unix.execute ("/bin/sh", ["-c", "/tmp/eval.exe /main"]) adam@1493: val inf = Unix.textInstreamOf proc adam@1494: val s = readAll inf adam@1493: val _ = Unix.reap proc adam@1493: adam@1493: val (befor, after) = Substring.position "" s adam@1493: in adam@1493: if Substring.isEmpty after then adam@1493: print ("Bad output for " ^ fname ^ "! [1]\n") adam@1493: else adam@1493: let adam@1493: val after = Substring.slice (after, 4, NONE) adam@1493: val (befor, after) = Substring.position "" after adam@1493: in adam@1493: if Substring.isEmpty after then adam@1493: print ("Bad output for " ^ fname ^ "! [2]\n") adam@1493: else adam@1493: let adam@1493: val outf = TextIO.openOut "/tmp/final.ur" adam@1493: adam@1493: fun eatNls source = adam@1493: let adam@1493: val (befor, after) = Substring.position "(*NL*)" source adam@1493: in adam@1493: if Substring.isEmpty after then adam@1493: TextIO.outputSubstr (outf, source) adam@1493: else adam@1493: (TextIO.outputSubstr (outf, befor); adam@1493: eatNls (Substring.slice (after, 6, NONE))) adam@1493: end adam@1493: adam@1493: val cmd = "emacs --eval \"(progn " adam@1493: ^ "(global-font-lock-mode t) " adam@1493: ^ "(add-to-list 'load-path \\\"" adam@1493: ^ Config.sitelisp adam@1493: ^ "/\\\") " adam@1493: ^ "(load \\\"urweb-mode-startup\\\") " adam@1493: ^ "(urweb-mode) " adam@1493: ^ "(find-file \\\"/tmp/final.ur\\\") " adam@1493: ^ "(switch-to-buffer (htmlize-buffer)) " adam@1494: ^ "(write-file \\\"/tmp/final.html\\\") " adam@1493: ^ "(kill-emacs))\"" adam@1493: in adam@1493: eatNls befor; adam@1493: TextIO.closeOut outf; adam@1494: ignore (OS.Process.system cmd); adam@1494: fixupFile (fname, title) adam@1493: end adam@1493: end adam@1493: end adam@1493: else adam@1493: () adam@1493: end adam@1493: adam@1493: fun make dirname = adam@1493: let adam@1493: val dir = OS.FileSys.openDir dirname adam@1493: adam@1493: fun doDir () = adam@1493: case OS.FileSys.readDir dir of adam@1493: NONE => OS.FileSys.closeDir dir adam@1493: | SOME fname => adam@1493: (if OS.Path.ext fname = SOME "ur" then adam@1493: doUr (OS.Path.joinDirFile {dir = dirname, file = fname}) adam@1493: else adam@1493: (); adam@1493: doDir ()) adam@1493: in adam@1493: Settings.setProtocol "static"; adam@1493: doDir () adam@1493: end adam@1493: adam@1493: end