(* Copyright (c) 2009 Gian Perrone * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - The names of contributors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. *) table user : { Id : int, Username : string, Password : string, DisplayName : string } PRIMARY KEY Id table blog : { Id : int, Title : string, Body : string, Created : time, Public : bool, Author : int} PRIMARY KEY Id, CONSTRAINT Author FOREIGN KEY Author REFERENCES user(Id) sequence commentS table comment : { Id : int, Parent : int, CommentBody : string, CommentCreated : time, AuthorName : string, Key : string } PRIMARY KEY Id, CONSTRAINT Parent FOREIGN KEY Parent REFERENCES blog(Id) cookie usersession : int * string style blogentry style blogentrytitle style blogentrydetail style blogentrybody style blogentryauthor style blogentrycomments style blogcontent style blogtitle style commentform style commentbutton style accountlinks style bodyedit style commentbox style loginbox val btitle = "Test Urblog Blog" datatype formatting = Italics of formatting | Bold of formatting | Image of string | Text of string | Para of list formatting fun parseMarkup s = let fun match s t = if strlen s < strlen t then False else (String.substring s {Start=0, Len=strlen t}) = t fun consume s t = if match s t then String.substring s {Start=strlen t, Len=(strlen s - strlen t)} else "(ERROR)" fun frst s = strsub s 0 fun rest s = String.substring s {Start = 1, Len=(strlen s) - 1} fun text s l = if s = "" || frst s = #"[" || frst s = #"\n" then l else text (rest s) (l ^ (show (frst s))) fun frag s = if s = "" then (Text "","") else if match s "[b]" then let val s' = consume s "[b]" val t = text s' "" val s'' = consume (consume s' t) "[/b]" in (Bold (Text t), s'') end else if match s "[i]" then let val s' = consume s "[i]" val t = text s' "" val s'' = consume (consume s' t) "[/i]" in (Italics (Text t), s'') end else if match s "[img]" then let val s' = consume s "[img]" val t = text s' "" val s'' = consume (consume s' t) "[/img]" in (Image t, s'') end else let val t = text s "" in (Text t, consume s t) end fun doc s l = if s = "" then (Para l) :: Nil else if match s "\n\n" then (Para l) :: doc (consume s "\n\n") Nil else let val (f,s') = frag s in doc s' (List.append l (f :: Nil)) end fun toXML l = case l of (Italics v) => {toXML v} | (Bold v) => {toXML v} | (Image v) => | (Text t) => {cdata t} | Para l =>

{List.mapX (fn x => {toXML x}) l}

in {List.mapX toXML (doc s Nil)} end structure Admin = Editor.Make( struct val tab = blog val title = "Blog Administration" val cols = {Title = Editor.string "Title", Body = {Nam = "Entry Body", Show = (fn b => {[if strlen b > 25 then substring b 0 25 else b]}...), Widget = (fn [nm :: Name] => ), WidgetPopulated = (fn [nm :: Name] b => {[b]} ), Parse = (fn s => readError s), Inject = _ }, Created = Editor.time "Entry Date", Public = Editor.bool "Public?", Author = {Nam = "", Show = (fn b => ), Widget = (fn [nm :: Name] => ), WidgetPopulated = (fn [nm :: Name] b => ), Parse = (fn s => readError s), Inject = _ }, } val page = fn t c => return {[t]} - {[btitle]}

{[btitle]}

{c}
val blogentrytitle = blogentrytitle val blogentry = blogentry end) val admin = Admin.editor fun counter id = r <- oneRow (SELECT COUNT( * ) AS N FROM comment WHERE comment.Parent = {[id]}); return r.N fun page t c = aLinks <- ifAuthed New Entry | Logout ; return {[t]} - {[btitle]}
{aLinks}
and account () = pg <- return

Account Settings

; pg' <- page "Account Settings" pg; return pg' and logout () = setCookie usersession (0, ""); main() and login r = re' <- oneOrNoRows(SELECT user.Id, user.Username, user.Password FROM user WHERE user.Username = {[r.U]} AND user.Password = {[r.P]}); case re' of None => error Invalid Login | Some re => setCookie usersession (re.User.Id, re.User.Password); main () and loginForm () = ifAuthed

Login

Username:

Password:

and handler r = id <- nextval commentS; dml (INSERT INTO comment (Id, Parent, AuthorName, CommentBody, CommentCreated, Key) VALUES ({[id]}, {[readError r.Parent]}, {[r.AuthorName]}, {[r.CommentBody]}, CURRENT_TIMESTAMP, "")); (detail (readError r.Parent)) and mkCommentForm id s =

Your Name:


Your Comment: