-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathcss-lite.lisp
159 lines (130 loc) · 5.33 KB
/
css-lite.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
(in-package "CSS-LITE")
;;; main interface
(defvar *css-stream* nil)
(defvar *indent-css* nil
"Indicates if the properties of a selector should be indented or not.
There are three possible values:
* nil - The default value, and indicates that no indentation should be
applied
* the symbol 'tab - Indicates that the properties should be indented
using the #\Tab character
* an integer greater than 0 - Indicates how many #\Space characters
should be used to indent the properties")
(defmacro css (&body rules)
`(format *css-stream* "~@{~A~}" ,@(mapcan #'process-css-rule rules)))
(defmacro css-string (&body rules)
`(with-output-to-string (*css-stream*)
(css ,@rules)))
(defun inline-css (&rest properties)
(format nil "~{~A~}" (process-css-properties properties t :newlines nil)))
;; The parenscript implementation of this function is in the
;; paren-css-lite.lisp file
(defun css-id-name (symbol)
;; This should probably be implemented using read-time conditionals,
;; but I can't seem to get them to work correctly. So for now let's
;; just directly access the `*features*' to check if parenscript has
;; been loaded into the image. - rolando2424
(unless (member :parenscript *features*)
(format nil "#~(~a~)" symbol)))
(defmacro make-css-var (var-name var-val)
`(progn
(setq ,var-name ,var-val)
(setf (get ',var-name 'css-var) t)))
(defmacro make-css-func (func-name &body forms)
`(progn
(defun ,func-name ,@forms)
(setf (get ',func-name 'css-func) t)))
(make-css-func comment (comment-string) (list (concatenate 'string "/*" comment-string) "*/"))
;;; implementation
(defun selector-to-string (selector)
(handler-case
(if (listp selector)
(destructuring-bind (specifier element)
selector
(ecase specifier
(:hover (format nil "~a:hover" (selector-to-string element)))
(:id (css-id-name element))))
(cond ((and (symbolp selector) (not (symbol-package selector))) (css-id-name selector))
((eql :and selector) ",")
(t (to-string selector))))
(error () (error "~s isn't a valid CSS selector." selector))))
(defun css-selectors-to-string (selectors)
(reduce (lambda (s1 s2) (concatenate 'string s1 " " s2)) (mapcar #'selector-to-string selectors)))
(defvar +newline+ (format nil "~%"))
(defun css-func-p (val)
(if (symbolp val)
(get val 'css-func)
nil))
(defun css-var-p (val)
(if (symbolp val)
(get val 'css-var)
nil))
(defun css-comment-p (val)
"Return T if `val' is the start of a CSS comment, otherwise return NIL."
(string= val "/*" :end1 2))
(defun expand-tree (tree)
(let ((result '()))
(labels ((scan (item)
(if (listp item)
(if (css-func-p (car item))
;; this calls the function
(scan (eval `(,(car item) ,@(cdr item))))
(map nil #'scan item))
(if (css-var-p item)
(scan (symbol-value item))
(push item result)))))
(scan tree))
(nreverse result)))
(defun process-css-properties (properties eval-vals &key (newlines t))
(loop for (name val) on
(expand-tree properties)
by #'cddr appending
(list
(if newlines +newline+ "")
(concatenate 'string
;; Indent the property as specified in the variable `*indent-css*'
(cond ((null *indent-css*) "")
((equal *indent-css* 'tab)
(string #\Tab))
((plusp *indent-css*)
(make-string *indent-css* :initial-element #\Space))
;; XXX: If the value of `*indent-css*' is invalid, this
;; `cond' does the same thing as if `*indent-css*' had the
;; value `nil'. Should it raise an error?
)
(to-string name)
;; Only add the ':' character if this isn't a comment
(unless (css-comment-p name)
":"))
(if eval-vals (to-string val)
`(to-string ,val))
;; The ';' character should only be added if this isn't a
;; comment
(if (css-comment-p name)
""
";"))))
(defun process-css-rule (rule &key (parent-selectors nil))
(let ((selectors (if parent-selectors
(concatenate 'list parent-selectors (car rule))
(car rule)))
(properties (cadr rule))
(children-rules (cddr rule)))
(append (list +newline+ (css-selectors-to-string selectors) " {")
(process-css-properties properties nil)
(list +newline+ "}" +newline+)
(mapcan
#'(lambda (child-rules)
(process-css-rule child-rules :parent-selectors selectors))
children-rules))))
(defun to-string (x)
(cond ((stringp x) x)
((symbolp x) (string-downcase (symbol-name x)))
((listp x) (apply #'concatenate 'string
(loop for (val . rest) on x
with comma = ", "
unless rest do (setf comma "")
collect (if (stringp val)
(format nil "'~a'" val)
(to-string val))
collect comma)))
(t (princ-to-string x))))