├── .gitattributes ├── LICENSE ├── README.md ├── asdf.lisp ├── binary-lass.asd ├── binary.lisp ├── compiler.lisp ├── docs └── index.html ├── generate-binary.sh ├── lass.asd ├── lass.el ├── lass.lisp ├── package.lisp ├── property-funcs.lisp ├── readable-list.lisp ├── special.lisp └── writer.lisp /.gitattributes: -------------------------------------------------------------------------------- 1 | doc/ linguist-vendored 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Yukari Hafner 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About LASS 2 | ---------- 3 | Writing CSS files comes with a lot of repetition and is generally much too verbose. With lispy syntax, shortcuts, and improvements, LASS aims to help you out in writing CSS quick and easy. LASS was largely inspired by [SASS](http://sass-lang.com/). 4 | 5 | How To & Examples 6 | ----------------- 7 | LASS supports two modes, one being directly in your lisp code, the other in pure LASS files. Adding LASS into your code is easy: 8 | 9 | (lass:compile-and-write 10 | '(div 11 | :background black)) 12 | 13 | "div{ 14 | background: black; 15 | }" 16 | 17 | LASS works on the following simple principles: A list is a block. The first argument in the list is a selector. The body of the list makes up the properties and sub-blocks. A property is started with a keyword that is used as the property name. Following is a bunch of property arguments until a new keyword, list, or the end is reached. A list inside a block is, again, a block with the twist that the parent block's selector is prepended to the sub-block's selector. 18 | 19 | (lass:compile-and-write 20 | '(nav 21 | (ul 22 | :list-style none 23 | (li 24 | :margin 0 :padding 0 25 | :display inline-block))))) 26 | 27 | "nav ul{ 28 | list-style: none; 29 | } 30 | 31 | nav ul li{ 32 | margin: 0; 33 | padding: 0; 34 | display: inline-block; 35 | }" 36 | 37 | Since LASS' `COMPILE-SHEET` simply takes a bunch of lists as its argument, you can use the backquote and comma to integrate variables from your lisp environment: 38 | 39 | (let ((color "#0088EE")) 40 | (lass:compile-and-write 41 | `(div 42 | :background ,color)))) 43 | 44 | "div{ 45 | background: #0088EE; 46 | }" 47 | 48 | Alternatively however, and this is especially useful in pure LASS files, you can use the `LET` block to create LASS-specific bindings: 49 | 50 | (lass:compile-and-write 51 | '(:let ((color "#0088EE")) 52 | (div 53 | :background #(color)))) 54 | 55 | "div{ 56 | background: #0088EE; 57 | }" 58 | 59 | You can also let bind entire property blocks: 60 | 61 | (lass:compile-and-write 62 | '(:let ((colors :background "#0088EE")) 63 | (div #(colors)) 64 | (:media "(prefers-color-scheme: light)" (div #(colors))))) 65 | 66 | "div{ 67 | background: #0088EE; 68 | } 69 | 70 | @media (prefers-color-scheme: light){ 71 | div{ 72 | background: #0088EE; 73 | } 74 | }" 75 | 76 | LASS' selector mechanism is very flexible and allows for some complex logic to reduce duplication: 77 | 78 | (lass:compile-and-write 79 | '(article 80 | ((:or p blockquote) 81 | :margin 0 :padding 0 82 | 83 | (a 84 | :color black) 85 | 86 | ((:and a :hover) 87 | :color darkred)))) 88 | 89 | "article p, article blockquote{ 90 | margin: 0; 91 | padding: 0; 92 | } 93 | 94 | article p a, article blockquote a{ 95 | color: black; 96 | } 97 | 98 | article p a:hover, article blockquote a:hover{ 99 | color: darkred; 100 | }" 101 | 102 | But it can go even further: 103 | 104 | (lass:compile-and-write 105 | '((:and 106 | (:or article section) 107 | (:= data-author (:or yukari ran chen)) 108 | (:nth-child (:or 1 2 3))) 109 | :display none)) 110 | 111 | "article[data-author=\"yukari\"]:nth-child(1), 112 | article[data-author=\"yukari\"]:nth-child(2), 113 | article[data-author=\"yukari\"]:nth-child(3), 114 | article[data-author=\"ran\"]:nth-child(1), 115 | article[data-author=\"ran\"]:nth-child(2), 116 | article[data-author=\"ran\"]:nth-child(3), 117 | article[data-author=\"chen\"]:nth-child(1), 118 | article[data-author=\"chen\"]:nth-child(2), 119 | article[data-author=\"chen\"]:nth-child(3), 120 | section[data-author=\"yukari\"]:nth-child(1), 121 | section[data-author=\"yukari\"]:nth-child(2), 122 | section[data-author=\"yukari\"]:nth-child(3), 123 | section[data-author=\"ran\"]:nth-child(1), 124 | section[data-author=\"ran\"]:nth-child(2), 125 | section[data-author=\"ran\"]:nth-child(3), 126 | section[data-author=\"chen\"]:nth-child(1), 127 | section[data-author=\"chen\"]:nth-child(2), 128 | section[data-author=\"chen\"]:nth-child(3){ 129 | display: none; 130 | }" 131 | 132 | Whoa nelly! 133 | 134 | If you ever need to expand a selector into a parent block, for instance to specialise on different classes of the block, you can use the parent pseudo-selector: 135 | 136 | (lass:compile-and-write 137 | '((:or article section) 138 | :background black 139 | ((:parent .bright) 140 | :background white))) 141 | 142 | "article, 143 | section{ 144 | background: black; 145 | } 146 | 147 | article.bright, 148 | section.bright{ 149 | background: white; 150 | }" 151 | 152 | Some CSS properties are not fully specified yet and require browser-specific prefixes. LASS can help you with that, too: 153 | 154 | (lass:compile-and-write 155 | '(.fun 156 | :linear-gradient "deg(45)" black 0% darkgray 100% 157 | :transform rotate -45deg)) 158 | 159 | ".fun{ 160 | background: -moz-linear-gradient(deg(45), black 0%, darkgray 100%); 161 | background: -o-linear-gradient(deg(45), black 0%, darkgray 100%); 162 | background: -webkit-linear-gradient(deg(45), black 0%, darkgray 100%); 163 | background: -ms-linear-gradient(deg(45), black 0%, darkgray 100%); 164 | background: linear-gradient(deg(45), black 0%, darkgray 100%); 165 | -moz-transform: rotate(-45deg); 166 | -o-transform: rotate(-45deg); 167 | -webkit-transform: rotate(-45deg); 168 | -ms-transform: rotate(-45deg); 169 | transform: rotate(-45deg); 170 | }" 171 | 172 | LASS also supports the various `@QUERY` operator blocks: 173 | 174 | (lass:compile-and-write 175 | '(:media "(max-width: 800px)" 176 | (div 177 | :margin 0))) 178 | 179 | "@media (max-width: 800px){ 180 | div{ 181 | margin: 0; 182 | } 183 | }" 184 | 185 | By default LASS activates pretty-printing and inserts newlines and spaces where appropriate in order to make the result readable and easy to debug. However, you can also deactivate that and directly produce minified CSS: 186 | 187 | (let ((lass:*pretty* NIL)) 188 | (lass:compile-and-write 189 | '(:media "(max-width: 800px)" 190 | (div 191 | :margin 0)))) 192 | 193 | "@media (max-width: 800px){div{margin:0;}}" 194 | 195 | As mentioned above you can write pure LASS files to compile down to a CSS file. To do that, simply use `GENERATE`: 196 | 197 | ![generate-example](http://shinmera.tymoon.eu/public/screenshot-2014.09.04-23:57:38.png) 198 | 199 | Blocks 200 | ------ 201 | Each block in a LASS sheet consists of a list containing a selector followed by one or more properties or sub-blocks. 202 | 203 | (selector [property | block]*) 204 | 205 | Selectors 206 | --------- 207 | The following list contains examples for the various uses of selectors. 208 | 209 | * Any element 210 | `*` 211 | * An element with tag-name `e` 212 | `e` 213 | * An element with tag-name `e` or `f` 214 | `(:or e f)` 215 | * An `e` element with the `:link` pseudo-selector 216 | `(:and e :link)` 217 | * The first formatted line of an `e` element 218 | `(:and e |::first-line|)` or `(:and e "::first-line")` 219 | * An `e` element with a "warning" class 220 | `e.warning` 221 | * An `e` element with ID equal to `warning` 222 | `|e#warning|` or `"e#warning"` 223 | * An `e` element with a `foo` attribute 224 | `e[foo]` 225 | * An `e` element whose `foo` attribute value is exactly equal to `bar` 226 | `(:and :a (:= foo "bar"))` 227 | * An `e` element whose `foo` attribute value is a list of whitespace-separated values, one of which is exactly equal to `bar` 228 | `(:and :a (:~= foo "bar"))` 229 | * An `e` element whose `foo` attribute has a hyphen-separated list of values beginning (from the left) with `bar` 230 | `(:and :a (:/= foo "bar"))` 231 | * An `e` element whose `foo` attribute value begins exactly with the string `bar` 232 | `(:and :a (:^= foo "bar"))` 233 | * An `e` element whose `foo` attribute value ends exactly with the string `bar` 234 | `(:and :a (:$= foo "bar"))` 235 | * An `e` element whose `foo` attribute value contains the substring `bar` 236 | `(:and :a (:*= foo "bar"))` 237 | * An `e` element that matches the pseudo-selector `nth-child(2)` 238 | `(e (:nth-child 2))` 239 | * An `f` element preceded by an `e` element 240 | `(e ~ f)` 241 | * An `f` element immediately precede by an `e` element 242 | `(e + f)` 243 | * An `f` element which is a descendant of `e` 244 | `(e f)` 245 | * An `f` element which is a direct descendant of `e` 246 | `(e > f)` 247 | 248 | 249 | Selector Combinations 250 | --------------------- 251 | As illustrated briefly above, LASS includes two combinators for selectors, `:and` and `:or`. These combinators are *combinatoric*, meaning that all possible combinations are explored. Consider the following selector: 252 | 253 | ((foo (:and a .title (:or :active :hover)) (:or span div))) 254 | 255 | Enumerating all possible answers to this combination would result in the following list 256 | 257 | foo a.title:active span 258 | foo a.title:active div 259 | foo a.title:hover span 260 | foo a.title:hover div 261 | 262 | The number of possible combinations can quickly explode in size the more options are available. This means that for complex relations and expressions, LASS can be extremely concise. Note that combinators are available at any position in a selector, this includes the arguments of a pseudo-selector like `:nth-child`. 263 | 264 | Properties 265 | ---------- 266 | A property consists of a keyword symbol and a sequence of values. The values to a property are gathered up until either a non-value list or a new keyword is encountered. Originally it stopped as soon as a list was encountered, but this behaviour was changed and specially recognised lists are integrated to allow a more native look for certain values like colours, urls, and so on. Certain properties are specifically declared and will error if they are passed the wrong number or invalid kind of values. For most however, LASS will just blindly put things into the CSS file as you give them. It is up to you to make sure that the values are valid. 267 | 268 | :text-style underline 269 | :color (rgb 212 112 30) 270 | :background (url "/foo") 271 | :border 1px solid black 272 | 273 | Certain properties currently still require vendor-specific declarations. LASS tries to do that automatically for you, but it also needs to know about these declarations and as such, they need to be manually added. Some of the more common ones are included in LASS by default, but if you encounter one that isn't, you are welcome to send a pull request (see Extending LASS on how to do it). 274 | 275 | Sub-Blocks 276 | ---------- 277 | A block can contain other blocks. These sub-blocks are recursively flattened into the structure by simply prepending the selector of the parent block. Thus 278 | 279 | (foo (bar (baz) (bam))) 280 | 281 | Is equivalent to 282 | 283 | (foo) ((foo bar)) ((foo bar baz)) ((foo bar bam)) 284 | 285 | Allowing this kind of nesting allows you to more closely mirror the structure present in your HTML file that you want to style. Combining this with the selector combinations, this system allows reducing code duplication a lot. 286 | 287 | Special Blocks 288 | -------------- 289 | In CSS3 there are special properties and blocks that are preceded by an `@` symbol. The most well-known examples therefore are probably `@include` and `@media`. LASS implements all of these special blocks by a keyword symbol equivalent selector. Therefore the above two would translate to the following in LASS. 290 | 291 | (:include (url "foo")) 292 | (:media "(max-width: 800px)" 293 | (foo)) 294 | 295 | Variables 296 | --------- 297 | Often times it is useful to define variables that you can use within your style so that colours and fonts can quickly be exchanged. LASS allows you to do that too using the `:let` directive and by abusing the vector type. It is probably best illustrated using an example: 298 | 299 | (:let ((foo "#0088EE")) 300 | ((a:active) :color #(foo))) 301 | 302 | 303 | Extending LASS 304 | -------------- 305 | Pretty much every part of LASS is extensible through methods. Most useful will however probably be the `DEFINE-SPECIAL-PROPERTY`, `DEFINE-BROWSER-PROPERTY` and `DEFINE-SPECIAL-SELECTOR` helper-macros. Here's some examples from the `SPECIAL.LISP` file that defines some standard special handlers: 306 | 307 | (define-special-property font-family (&rest faces) 308 | (list (make-property "font-family" (format NIL "~{~a~^, ~}" (mapcar #'resolve faces))))) 309 | 310 | (define-browser-property linear-gradient (direction &rest colors) 311 | (:default (property) 312 | (make-property "background" (format NIL "~a(~a~{, ~a ~a~})" 313 | property (resolve direction) (mapcar #'resolve colors))))) 314 | 315 | For more control, have a look at the various `COMPILE-*` generic functions. 316 | 317 | Emacs Support 318 | ------------- 319 | LASS includes a tiny elisp file, `lass.el`. Add LASS' directory to your emacs `LOAD-PATH` and `REQUIRE` lass. 320 | 321 | (add-to-list 'load-path "[path-to-lass-source-dir]/") 322 | (require 'lass) 323 | 324 | Once you visit a `.lass` file, it will automatically start in the `LASS` major-mode, which is a derived-mode from `COMMON-LISP-MODE`. Whenever you save, it will automatically try to compile the lass file to its CSS equivalent. If slime is connected, it will try to quickload LASS and evaluate `GENERATE`. If slime is not connected, it instead executes a shell command. In order for that to work, the [`lass` binary](https://github.com/Shinmera/LASS/releases) must be in your path. 325 | 326 | If your operating system is not directly supported with a binary, you can build it yourself using a build tool like [Buildapp](http://www.xach.com/lisp/buildapp/), the ASDF system `BINARY-LASS` and the entry-point `BINARY-LASS:CMD-WRAPPER`. 327 | 328 | To generate pretty `.lass` files, set `lass-generate-pretty-p` to `t`. 329 | 330 | ASDF Integration 331 | ---------------- 332 | If you want to compile LASS files to CSS in your systems, you can now (v0.4+) do this via a `lass-file` component type, and `:defsystem-depends-on`-ing LASS. 333 | 334 | (asdf:defsystem my-system 335 | :defsystem-depends-on (:lass) 336 | :components ((:lass-file "test-file"))) 337 | 338 | You can also specify an `:output` argument to a `lass-file` to specify what the target css file should be. 339 | 340 | ## Support 341 | If you'd like to support the continued development of LASS, please consider becoming a backer on Patreon: 342 | 343 | 344 | Patreon 345 | 346 | -------------------------------------------------------------------------------- /asdf.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | (defclass lass-file (asdf:source-file) 4 | ((output :initarg :output :initform NIL :accessor output)) 5 | (:default-initargs :type "lass") 6 | (:documentation "An ASDF source-file component to allow compilation of LASS to CSS in ASDF systems.")) 7 | 8 | ;; Hack to ensure that ASDF recognises the class 9 | ;; as a keyword, which I think is currently a bug. 10 | ;; If LASS is only in DEFSYSTEM-DEPENDS-ON and the 11 | ;; system tries to specify a LASS-FILE component, 12 | ;; ASDF complains about an unknown component type 13 | ;; even though the class exists. Loading LASS and 14 | ;; the system separately however works just fine. 15 | ;; 16 | ;; Since ASDF by default searches classes in 17 | ;; ASDF/INTERFACE we simply smuggle our own class 18 | ;; into that package. Sneaky, but the only sensible 19 | ;; workaround for now. 20 | (defclass asdf/interface::lass-file (lass-file) 21 | ()) 22 | 23 | (defmethod asdf:source-file-type ((c lass-file) (s asdf:module)) "lass") 24 | 25 | (defmethod asdf:output-files ((op asdf:compile-op) (c lass-file)) 26 | (values 27 | (list (merge-pathnames 28 | (or (output c) 29 | (pathname-name (asdf:component-pathname c))) 30 | (make-pathname :type "css" :defaults (asdf:component-pathname c)))) 31 | T)) 32 | 33 | (defmethod asdf:perform ((op asdf:load-op) (c lass-file)) 34 | T) 35 | 36 | (defmethod asdf:perform ((op asdf:compile-op) (c lass-file)) 37 | (lass:generate (asdf:component-pathname c) 38 | :out (first (asdf::output-files op c)))) 39 | -------------------------------------------------------------------------------- /binary-lass.asd: -------------------------------------------------------------------------------- 1 | (defsystem binary-lass 2 | :name "LASS Binary" 3 | :version "0.1.1" 4 | :license "zlib" 5 | :author "Yukari Hafner " 6 | :maintainer "Yukari Hafner " 7 | :description "System to create a binary executable for LASS." 8 | :homepage "https://Shinmera.github.io/LASS/" 9 | :bug-tracker "https://github.com/Shinmera/LASS/issues" 10 | :source-control (:git "https://github.com/Shinmera/LASS.git") 11 | :serial T 12 | :components ((:file "binary")) 13 | :depends-on (:lass)) 14 | -------------------------------------------------------------------------------- /binary.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:binary-lass 2 | (:nicknames #:org.tymoonnext.lass.bianry) 3 | (:use #:cl) 4 | (:export 5 | #:main 6 | #:cmd-wrapper)) 7 | (in-package #:binary-lass) 8 | 9 | (defun main (&optional in out (pretty "false")) 10 | (if in 11 | (let* ((in (uiop:parse-native-namestring in)) 12 | (out (or (when out (uiop:parse-native-namestring out)) 13 | (merge-pathnames (make-pathname :type "css") in))) 14 | (pretty (or (string-equal pretty "true") 15 | (string-equal pretty "T")))) 16 | (lass:generate in :out out :pretty pretty) 17 | (uiop:native-namestring out)) 18 | (format T "Usage: lass LASS-FILE [ OUTPUT-CSS-FILE [ PRETTY-PRINTING ] ] ~%~%LASS v~a ~a~%" 19 | (asdf:component-version (asdf:find-system :LASS)) 20 | (asdf:system-homepage (asdf:find-system :LASS))))) 21 | 22 | (defun cmd-wrapper (&optional args) 23 | (let ((args (or args 24 | #+SBCL *posix-argv* 25 | #+LISPWORKS system:*line-arguments-list* 26 | #+CMU extensions:*command-line-words* 27 | #+CCL ccl:*command-line-argument-list* 28 | NIL))) 29 | (handler-case 30 | (apply #'main (cdr args)) 31 | (error (err) 32 | (format T "ERROR: ~a" err))))) 33 | -------------------------------------------------------------------------------- /compiler.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | (defvar *vars* (make-hash-table) 4 | "Special variable containing LASS-environment variables. 5 | 6 | See the definition of the LET block.") 7 | 8 | (defun read-to-vector (file) 9 | (with-open-file (stream file :element-type '(unsigned-byte 8)) 10 | (let ((length (file-length stream))) 11 | (assert length) 12 | (let ((result (make-array length :element-type '(unsigned-byte 8)))) 13 | (read-sequence result stream) 14 | result)))) 15 | 16 | (defgeneric resolve (thing) 17 | (:documentation "Resolves THING to a value that makes sense for LASS. 18 | 19 | By default the following types are handled: 20 | NULL: NIL 21 | STRING: the THING itself 22 | ARRAY: the variable stored in *VARS* under THING 23 | KEYWORD: Colon-prefixed, downcased symbol-name of THING 24 | SYMBOL: Downcased symbol-name of THING 25 | PATHNAME: If designating an image, base64 encoded inline image data. 26 | T: PRINC-TO-STRING of THING") 27 | (:method ((thing null)) 28 | NIL) 29 | (:method ((thing string)) 30 | thing) 31 | (:method ((thing array)) 32 | (gethash (aref thing 0) *vars*)) 33 | (:method ((thing symbol)) 34 | (if (keywordp thing) 35 | (format NIL ":~a" (string-downcase thing)) 36 | (string-downcase thing))) 37 | (:method ((file pathname)) 38 | (let ((type (mimes:mime-lookup file))) 39 | (if (and (< 5 (length type)) 40 | (string= type "image" :end1 5)) 41 | (format NIL "url('data:~a;base64,~a')" 42 | type (base64:usb8-array-to-base64-string 43 | (read-to-vector file))) 44 | (error "Don't know how to resolve files of type ~a" (or type file))))) 45 | (:method ((thing T)) 46 | (princ-to-string thing))) 47 | 48 | (defun make-property (property &optional value) 49 | "Creates a property object with PROPERTY as its key and VALUE as its value." 50 | (list :property property value)) 51 | 52 | (defun make-block (selector values) 53 | "Creates a block object with SELECTOR and VALUES." 54 | (list* :block (list* :selector selector) values)) 55 | 56 | (defun make-superblock (type selector blocks) 57 | "Creates a block object that can contain other blocks, such as @media, etc." 58 | (list* :superblock type (when selector (list* :selector selector)) blocks)) 59 | 60 | (defgeneric compile-property (key value) 61 | (:documentation "Compile a property of KEY and VALUE to a list of property objects. 62 | By default, the following cases are handled: 63 | 64 | (T LIST) 65 | A list is created with one property object, wherein the property-value is the 66 | Space-concatenated list of RESOLVEd VALUEs. The KEY is DOWNCASEd. 67 | 68 | (T T) 69 | A list is created with one property object, wherein the property-value is the 70 | RESOLVEd VALUE. The KEY is DOWNCASEd. 71 | 72 | 73 | Special handling of properties may occur. 74 | See DEFINE-SPECIAL-PROPERTY") 75 | (:method (key (value list)) 76 | (list (make-property 77 | (string-downcase key) 78 | (format NIL "~{~a~^ ~}" (mapcar #'resolve value))))) 79 | 80 | (:method (key value) 81 | (list (make-property 82 | (string-downcase key) 83 | (resolve value))))) 84 | 85 | ;; THIS IS SOME PRETTY SHODDY MAGIC CODE HERE 86 | ;; BEWARE OF DRAGONS AND ALL THAT 87 | ;; YOU HAVE BEEN WARNED. 88 | ;; 89 | ;; Can't wait for bugs about this to hit me down the line. 90 | (defgeneric compile-constraint (func args) 91 | (:documentation "Compiles a constraint of type FUNC with arguments ARGS to a list of alternative selectors. 92 | By default, the following cases are handled: 93 | 94 | (T T) 95 | Concatenates its ARGS together with spaces. 96 | Preserves OR combinations. 97 | 98 | (NULL NULL) 99 | Returns NIL 100 | 101 | (T NULL) 102 | Returns FUNC 103 | 104 | (:OR T) 105 | Passes all ARGS to COMPILE-SELECTOR individually and then APPENDS 106 | all the results together. 107 | 108 | (:AND T) 109 | Concatenates its ARGS together without spaces. 110 | Preserves OR combinations. 111 | 112 | 113 | Special handling of constraints may occur. 114 | See DEFINE-SPECIAL-SELECTOR.") 115 | (:method (func args) 116 | (let ((cfunc (cond ((listp func) 117 | (if (symbolp (first func)) 118 | (compile-selector func) 119 | func)) 120 | (T (list (resolve func))))) 121 | (cargs (compile-selector (car args)))) 122 | (loop with result = () 123 | for func in cfunc 124 | do (loop for arg in cargs 125 | do (if (and (listp arg) (eql :parent (car arg))) 126 | (dolist (arg (compile-constraint :and (list* func (rest arg)))) 127 | (push arg result)) 128 | (push (list :constraint :combine " " func arg) result))) 129 | finally (return (compile-constraint (nreverse result) (cdr args)))))) 130 | (:method ((func null) (args null)) 131 | NIL) 132 | (:method (func (args null)) 133 | func) 134 | (:method ((func (eql :constraint)) args) 135 | (list (list* func args))) 136 | (:method ((func (eql :or)) args) 137 | (apply #'append (mapcar #'compile-selector args))) 138 | (:method ((func (eql :and)) args) 139 | (when args 140 | (if (cdr args) 141 | (let ((cfunc (compile-selector (first args))) 142 | (cargs (compile-selector (second args)))) 143 | (loop with result = () 144 | for func in cfunc 145 | do (loop for arg in cargs 146 | do (push (list :constraint :combine "" func arg) result)) 147 | finally (return (compile-constraint :and (cons (cons :OR (nreverse result)) (cddr args)))))) 148 | (if (and (listp (first args)) (eql (first (first args)) :OR)) 149 | (rest (first args)) 150 | args)))) 151 | (:method ((func (eql :parent)) args) 152 | (list (list* func args)))) 153 | 154 | (defgeneric compile-selector (selector) 155 | (:documentation "Compiles the SELECTOR form into a list of alternative selectors. 156 | By default, the following cases are handled: 157 | 158 | (NULL) 159 | Returns NIL. 160 | 161 | (LIST) 162 | Calls COMPILE-CONSTRAINT with the SELECTOR's CAR and CDR. 163 | 164 | (T) 165 | Returns a list with the RESOLVEd SELECTOR.") 166 | (:method ((selector null)) 167 | NIL) 168 | (:method ((selector list)) 169 | (compile-constraint (car selector) (cdr selector))) 170 | (:method ((selector T)) 171 | (list (list :constraint :literal (resolve selector))))) 172 | 173 | (defgeneric compile-media-constraint (func args) 174 | (:method (func args) 175 | (loop for prop in (compile-property func args) 176 | collect (list* :constraint prop))) 177 | (:method ((func null) (args null)) 178 | NIL) 179 | (:method (func (args null)) 180 | func) 181 | (:method ((func (eql :constraint)) args) 182 | (list (list* func args))) 183 | (:method ((func (eql :property)) args) 184 | (list (list* :constraint func args))) 185 | (:method ((func (eql :or)) args) 186 | (loop for arg in args 187 | nconc (compile-media-query arg))) 188 | (:method ((func (eql :and)) args) 189 | (cond ((rest args) 190 | (let ((cfunc (compile-media-query (first args))) 191 | (cargs (compile-media-query (second args)))) 192 | (loop with result = () 193 | for func in cfunc 194 | do (loop for arg in cargs 195 | do (push (list :constraint :and func arg) result)) 196 | finally (return (compile-media-constraint :and (list* (list* :or (nreverse result)) 197 | (cddr args))))))) 198 | (args 199 | (compile-media-query (first args))))) 200 | (:method ((func (eql :url)) args) 201 | (list (list* :constraint :func "url" (mapcar #'resolve args)))) 202 | (:method ((func (eql :url-prefix)) args) 203 | (list (list* :constraint :func "url-prefix" (mapcar #'resolve args)))) 204 | (:method ((func (eql :domain)) args) 205 | (list (list* :constraint :func "domain" (mapcar #'resolve args)))) 206 | (:method ((func (eql :regexp)) args) 207 | (list (list* :constraint :func "regexp" (mapcar #'resolve args))))) 208 | 209 | (defgeneric compile-media-query (query) 210 | (:method ((query null)) 211 | NIL) 212 | (:method ((query list)) 213 | (compile-media-constraint (car query) (cdr query))) 214 | (:method ((query T)) 215 | (list (list :constraint :literal (resolve query))))) 216 | 217 | (defgeneric consume-item (item readable-list) 218 | (:documentation "Consumes items from READABLE-LIST as required by the ITEM. 219 | Returned should be two values, the first being a property to compile (or NIL) 220 | and the second a block to compile (or NIL). 221 | By default, the following cases are handled: 222 | 223 | (LIST) 224 | Simply returns the ITEM as a block. 225 | 226 | (SYMBOL) 227 | If it's a keyword, reads until the next keyword or list and returns that as a 228 | property to compile. With property-functions, some sublists may also be read 229 | in automatically. See DEFINE-PROPERTY-FUNCTION. If it is a regular symbol, 230 | CALL-NEXT-METHOD is invoked. 231 | 232 | (T) 233 | Signals an error.") 234 | (:method (thing readable-list) 235 | (error "Don't know what to do with ~s (not part of a property)." thing)) 236 | (:method ((subblock list) readable-list) 237 | (values 238 | NIL 239 | subblock)) 240 | (:method ((property symbol) readable-list) 241 | (if (keywordp property) 242 | (values 243 | (let ((propvals ())) 244 | (loop for (next fullp) = (multiple-value-list (peek readable-list)) 245 | while fullp 246 | do (etypecase next 247 | (keyword (return)) 248 | (list (return)) 249 | (T (push (consume readable-list) propvals)))) 250 | (cons property (nreverse propvals))) 251 | NIL) 252 | (call-next-method))) 253 | (:method ((reference array) readable-list) 254 | (values NIL 255 | `((:parent) ,@(resolve reference))))) 256 | 257 | (defgeneric compile-block (header fields) 258 | (:documentation "Compiles the block with given HEADER and FIELDS list. 259 | By default, the following case is handled: 260 | 261 | (T T) 262 | Blocks are handled in the following way: 263 | The HEADER is used as a selector and compiled through COMPILE-SELECTOR. 264 | Fields are semantically segregated through KEYWORDS and LISTS. 265 | 266 | Every time a KEYWORD is encountered, it is taken as the current property 267 | and all following objects until either a LIST or a KEYWORD is encountered 268 | are gathered as the property's values. 269 | 270 | Every time a LIST is encountered, it is taken as a SUB-BLOCK and is 271 | passed to COMPILE-BLOCK with the HEADER being the current block's 272 | selector prepended to the selector of the sub-block. 273 | 274 | 275 | Special handling of blocks may occur. 276 | See DEFINE-SPECIAL-BLOCK.") 277 | (:method (selector fields) 278 | (let ((selector (compile-selector selector)) 279 | (readable (make-readable-list fields)) 280 | (props ()) 281 | (blocks ())) 282 | ;; compute props and blocks 283 | (flet ((add-prop (prop) 284 | (when prop 285 | (dolist (prop (compile-property (car prop) (cdr prop))) 286 | (push prop props)))) 287 | (add-block (block) 288 | (when block 289 | (dolist (block (compile-block (list selector (car block)) (cdr block))) 290 | (push block blocks))))) 291 | (loop until (empty-p readable) 292 | for (prop block) = (multiple-value-list (consume-item (consume readable) readable)) 293 | do (add-prop prop) 294 | (add-block block))) 295 | ;; Returns list of blocks with ours consed to front 296 | (cons (make-block selector (nreverse props)) 297 | (nreverse blocks))))) 298 | 299 | (defun compile-sheet (&rest blocks) 300 | "Compiles a LASS sheet composed of BLOCKS. 301 | Each BLOCK is passed to COMPILE-BLOCK. The results thereof are appended 302 | together into one list of blocks and properties." 303 | (let ((sheet ())) 304 | (dolist (block blocks) 305 | (dolist (resulting-block (compile-block (car block) (cdr block))) 306 | (push resulting-block sheet))) 307 | (nreverse sheet))) 308 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Lass

lass

0.6.0

Lisp Augmented Style Sheets. Compiles LASS to CSS.

Table of Contents

About LASS

Writing CSS files comes with a lot of repetition and is generally much too verbose. With lispy syntax, shortcuts, and improvements, LASS aims to help you out in writing CSS quick and easy. LASS was largely inspired by SASS.

How To & Examples

LASS supports two modes, one being directly in your lisp code, the other in pure LASS files. Adding LASS into your code is easy:

(lass:compile-and-write
  2 |  '(div
  3 |    :background black))
  4 | 
  5 | "div{
  6 |     background: black;
  7 | }"
  8 | 

