demo-counter2.lisp (10766B)
1 ;;; Copyright (C) 2013, 2014, 2015, 2016 Tomas Hlavaty <tom@logand.com> 2 ;;; 3 ;;; Permission is hereby granted, free of charge, to any person 4 ;;; obtaining a copy of this software and associated documentation 5 ;;; files (the "Software"), to deal in the Software without 6 ;;; restriction, including without limitation the rights to use, copy, 7 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies 8 ;;; of the Software, and to permit persons to whom the Software is 9 ;;; furnished to do so, subject to the following conditions: 10 ;;; 11 ;;; The above copyright notice and this permission notice shall be 12 ;;; included in all copies or substantial portions of the Software. 13 ;;; 14 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 18 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 ;;; DEALINGS IN THE SOFTWARE. 22 23 ;;; TODO post forms 24 25 (defpackage :rw.demo.counter2 26 (:use :cl)) 27 28 (in-package :rw.demo.counter2) 29 30 (defvar *query-parameters*) 31 (defvar *state-alist*) 32 (defvar *action*) 33 (defvar *widget-path*) 34 (defvar *widget-child*) 35 (defvar *action-index*) 36 (defvar *var-index*) 37 (defvar *getters*) 38 39 (defun encode-var (value stream) 40 (etypecase value 41 (null) 42 (integer 43 (unless (minusp value) 44 (write-char #\+ stream)) 45 (princ value stream)))) 46 47 (defun decode-var (x) 48 (when x 49 (ecase (char x 0) 50 ((#\+ #\-) (parse-integer x))))) 51 52 (defun encode-url (state-alist action) 53 (with-output-to-string (s) 54 (write-string "?" s) 55 (loop 56 for x in state-alist 57 for i from 0 58 do (progn 59 (when (plusp i) 60 (write-char #\& s)) 61 (write-string (car x) s) 62 (write-char #\= s)) 63 (encode-var (cdr x) s)) 64 (when (and state-alist action) 65 (write-string "&" s)) 66 (when action 67 (write-string "a=" s) 68 (write-string action s)))) 69 70 (defun decode-url () 71 (loop 72 for x in *query-parameters* 73 if (equal "a" (car x)) 74 collect (cdr x) into action 75 else collect (cons (car x) (decode-var (cdr x))) into state-alist 76 finally (return (values state-alist (car action))))) 77 78 (defun encode-path (path i) 79 (with-output-to-string (s) 80 (when path 81 (princ (car path) s) 82 (dolist (x (cdr path)) 83 (write-char #\: s) 84 (princ x s))) 85 (write-char #\; s) 86 (princ i s))) 87 88 (defun lookup-var (path) 89 (cdr (assoc path *state-alist* :test #'equal))) 90 91 (defun widget-var (default get set &optional id) 92 (let ((path (encode-path *widget-path* (incf *var-index*)))) 93 (funcall set (or (and id (lookup-var id)) 94 (lookup-var path) 95 default)) 96 (when *action* 97 (push (lambda () (cons (or id path) (funcall get))) *getters*)))) 98 99 (defun widget (thunk) 100 (lambda () 101 (let ((*widget-path* (cons (incf *widget-child*) *widget-path*)) 102 (*widget-child* 0) 103 (*action-index* 0) 104 (*var-index* 0)) 105 (funcall thunk)))) 106 107 (defun widget-action (thunk) 108 (let ((path (encode-path *widget-path* (incf *action-index*)))) 109 (lambda () 110 (if *action* 111 (when (equal *action* path) 112 (funcall thunk) 113 nil) 114 (encode-url *state-alist* path))))) 115 116 (defun draw (thunk) 117 (multiple-value-bind (*state-alist* *action*) (ignore-errors (decode-url)) 118 (let (*widget-path* 119 (*widget-child* 0) 120 *getters* 121 (rw.ui::*click-link* (lambda (x) (funcall x))) 122 (rw.ui::*click-form* (lambda (x) (funcall x))) ;;;;;;;;;;; 123 (w (funcall thunk))) 124 (if *action* 125 (progn 126 (funcall w) 127 (rw.ui::http-redirect 128 (encode-url (mapcar #'funcall *getters*) nil))) 129 `(:http-1.0 130 :code 200 131 :headers (("Content-Type" . "text/html;charset=utf-8") 132 ("cache-control" . "no-cache,no-store") 133 ("pragma" . "no-cache") 134 ("expires" . "-1")) 135 :body ,(funcall w)))))) 136 137 ;;; example 138 139 (defun counter-widget (i) 140 (let (n) 141 (widget 142 (lambda () 143 (widget-var 0 (lambda () n) (lambda (x) (setq n x))) 144 `(:p ,i ": " 145 " " ,(rw.ui:link "up" (widget-action (lambda () (incf n)))) 146 " " ,(rw.ui:link "down" (widget-action (lambda () (decf n)))) 147 " " (:b ,n)))))) 148 149 (defun calendar-widget (year0 month0 &key (first-weekday 0) (show-weeks t)) 150 (let (year month) 151 (widget 152 (lambda () 153 (widget-var year0 (lambda () year) (lambda (x) (setq year x))) 154 (widget-var month0 (lambda () month) (lambda (x) (setq month x))) 155 (let ((weeks (when show-weeks (rw.calendar::week-generator year month)))) 156 `((:table :style "font-family:monospace") 157 (:tr 158 ,@(when weeks '((:td ""))) 159 ((:td :colspan 3 :align "center") 160 ,(rw.ui:link "<" (widget-action 161 (lambda () 162 (decf month) 163 (when (< month 1) 164 (decf year) 165 (setq month 12))))) 166 " " ,(rw.calendar::pretty-month month) " " 167 ,(rw.ui:link ">" (widget-action 168 (lambda () 169 (incf month) 170 (when (< 12 month) 171 (incf year) 172 (setq month 1)))))) 173 ((:td :align "center") 174 ,(rw.ui:link "@" (widget-action 175 (lambda () (setq year year0 month month0))))) 176 ((:td :colspan 3 :align "center") 177 ,(rw.ui:link "<" (widget-action 178 (lambda () (decf year)))) 179 " " ,year " " 180 ,(rw.ui:link ">" (widget-action 181 (lambda () (incf year)))))) 182 (:tr 183 ,@(when weeks '((:td " "))) 184 ,@(loop 185 with g = (rw.calendar::weekday-generator first-weekday) 186 for i from 0 below 7 187 for n = (funcall g) 188 collect `((:td :style 189 (:style :color ,(when (rw.calendar::weekend n) "red"))) 190 ,(rw.calendar::pretty-day n)))) 191 ,@(loop 192 with g = (rw.calendar::day-generator year month first-weekday) 193 for i from 0 below 6 194 collect `(:tr 195 ,@(when weeks `(((:td :align "right") ,(funcall weeks)))) 196 ,@(loop 197 for j from 0 below 7 198 for d = (funcall g) 199 collect `((:td :align "right") 200 ,(if d 201 (rw.ui:link d (widget-action 202 (lambda ()))) 203 ""))))))))))) 204 205 (defun form (draw) 206 `((:form 207 :action ,(funcall rw.ui::*click-link* (widget-action 'rw.ui::no)) 208 :method "post" 209 :enctype "multipart/form-data" 210 :style "padding:0;margin:0;border:0") 211 ((:div :style "width:0;height:0;overflow:hidden") 212 ,(rw.ui:submit nil (widget-action 'rw.ui::no1))) 213 ,(if (functionp draw) (funcall draw) draw))) 214 215 (defun var-widget (id) 216 (let (v) 217 (widget 218 (lambda () 219 (widget-var 0 (lambda () v) (lambda (x) (setq v x)) id) 220 (progn ;;form 221 `(:p ,id "=" ,v " " 222 #+nil ,(rw.ui:entry (lambda (x)) (princ-to-string v)) 223 #+nil ,(rw.ui:submit "ok" (lambda (x) (print (list :@@@ x)))))))))) 224 225 (defun toplevel-widget () 226 (let ((w (nconc 227 (mapcar 'counter-widget '(1 2 3 4)) 228 (list 229 (calendar-widget 2012 7) 230 (calendar-widget 2013 8) 231 (var-widget "v1") 232 (var-widget "v2"))))) 233 (lambda () 234 `(:html 235 (:head 236 ((:meta :http-equiv "content-type" 237 :content "text/html;charset=utf-8")) 238 ((:meta :http-equiv "cache-control" :content "no-cache,no-store")) 239 ((:meta :http-equiv "pragma" :content "no-cache")) 240 ((:meta :http-equiv "expires" :content -1)) 241 (:title "counter")) 242 (:body ,@(mapcar #'funcall w)))))) 243 244 (defun draw-counter () 245 (draw (lambda () (toplevel-widget)))) 246 247 (defun counter-handler (msg stream method query protocol headers &optional body) 248 (declare (ignore protocol headers)) 249 (ecase msg 250 (:read (rw:till (rw:peek-reader stream))) 251 (:write 252 (let ((rw.ui:*http-server* 253 (let ((pp (rw.http::post-parameters method body))) 254 (lambda (msg &rest args) 255 (declare (ignore args)) 256 (ecase msg 257 (:method method) 258 (:post-parameters pp))))) 259 (*query-parameters* (nth-value 1 (rw.uri:parse-query-string query)))) 260 (draw-counter))))) 261 262 (defun start () 263 (rw.http:server (rw.socket:make-ipv4-address "0.0.0.0") 264 2349 265 'counter-handler 266 :quit (lambda () nil) 267 :allowed-methods '(:get :post) 268 :ignore-errors-p t)) 269 270 ;;(start) 271 272 (defun save-image () 273 #-(or ccl sbcl) 274 (error "TODO RW.DEMO.COUNTER::SAVE-IMAGE") 275 #+ccl ;; TODO no debug on ^C 276 (ccl:save-application "cl-rw-demo-counter" 277 :prepend-kernel t 278 :error-handler :quit-quietly 279 :toplevel-function (lambda () 280 (handler-case 281 (progn 282 (start) 283 (loop (sleep 1))) 284 (condition () 285 (ccl:quit 1))))) 286 #+sbcl 287 (sb-ext:save-lisp-and-die "cl-rw-demo-counter" 288 :executable t 289 :toplevel (lambda () 290 (handler-case 291 (progn 292 (start) 293 (loop (sleep 1))) 294 (condition () 295 (sb-ext:exit :code 1 :abort t))))))