| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- ;;;; collect.lisp
- ;;;;
- ;;;; Copyright (c) 2015 Samuel W. Flint <swflint@flintfam.org>
- (in-package #:collect)
- ;;; "collect" goes here. Hacks and glory await!
- (defmacro define-application (name file &body body-forms)
- `(progn
- (restas:define-module ,name
- (:use :cl
- :cl-who
- :parenscript
- :sqlite
- :restas
- :iter)
- (:import-from #:hunchentoot
- #:post-parameter)
- (:import-from #:lass
- #:compile-and-write))
- (in-package ,name)
- (defvar *tables*)
- (defvar *db*)
- (defun start (port)
- (setq *db* (connect ,file))
- (iter (for (name . query))
- (do (execute-non-query *db* query)))
- (restas:start ,name :port 8080))
- (define-route main-css ("main.css" :type "text/css")
- (compile-and-write
- (*
- :margin 0
- :padding 0
- :font-family "Georgia, Palantino, Times, 'Times New Roman', sans-serif")
- (body
- :backgroud "#fff")
- (a
- :text-decoration "none")
- ("a:link, a:visited"
- :color "#f30")
- ("a:hover"
- :color "#f90")
- (div.main-content
- :position "absolute"
- :top "40px"
- :left "280px"
- :width "500px"
- (h1
- :font-size "40px"
- :font-weight "normal"
- :line-height "43px"
- :letter-spacing "-1px")
- (div.results
- :margin-top "20px"
- (ul
- :list-style-type "none")
- (li
- :font-size "18px"
- :line-height "24px"
- :margin-left "70px"))
- (div.page-list
- :text-align "center"
- :float "bottom")
- (dl
- :margin-top "10px")
- (dt
- :font-size "18px"
- :font-weight "bold")
- (dd
- :margin-left "20px"
- :margin-bottom "15px"))
- (div.nav
- :position absolute
- :top "40px"
- :left "20px"
- :width "200px"
- :padding "20px 20px 0 0"
- :border-right "1px solid #ccc"
- :text-align "right"
- (h2
- :text-transform "uppercase"
- :font-size "13px"
- :color "#333"
- :letter-spacing "1px"
- :line-height "20px"
- (a
- :color "#333"))
- (ul
- :list-style-type "none"
- :margin "20px 0")
- (li
- :font-size "14px"
- :line-height "20 px")
- (hr
- :height "1px"
- :color "#ccc"
- :margin-top "5px"
- :margin-bottom "5px"))))
- ,@body-forms))
- (defun to-sql-type (type)
- (cadr (assoc type '((:integer "INTEGER")
- (:text "TEXT")
- (:real "REAL")
- (:blob "BLOB")))))
- (defun relation (relationship)
- (if (not (null relationship))
- (destructuring-bind (table column) relationship
- (declare (string table column))
- (format nil " REFERENCES ~a (~a)"
- table
- column))
- ""))
- (defun col-to-clause (column)
- (destructuring-bind (name type &key (nullable t) (primary nil) relationship) column
- (declare (string name)
- (keyword type)
- (ignorable nullable relationship))
- (if (null primary)
- (if (null nullable)
- (format nil "~a ~a~a,"
- name
- (to-sql-type type)
- (relation relationship))
- (format nil "~a ~a~a NOT NULL,"
- name
- (to-sql-type type)
- (relation relationship)))
- (format nil "~a ~a PRIMARY KEY,"
- name
- (to-sql-type type)))))
- (defmacro define-table (name (&rest cols) &key &allow-other-keys)
- (let* ((clauses (map 'list #'col-to-clause cols))
- (sql-command (format nil "CREATE TABLE IF NOT EXISTS ~a~%(~%~{~a~%~});"
- name
- clauses)))
- `(push (cons ,name ,sql-command) *tables*)))
- (defun make-form-entry (entry)
- (destructuring-bind (label column &key (type :text) other-column options) entry
- (declare (string label column)
- (ignorable other-column options))
- (let ((label-name (concatenate 'string "fld_" column))
- (the-options (if (null options)
- (if (not (null other-column))
- `(,(car other-column) ,(cadr other-column))))))
- (case type
- (:text
- `(:tr
- (:td ,label)
- (:td (:input :type :text
- :name ,label-name))))
- (:drop-down
- `(:tr
- (:td ,label)
- (:td
- (:select :name ,label-name
- (loop for option in ,the-options
- collect (make-option-drop-down option))))))
- (:radio-buttons
- `(:tr
- (:td ,label)
- (:td (loop for option in ,the-options
- collect (make-option-radio option ,label-name)))))))))
- (defun make-option-drop-down (option)
- (let ((name (first option))
- (value (second option)))
- `(:option :value ,value ,name)))
- (defun make-option-radio (option label)
- (let ((name (first option))
- (value (second option)))
- `(:div (:input :type "radio" :name ,label :value ,value)
- ,name (:br))))
- (defun make-update-query (table columns fields)
- (let ((actual-query (format nil "INSERT INTO ~a (~{~a~^, ~}) values (~{~a~^, ~})"
- table
- columns
- (iter (for i in columns)
- (collect "?"))))
- (fields (iter (for field in fields)
- (collect `(post-parameter ,field)))))
- `(execute-non-query *db* ,actual-query ,@fields)))
- (defmacro define-form (name table (&rest fields) &key (title name) (description title) &allow-other-keys)
- (let* ((columns (map 'list #'second fields))
- (field-names (map 'list #'(lambda (column)
- (concatenate 'string "fld_" column))
- columns))
- (form-entries (map 'list #'make-form-entry fields))
- (update-query (make-update-query table columns field-names))
- (route-name (intern (string-upcase name)))
- (route-name-post (intern (string-upcase (concatenate 'string name "-post")))))
- `(progn
- (define-route ,route-name (,name)
- (with-html-output-to-string (out)
- (:html
- (:head (:title ,title)
- (:link :rel "style_sheet" :href (genurl 'main-css)))
- (:body (:div :class "nav")
- (:div :class "main-content"
- (:h1 ,title)
- (:p ,description)
- (:form :action (genurl ',route-name-post)
- :method :post
- (:table ,@form-entries
- (:tr (:td)
- (:td (:input :type :submit :value "Submit"))))))))))
- (define-route ,route-name-post (,name :method :post)
- ,update-query
- (redirect ',route-name)))))
|