demo-counter3.lisp (10812B)
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 (defpackage :rw.demo.counter3 24 (:use :cl)) 25 26 (in-package :rw.demo.counter3) 27 28 (defvar *slet-state*) 29 (defvar *slet-state-decoded*) 30 (defvar *sflet-action*) 31 (defvar *widget-path*) 32 (defvar *widget-child*) 33 (defvar *action-index*) 34 (defvar *var-index*) 35 (defvar *slet-getters*) 36 37 (defun encode-url (state action) 38 (with-output-to-string (s) 39 (write-string "?" s) 40 (when state 41 (write-string "s=" s) 42 (write-string state s)) 43 (when (and state action) 44 (write-string "&" s)) 45 (when action 46 (write-string "a=" s) 47 (write-string action s)))) 48 49 (defun encode-path (path i) 50 (with-output-to-string (s) 51 (when path 52 (princ (car path) s) 53 (dolist (x (cdr path)) 54 (write-char #\! s) 55 (princ x s))) 56 (write-char #\! s) 57 (princ i s))) 58 59 (defun var-reader (r) 60 (lambda () 61 (when (rw:peek r) 62 (assert (eql #\! (rw:next r))) 63 (cons (let ((x (rw:till r '(#\.)))) 64 (when x 65 (coerce x 'string))) 66 (progn 67 (assert (eql #\. (rw:next r))) 68 (assert (member 69 (rw:peek r) 70 '(nil #\! #\- #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))) 71 (let ((x (rw:till r '(#\!)))) 72 (when x 73 (parse-integer (coerce x 'string))))))))) 74 75 (defun decode-state (state) 76 (rw:till (rw:peek-reader (var-reader (rw:peek-reader (rw:reader state)))))) 77 78 ;;(decode-state "!1!1.!1!2.3!2!1.-1") 79 80 (defun encode-state () 81 (with-output-to-string (s) 82 (dolist (x *slet-getters*) 83 (multiple-value-bind (k v) (funcall x) 84 (write-char #\! s) 85 (write-string k s) 86 (write-char #\. s) 87 (etypecase v 88 (null) 89 (integer (princ v s))))))) 90 91 (defun lookup-var (path) 92 (cdr (assoc path *slet-state-decoded* :test #'equal))) 93 94 (defun widget-var (default get set) 95 (let ((path (encode-path *widget-path* (incf *var-index*)))) 96 (funcall set (or (lookup-var path) default)) 97 (when *sflet-action* 98 ;; TODO when == default => dont put into state 99 (push (lambda () (values path (funcall get))) *slet-getters*)))) 100 101 (defun widget (thunk) 102 (lambda () 103 (let ((*widget-path* (cons (incf *widget-child*) *widget-path*)) 104 (*widget-child* 0) 105 (*action-index* 0) 106 (*var-index* 0)) 107 (funcall thunk)))) 108 109 (defun widget-action (thunk) 110 (let ((path (encode-path *widget-path* (incf *action-index*)))) 111 (lambda () 112 (if *sflet-action* 113 (when (equal *sflet-action* path) 114 (funcall thunk) 115 nil) 116 (encode-url *slet-state* path))))) 117 118 (defun counter-widget (i) 119 (let ((n 0)) 120 (flet ((up () (incf n)) 121 (down () (decf n))) 122 (widget 123 (lambda () 124 (widget-var 0 (lambda () n) (lambda (x) (setq n x))) 125 `(:p ,i ": " 126 " " ,(rw.ui:link "up" (widget-action #'up)) 127 " " ,(rw.ui:link "down" (widget-action #'down)) 128 " " (:b ,n))))))) 129 130 (defun calendar-widget (year0 month0 &key (first-weekday 0) (show-weeks t)) 131 (let ((year year0) 132 (month month0)) 133 (flet ((nop ()) ;; problem, need link for each day 134 (previous-month () 135 (decf month) 136 (when (< month 1) 137 (decf year) 138 (setq month 12))) 139 (next-month () 140 (incf month) 141 (when (< 12 month) 142 (incf year) 143 (setq month 1))) 144 (reset () (setq year year0 month month0)) 145 (previous-year () (decf year)) 146 (next-year () (incf year))) 147 (widget 148 (lambda () 149 (widget-var year0 (lambda () year) (lambda (x) (setq year x))) 150 (widget-var month0 (lambda () month) (lambda (x) (setq month x))) 151 (let ((weeks (when show-weeks (rw.calendar::week-generator year month)))) 152 `((:table :style "font-family:monospace") 153 (:tr 154 ,@(when weeks '((:td ""))) 155 ((:td :colspan 3 :align "center") 156 ,(rw.ui:link "<" (widget-action #'previous-month)) 157 " " ,(rw.calendar::pretty-month month) " " 158 ,(rw.ui:link ">" (widget-action #'next-month))) 159 ((:td :align "center") ,(rw.ui:link "@" (widget-action #'reset))) 160 ((:td :colspan 3 :align "center") 161 ,(rw.ui:link "<" (widget-action #'previous-year)) 162 " " ,year " " 163 ,(rw.ui:link ">" (widget-action #'next-year)))) 164 (:tr 165 ,@(when weeks '((:td " "))) 166 ,@(loop 167 with g = (rw.calendar::weekday-generator first-weekday) 168 for i from 0 below 7 169 for n = (funcall g) 170 collect `((:td :style 171 (:style :color ,(when (rw.calendar::weekend n) "red"))) 172 ,(rw.calendar::pretty-day n)))) 173 ,@(loop 174 with g = (rw.calendar::day-generator year month first-weekday) 175 for i from 0 below 6 176 collect `(:tr 177 ,@(when weeks `(((:td :align "right") ,(funcall weeks)))) 178 ,@(loop 179 for j from 0 below 7 180 for d = (funcall g) 181 collect `((:td :align "right") 182 ,(if d 183 (rw.ui:link d (widget-action #'nop)) 184 "")))))))))))) 185 186 (defun toplevel-widget () 187 (let ((w (mapcar 'counter-widget '(1 2 3 4))) 188 (w2 (calendar-widget 2012 7))) 189 (lambda () 190 `(:html 191 (:head 192 ((:meta :http-equiv "content-type" 193 :content "text/html;charset=utf-8")) 194 ((:meta :http-equiv "cache-control" :content "no-cache,no-store")) 195 ((:meta :http-equiv "pragma" :content "no-cache")) 196 ((:meta :http-equiv "expires" :content -1)) 197 (:title "counter")) 198 (:body ,@(mapcar #'funcall w) ,(funcall w2)))))) 199 200 (defvar *query-parameters*) 201 202 (defun query-parameter (key) 203 (cdr (assoc key *query-parameters* :test #'equal))) 204 205 (defun draw-counter () 206 (let* ((*slet-state* (query-parameter "s")) 207 (*slet-state-decoded* (ignore-errors (decode-state *slet-state*))) 208 (*sflet-action* (query-parameter "a")) 209 *widget-path* 210 (*widget-child* 0) 211 *slet-getters* 212 (rw.ui::*click-link* (lambda (click) (funcall click))) 213 #+nil(rw.ui::*click-form* (lambda (set) "TODO"))) 214 (let ((w (toplevel-widget))) 215 (if *sflet-action* 216 (progn 217 (funcall w) 218 (rw.ui::http-redirect (encode-url (encode-state) nil))) 219 `(:http-1.0 220 :code 200 221 :headers (("Content-Type" . "text/html;charset=utf-8") 222 ("cache-control" . "no-cache,no-store") 223 ("pragma" . "no-cache") 224 ("expires" . "-1")) 225 :body ,(funcall w))))) 226 #+nil 227 (rw.ui:draw (lambda () 228 (let ((w (toplevel-widget))) 229 (lambda () 230 `(:http-1.0 231 :code 200 232 :headers (("Content-Type" . "text/html;charset=utf-8") 233 ("cache-control" . "no-cache,no-store") 234 ("pragma" . "no-cache") 235 ("expires" . "-1")) 236 :body ,(funcall w))))) 237 'construct 238 'deconstruct)) 239 240 (defun counter-handler (msg stream method query protocol headers &optional body) 241 (declare (ignore protocol headers)) 242 (ecase msg 243 (:read (rw:till (rw:peek-reader stream))) 244 (:write 245 (let ((rw.ui:*http-server* 246 (let ((pp (rw.http::post-parameters method body))) 247 (lambda (msg &rest args) 248 (declare (ignore args)) 249 (ecase msg 250 (:method method) 251 (:post-parameters pp))))) 252 (*query-parameters* (nth-value 1 (rw.uri:parse-query-string query)))) 253 (draw-counter))))) 254 255 (defun start () 256 (rw.http:server (rw.socket:make-ipv4-address "0.0.0.0") 257 2349 258 'counter-handler 259 :quit (lambda () nil) 260 :allowed-methods '(:get :post) 261 :ignore-errors-p t)) 262 263 ;;(start) 264 265 (defun save-image () 266 #-(or ccl sbcl) 267 (error "TODO RW.DEMO.COUNTER::SAVE-IMAGE") 268 #+ccl ;; TODO no debug on ^C 269 (ccl:save-application "cl-rw-demo-counter" 270 :prepend-kernel t 271 :error-handler :quit-quietly 272 :toplevel-function (lambda () 273 (handler-case 274 (progn 275 (start) 276 (loop (sleep 1))) 277 (condition () 278 (ccl:quit 1))))) 279 #+sbcl 280 (sb-ext:save-lisp-and-die "cl-rw-demo-counter" 281 :executable t 282 :toplevel (lambda () 283 (handler-case 284 (progn 285 (start) 286 (loop (sleep 1))) 287 (condition () 288 (sb-ext:exit :code 1 :abort t))))))