// REFERENCE: System.Web using Nemerle.Collections; using Nemerle.Utility; using Nemerle.IO; using Nemerle.Text; module NSWiki { mutable post : Hashtable [string, string]; mutable get : Hashtable [string, string]; wiki_url : string = "http://localhost/svn/nswiki/"; working_dir : string = "/shm/nswiki"; this_url : string = "/cgi-bin/nswiki"; /** * Splits a string given a separator character. Checks if the split was * unique -- or, if the separator occured exactly once in the original * string. */ split_unique_at (separator : char, str : string) : string * string * bool { def pos = str.IndexOf (separator); def (l, r) = if (pos != -1) (str.Substring (0, pos), str.Substring (pos + 1)) else (str, ""); def validity = l.IndexOf (separator) == -1 && r.IndexOf (separator) == -1; (l, r, validity) } /** * Parses a string of form: * * param_1=val_1¶m_2=val_2&...¶m_k=val_k * * Checks the validity of such a request and throws exception in * case of error. */ parse_get_params (get_parms : string) : list [string * string] { def decode (str : string) { System.Web.HttpUtility.UrlDecode (str, System.Text.Encoding.UTF8) }; NArray.Fold (get_parms.Split (array ['&']), [], fun (parm, acc) { def (param, value, split_was_unique) = split_unique_at ('=', parm); unless (split_was_unique) throw System.ArgumentException ("invalid query string"); def param = decode (param); def value = decode (value); if (param.Length == 0) acc else (param, value) :: acc }) } /** * Retrieves the post data */ parse_post_data () : list [string * string] { def buf = array (500000); def count = System.Console.In.ReadBlock (buf, 0, buf.Length); parse_get_params (System.String (buf, 0, count)); } hashtable_of_assoc_list (l : list [string * string]) : Hashtable [string, string] { def ht = Hashtable (); List.Iter (l, ht.Set); ht } getenv (name : string) : string { def ret = System.Environment.GetEnvironmentVariable (name); if (ret == null) "" else ret } get_with_default (ht : Hashtable [string, string], key : string) : string { match (ht.Get (key)) { | Some (r) => r | None => "" } } display_headers () : void { printf ("Content-type: text/html; encoding=utf-8\n\n"); printf (@" [html] [head] [title]Wiki [body]"); } display_footers () : void { printf ("\n"); } quote (s : string) : string { s.Replace ("&", "&") .Replace ("<", "<") .Replace (">", ">") } execute_command (filename : string, args : list [string]) : string * string { def p = System.Diagnostics.Process (); def si = p.StartInfo; si.RedirectStandardError = true; si.RedirectStandardInput = true; si.RedirectStandardOutput = true; def args = List.Map (args, fun (s : string) { s.Replace (" ", "\\ ") .Replace ("\"", "\\\"") .Replace ("\'", "\\\'") }); si.Arguments = NString.Concat (" ", args); si.FileName = filename; // print ($ "running $(si.FileName) $(si.Arguments)\n"); si.UseShellExecute = false; try { _ = p.Start (); // don't provide any input p.StandardInput.Close (); def stdout = p.StandardOutput.ReadToEnd (); def stderr = p.StandardError.ReadToEnd (); (stdout, stderr) } catch { | e is System.Exception => ("", e.ToString ()) } } inside_checkout['a] (rev : int, f : string -> 'a) : 'a { def page = get_page_name (); def dir = make_tmpdir (); try { def (o, e) = execute_command ("svn", ["checkout", "--non-interactive", wiki_url + page, dir + "svn"] + (if (rev == 0) [] else ["-r", rev.ToString ()])); when (!System.IO.Directory.Exists (dir + "svn/.svn")) fatal ("cannot checkout " + page + ": " + o + e); f (dir + "svn/") } finally { // clean (dir); } } fatal (msg : string) : void { throw System.Exception (msg) } get_page_name () : string { def page = get_with_default (get, "p"); def page = if (page == "") get_with_default (post, "p") else page; regexp match (page) { | "^[A-Za-z0-9_-]+$" => page | _ => fatal ($ "'$(page)' is an invalid page name"); "" } } page_exists (page : string) : bool { def (o, _) = execute_command ("svn", ["ls", "--non-interactive", wiki_url + page + "/content"]); o != "" } display_form () : void { def page = get_page_name (); def (content, rev) = get_source (); def qcontent = quote (content); def allowed = NString.Concat (", ", List.Map (allowed_markup, fun (s) { "[tt]<" + s + ">" })); display_headers (); print (@"


