|
| 1 | +(page "App server" |
| 2 | +(import "docs/app0.html") |
| 3 | +(newtable "App server" |
| 4 | +(def asv "[port]" "Starts the application server." (faketest "(asv 8080)" "")) |
| 5 | +) |
| 6 | + |
| 7 | +(newtable "User management" |
| 8 | + |
| 9 | + |
| 10 | +(def get-user "req" "Gets the user id string associated with <code>req</code>. Returns <code>nil</code> if no associated user." (faketest "(get-user req)" "foo" "")) |
| 11 | + |
| 12 | +(def predicate admin "user" "Tests if <code>user</code> is an administrator; i.e. is in <code>admins*</code>." (tests (admin "foo"))) |
| 13 | + |
| 14 | +(def predicate goodname "str [min [max]]" "Tests that <code>str</code> is of the appropriate length and contains no bad characters." (tests (goodname "abc") (goodname "ab!"))) |
| 15 | + |
| 16 | +(def logout-user "user" "Logs out <code>user</code>. The user's entry is removed from <code>logins*</code>, <code>cookie->user*</code>, <code>user->cookie*</code>, and the updated <code>cookie->user*</code> is written to <code>cookfile*</code>." (tests (logout-user "foo"))) |
| 17 | + |
| 18 | +(def set-pw "user pw" "Creates (or updates) account with the name <code>user</code> and password <code>pw</code>. Saves <code>hpasswords*</code> in <code>hpwfile*</code>." (faketest "(set-pw \"foo\" \"bar\")" "")) |
| 19 | + |
| 20 | +(mac defopl "name parm [body]" "Version of <code>defop</code> to create handler that will redirect to login page if the user is not logged in." (faketest "(defopl foo req (prn \"Welcome!\"))" "")) |
| 21 | + |
| 22 | +(mac uform "user req after [body ...]" "Generates form that ensures it was submitted by <code>user</code> (by using <code>when-umatch</code>). <code>body</code> outputs the form body to stdout. After submission, the continuation code <code>after</code> is executed; <code>req</code> specifies the varible name in <code>after</code> to receive the request." |
| 23 | +(faketest "(uform user req (prn \"Result\") (prn \"The form\") (submit))" "")) |
| 24 | + |
| 25 | +(mac urform "user req after [body ...]" "Generates form with redirection target with guard that <code>user</code> submitted it. After submission, the continuation expression <code>after</code> is executed and must return the redirect string; <code>req</code> specifies the varible name in <code>after</code> to receive the request." (faketest "(urform user req \"newpage\" (prn \"Form\") (submit))" "")) |
| 26 | + |
| 27 | +(mac when-umatch "user req [body...]" "If <code>user</code> matches the user associated with <code>req</code>, executes <code>body</code>. Otherwise executes <code>mismatch-message</code>." |
| 28 | +(faketest "(defopl ul req (let user (get-user req) |
| 29 | + (when-umatch user req (prn \"You are \" user))))" "")) |
| 30 | + |
| 31 | +(mac when-umatch/r "user req [body ...]" "Test <code>user</code> for use with redirect. If <code>user</code> is the user associated with <code>req</code>, executes body. Otherwise returns \"mismatch\", to redirect to the mismatch page." |
| 32 | + (faketest "(when-umatch/r user req (logout-user user) \"example\")" "")) |
| 33 | + |
| 34 | +(mac ulink "user text [body ...]" "Outputs a HTML link with <code>text</code>. When clicked, the link will execute <code>body</code> if the user matches <code>user</code>. Similar to <code>onlink</code>, but with the user guard. Renamed from userlink in arc3." |
| 35 | + (faketest "(userlink user \"click here\" (prn \"Thanks for clicking\"))" "") |
| 36 | +) |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | +(def admin-page "user [msg]" "Generates the administrator page. This page allows new accounts to be created. The current admin login (<code>user</code>) is displayed at the top of the page, along with <code>msg</code>, if present." (faketest "(admin-page user \"Please administer...\")" "")) |
| 42 | + |
| 43 | + |
| 44 | +(def login-page "switch [msg [afterward]]" "Generates a login page. <code>switch</code> is <code>'register</code>, <code>'login</code>, or <code>'both</code>, allowing account creation, account login, or both operations respectively. The top of the page displays <code>msg</code>. After the page completes, the <code>afterward</code> continuation is executed (by default <code>hello-page</code>). <code>afterward</code> is either a function or a (function, redirect-string) pair. The function takes the user name and IP as arguments." |
| 45 | +(faketest "(defop mylogin req (login-page 'login \"Hello\" |
| 46 | + (fn (user ip) (prn \"Welcome \" user ip))))" "")) |
| 47 | + |
| 48 | + |
| 49 | + |
| 50 | +) |
| 51 | + |
| 52 | +(newtable "Typed and marked-up forms" |
| 53 | +(def vars-form "user fields f done [button [lasts]]" "Generates a form for <code>user</code>. <code>fields</code> is a list of <code>(type label value view modify question)</code> lists specifying the form. When submitted, <code>f</code> is executed on each field, with the arguments <code>label newval</code>. Then continuation function <code>done</code> is executed. If there is a modifiable field, a submit button is generated with label specified by <code>button</code>. The lifetime of the associated fnid can be specified with <code>lasts</code>.") |
| 54 | + |
| 55 | +(def md-from-form "str [nolinks]" "Converts <code>str</code> to markdown after escaping it. URLs will be converted to links unless <code>nolinks</code> is set. Used to generate markdown from form input." (tests (md-from-form "Hello *world* &"))) |
| 56 | + |
| 57 | +(def markdown "s [maxurl [nolinks]]" "Applies the markdown rules to <code>s</code> to generate HTML." (htmltests (show prn (markdown "Text\n\n Code\nhttp://arcfn.com, and *stuff*")))) |
| 58 | + |
| 59 | +(def unmarkdown "s" "Inverse of <code>markdown</code> to convert HTML to a marked-down string." (tests (unmarkdown "Text<p><pre><code> Code</code></pre>"))) |
| 60 | +(def paras "s" "Returns list of paragraph indices. New in arc3." (tests (paras "ab\n\ncde\n\nfgh"))) |
| 61 | + |
| 62 | +) |
| 63 | + |
| 64 | + |
| 65 | +(newtable "Variables" |
| 66 | +(var good-logins* "" "A queue of successful logins, holding lists of the timestamp, IP, and user id.") |
| 67 | + |
| 68 | +(var bad-logins* "" "A queue of unsuccessful logins, holding lists of the timestamp, IP, and user id.") |
| 69 | +(var hpasswords* "" "Table of passwords mapping from user to hash.") |
| 70 | +(var admins* "" "Admin stuff.") |
| 71 | +(var cookie->user* "" "Table mapping cookies to users.") |
| 72 | +(var user->cookie* "" "Table mapping users to cookies.") |
| 73 | +(var logins* "" "Table of logins mapping from user name to IP address.") |
| 74 | +(var hpwfile* "" "Password file, backs <code>hpasswords*</code>." (tests hpwfile*)) |
| 75 | +(var adminfile* "" "Admin file, backs <code>admins*</code>." (tests adminfile*)) |
| 76 | +(var cookfile* "" "Cookie file, backs <code>cookie->user*</code>." (tests cookfile*)) |
| 77 | +(var formwid* "" "Specifies width of form field." (tests formwid*)) |
| 78 | +(var bigformwid* "" "" (tests bigformwid*)) |
| 79 | +(var numwid* "" "" (tests numwid*)) |
| 80 | +(var formatdoc-url* "" "" (tests formatdoc-url*)) |
| 81 | +(var oidfile* "" "Openids file; apparently unused. New in arc3." (tests oidfile*)) |
| 82 | +(var dc-usernames* "" "Downcased usernames. New in arc3." (tests dc-usernames*)) |
| 83 | +(var months* "" "Month names. New in arc3." (tests months*)) |
| 84 | +(var month-names* "" "Table from month name to month number. New in arc3." (tests (month-names* "jan") (month-names* "February"))) |
| 85 | +) |
| 86 | + |
| 87 | +(newtable "Internals" |
| 88 | + |
| 89 | +(def load-userinfo "" "Initializes <code>hpasswords*</code>, <code>admins*</code>, and <code>cookie->user</code>." (faketest "(load-userinfo)" "")) |
| 90 | + |
| 91 | + |
| 92 | +(def mismatch-message "" "Prints an error message if the user doesn't match the cookie." (tests (mismatch-message))) |
| 93 | + |
| 94 | +(def admin-gate "user" "Gates access to admin-page. If <code>user</code> is an admin, displays <code>admin-page</code>, otherwise redirects to <code>login-page</code>." (faketest "(admin-gate \"myuserid\")" "t")) |
| 95 | + |
| 96 | +(def predicate user-exists "user" "Tests if <code>user</code> is not <code>nil</code> and present in <code>hpasswords*</code>." (faketest "(user-exists \"myuserid\")" "t")) |
| 97 | + |
| 98 | +(def cook-user "user" "Generates and saves a cookie for <code>user</code>. Returns the cookie id." (tests (cook-user "testuser"))) |
| 99 | + |
| 100 | +(def new-user-cookie "" "Generates a unique cookie id." (tests (new-user-cookie))) |
| 101 | + |
| 102 | + |
| 103 | +(def create-acct "user pw" "Creates a user account. Just a wrapper around <code>set-pw</code>." (tests (create-acct "foo" "secret"))) |
| 104 | + |
| 105 | +(def disable-acct "user" "Disables user account by logging user out and changing the password to a random string." (tests (disable-acct "badperson"))) |
| 106 | + |
| 107 | +(def hello-page "user ip" "Displays a simple page saying 'hello <i>user</i> at <i>ip</i>'.") |
| 108 | + |
| 109 | +(def prcookie "cook" "Prints a header field to update cookie <code>user</code> to the value <code>cook</code>." (htmltests (prcookie "myvalue"))) |
| 110 | + |
| 111 | +(def pwfields "[label]" "Generates HTML for username and password fields, and a submit button, labelled \"login\" by default." (htmltests (show pwfields))) |
| 112 | + |
| 113 | +(def good-login "user pw ip" "Tests if the user and password are valid according to <code>hpasswords*</code>. Returns <code>user</code> on success, and <code>nil</code> on failure. Updates <code>good-logins*</code> or <code>bad-logins</code> as appropriate." (tests (good-login "foo" "bar" "127.0.0.1"))) |
| 114 | + |
| 115 | +(def shash "str" "Hashes <code>str</code> to a sha1 digest using <code>openssl</code>." (tests (shash "foo"))) |
| 116 | + |
| 117 | +(def bad-newacct "user pw" "Tests if the new userid and password are bad (bad length, bad characters, or already in use). Returns an error message if the new account specification is bad, and <code>nil</code> if the information is okay." (tests (bad-newacct "foo" "x"))) |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | +(def varfield "typ id val" "Prints HTML for an input field of type <code>typ</code>, name <code>id</code>, and value <code>val</code>. <code>typ</code> is one of |
| 122 | +<code>bigtoks</code>, <code>date</code>, <code>doc</code>, <code>int</code>, <code>lines</code>, <code>mdtext</code>, <code>mdtext2</code>, <code>num</code>, <code>posint</code>, <code>string</code>, <code>string1</code>, <code>sym</code>, <code>syms</code>, <code>text</code>, <code>time</code>, <code>toks</code>, <code>url</code>, <code>users</code>. The type of field and the processing of val depend on <code>typ</code>." (htmltests (show varfield 'syms 'foo '(a b c)))) |
| 123 | + |
| 124 | +(def text-rows "text width [pad]" "Detemines how many rows to hold <code>text</code> based on <code>width</code> and padding." (tests (text-rows "abcde" 2))) |
| 125 | + |
| 126 | +(def needrows "text cols [pad]" "Determines how many rows are needed to hold <code>text</code>, based on the length of the text and the number of newlines." (tests (needrows "abcde" 2))) |
| 127 | + |
| 128 | +(def varline "type id val [liveurls]" "Prints <code>val</code> according to <code>type</code>. <code>id</code> is ignored. If <code>liveurls</code> is true, links will be made to URLs." (htmltests (varline 'yesno 'junk 1))) |
| 129 | + |
| 130 | +(def predicate text-type "type" "Tests if <code>type</code> is one of <code>string</code>, <code>string1</code>, <code>url</code>, <code>text</code>, <code>mdtext</code>, <code>mdtext2</code>." (tests (text-type 'string1))) |
| 131 | + |
| 132 | +(def readvar "type str [fail]" "Reads variable of <code>type</code> from <code>str</code>. Returns <code>fail</code> (default <code>nil</code> on failure)." (tests (readvar 'string "a<b>c"))) |
| 133 | + |
| 134 | +(def showvars "fields [liveurls]" "Generates table rows for a <code>varfield</code> list of fields. If liveurls is true, will make links to URLs.") |
| 135 | + |
| 136 | + |
| 137 | +(def indented-code "s i [newlines [spaces]]" "Tests if <code>s</code> is indented code under the markup rules. Returns a pair of the index of the start of the code, and the number of spaces of indentation. Returns <code>nil</code> if not indented code. The first <code>i</code> characters are skipped." (tests (indented-code "\n\n abc" 0))) |
| 138 | + |
| 139 | +(def parabreak "s i [newlines]" "If <code>s</code> starts with a paragraph break (at least one blank line), returns the index of the start of the paragraph. Otherwise returns <code>nil</code>. Skips the first <code>i</code> characters." (tests (parabreak "\n\nabc\ndef" 0))) |
| 140 | + |
| 141 | +(def urlend "s i" "Finds the logical end of a URL embedded in a string, and returns the index of the first character not in the URL. The first <code>i</code> characters are skipped." (tests (let url "http://arcfn.com; stuff" (cut url 0 (urlend url 0))))) |
| 142 | + |
| 143 | +(def predicate delimc "c" "Tests if <code>c</code> is a delimiter: a parenthesis, square bracket, curly bracket, or double quote." (tests (delimc #\}))) |
| 144 | + |
| 145 | +(def code-block "s i" "Markdown formatting: Returns a 'code block', which is terminated by a line that is not indented with whitespace. The first <code>i+1</code> characters are skipped." (tests (code-block "abc\n def\n ghi\njkl" 0))) |
| 146 | + |
| 147 | +(def login-form "label switch handler afterward" "Creates login form for login-page. New in arc3." ) |
| 148 | +(def login-handler "req switch afterward" "Handler called from login-page. New in arc3." ) |
| 149 | +(def create-handler "req switch afterward" "Handler called from login-page to create account. New in arc3." ) |
| 150 | +(def login "user ip cookie afterward" "Handles successful login. New in arc3." ) |
| 151 | +(def failed-login "user ip cookie afterward" "Handles login failure. New in arc3." ) |
| 152 | +(def predicate username-taken "user" "Tests if username is in dc-usernames* and updates that table. New in arc3." (tests (username-taken "joe"))) |
| 153 | +(def next-parabreak "s i" "Returns index of next paragraph break after i, if any. New in arc3." (tests (next-parabreak "ab\n\ncd" 0))) |
| 154 | +(def predicate opendelim "c" "Tests is character is an opening delimiter. New in arc3." (tests (opendelim #\<))) |
| 155 | +(def predicate closedelim "c" "Tests is character is an closing delimiter. New in arc3." (tests (closedelim #\]))) |
| 156 | +(def english-time "min" "Converts time in minutes to string. New in arc3." (tests (english-time 720)) (tests (english-time 800))) |
| 157 | +(def parse-time "s" "Parses a time string to minutes. New in arc3." (tests (parse-time "12:30pm")) ) |
| 158 | +(def english-date "(y m d)" "Converts date to string. New in arc3." (tests (english-date (2009 6 8)))) |
| 159 | +(def monthnum "s" "Converts month name to number. New in arc3." (tests (monthnum "Mar"))) |
| 160 | +(def date-nums "s" "Used by date-nums to parses a date string to (y m d). New in arc3." (tests (date-nums "2009-12-31") (date-nums "December 31, 2009") (date-nums "June 5"))) |
| 161 | +(def predicate valid-date "(y m d)" "Minimal validation on date. New in arc3." (tests (valid-date '(2009 1 31)) (valid-date '(2009 2 31)) (valid-date '(2009 2 32)))) |
| 162 | +(def parse-date "s" "Parses a date string to (y m d) with some validation. New in arc3." (tests (parse-date "2009-12-31") (parse-date "December 32, 2009"))) |
| 163 | + |
| 164 | +)) |
0 commit comments