LASS works on the following simple principles: A list is a block. The first argument in the list is a selector. The body of the list makes up the properties and sub-blocks. A property is started with a keyword that is used as the property name. Following is a bunch of property arguments until a new keyword, list, or the end is reached. A list inside a block is, again, a block with the twist that the parent block's selector is prepended to the sub-block's selector.

(lass:compile-and-write
  9 |  '(nav
 10 |    (ul
 11 |     :list-style none
 12 |     (li
 13 |      :margin 0 :padding 0
 14 |      :display inline-block)))))
 15 | 
 16 | "nav ul{
 17 |     list-style: none;
 18 | }
 19 | 
 20 | nav ul li{
 21 |     margin: 0;
 22 |     padding: 0;
 23 |     display: inline-block;
 24 | }"
 25 | 

Since LASS' COMPILE-SHEET simply takes a bunch of lists as its argument, you can use the backquote and comma to integrate variables from your lisp environment:

(let ((color "#0088EE"))
 26 |   (lass:compile-and-write
 27 |    `(div
 28 |      :background ,color))))
 29 | 
 30 | "div{
 31 |     background: #0088EE;
 32 | }"
 33 | 

Alternatively however, and this is especially useful in pure LASS files, you can use the LET block to create LASS-specific bindings:

(lass:compile-and-write
 34 |  '(:let ((color "#0088EE"))
 35 |    (div
 36 |     :background #(color))))
 37 | 
 38 | "div{
 39 |     background: #0088EE;
 40 | }"
 41 | 

You can also let bind entire property blocks:

(lass:compile-and-write 42 | '(:let ((colors :background "#0088EE")) 43 | (div #(colors)) 44 | (:media "(prefers-color-scheme: light)" (div #(colors)))))

"div{
 45 |     background: #0088EE;
 46 | }
 47 | 
 48 | @media (prefers-color-scheme: light){
 49 |     div{
 50 |         background: #0088EE;
 51 |     }
 52 | }"
 53 | 

LASS' selector mechanism is very flexible and allows for some complex logic to reduce duplication:

(lass:compile-and-write
 54 |  '(article
 55 |    ((:or p blockquote)
 56 |     :margin 0 :padding 0
 57 | 
 58 |     (a
 59 |      :color black)
 60 | 
 61 |     ((:and a :hover)
 62 |      :color darkred))))
 63 | 
 64 | "article p, article blockquote{
 65 |     margin: 0;
 66 |     padding: 0;
 67 | }
 68 | 
 69 | article p a, article blockquote a{
 70 |     color: black;
 71 | }
 72 | 
 73 | article p a:hover, article blockquote a:hover{
 74 |     color: darkred;
 75 | }"
 76 | 

But it can go even further:

(lass:compile-and-write
 77 |  '((:and
 78 |     (:or article section)
 79 |     (:= data-author (:or yukari ran chen))
 80 |     (:nth-child (:or 1 2 3)))
 81 |    :display none))
 82 | 
 83 | "article[data-author=\"yukari\"]:nth-child(1),
 84 |  article[data-author=\"yukari\"]:nth-child(2),
 85 |  article[data-author=\"yukari\"]:nth-child(3),
 86 |  article[data-author=\"ran\"]:nth-child(1),
 87 |  article[data-author=\"ran\"]:nth-child(2),
 88 |  article[data-author=\"ran\"]:nth-child(3),
 89 |  article[data-author=\"chen\"]:nth-child(1),
 90 |  article[data-author=\"chen\"]:nth-child(2),
 91 |  article[data-author=\"chen\"]:nth-child(3),
 92 |  section[data-author=\"yukari\"]:nth-child(1),
 93 |  section[data-author=\"yukari\"]:nth-child(2),
 94 |  section[data-author=\"yukari\"]:nth-child(3),
 95 |  section[data-author=\"ran\"]:nth-child(1),
 96 |  section[data-author=\"ran\"]:nth-child(2),
 97 |  section[data-author=\"ran\"]:nth-child(3),
 98 |  section[data-author=\"chen\"]:nth-child(1),
 99 |  section[data-author=\"chen\"]:nth-child(2),
100 |  section[data-author=\"chen\"]:nth-child(3){
101 |     display: none;
102 | }"
103 | 

Whoa nelly!

If you ever need to expand a selector into a parent block, for instance to specialise on different classes of the block, you can use the parent pseudo-selector:

(lass:compile-and-write
104 |  '((:or article section)
105 |    :background black
106 |    ((:parent .bright)
107 |     :background white)))
108 | 
109 | "article,
110 | section{
111 |     background: black;
112 | }
113 | 
114 | article.bright,
115 | section.bright{
116 |     background: white;
117 | }"
118 | 

Some CSS properties are not fully specified yet and require browser-specific prefixes. LASS can help you with that, too:

(lass:compile-and-write
119 |  '(.fun
120 |    :linear-gradient "deg(45)" black 0% darkgray 100%
121 |    :transform rotate -45deg))
122 | 
123 | ".fun{
124 |     background: -moz-linear-gradient(deg(45), black 0%, darkgray 100%);
125 |     background: -o-linear-gradient(deg(45), black 0%, darkgray 100%);
126 |     background: -webkit-linear-gradient(deg(45), black 0%, darkgray 100%);
127 |     background: -ms-linear-gradient(deg(45), black 0%, darkgray 100%);
128 |     background: linear-gradient(deg(45), black 0%, darkgray 100%);
129 |     -moz-transform: rotate(-45deg);
130 |     -o-transform: rotate(-45deg);
131 |     -webkit-transform: rotate(-45deg);
132 |     -ms-transform: rotate(-45deg);
133 |     transform: rotate(-45deg);
134 | }"
135 | 

LASS also supports the various @QUERY operator blocks:

(lass:compile-and-write
136 |  '(:media "(max-width: 800px)"
137 |    (div
138 |     :margin 0)))
139 | 
140 | "@media (max-width: 800px){
141 |     div{
142 |         margin: 0;
143 |     }
144 | }"
145 | 

By default LASS activates pretty-printing and inserts newlines and spaces where appropriate in order to make the result readable and easy to debug. However, you can also deactivate that and directly produce minified CSS:

(let ((lass:*pretty* NIL))
146 |   (lass:compile-and-write
147 |    '(:media "(max-width: 800px)"
148 |      (div
149 |       :margin 0))))
150 | 
151 | "@media (max-width: 800px){div{margin:0;}}"
152 | 

As mentioned above you can write pure LASS files to compile down to a CSS file. To do that, simply use GENERATE:

generate-example

Blocks

Each block in a LASS sheet consists of a list containing a selector followed by one or more properties or sub-blocks.

(selector [property | block]*)
153 | 

Selectors

The following list contains examples for the various uses of selectors.

  • Any element
    *
  • An element with tag-name e
    e
  • An element with tag-name e or f
    (:or e f)
  • An e element with the :link pseudo-selector
    (:and e :link)
  • The first formatted line of an e element
    (:and e |::first-line|) or (:and e "::first-line")
  • An e element with a "warning" class
    e.warning
  • An e element with ID equal to warning
    |e#warning| or "e#warning"
  • An e element with a foo attribute
    e[foo]
  • An e element whose foo attribute value is exactly equal to bar
    (:and :a (:= foo "bar"))
  • An e element whose foo attribute value is a list of whitespace-separated values, one of which is exactly equal to bar
    (:and :a (:~= foo "bar"))
  • An e element whose foo attribute has a hyphen-separated list of values beginning (from the left) with bar
    (:and :a (:/= foo "bar"))
  • An e element whose foo attribute value begins exactly with the string bar
    (:and :a (:^= foo "bar"))
  • An e element whose foo attribute value ends exactly with the string bar
    (:and :a (:$= foo "bar"))
  • An e element whose foo attribute value contains the substring bar
    (:and :a (:*= foo "bar"))
  • An e element that matches the pseudo-selector nth-child(2)
    (e (:nth-child 2))
  • An f element preceded by an e element
    (e ~ f)
  • An f element immediately precede by an e element
    (e + f)
  • An f element which is a descendant of e
    (e f)
  • An f element which is a direct descendant of e
    (e > f)

Selector Combinations

As illustrated briefly above, LASS includes two combinators for selectors, :and and :or. These combinators are combinatoric, meaning that all possible combinations are explored. Consider the following selector:

((foo (:and a .title (:or :active :hover)) (:or span div)))
154 | 

Enumerating all possible answers to this combination would result in the following list

foo a.title:active span
155 | foo a.title:active div
156 | foo a.title:hover span
157 | foo a.title:hover div
158 | 

The number of possible combinations can quickly explode in size the more options are available. This means that for complex relations and expressions, LASS can be extremely concise. Note that combinators are available at any position in a selector, this includes the arguments of a pseudo-selector like :nth-child.

Properties

A property consists of a keyword symbol and a sequence of values. The values to a property are gathered up until either a non-value list or a new keyword is encountered. Originally it stopped as soon as a list was encountered, but this behaviour was changed and specially recognised lists are integrated to allow a more native look for certain values like colours, urls, and so on. Certain properties are specifically declared and will error if they are passed the wrong number or invalid kind of values. For most however, LASS will just blindly put things into the CSS file as you give them. It is up to you to make sure that the values are valid.

:text-style underline
159 | :color (rgb 212 112 30)
160 | :background (url "/foo")
161 | :border 1px solid black
162 | 

Certain properties currently still require vendor-specific declarations. LASS tries to do that automatically for you, but it also needs to know about these declarations and as such, they need to be manually added. Some of the more common ones are included in LASS by default, but if you encounter one that isn't, you are welcome to send a pull request (see Extending LASS on how to do it).

Sub-Blocks

A block can contain other blocks. These sub-blocks are recursively flattened into the structure by simply prepending the selector of the parent block. Thus

(foo (bar (baz) (bam)))
163 | 

Is equivalent to

(foo) ((foo bar)) ((foo bar baz)) ((foo bar bam))
164 | 

Allowing this kind of nesting allows you to more closely mirror the structure present in your HTML file that you want to style. Combining this with the selector combinations, this system allows reducing code duplication a lot.

Special Blocks

In CSS3 there are special properties and blocks that are preceded by an @ symbol. The most well-known examples therefore are probably @include and @media. LASS implements all of these special blocks by a keyword symbol equivalent selector. Therefore the above two would translate to the following in LASS.

(:include (url "foo"))
165 | (:media "(max-width: 800px)"
166 |  (foo))
167 | 

Variables

Often times it is useful to define variables that you can use within your style so that colours and fonts can quickly be exchanged. LASS allows you to do that too using the :let directive and by abusing the vector type. It is probably best illustrated using an example:

(:let ((foo "#0088EE"))
168 |   ((a:active) :color #(foo)))
169 | 

Extending LASS

Pretty much every part of LASS is extensible through methods. Most useful will however probably be the DEFINE-SPECIAL-PROPERTY, DEFINE-BROWSER-PROPERTY and DEFINE-SPECIAL-SELECTOR helper-macros. Here's some examples from the SPECIAL.LISP file that defines some standard special handlers:

(define-special-property font-family (&rest faces)
170 |   (list (make-property "font-family" (format NIL "~{~a~^, ~}" (mapcar #'resolve faces)))))
171 | 
172 | (define-browser-property linear-gradient (direction &rest colors)
173 |   (:default (property)
174 |     (make-property "background" (format NIL "~a(~a~{, ~a ~a~})"
175 |                                          property (resolve direction) (mapcar #'resolve colors)))))
176 | 

For more control, have a look at the various COMPILE-* generic functions.

Emacs Support

LASS includes a tiny elisp file, lass.el. Add LASS' directory to your emacs LOAD-PATH and REQUIRE lass.

(add-to-list 'load-path "[path-to-lass-source-dir]/")
177 | (require 'lass)
178 | 

Once you visit a .lass file, it will automatically start in the LASS major-mode, which is a derived-mode from COMMON-LISP-MODE. Whenever you save, it will automatically try to compile the lass file to its CSS equivalent. If slime is connected, it will try to quickload LASS and evaluate GENERATE. If slime is not connected, it instead executes a shell command. In order for that to work, the lass binary must be in your path.

If your operating system is not directly supported with a binary, you can build it yourself using a build tool like Buildapp, the ASDF system BINARY-LASS and the entry-point BINARY-LASS:CMD-WRAPPER.

To generate pretty .lass files, set lass-generate-pretty-p to t.

ASDF Integration

If you want to compile LASS files to CSS in your systems, you can now (v0.4+) do this via a lass-file component type, and :defsystem-depends-on-ing LASS.

(asdf:defsystem my-system
179 |   :defsystem-depends-on (:lass)
180 |   :components ((:lass-file "test-file")))
181 | 

You can also specify an :output argument to a lass-file to specify what the target css file should be.

Support

If you'd like to support the continued development of LASS, please consider becoming a backer on Patreon:

Patreon

System Information

0.6.0
Yukari Hafner
zlib

Definition Index

  • LASS

    • ORG.TYMOONNEXT.LASS
    No documentation provided.
    • EXTERNAL SPECIAL-VARIABLE

      *INDENT-LEVEL*

          Source
          Directs the current amount of spaces used to indent.
        • EXTERNAL SPECIAL-VARIABLE

          *INDENT-SPACES*

              Source
              Specifies the number of spaces to use for indentation.
            • EXTERNAL SPECIAL-VARIABLE

              *PRETTY*

                  Source
                  Directs whether to pretty-print using whitespace or not.
                • EXTERNAL SPECIAL-VARIABLE

                  *VARS*

                      Source
                      Special variable containing LASS-environment variables.
                      182 | 
                      183 | See the definition of the LET block.
                    • EXTERNAL CLASS

                      LASS-FILE

                          Source
                          An ASDF source-file component to allow compilation of LASS to CSS in ASDF systems.
                        • EXTERNAL FUNCTION

                          COMPILE-SHEET

                            • &REST
                            • BLOCKS
                            • &REST
                            Source
                            Compiles a LASS sheet composed of BLOCKS.
                            184 | Each BLOCK is passed to COMPILE-BLOCK. The results thereof are appended
                            185 | together into one list of blocks and properties.
                          • EXTERNAL FUNCTION

                            GENERATE

                              • IN
                              • &KEY
                              • OUT
                              • PRETTY
                              • IF-EXISTS
                              • &REST
                              Source
                              Generate a CSS file from a LASS file.
                              186 | 
                              187 | IN        --- The LASS input file. Has to be READable.
                              188 | OUT       --- The target file, by default a file of same location and name, but with CSS type.
                              189 | PRETTY    --- Whether to minify or not. See WRITE-SHEET.
                              190 | IF-EXISTS --- See WITH-OPEN-FILE
                              191 | 
                              192 | Returns OUT
                            • EXTERNAL FUNCTION

                              INDENT

                                  Source
                                  Returns a string of the appropriate number of spaces depending on *PRETTY* and *INDENT-LEVEL*
                                • EXTERNAL FUNCTION

                                  MAKE-BLOCK

                                    • SELECTOR
                                    • VALUES
                                    • &REST
                                    Source
                                    Creates a block object with SELECTOR and VALUES.
                                  • EXTERNAL FUNCTION

                                    MAKE-PROPERTY

                                      • PROPERTY
                                      • &OPTIONAL
                                      • VALUE
                                      • &REST
                                      Source
                                      Creates a property object with PROPERTY as its key and VALUE as its value.
                                    • EXTERNAL FUNCTION

                                      PROPERTY-FUNCTION

                                        • NAME
                                        • &REST
                                        Source
                                        Returns a function to process a property function of NAME, if any.
                                      • EXTERNAL FUNCTION

                                        RESOLVE-FUNCTION

                                          • FUNCTION
                                          • &REST
                                          • ARGS
                                          • &REST
                                          Source
                                          Turns the FUNCTION with its ARGS into a properly usable property value.
                                        • EXTERNAL FUNCTION

                                          WRITE-SHEET

                                            • SHEET
                                            • &KEY
                                            • STREAM
                                            • PRETTY
                                            • &REST
                                            Source
                                            Writes the compiled SHEET object to STREAM.
                                            193 | If PRETTY is non-NIL, spaces and newlines are inserted as appropriate
                                            194 | in order to create a human-readable stylesheet. Otherwise whitespace is
                                            195 | only used where necessary, producing a minified version.
                                            196 | 
                                            197 | STREAM can be a STREAM, T for *STANDARD-OUTPUT*, or NIL for a STRING.
                                          • EXTERNAL FUNCTION

                                            WRITE-SHEET-PART

                                              • STREAM
                                              • BLOCK
                                              • CP
                                              • AP
                                              • &REST
                                              Source
                                              Wrapper around WRITE-SHEET-OBJECT so that we can call it from FORMAT.
                                              198 | Calls WRITE-SHEET-OBJECT with (CAR BLOCK) (CDR BLOCK) STREAM.
                                            • EXTERNAL GENERIC-FUNCTION

                                              COMPILE-BLOCK

                                                • HEADER
                                                • FIELDS
                                                • &REST
                                                Source
                                                Compiles the block with given HEADER and FIELDS list.
                                                199 | By default, the following case is handled:
                                                200 | 
                                                201 |  (T T)
                                                202 | Blocks are handled in the following way:
                                                203 | The HEADER is used as a selector and compiled through COMPILE-SELECTOR.
                                                204 | Fields are semantically segregated through KEYWORDS and LISTS.
                                                205 | 
                                                206 | Every time a KEYWORD is encountered, it is taken as the current property
                                                207 | and all following objects until either a LIST or a KEYWORD is encountered
                                                208 | are gathered as the property's values.
                                                209 | 
                                                210 | Every time a LIST is encountered, it is taken as a SUB-BLOCK and is
                                                211 | passed to COMPILE-BLOCK with the HEADER being the current block's
                                                212 | selector prepended to the selector of the sub-block.
                                                213 | 
                                                214 | 
                                                215 | Special handling of blocks may occur. 
                                                216 | See DEFINE-SPECIAL-BLOCK.
                                              • EXTERNAL GENERIC-FUNCTION

                                                COMPILE-CONSTRAINT

                                                  • FUNC
                                                  • ARGS
                                                  • &REST
                                                  Source
                                                  Compiles a constraint of type FUNC with arguments ARGS to a list of alternative selectors.
                                                  217 | By default, the following cases are handled:
                                                  218 | 
                                                  219 |  (T T)
                                                  220 | Concatenates its ARGS together with spaces.
                                                  221 | Preserves OR combinations.
                                                  222 | 
                                                  223 |  (NULL NULL)
                                                  224 | Returns NIL
                                                  225 | 
                                                  226 |  (T NULL)
                                                  227 | Returns FUNC
                                                  228 | 
                                                  229 |  (:OR T)
                                                  230 | Passes all ARGS to COMPILE-SELECTOR individually and then APPENDS
                                                  231 | all the results together.
                                                  232 | 
                                                  233 |  (:AND T)
                                                  234 | Concatenates its ARGS together without spaces.
                                                  235 | Preserves OR combinations.
                                                  236 | 
                                                  237 | 
                                                  238 | Special handling of constraints may occur.
                                                  239 | See DEFINE-SPECIAL-SELECTOR.
                                                • EXTERNAL GENERIC-FUNCTION

                                                  COMPILE-PROPERTY

                                                    • KEY
                                                    • VALUE
                                                    • &REST
                                                    Source
                                                    Compile a property of KEY and VALUE to a list of property objects.
                                                    240 | By default, the following cases are handled:
                                                    241 | 
                                                    242 |  (T LIST)
                                                    243 | A list is created with one property object, wherein the property-value is the
                                                    244 | Space-concatenated list of RESOLVEd VALUEs. The KEY is DOWNCASEd.
                                                    245 | 
                                                    246 |  (T T)
                                                    247 | A list is created with one property object, wherein the property-value is the
                                                    248 | RESOLVEd VALUE. The KEY is DOWNCASEd.
                                                    249 | 
                                                    250 | 
                                                    251 | Special handling of properties may occur.
                                                    252 | See DEFINE-SPECIAL-PROPERTY
                                                  • EXTERNAL GENERIC-FUNCTION

                                                    COMPILE-SELECTOR

                                                      • SELECTOR
                                                      • &REST
                                                      Source
                                                      Compiles the SELECTOR form into a list of alternative selectors.
                                                      253 | By default, the following cases are handled:
                                                      254 | 
                                                      255 |  (NULL)
                                                      256 | Returns NIL.
                                                      257 | 
                                                      258 |  (LIST)
                                                      259 | Calls COMPILE-CONSTRAINT with the SELECTOR's CAR and CDR.
                                                      260 | 
                                                      261 |  (T)
                                                      262 | Returns a list with the RESOLVEd SELECTOR.
                                                    • EXTERNAL GENERIC-FUNCTION

                                                      CONSUME-ITEM

                                                        • ITEM
                                                        • READABLE-LIST
                                                        • &REST
                                                        Source
                                                        Consumes items from READABLE-LIST as required by the ITEM.
                                                        263 | Returned should be two values, the first being a property to compile (or NIL)
                                                        264 | and the second a block to compile (or NIL).
                                                        265 | By default, the following cases are handled:
                                                        266 | 
                                                        267 |  (LIST)
                                                        268 | Simply returns the ITEM as a block.
                                                        269 | 
                                                        270 |  (SYMBOL)
                                                        271 | If it's a keyword, reads until the next keyword or list and returns that as a
                                                        272 | property to compile. With property-functions, some sublists may also be read
                                                        273 | in automatically. See DEFINE-PROPERTY-FUNCTION. If it is a regular symbol,
                                                        274 | CALL-NEXT-METHOD is invoked.
                                                        275 | 
                                                        276 |  (T)
                                                        277 | Signals an error.
                                                      • EXTERNAL GENERIC-FUNCTION

                                                        RESOLVE

                                                          • THING
                                                          • &REST
                                                          Source
                                                          Resolves THING to a value that makes sense for LASS.
                                                          278 | 
                                                          279 | By default the following types are handled:
                                                          280 | NULL:     NIL
                                                          281 | STRING:   the THING itself
                                                          282 | ARRAY:    the variable stored in *VARS* under THING
                                                          283 | KEYWORD:  Colon-prefixed, downcased symbol-name of THING
                                                          284 | SYMBOL:   Downcased symbol-name of THING
                                                          285 | PATHNAME: If designating an image, base64 encoded inline image data.
                                                          286 | T:        PRINC-TO-STRING of THING
                                                        • EXTERNAL GENERIC-FUNCTION

                                                          WRITE-SHEET-OBJECT

                                                            • TYPE
                                                            • OBJECT
                                                            • STREAM
                                                            • &REST
                                                            Source
                                                            Writes the OBJECT of type TYPE to STREAM.
                                                            287 | 
                                                            288 | By default the following TYPEs are handled:
                                                            289 |  :BLOCK (SELECTOR-LIST OBJECTS*)
                                                            290 | Prints the SELECTOR-LIST separated by commas, followed by an opening brace
                                                            291 | and the printed list of OBJECTS using WRITE-SHEET-PART. Finally the body is
                                                            292 | closed off with a closing brace. Newlines and spaces may be inserted where
                                                            293 | necessary if *PRETTY* is non-NIL.
                                                            294 | 
                                                            295 |  :PROPERTY (KEY VALUE)
                                                            296 | Prints the KEY. If VALUE is non-NIL, a colon is printed followed by the
                                                            297 | VALUE. Finally a semicolon is printed. Spaces may be inserted where necessary
                                                            298 | if *PRETTY* is non-NIL.
                                                          • EXTERNAL MACRO

                                                            DEFINE-BROWSER-PROPERTY

                                                              • NAME
                                                              • ARGS
                                                              • &BODY
                                                              • BROWSER-OPTIONS
                                                              • &REST
                                                              Source
                                                              Helper macro to define properties that have browser-dependant versions.
                                                              299 | 
                                                              300 | NAME            --- The base name of the property name or value.
                                                              301 | ARGS            --- Property arguments, see DEFINE-SPECIAL-PROPERTY.
                                                              302 | BROWSER-OPTIONS ::= (OPTION (symbol) FORM*)
                                                              303 | OPTION          ::= :MOZ | :O | :WEBKIT | :MS | :W3C | :DEFAULT
                                                              304 | 
                                                              305 | Each browser-option body should return a single property. The SYMBOL
                                                              306 | in the option definition is bound to the computed property name
                                                              307 |  (eg -moz-NAME for the :MOZ option).
                                                              308 | You can define special handling of the browsers by defining options
                                                              309 | specifically for them. If no handling is defined, the DEFAULT option
                                                              310 | is used as a fallback.
                                                            • EXTERNAL MACRO

                                                              DEFINE-PRIMITIVE-PROPERTY-CONSUMER

                                                                • SPECIALIZER
                                                                  • PROPVALS
                                                                  • READABLE
                                                                  • NEXT
                                                                  • &REST
                                                                • &BODY
                                                                • LOOP-BODY
                                                                • &REST
                                                                Source
                                                                Defines a CONSUME-ITEM method for the given item SPECIALIZER.
                                                                311 | 
                                                                312 | SPECIALIZER --- The method specializer for the item.
                                                                313 | PROPVALS    --- The list that should contain the property values.
                                                                314 | READABLE    --- The readable-list being operated on currently.
                                                                315 | NEXT        --- Bound to the next (unconsumed) item in the readable-list.
                                                                316 | LOOP-BODY   --- The body of the reading loop to execute until the readable is empty.
                                                                317 | 
                                                                318 | The return value of the loop-body is discarded. You can use (RETURN) to exit the loop,
                                                                319 | for example for when you encounter an item you don't want to read.
                                                              • EXTERNAL MACRO

                                                                DEFINE-PROPERTY-FUNCTION

                                                                  • NAME
                                                                  • ARGS
                                                                  • &BODY
                                                                  • BODY
                                                                  • &REST
                                                                  Source
                                                                  Define a new property function NAME, accepting ARGS. 
                                                                  320 | The body should return a value to use directly, if possible a string.
                                                                  321 | The results of a property-function should not be RESOVLEd.
                                                                  322 | 
                                                                  323 | Property functions are function calls that occur as (part of) the
                                                                  324 | value of a property. Due to ambiguity issues with a general sub-block,
                                                                  325 | property functions need to be explicitly defined and may completely
                                                                  326 | differ depending on the property. Property functions defined with this
                                                                  327 | are only the defaults available for all properties. If you want to
                                                                  328 | minimise collision probability or avoid an illegal function for a
                                                                  329 | certain property, you should define a direct method on CONSUME-ITEM
                                                                  330 | to handle the reading of the property values manually.
                                                                • EXTERNAL MACRO

                                                                  DEFINE-PROPERTY-FUNCTION-CASE

                                                                    • PROPERTY
                                                                      • ARGS
                                                                      • &REST
                                                                    • &BODY
                                                                    • FUNCTION-CLAUSES
                                                                    • &REST
                                                                    Source
                                                                    Defines a CONSUME-ITEM method for PROPERTY that has special handling for property-functions.
                                                                    331 | 
                                                                    332 | FUNCTION-CLAUSES ::= function-clause*
                                                                    333 | FUNCTION-CLAUSE  ::= (function-name form*)
                                                                    334 | 
                                                                    335 | Each function-name is compared by STRING-EQUAL and each clause should return the
                                                                    336 | property-value to use in its place, or NIL if it should be skipped.
                                                                    337 | 
                                                                    338 | You can use (RETURN) in a clause body to stop reading values altogether.
                                                                  • EXTERNAL MACRO

                                                                    DEFINE-SIMPLE-PROPERTY-FUNCTIONS

                                                                      • PROPERTY
                                                                      • &REST
                                                                      • FUNCSPECS
                                                                      • &REST
                                                                      Source
                                                                      Defines a CONSUME-ITEM method for PROPERTY that has special handling for property-functions.
                                                                      340 | 
                                                                      341 | FUNCSPECS ::= funcspec*
                                                                      342 | FUNCSPEC  ::= (funcname arg* [&optional arg*] [&key arg*])
                                                                      343 | 
                                                                      344 | See DEFINE-PROPERTY-FUNCTION-CASE.
                                                                    • EXTERNAL MACRO

                                                                      DEFINE-SPECIAL-BLOCK

                                                                        • NAME
                                                                        • ARGS
                                                                        • &BODY
                                                                        • BODY
                                                                        • &REST
                                                                        Source
                                                                        Define handling of a special block type.
                                                                        345 | In order for the block to be recognised, it has to begin with the NAME as a keyword.
                                                                        346 | The ARGS is a lambda-list that parses the block contents.
                                                                        347 | 
                                                                        348 | Expected as a return value is a /list/ of either attributes or blocks.
                                                                        349 | 
                                                                        350 | See COMPILE-BLOCK
                                                                      • EXTERNAL MACRO

                                                                        DEFINE-SPECIAL-PROPERTY

                                                                          • NAME
                                                                          • ARGS
                                                                          • &BODY
                                                                          • BODY
                                                                          • &REST
                                                                          Source
                                                                          Define handling of a special property.
                                                                          351 | The ARGS is a lambda-list that parses the arguments passed to the property.
                                                                          352 | 
                                                                          353 | Expected as a return value is a /list/ of either attributes or blocks.
                                                                          354 | 
                                                                          355 | See COMPILE-PROPERTY
                                                                        • EXTERNAL MACRO

                                                                          DEFINE-SPECIAL-SELECTOR

                                                                            • NAME
                                                                            • ARGS
                                                                            • &BODY
                                                                            • BODY
                                                                            • &REST
                                                                            Source
                                                                            Define handling of a special selector type.
                                                                            356 | In order for the selector to be recognised, it has to begin with the NAME as a keyword.
                                                                            357 | The ARGS is a lambda-list that parses the selector contents.
                                                                            358 | 
                                                                            359 | Expected as a return value is a /list/ of alternate versions of selectors.
                                                                            360 | 
                                                                            361 | See COMPILE-CONSTRAINT.
                                                                        -------------------------------------------------------------------------------- /generate-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | readonly BUILDAPP=/usr/bin/buildapp 4 | readonly SOURCE_TREE=~/quicklisp/local-projects/ 5 | 6 | $BUILDAPP --output lass \ 7 | --entry "BINARY-LASS:CMD-WRAPPER" \ 8 | --load-system "binary-lass" \ 9 | --asdf-tree $SOURCE_TREE \ 10 | --compress-core 11 | -------------------------------------------------------------------------------- /lass.asd: -------------------------------------------------------------------------------- 1 | (defsystem lass 2 | :name "LASS" 3 | :version "0.6.0" 4 | :license "zlib" 5 | :author "Yukari Hafner " 6 | :maintainer "Yukari Hafner " 7 | :description "Lisp Augmented Style Sheets. Compiles LASS to CSS." 8 | :homepage "https://Shinmera.github.io/LASS/" 9 | :bug-tracker "https://github.com/Shinmera/LASS/issues" 10 | :source-control (:git "https://github.com/Shinmera/LASS.git") 11 | :serial T 12 | :components ((:file "package") 13 | (:file "readable-list") 14 | (:file "compiler") 15 | (:file "property-funcs") 16 | (:file "writer") 17 | (:file "lass") 18 | (:file "special") 19 | (:file "asdf")) 20 | :depends-on (:trivial-indent 21 | :trivial-mimes 22 | :cl-base64)) 23 | -------------------------------------------------------------------------------- /lass.el: -------------------------------------------------------------------------------- 1 | ;; This file is a part of LASS 2 | ;; (c) 2014 TymoonNET/NexT http://tymoon.eu (shinmera@tymoon.eu) 3 | ;; Author: Nicolas Hafner 4 | 5 | (defcustom lass-generate-pretty-p nil 6 | "Whether to generate pretty .lass files." 7 | :type 'boolean) 8 | 9 | (defun lass-compile-current () 10 | (interactive) 11 | (or 12 | (when (and (and (fboundp 'slime-connected-p) (slime-connected-p)) 13 | (or (slime-eval '(cl:not (cl:null (cl:find-package :lass)))) 14 | (and (slime-eval '(cl:not (cl:null (cl:find-package :ql)))) 15 | (slime-eval '(ql:quickload :lass))))) 16 | (message "LASS compiled to %s" 17 | (slime-eval `(uiop:native-namestring 18 | (lass:generate 19 | (uiop:parse-native-namestring ,(buffer-file-name)) 20 | :pretty ,lass-generate-pretty-p))))) 21 | (when (and (and (fboundp 'sly-connected-p) (sly-connected-p)) 22 | (or (sly-eval '(cl:not (cl:null (cl:find-package :lass)))) 23 | (and (sly-eval '(cl:not (cl:null (cl:find-package :ql)))) 24 | (sly-eval '(ql:quickload :lass))))) 25 | (message "LASS compiled to %s" 26 | (sly-eval `(uiop:native-namestring 27 | (lass:generate 28 | (uiop:parse-native-namestring ,(buffer-file-name)) 29 | :pretty ,lass-generate-pretty-p))))) 30 | (message "LASS compiled. %s" (shell-command-to-string (format "lass %s" (shell-quote-argument (buffer-file-name))))))) 31 | 32 | (define-derived-mode lass-mode common-lisp-mode 33 | "LASS" "Mode with auto-compiling for LASS files." 34 | (add-hook 'after-save-hook 'lass-compile-current nil t)) 35 | 36 | (add-to-list 'auto-mode-alist '("\\.lass\\'" . lass-mode)) 37 | 38 | (provide 'lass) 39 | -------------------------------------------------------------------------------- /lass.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | (defmacro define-special-block (name args &body body) 4 | "Define handling of a special block type. 5 | In order for the block to be recognised, it has to begin with the NAME as a keyword. 6 | The ARGS is a lambda-list that parses the block contents. 7 | 8 | Expected as a return value is a /list/ of either attributes or blocks. 9 | 10 | See COMPILE-BLOCK" 11 | (let ((argsym (gensym "ARGS"))) 12 | `(defmethod compile-block ((,(gensym "TYPE") (eql ,(intern (string name) "KEYWORD"))) ,argsym) 13 | (destructuring-bind ,args ,argsym 14 | ,@body)))) 15 | 16 | (defmacro define-special-property (name args &body body) 17 | "Define handling of a special property. 18 | The ARGS is a lambda-list that parses the arguments passed to the property. 19 | 20 | Expected as a return value is a /list/ of either attributes or blocks. 21 | 22 | See COMPILE-PROPERTY" 23 | (let ((argsym (gensym "ARGS"))) 24 | `(defmethod compile-property ((,(gensym "ATTR") (eql ,(intern (string name) "KEYWORD"))) ,argsym) 25 | (destructuring-bind ,args ,argsym 26 | ,@body)))) 27 | 28 | (defmacro define-special-selector (name args &body body) 29 | "Define handling of a special selector type. 30 | In order for the selector to be recognised, it has to begin with the NAME as a keyword. 31 | The ARGS is a lambda-list that parses the selector contents. 32 | 33 | Expected as a return value is a /list/ of alternate versions of selectors. 34 | 35 | See COMPILE-CONSTRAINT." 36 | (let ((argsym (gensym "ARGS"))) 37 | `(defmethod compile-constraint ((,(gensym "FUNC") (eql ,(intern (string name) "KEYWORD"))) ,argsym) 38 | (destructuring-bind ,args ,argsym 39 | ,@body)))) 40 | 41 | (defun generate (in &key (out (merge-pathnames (make-pathname :type "css") in)) (pretty NIL) (if-exists :supersede)) 42 | "Generate a CSS file from a LASS file. 43 | 44 | IN --- The LASS input file. Has to be READable. 45 | OUT --- The target file, by default a file of same location and name, but with CSS type. 46 | PRETTY --- Whether to minify or not. See WRITE-SHEET. 47 | IF-EXISTS --- See WITH-OPEN-FILE 48 | 49 | Returns OUT" 50 | (let ((eof (gensym "EOF"))) 51 | (with-open-file (outstream out :direction :output :if-exists if-exists) 52 | (write-sheet 53 | (apply #'compile-sheet 54 | (with-open-file (instream in :direction :input) 55 | (loop for read = (read instream NIL eof) 56 | until (eql read eof) 57 | collect read))) 58 | :stream outstream :pretty pretty)) 59 | out)) 60 | 61 | (defun compile-and-write (&rest forms) 62 | "Shortcut for (WRITE-SHEET (COMPILE-SHEET FORMS*))" 63 | (write-sheet 64 | (apply #'compile-sheet 65 | forms))) 66 | -------------------------------------------------------------------------------- /package.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:LASS 2 | (:nicknames #:org.tymoonnext.lass) 3 | (:use #:cl) 4 | ;; asdf.lisp 5 | (:export 6 | #:lass-file) 7 | ;; compiler.lisp 8 | (:export 9 | #:*vars* 10 | #:resolve 11 | #:make-property 12 | #:make-block 13 | #:compile-property 14 | #:compile-constraint 15 | #:compile-selector 16 | #:consume-item 17 | #:compile-block 18 | #:compile-sheet) 19 | ;; lass.lisp 20 | (:export 21 | #:define-special-block 22 | #:define-special-property 23 | #:define-special-selector 24 | #:generate 25 | #:compile-and-write) 26 | ;; property-funcs.lisp 27 | (:export 28 | #:property-function 29 | #:remove-property-function 30 | #:define-property-function 31 | #:define-simple-property-function 32 | #:resolve-function 33 | #:define-primitive-property-consumer 34 | #:define-property-function-case 35 | #:define-simple-property-functions) 36 | ;; special.lisp 37 | (:export 38 | #:define-single-arg-selector 39 | #:define-browser-property) 40 | ;; writer.lisp 41 | (:export 42 | #:*pretty* 43 | #:*indent-level* 44 | #:*indent-spaces* 45 | #:indent 46 | #:write-sheet-object 47 | #:write-sheet-part 48 | #:write-sheet)) 49 | -------------------------------------------------------------------------------- /property-funcs.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | (defvar *property-functions* (make-hash-table :test 'equalp)) 4 | 5 | (defun property-function (name) 6 | "Returns a function to process a property function of NAME, if any." 7 | (gethash (string name) *property-functions*)) 8 | 9 | (defun (setf property-function) (function name) 10 | "Sets FUNCTION as the new processor for the property function NAME." 11 | (setf (gethash (string name) *property-functions*) 12 | function)) 13 | 14 | (defun remove-property-function (name) 15 | "Removes the property function NAME." 16 | (remhash (string name) *property-functions*)) 17 | 18 | (defmacro define-property-function (name args &body body) 19 | "Define a new property function NAME, accepting ARGS. 20 | The body should return a value to use directly, if possible a string. 21 | The results of a property-function should not be RESOVLEd. 22 | 23 | Property functions are function calls that occur as (part of) the 24 | value of a property. Due to ambiguity issues with a general sub-block, 25 | property functions need to be explicitly defined and may completely 26 | differ depending on the property. Property functions defined with this 27 | are only the defaults available for all properties. If you want to 28 | minimise collision probability or avoid an illegal function for a 29 | certain property, you should define a direct method on CONSUME-ITEM 30 | to handle the reading of the property values manually." 31 | `(setf (property-function ,(string name)) 32 | #'(lambda ,args ,@body))) 33 | 34 | (defmacro define-simple-property-function (name args) 35 | "Defines a property function that returns name(arg1,arg2...). 36 | Only required arguments are allowed." 37 | (assert (loop for key in '(&key &optional &rest &allow-other-keys) 38 | never (find key args)) () "Only required arguments are allowed.") 39 | `(define-property-function ,name ,args 40 | (format NIL ,(format NIL "~(~a~)(~{~*~~a~^,~})" name args) 41 | ,@(loop for arg in args collect `(resolve ,arg))))) 42 | 43 | (defun resolve-function (function &rest args) 44 | "Turns the FUNCTION with its ARGS into a properly usable property value." 45 | (let ((resolver (property-function function))) 46 | (if resolver 47 | (apply resolver args) 48 | (format NIL "~(~a~)(~{~a~^,~})" function args)))) 49 | 50 | (defmethod resolve ((thing list)) 51 | (apply #'resolve-function (car thing) (cdr thing))) 52 | 53 | ;; We redefine the method here in order to delegate to property functions 54 | ;; if they do actually exist. 55 | (defmethod consume-item ((property symbol) readable-list) 56 | (if (keywordp property) 57 | (values 58 | (let ((propvals ())) 59 | (loop for (next fullp) = (multiple-value-list (peek readable-list)) 60 | while fullp 61 | do (typecase next 62 | (keyword (return)) 63 | (list 64 | (or 65 | (unless (listp (car next)) 66 | (let ((resolver (property-function (car next)))) 67 | (when resolver 68 | (push (consume readable-list) propvals)))) 69 | (return))) 70 | (T (push (consume readable-list) propvals)))) 71 | (cons property (nreverse propvals))) 72 | NIL) 73 | (call-next-method))) 74 | 75 | (defmacro define-primitive-property-consumer (specializer (propvals readable next) &body loop-body) 76 | "Defines a CONSUME-ITEM method for the given item SPECIALIZER. 77 | 78 | SPECIALIZER --- The method specializer for the item. 79 | PROPVALS --- The list that should contain the property values. 80 | READABLE --- The readable-list being operated on currently. 81 | NEXT --- Bound to the next (unconsumed) item in the readable-list. 82 | LOOP-BODY --- The body of the reading loop to execute until the readable is empty. 83 | 84 | The return value of the loop-body is discarded. You can use (RETURN) to exit the loop, 85 | for example for when you encounter an item you don't want to read." 86 | (let ((property (gensym "PROPERTY")) 87 | (fullp (gensym "FULLP"))) 88 | `(defmethod consume-item ((,property ,specializer) ,readable) 89 | (values 90 | (let ((,propvals ())) 91 | (loop for (,next ,fullp) = (multiple-value-list (peek ,readable)) 92 | while ,fullp 93 | do (progn ,@loop-body)) 94 | (cons ,property (nreverse ,propvals))) 95 | NIL)))) 96 | 97 | (defmacro define-property-function-case (property (args) &body function-clauses) 98 | "Defines a CONSUME-ITEM method for PROPERTY that has special handling for property-functions. 99 | 100 | FUNCTION-CLAUSES ::= function-clause* 101 | FUNCTION-CLAUSE ::= (function-name form*) 102 | 103 | Each function-name is compared by STRING-EQUAL and each clause should return the 104 | property-value to use in its place, or NIL if it should be skipped. 105 | 106 | You can use (RETURN) in a clause body to stop reading values altogether." 107 | (let ((propvals (gensym "PROPVALS")) 108 | (readable (gensym "READABLE")) 109 | (next (gensym "NEXT")) 110 | (result (gensym "RESULT"))) 111 | `(define-primitive-property-consumer (eql ,property) (,propvals ,readable ,next) 112 | (typecase ,next 113 | (keyword (return)) 114 | (list 115 | (let* ((,args (cdr ,next)) 116 | (,result 117 | (cond ,@(loop for (func . forms) in function-clauses 118 | for alternatives = (if (listp func) func (list func)) 119 | collect `((or ,@(loop for alt in alternatives 120 | collect `(string-equal (car ,next) ,(string alt)))) 121 | ,@forms))))) 122 | (if ,result 123 | (progn (push ,result ,propvals) (advance ,readable)) 124 | (return)))) 125 | (T (push (consume ,readable) ,propvals)))))) 126 | 127 | (defmacro define-simple-property-functions (property &rest funcspecs) 128 | "Defines a CONSUME-ITEM method for PROPERTY that has special handling for property-functions. 129 | 130 | FUNCSPECS ::= funcspec* 131 | FUNCSPEC ::= (funcname arg* [&optional arg*] [&key arg*]) 132 | 133 | See DEFINE-PROPERTY-FUNCTION-CASE." 134 | (let ((arglist (gensym "ARGLIST"))) 135 | `(define-property-function-case ,property (,arglist) 136 | ,@(loop for (name args) in funcspecs 137 | collect `(,name (destructuring-bind ,args ,arglist 138 | (format NIL ,(format NIL "~(~a~)(~~a~~@{~~@[,~~a~~]~~})" name) 139 | ,@(loop for arg in args 140 | unless (find arg '(&optional &key)) 141 | collect `(resolve ,arg))))))))) 142 | -------------------------------------------------------------------------------- /readable-list.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | (defclass readable-list () 4 | ((inner :initarg :list :initform () :accessor inner))) 5 | 6 | (defun make-readable-list (&optional list) 7 | (make-instance 'readable-list :list list)) 8 | 9 | (defmacro with-empty-check ((lis) &body body) 10 | `(if (null (inner ,lis)) 11 | (values NIL NIL) 12 | (values (progn ,@body) T))) 13 | 14 | (defun empty-p (readable-list) 15 | (null (inner readable-list))) 16 | 17 | (defun consume (readable-list) 18 | (with-empty-check (readable-list) 19 | (pop (inner readable-list)))) 20 | 21 | (defun peek (readable-list) 22 | (with-empty-check (readable-list) 23 | (first (inner readable-list)))) 24 | 25 | (defun advance (readable-list) 26 | (with-empty-check (readable-list) 27 | (setf (inner readable-list) 28 | (cdr (inner readable-list))))) 29 | 30 | (defun pushback (item readable-list) 31 | (push item (inner readable-list))) 32 | -------------------------------------------------------------------------------- /special.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | ;;; FUNCS 4 | (macrolet ((define-properties (&rest rest) 5 | `(progn 6 | ,@(loop for (name args) on rest by #'cddr 7 | collect `(define-simple-property-function ,name ,args))))) 8 | (define-properties 9 | rgb (red green blue) 10 | rgba (red green blue alpha) 11 | hsl (hue saturation lightness) 12 | hsla (hue saturation lightness alpha))) 13 | (define-property-function uni (&rest codes) (format NIL "\"~{\\~x~}\"" codes)) 14 | (define-property-function hex (hex) (format NIL "#~6,'0d" hex)) 15 | (define-property-function url (url) (format NIL "url(~s)" url)) 16 | (define-property-function attr (attribute) (format NIL "attr(~a)" (resolve attribute))) 17 | 18 | ;;; https://www.w3.org/TR/css-variables-1/ 19 | (define-property-function var (name &rest fallback-vals) 20 | (format nil "var(~(~A~)~{~^, ~(~A~)~})" name fallback-vals)) 21 | 22 | (define-property-function calc (func) 23 | (with-output-to-string (out) 24 | (labels ((proc (func) 25 | (write-string "(" out) 26 | (if (or (not (listp func)) 27 | (property-function (first func))) 28 | (write-string (resolve func) out) 29 | (destructuring-bind (func first &rest rest) func 30 | (proc first) 31 | (loop :for arg :in rest 32 | :do (format out " ~A " (resolve func)) 33 | (proc arg)))) 34 | (write-string ")" out))) 35 | (write-string "calc" out) 36 | (proc func)))) 37 | 38 | (define-simple-property-function counter (var)) 39 | 40 | ;;; BLOCKS 41 | 42 | (define-special-block charset (charset) 43 | (list (list :property (format NIL "@charset ~a" (resolve charset))))) 44 | 45 | (define-special-block document (selector &rest body) 46 | (list (make-superblock 47 | "document" 48 | (compile-media-query selector) 49 | (apply #'compile-sheet body)))) 50 | 51 | (define-special-block font-face (&rest body) 52 | (compile-block "@font-face" body)) 53 | 54 | (define-special-block import (url &rest media-queries) 55 | (list (make-property 56 | (format NIL "@import ~a~{ ~a~}" 57 | (resolve url) 58 | (mapcar #'resolve media-queries))))) 59 | 60 | (define-special-block keyframes (identifier &rest body) 61 | (list (make-superblock 62 | "keyframes" 63 | (list (list :constraint :literal (resolve identifier))) 64 | (apply #'compile-sheet body)))) 65 | 66 | (define-special-block media (query &rest body) 67 | (list (make-superblock 68 | "media" 69 | (compile-media-query query) 70 | (apply #'compile-sheet body)))) 71 | 72 | (define-special-block namespace (prefix/namespace &optional namespace) 73 | (list (make-property 74 | (format NIL "@namespace ~a~@[ ~a~]" 75 | (resolve prefix/namespace) 76 | (when namespace (resolve namespace)))))) 77 | 78 | (define-special-block page (pseudo-class &rest body) 79 | (compile-block (format NIL "@page ~a" 80 | (if (keywordp pseudo-class) 81 | (format NIL ":~a" (string-downcase pseudo-class)) 82 | (resolve pseudo-class))) body)) 83 | 84 | (define-special-block supports (selector &rest body) 85 | (list (make-superblock 86 | "supports" 87 | (compile-selector selector) 88 | (apply #'compile-sheet body)))) 89 | 90 | (defmacro bind-vars (bindings &body body) 91 | `(let ((*vars* (let ((table (make-hash-table))) 92 | (maphash #'(lambda (k v) (setf (gethash k table) v)) *vars*) 93 | (loop for (k . v) in ,bindings 94 | do (setf (gethash k table) 95 | (if (rest v) 96 | v 97 | (resolve (car v))))) 98 | table))) 99 | ,@body)) 100 | 101 | (define-special-block let (bindings &rest body) 102 | (bind-vars bindings 103 | (apply #'compile-sheet body))) 104 | 105 | ;;; SELECTORS 106 | 107 | (defmacro define-attr-comparator (comp &optional (outcomp comp)) 108 | "Helper macro to define an attribute comparator selector." 109 | `(define-special-selector ,comp (attr value) 110 | (loop with out = () 111 | with values = (compile-selector value) 112 | for attr in (compile-selector attr) 113 | do (loop for value in values 114 | do (push (list :constraint :attribute attr ,(string outcomp) value) out)) 115 | finally (return (nreverse out))))) 116 | 117 | (define-attr-comparator =) 118 | (define-attr-comparator ~=) 119 | (define-attr-comparator *=) 120 | (define-attr-comparator $=) 121 | (define-attr-comparator ^=) 122 | (define-attr-comparator /= \|=) 123 | 124 | (defmacro define-single-arg-selector (name) 125 | "Helper macro to define a single-argument pseudo-selector like NOT or NTH-CHILD." 126 | `(define-special-selector ,name (arg) 127 | (loop for arg in (compile-selector arg) 128 | collect (list :constraint :selector ,(string-downcase name) arg)))) 129 | 130 | (define-single-arg-selector dir) 131 | (define-single-arg-selector lang) 132 | (define-single-arg-selector nth-child) 133 | (define-single-arg-selector nth-last-child) 134 | (define-single-arg-selector nth-last-of-type) 135 | (define-single-arg-selector nth-of-type) 136 | (define-single-arg-selector is) 137 | (define-single-arg-selector not) 138 | (define-single-arg-selector where) 139 | (define-single-arg-selector has) 140 | 141 | ;;; ATTRIBUTES 142 | 143 | (define-special-property font-family (&rest faces) 144 | (list (make-property "font-family" (format NIL "~{~a~^, ~}" (mapcar #'resolve faces))))) 145 | 146 | (define-special-property content (&rest content) 147 | (labels ((translate (content) 148 | (typecase content 149 | (string 150 | ;; Backwards compat with the usage of "'foo'" in LASS files 151 | (when (and (<= 2 (length content)) 152 | (char= #\' (char content 0)) 153 | (char= #\' (char content (1- (length content))))) 154 | (setf content (subseq content 1 (1- (length content))))) 155 | (with-output-to-string (out) 156 | (write-char #\" out) 157 | (unwind-protect 158 | (loop for char across content 159 | do (when (char= char #\") 160 | (write-char #\\ out)) 161 | (write-char char out)) 162 | (write-char #\" out)))) 163 | (T 164 | (resolve content))))) 165 | (list (make-property "content" (format NIL "~{~a~^ ~}" (mapcar #'translate content)))))) 166 | 167 | (defmacro define-browser-property (name args &body browser-options) 168 | "Helper macro to define properties that have browser-dependant versions. 169 | 170 | NAME --- The base name of the property name or value. 171 | ARGS --- Property arguments, see DEFINE-SPECIAL-PROPERTY. 172 | BROWSER-OPTIONS ::= (OPTION (symbol) FORM*) 173 | OPTION ::= :MOZ | :O | :WEBKIT | :MS | :W3C | :DEFAULT 174 | 175 | Each browser-option body should return a single property. The SYMBOL 176 | in the option definition is bound to the computed property name 177 | (eg -moz-NAME for the :MOZ option). 178 | You can define special handling of the browsers by defining options 179 | specifically for them. If no handling is defined, the DEFAULT option 180 | is used as a fallback." 181 | `(define-special-property ,name ,args 182 | (list ,@(loop for (browser prefix) in '((:moz "-moz-") 183 | (:o "-o-") 184 | (:webkit "-webkit-") 185 | (:ms "-ms-") 186 | (:w3c "")) 187 | for body = (or (assoc browser browser-options) 188 | (assoc :default browser-options)) 189 | collect `(let ((,(caadr body) ,(format NIL "~a~a" prefix (string-downcase name)))) 190 | ,@(cddr body)))))) 191 | 192 | (indent:define-indentation define-browser-property (4 6 &rest (&whole 2 0 4 2))) 193 | 194 | (define-browser-property text-stroke (width color) 195 | (:default (property) 196 | (make-property property (format NIL "~a ~a" (resolve width) (resolve color))))) 197 | 198 | (define-browser-property linear-gradient (direction &rest colors) 199 | (:default (property) 200 | (make-property "background" (format NIL "~a(~a~{, ~a ~a~})" 201 | property (resolve direction) (mapcar #'resolve colors))))) 202 | 203 | (define-browser-property radial-gradient (shape size position &rest colors) 204 | (:default (property) 205 | (make-property "background" (format NIL "~a(~a ~a at ~a~{, ~a ~a~})" 206 | property (resolve shape) (resolve size) (resolve position) (mapcar #'resolve colors))))) 207 | 208 | (define-browser-property repeating-radial-gradient (shape size position &rest colors) 209 | (:default (property) 210 | (make-property "background" (format NIL "~a(~a ~a at ~a~{, ~a ~a~})" 211 | property (resolve shape) (resolve size) (resolve position) (resolve colors))))) 212 | 213 | (define-browser-property transform (value/function &rest function-args) 214 | (:default (property) 215 | (make-property property (format NIL "~a~@[(~{~a~^, ~})~]" 216 | (resolve value/function) (mapcar #'resolve function-args))))) 217 | 218 | (define-browser-property transform-origin (value/x &optional y z) 219 | (:default (property) 220 | (make-property property (format NIL "~a~@[ ~a~]~@[ ~a~]" 221 | (resolve value/x) (resolve y) (resolve z))))) 222 | 223 | (define-browser-property transform-style (style) 224 | (:default (property) 225 | (make-property property (resolve style)))) 226 | 227 | (define-browser-property transition (value/property &optional duration timing-function &rest function-args) 228 | (:default (property) 229 | (make-property property (format NIL "~a~@[ ~a~]~:[~*~;~:* ~a~@[(~{~a~^, ~})~]~]" 230 | (resolve value/property) (resolve duration) (resolve timing-function) (mapcar #'resolve function-args))))) 231 | 232 | (define-browser-property transition-delay (value) 233 | (:default (property) 234 | (make-property property (resolve value)))) 235 | 236 | (define-browser-property transition-duration (value) 237 | (:default (property) 238 | (make-property property (resolve value)))) 239 | 240 | (define-browser-property transition-property (value) 241 | (:default (property) 242 | (make-property property (resolve value)))) 243 | 244 | (define-browser-property transition-timing-function (value/function &rest function-args) 245 | (:default (property) 246 | (make-property property (format NIL "~a~@[(~{~a~^, ~})~]" 247 | (resolve value/function) (mapcar #'resolve function-args))))) 248 | 249 | (define-browser-property user-select (value) 250 | (:default (property) 251 | (make-property property (resolve value)))) 252 | 253 | (define-browser-property appearance (value) 254 | (:default (property) 255 | (make-property property (resolve value)))) 256 | 257 | (define-simple-property-functions :filter 258 | (url (url)) 259 | (blur (radius)) 260 | (brightness (value)) 261 | (contrast (value)) 262 | (drop-shadow (x y &optional blur spread color)) 263 | (grayscale (value)) 264 | (hue-rotate (value)) 265 | (invert (value)) 266 | (opacity (value)) 267 | (saturate (value)) 268 | (sepia (value))) 269 | 270 | (define-browser-property filter (&rest args) 271 | (:default (property) 272 | (make-property property (format NIL "~{~a~^ ~}" (mapcar #'resolve args))))) 273 | 274 | (define-browser-property box-shadow (color &optional x y blur spread inset) 275 | (:default (property) 276 | (make-property property (format NIL "~a~@[ ~a~]~@[ ~a~]~@[ ~a~]~@[ ~a~]~@[ ~a~]" 277 | (resolve color) (resolve x) (resolve y) (resolve blur) (resolve spread) (resolve inset))))) 278 | -------------------------------------------------------------------------------- /writer.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:org.tymoonnext.lass) 2 | 3 | (defvar *pretty* T 4 | "Directs whether to pretty-print using whitespace or not.") 5 | (defvar *indent-level* 0 6 | "Directs the current amount of spaces used to indent.") 7 | (defvar *indent-spaces* 4 8 | "Specifies the number of spaces to use for indentation.") 9 | 10 | ;; SHEET ::= (BLOCK*) 11 | ;; BLOCK ::= (:BLOCK SELECTOR PROPERTY*) 12 | ;; SELECTOR ::= (string*) 13 | ;; PROPERTY ::= (:PROPERTY string string) 14 | 15 | (defun indent () 16 | "Returns a string of the appropriate number of spaces depending on *PRETTY* and *INDENT-LEVEL*" 17 | (make-string (if *pretty* *indent-level* 0) :initial-element #\Space)) 18 | 19 | (defgeneric write-sheet-object (type object stream) 20 | (:documentation "Writes the OBJECT of type TYPE to STREAM. 21 | 22 | By default the following TYPEs are handled: 23 | :BLOCK (SELECTOR-LIST OBJECTS*) 24 | Prints the SELECTOR-LIST separated by commas, followed by an opening brace 25 | and the printed list of OBJECTS using WRITE-SHEET-PART. Finally the body is 26 | closed off with a closing brace. Newlines and spaces may be inserted where 27 | necessary if *PRETTY* is non-NIL. 28 | 29 | :PROPERTY (KEY VALUE) 30 | Prints the KEY. If VALUE is non-NIL, a colon is printed followed by the 31 | VALUE. Finally a semicolon is printed. Spaces may be inserted where necessary 32 | if *PRETTY* is non-NIL.") 33 | (:method ((type (eql :superblock)) block stream) 34 | (when (and block (cddr block)) 35 | (destructuring-bind (type selector &rest blocks) block 36 | (format stream "~a@~a" (indent) type) 37 | (when selector 38 | (format stream " ") 39 | (let ((*indent-level* (+ *indent-level* 2 (length type)))) 40 | (write-sheet-object (car selector) (cdr selector) stream))) 41 | (format stream "{") 42 | (let ((*indent-level* (+ *indent-level* *indent-spaces*))) 43 | (dolist (block blocks) 44 | (when *pretty* (fresh-line stream)) 45 | (write-sheet-object (car block) (cdr block) stream))) 46 | (format stream "~@[~&~*~]~a}" *pretty* (indent))))) 47 | 48 | (:method ((type (eql :block)) block stream) 49 | (when (and block (cdr block)) 50 | (destructuring-bind (selector &rest body) block 51 | (format stream "~a" (indent)) 52 | (write-sheet-object (car selector) (cdr selector) stream) 53 | (format stream "{") 54 | (let ((*indent-level* (+ *indent-level* *indent-spaces*))) 55 | (dolist (inner body) 56 | (when *pretty* (fresh-line stream)) 57 | (write-sheet-object (car inner) (cdr inner) stream))) 58 | (format stream "~@[~&~*~]~a}" *pretty* (indent))))) 59 | 60 | (:method ((type (eql :property)) attribute stream) 61 | (when attribute 62 | (format stream "~a~a~:[~@[:~a~]~;~@[: ~a~]~];" (indent) (first attribute) *pretty* (second attribute)))) 63 | 64 | (:method ((type (eql :constraint)) constraint stream) 65 | (ecase (first constraint) 66 | ((NIL)) 67 | (:combine 68 | (destructuring-bind (combiner &rest parts) (rest constraint) 69 | (flet ((%wr (part) 70 | (if (consp part) 71 | (write-sheet-object (car part) (cdr part) stream) 72 | (write-sheet-object :constraint (list :literal part) stream)))) 73 | (%wr (first parts)) 74 | (dolist (part (rest parts)) 75 | (format stream combiner) 76 | (%wr part))))) 77 | (:attribute 78 | (destructuring-bind (attr comp val) (rest constraint) 79 | (format stream "[") 80 | (write-sheet-object (car attr) (rest attr) stream) 81 | (format stream "~a" comp) 82 | (format stream "~s]" (with-output-to-string (out) 83 | (write-sheet-object (car val) (rest val) out))))) 84 | (:property (format stream "(~a:~@[~* ~]~a)" (second constraint) *pretty* (third constraint))) 85 | (:and (format stream "~{~/lass::write-sheet-part/~^ and ~}" (rest constraint))) 86 | (:func (format stream "~a(~{~a~^,~})" (second constraint) (cddr constraint))) 87 | (:selector (format stream ":~a(~{~/lass::write-sheet-part/~^,~})" (second constraint) (cddr constraint))) 88 | (:literal (format stream "~a" (second constraint))))) 89 | 90 | (:method ((type (eql :text)) block stream) 91 | (when block 92 | (format stream "~{~a~}~@[~*~%~]" block *pretty*))) 93 | 94 | (:method ((type (eql :selector)) constraints stream) 95 | (when constraints 96 | (write-sheet-object (car (first constraints)) (cdr (first constraints)) stream) 97 | (dolist (constraint (rest constraints)) 98 | (format stream ",") 99 | (when *pretty* (format stream "~&~a" (indent))) 100 | (write-sheet-object (car constraint) (cdr constraint) stream))))) 101 | 102 | (defun write-sheet-part (stream block cp ap) 103 | "Wrapper around WRITE-SHEET-OBJECT so that we can call it from FORMAT. 104 | Calls WRITE-SHEET-OBJECT with (CAR BLOCK) (CDR BLOCK) STREAM." 105 | (declare (ignore cp ap)) 106 | (write-sheet-object (car block) (cdr block) stream)) 107 | 108 | (defun write-sheet (sheet &key (stream NIL) (pretty *pretty*)) 109 | "Writes the compiled SHEET object to STREAM. 110 | If PRETTY is non-NIL, spaces and newlines are inserted as appropriate 111 | in order to create a human-readable stylesheet. Otherwise whitespace is 112 | only used where necessary, producing a minified version. 113 | 114 | STREAM can be a STREAM, T for *STANDARD-OUTPUT*, or NIL for a STRING." 115 | (let ((*pretty* pretty)) 116 | (format stream (format NIL "~~{~~/lass::write-sheet-part/~~^~@[~*~%~%~]~~}" pretty) sheet))) 117 | --------------------------------------------------------------------------------