Following HTML tags are allowed in the content: $allowed. In addition an empty line outside <pre> starts a new paragraph. Links can be specified in the following form: <<http://some.url/foo/%ab Description of some url>> or <<WikiWord Description of the Wiki Word>>. The description is optional in both cases.
$(wikize (content)) "); display_footers (); } make_tmpdir () : string { def guid = System.Guid.NewGuid ().ToString (); _ = System.IO.Directory.CreateDirectory (guid); guid + "/" } clean (dir : string) : void { try { System.IO.Directory.Delete (dir, true) } catch { _ => () } } get_source () : string * int { def page = get_page_name (); // TODO: loggin? def new_msg = "New wiki term added."; _ = execute_command ("svn", ["mkdir", "--non-interactive", "-m", new_msg, wiki_url + page]); inside_checkout (0, fun (fn) { unless (System.IO.File.Exists (fn + "content")) { using (f = System.IO.File.Create (fn + "content")) (); def (o, e) = execute_command ("svn", ["add", "--non-interactive", fn + "content"]); when (e != "") fatal ($ "failed to svn add empty $page: $o $e"); def (o, e) = execute_command ("svn", ["commit", "--non-interactive", "-m", new_msg, fn + "content"]); // there is race possible here, if somebody adds this page, but we // don't care, user will just get an error message, and no editing // is lost when (e != "") fatal ($ "failed to svn commit empty $page: $o $e"); _ = execute_command ("svn", ["update", "--non-interactive", fn]); // just in case } def (o, e) = execute_command ("svn", ["info", fn]); def rev = regexp match (o.Replace ('\n', ' ')) { | @".*Revision: (?[0-9]+).*" => n | _ => fatal ($ "bad svn info format: $o [$e]"); 0 }; def content = using (f = System.IO.File.OpenText (fn + "content")) f.ReadToEnd (); (content, rev) }) } find_char (s : string, start : int, ch : char) : int { def loop (i) { if (i >= s.Length) -1 else if (s [i] == '\n' || s [i] == ch) i else loop (i + 1) } loop (start) } allowed_markup : list [string] = ["h2", "h3", "h4", "h5", "b", "i", "em", "ul", "li", "ol", "br", "hr", "pre", "tt"]; allowed_markup_ht : Hashtable [string, object] = Hashtable (); process_link (buf : System.Text.StringBuilder, tag : string) : void { def tag = tag.Substring (1); def (link, desc) = regexp match (tag) { | "^(?[a-z]+://[^ ]+) (?.*)" => (link, desc) | "^(?[a-z]+://[^ ]+)$" => (link, link) | "^(?[A-Za-z0-9_-]+) (?.*)$" => (link, desc) | "^(?[A-Za-z0-9_-]+)$" => (link, link) | _ => ("", "") } if (link == "") _ = buf.Append ("<<") .Append (tag) .Append (">>"); else _ = buf.Append ("") .Append (desc) .Append (""); } wikize (s : string) : string { // empty line = [p], but not in [pre] // <[link description]>, <> def buf = System.Text.StringBuilder (); mutable in_pre = false; // TODO mutable made_para = true; mutable new_line = true; for (mutable i = 0; i < s.Length; ++i) { def ch = s [i]; match (ch) { | ' ' when new_line | '\r' when new_line | '\t' when new_line => _ = buf.Append (ch); | '\n' when new_line => _ = buf.Append (ch); when (!made_para && !in_pre) _ = buf.Append ("[p]"); made_para = true; | '\n' => _ = buf.Append (ch); made_para = false; new_line = true; | '<' => new_line = false; def idx = find_char (s, i, '>'); if (idx < 0) { _ = buf.Append ("<"); } else { def tag = s.Substring (i + 1, idx - i - 1); i = idx; // note ++i in the loop // check if we are link if (tag.Length > 0 && tag [0] == '<') { // skip final >>, if present when (i + 1 < s.Length && s [i + 1] == '>') ++i; process_link (buf, tag); } else { if (allowed_markup_ht.Contains (tag.Trim ('/'))) _ = buf.Append ('<') .Append (tag) .Append ('>'); else // make it clear, something is wrong here _ = buf.Append ("<") .Append (tag) .Append (">"); } } | _ => _ = buf.Append (ch); new_line = false; } } buf.ToString () } commit_form () : void { def page = get_page_name (); def msg = "Wiki page edit."; _ = inside_checkout (int.Parse (get_with_default (post, "rev")), fun (fn) { using (f = System.IO.File.CreateText (fn + "content")) f.Write (get_with_default (post, "text")); // merge any changes _ = execute_command ("svn", ["update", "--non-interactive", fn]); // just commit conflicts _ = execute_command ("svn", ["resolved", fn + "content"]); def (o, e) = execute_command ("svn", ["commit", "--non-interactive", "-m", msg, fn + "content"]); when (e != "") fatal ($ "cannot svn commit edited page $page: $o $e"); def f = System.IO.File.OpenText (fn + "content"); display_headers (); print (wikize (f.ReadToEnd ())); display_footers (); null }) } public Main () : void { foreach (s in allowed_markup) allowed_markup_ht.Add (s, null); try { System.IO.Directory.SetCurrentDirectory (working_dir); def qs = getenv ("QUERY_STRING"); get = hashtable_of_assoc_list (parse_get_params (qs)); post = hashtable_of_assoc_list (parse_post_data ()); if (get_with_default (post, "submit") != "") { commit_form (); } else { display_form (); }; } catch { e => printf ("Content-type: text/plain; encoding=iso-8859-1\n\n%s\n%s\n", e.ToString (), e.StackTrace) } } }