├── .gitignore ├── .travis.yml ├── README.textile ├── epl-v10.html ├── project.clj ├── script ├── config_repl.clj └── repl ├── src ├── cssgen.clj └── cssgen │ ├── types.clj │ └── use.clj └── test ├── operations.clj ├── rules.clj ├── selectors.clj └── values.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /classes 3 | /cssgen.jar 4 | /*.jar 5 | /pom.xml 6 | /multi-lib 7 | /.lein-failures 8 | /.lein-deps-sum 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | script: lein multi test 3 | 4 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. cssgen 2 | 3 | "!https://secure.travis-ci.org/paraseba/cssgen.png!":http://travis-ci.org/paraseba/cssgen 4 | 5 | A *clojure* library to generate *CSS* code using an embedded domain-specific language (EDSL). In plain words: generate CSS files 6 | by writing clojure code. 7 | 8 | You can go "here":http://wiki.github.com/paraseba/cssgen/ to read more about cssgen. 9 | 10 | **Please take a look at the upcoming syntax in version 0.3.0, tell me what you think: "0.3.0-SNAPSHOT":https://github.com/paraseba/cssgen/tree/0.3.0** 11 | 12 | Using clojure instead of plain CSS you get a lot of benefices. You can use the full power of the language to do things 13 | like: 14 | 15 | * Define constants and use them in your rules 16 | 17 |
18 |
19 | (def width (px 960))
20 |
21 |
22 |
23 | * Operate with your constants using clojure expressions
24 |
25 |
26 |
27 | (def main-width (* width 0.62))
28 |
29 |
30 |
31 | * Define nested rules
32 |
33 |
34 |
35 | (rule "a.plain"
36 | :color :inherit
37 | :text-decoration :inherit
38 | :cursor :inherit
39 | (rule "&:active, &:focus"
40 | :outline :none)))
41 |
42 |
43 |
44 | * Define reusable CSS snippets using plain old clojure functions and vars
45 |
46 |
47 |
48 | (def has-layout
49 | (mixin
50 | ; This makes ie6 get layout
51 | :display "inline-block"
52 | ; and this puts it back to block
53 | (rule "&" :display :block)))
54 |
55 | (def clearfix
56 | (mixin
57 | :overflow :hidden
58 | has-layout))
59 |
60 |
61 |
62 | * Generate readable styles with better code organization
63 |
64 |
65 |
66 | (rule "#nav"
67 | (horizontal-list (px 9))
68 | (rule "a"
69 | (link-colors my-link-color my-visited-color my-hover-color)))
70 |
71 |
72 |
73 | * Easy CSS 'hacks'
74 |
75 |
76 |
77 | (defn- float-side [side]
78 | (mixin :display :inline :float side))
79 |
80 | (defvar float-left (float-side :left)
81 | "Implementation of float:left with fix for double-margin bug")
82 |
83 | (defvar float-right (float-side :right)
84 | "Implementation of float:right with fix for double-margin bug")
85 |
86 | (rule "#secondary"
87 | float-right)
88 |
89 |
90 |
91 |
92 | h2. Installation
93 |
94 | The easiest way to install cssgen is by using Leiningen. Just add the following dependency to your project.clj file:
95 |
96 |
97 |
98 | [cssgen "0.2.6"]
99 |
100 |
101 |
102 | h2. Usage
103 |
104 | I'll show some examples of use, but you should read the "wiki":http://wiki.github.com/paraseba/cssgen/ for more details and information.
105 |
106 | * CSS rules: to create a simple rule you use the rule
function, passing the selector as first argument, and a
107 | series of property pairs.
108 |
109 |
110 |
111 | (rule "ul.nav, ol"
112 | :color :black
113 | :background-color :#ddd
114 | :padding [:1px "2px" (px 3) 0])
115 |
116 |
117 |
118 | * Property values could be
119 | ** keywords,
120 | ** strings,
121 | ** any other type convertible to string with as-str
,
122 | ** values sequences
123 | ** special constructions like (px 9)
, (% 30)
, (col :#aaa)
(more about this
124 | later).
125 |
126 | If a property key must be associated with several values, you use a sequence of values, like in the padding
127 | property above. Of course, if everything is "literal", you could simply do :padding "1px 2px 3px 4px"
.
128 |
129 | * You can nest rules:
130 |
131 |
132 |
133 | (rule "#main, #secondary"
134 | :padding "10px"
135 |
136 | (rule "h1" ; this will generate a rule for "#main h1, #secondary h1"
137 | :color :blue))
138 |
139 |
140 |
141 | * If you need the parent selector on the nested rule, you can use "&" and it will get replaced:
142 |
143 |
144 |
145 | (rule "a"
146 | :color "#00C"
147 |
148 | (rule "&:hover" ; this will generate a rule for a:hover
149 | :color "#0CC"))
150 |
151 |
152 |
153 | * You can define mixins with multiple rules and properties using functions or vars. Those can later be used in other
154 | definitions
155 |
156 |
157 |
158 | (defn link-colors
159 | ([normal] (link-colors normal nil))
160 | ([normal hover]
161 | (mixin
162 | :color normal
163 | (if visited (rule "&:visited" :color visited))
164 | (if hover (rule "&:hover" :color hover)))))
165 |
166 | (rule "a"
167 | (link-colors "#00c" "#0cc"))
168 |
169 |
170 |
171 | * As you can see in the previous example, nils in the properties list will be ignored:
172 |
173 | * You can easily define constants
174 |
175 |
176 |
177 | (def width (px 960))
178 | (def h1-font-size (em 1.5))
179 | (def h1-color (col :#0000ca))
180 | (def h2-color (col :#0dd))
181 | (def h3-color ($ :#0dd)) ; $ is just an alias for col
182 | (def form-size (% 60))
183 |
184 |
185 |
186 | * And use the basic arithmetic operations on them
187 |
188 |
189 |
190 | (def main-width (* 0.7 width))
191 | (def h2-font-size (- h1-font-size (em 0.3)))
192 | (def h4-color (/ (+ h2-color h3-color) 2))
193 |
194 |
195 |
196 | * To generate a new CSS file from the current clj code do:
197 |
198 |
199 |
200 | (use 'cssgen.use)
201 | (css-ns killerapp.css.screen
202 | (:use (killerapp.css reset layout colors)))
203 |
204 | (css-file "public/css/screen.css" ;this is the path to the target CSS file
205 | .......
206 | ....... ; all your rules
207 | .......)
208 |
209 |
210 |
211 | Instead of css-ns
you could use the normal (ns)
call, but it gets a little tricky since we are redefining arithmetic
212 | operations.
213 |
214 | h2. ToDo
215 |
216 | * More syntactic sugar
217 | * Helper functions to operate with colors and lengths
218 | * Helpers to define dimensions and colors
219 | * Methods to define rules without actually generating any CSS file
220 | * Watcher to re-generate css files if source changed ???
221 | * Property namespaces (font-*)
222 | * Write a sass "compiler" to migrate from sass to cssgen
223 | * Use that compiler to generate the whole "compass":http://compass-style.org/ tree in cssgen.
224 | * Document
225 | * Command line interface
226 |
227 | h3. Check the "wiki":http://wiki.github.com/paraseba/cssgen/ for more usage information.
228 |
229 | If you have a feature request, problem or comment, just drop me a line.
230 |
--------------------------------------------------------------------------------
/epl-v10.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Eclipse Public License - v 1.0
31 | 32 |THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.
36 | 37 |1. DEFINITIONS
38 | 39 |"Contribution" means:
40 | 41 |a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and
43 |b) in the case of each subsequent Contributor:
44 |i) changes to the Program, and
45 |ii) additions to the Program;
46 |where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.
54 | 55 |"Contributor" means any person or entity that distributes 56 | the Program.
57 | 58 |"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.
61 | 62 |"Program" means the Contributions distributed in accordance 63 | with this Agreement.
64 | 65 |"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.
67 | 68 |2. GRANT OF RIGHTS
69 | 70 |a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.
76 | 77 |b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.
88 | 89 |c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.
101 | 102 |d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.
105 | 106 |3. REQUIREMENTS
107 | 108 |A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:
110 | 111 |a) it complies with the terms and conditions of this 112 | Agreement; and
113 | 114 |b) its license agreement:
115 | 116 |i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;
120 | 121 |ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;
124 | 125 |iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and
128 | 129 |iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.
133 | 134 |When the Program is made available in source code form:
135 | 136 |a) it must be made available under this Agreement; and
137 | 138 |b) a copy of this Agreement must be included with each 139 | copy of the Program.
140 | 141 |Contributors may not remove or alter any copyright notices contained 142 | within the Program.
143 | 144 |Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.
147 | 148 |4. COMMERCIAL DISTRIBUTION
149 | 150 |Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.
172 | 173 |For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.
183 | 184 |5. NO WARRANTY
185 | 186 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.
197 | 198 |6. DISCLAIMER OF LIABILITY
199 | 200 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 | 209 |7. GENERAL
210 | 211 |If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.
216 | 217 |If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.
223 | 224 |All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.
232 | 233 |Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.
252 | 253 |This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.
258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject cssgen "0.2.6" 2 | :description "Generate CSS from clojure code. Like an embedded sass." 3 | :url "http://github.com/paraseba/cssgen" 4 | :dependencies [[org.clojure/clojure "1.2.0"] 5 | [org.clojure/algo.generic "0.1.0"]] 6 | :dev-dependencies [[lein-multi "1.0.0"]] 7 | :multi-deps {"1.4" [[org.clojure/clojure "1.4.0"] 8 | [org.clojure/algo.generic "0.1.0"]] 9 | "1.3" [[org.clojure/clojure "1.3.0"] 10 | [org.clojure/algo.generic "0.1.0"]]}) 11 | 12 | -------------------------------------------------------------------------------- /script/config_repl.clj: -------------------------------------------------------------------------------- 1 | (use 'clojure.contrib.pprint) 2 | (require 'clojure.contrib.repl-utils) 3 | (set! *print-length* 500) 4 | (clojure.contrib.repl-utils/add-break-thread!) 5 | -------------------------------------------------------------------------------- /script/repl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CLASSPATH=`lein classpath` 4 | rlwrap java -Xmx1G -cp $CLASSPATH clojure.main -i script/config_repl.clj -r 5 | 6 | -------------------------------------------------------------------------------- /src/cssgen.clj: -------------------------------------------------------------------------------- 1 | (ns cssgen 2 | (:require [clojure.string :as s] 3 | [clojure.java.io :as io]) 4 | (:use [cssgen.types :only (repr)])) 5 | 6 | (defprotocol Container 7 | (nest [child parent]) 8 | (add-properties [this new-props]) 9 | (add-rules [this new-rules])) 10 | 11 | (defrecord Mixin [properties rules] 12 | Container 13 | (add-properties [_ new-props] (Mixin. (concat properties new-props) rules)) 14 | (add-rules [_ new-rules] (Mixin. properties (concat rules new-rules))) 15 | (nest [this parent] 16 | (-> parent 17 | (add-properties properties) 18 | (add-rules rules)))) 19 | 20 | (defrecord Rule [selector properties rules] 21 | Container 22 | (add-properties [_ new-props] (Rule. selector (concat properties new-props) rules)) 23 | (add-rules [_ new-rules] (Rule. selector properties (concat rules new-rules))) 24 | (nest [this parent] 25 | (add-rules parent [this]))) 26 | 27 | (defn- properties [x] (:properties x)) 28 | (defn- rules [x] (:rules x)) 29 | (defn- selector [x] (:selector x)) 30 | (def ^{:private true} empty-mixin (Mixin. [] [])) 31 | (defn- empty-rule [selector] (Rule. selector [] [])) 32 | 33 | (defn- container? [x] 34 | (or (instance? Mixin x) (instance? Rule x))) 35 | 36 | (defn- parse-and-nest [base forms] 37 | (letfn [(process-group [base group] 38 | (if (container? (first group)) 39 | (reduce #(nest %2 %1) base group) 40 | (add-properties base (partition 2 group))))] 41 | (let [grouped (partition-by container? (filter (complement nil?) forms))] 42 | (reduce process-group base grouped)))) 43 | 44 | (defn rule [selector & forms] 45 | (parse-and-nest (empty-rule selector) forms)) 46 | 47 | (defn mixin [& forms] 48 | (parse-and-nest empty-mixin forms)) 49 | 50 | (defn- rule-css [rule] 51 | (letfn [(format-prop [prop] 52 | (let [key (repr (first prop)) 53 | vals (s/join " " (map repr (next prop)))] 54 | (format " %s: %s;" key vals))) 55 | 56 | (format-props [props] 57 | (let [lines (map format-prop props)] 58 | (s/join "\n" lines))) 59 | 60 | (nest-single-selector [parent child] 61 | (if (.contains child "&") 62 | (.replace child "&" parent) 63 | (str parent (if-not (s/blank? parent) " ") child))) 64 | 65 | (nest-selector [parent child] 66 | (let [parents (s/split (or parent "") #",") 67 | children (s/split (or child "") #",")] 68 | (s/join ", " (for [p parents c children] 69 | (nest-single-selector (s/trim p) (s/trim c)))))) 70 | 71 | (child-rule-css [rule parent-selector] 72 | (let [selector (selector rule) 73 | properties (properties rule) 74 | children (rules rule) 75 | nested-selector (nest-selector parent-selector selector) 76 | parent-css (format "%s {\n%s\n}\n" 77 | nested-selector (format-props properties)) 78 | children-css (s/join "\n" (for [c children] 79 | (child-rule-css c nested-selector)))] 80 | (str parent-css children-css)))] 81 | 82 | (child-rule-css rule nil))) 83 | 84 | (defn css [& rules] 85 | (apply str (map rule-css rules))) 86 | 87 | (defn css-file [path & rules] 88 | (spit path (apply css rules))) 89 | 90 | -------------------------------------------------------------------------------- /src/cssgen/types.clj: -------------------------------------------------------------------------------- 1 | (ns cssgen.types 2 | (:require [clojure.string :as s] 3 | [clojure.algo.generic.arithmetic :as generic])) 4 | 5 | ;; copied from contrib 6 | (defn- as-str [x] 7 | (if (instance? clojure.lang.Named x) 8 | (name x) 9 | (str x))) 10 | 11 | (defprotocol Value 12 | (repr [x])) 13 | 14 | (defrecord Length [mag unit] 15 | Value 16 | (repr [_] (str (as-str mag) (as-str unit)))) 17 | 18 | (defrecord Color [r g b] 19 | Value 20 | (repr [_] (format "#%02X%02X%02X" (int r) (int g) (int b)))) 21 | 22 | 23 | (extend-protocol Value 24 | nil 25 | (repr [_] "") 26 | 27 | clojure.lang.IPersistentVector 28 | (repr [v] (s/join " " (map repr v))) 29 | 30 | clojure.lang.Keyword 31 | (repr [k] (name k)) 32 | 33 | java.lang.String 34 | (repr [s] s) 35 | 36 | Object 37 | (repr [i] (.toString i))) 38 | 39 | 40 | (defn- make-color 41 | ([r g b] (letfn [(limit [x] (max 0 (min x 255)))] 42 | (Color. (limit r) (limit g) (limit b)))) 43 | 44 | ([string] (letfn [(remove-number-sign [s] (s/replace-first s #"#" "")) 45 | (duplicate [s] (if (= (.length s) 3) (apply str (interleave s s)) s))] 46 | (let [components (->> string as-str remove-number-sign duplicate (re-seq #"..")) 47 | [r g b] (map #(Integer/parseInt % 16) components)] 48 | 49 | (make-color r g b))))) 50 | 51 | 52 | (defn- make-length [mag unit] 53 | {:pre [(number? mag)]} 54 | (Length. mag (as-str unit))) 55 | 56 | (defmacro ^{:private true} def-length-constr [name] 57 | `(defn ~name [x#] (make-length x# ~(keyword name)))) 58 | 59 | (def-length-constr em) 60 | (def-length-constr ex) 61 | (def-length-constr px) 62 | (def-length-constr in) 63 | (def-length-constr cm) 64 | (def-length-constr mm) 65 | (def-length-constr pt) 66 | (def-length-constr pc) 67 | (def-length-constr %) 68 | (def-length-constr deg) 69 | 70 | (defn col 71 | ([x] (make-color x)) 72 | ([r g b] (make-color r g b))) 73 | (def $ col) 74 | 75 | (defmethod generic/+ [Length Length] 76 | [{ua :unit ma :mag} {ub :unit mb :mag}] 77 | {:pre [(= ua ub)]} 78 | (make-length (+ ma mb) ua)) 79 | 80 | (defmethod generic/- Length 81 | [{ua :unit ma :mag}] 82 | (make-length (- ma) ua)) 83 | 84 | (defmethod generic/- [Length Length] 85 | [{ua :unit ma :mag} {ub :unit mb :mag}] 86 | {:pre [(= ua ub)]} 87 | (make-length (- ma mb) ua)) 88 | 89 | (defmethod generic/* [Length Number] 90 | [{ua :unit ma :mag} num] 91 | (make-length (* ma num) ua)) 92 | 93 | (defmethod generic/* [Number Length] 94 | [num {ua :unit ma :mag}] 95 | (make-length (* ma num) ua)) 96 | 97 | (generic/defmethod* generic / [Length Number] 98 | [{ua :unit ma :mag} num] 99 | (make-length ((generic/qsym generic /) ma num) ua)) 100 | 101 | 102 | (defmacro ^{:private true} compwise-col-col-op [sym f] 103 | (let [f f] 104 | `(defmethod ~sym [Color Color] 105 | [{ra# :r ga# :g ba# :b} {rb# :r gb# :g bb# :b}] 106 | (make-color (~f ra# rb#) (~f ga# gb#) (~f ba# bb#))))) 107 | 108 | (compwise-col-col-op generic/+ +) 109 | (compwise-col-col-op generic/- -) 110 | (compwise-col-col-op generic/* *) 111 | (generic/defmethod* generic / [Color Color] 112 | [{ra :r ga :g ba :b} {rb :r gb :g bb :b}] 113 | (make-color ((generic/qsym generic /) ra rb) 114 | ((generic/qsym generic /) ga gb) 115 | ((generic/qsym generic /) ba bb))) 116 | 117 | 118 | (defmacro ^{:private true} compwise-col-num-op [sym f] 119 | (let [f f] 120 | `(do 121 | (defmethod ~sym [Color Number] 122 | [{r# :r g# :g b# :b} num#] 123 | (make-color (~f r# num#) (~f g# num#) (~f b# num#))) 124 | (defmethod ~sym [Number Color] 125 | [num# {r# :r g# :g b# :b}] 126 | (make-color (~f num# r#) (~f num# g#) (~f num# b#)))))) 127 | 128 | (compwise-col-num-op generic/+ +) 129 | (compwise-col-num-op generic/- -) 130 | (compwise-col-num-op generic/* *) 131 | (generic/defmethod* generic / [Color Number] 132 | [{r :r g :g b :b} num] 133 | (make-color ((generic/qsym generic /) r num) 134 | ((generic/qsym generic /) g num) 135 | ((generic/qsym generic /) b num))) 136 | (generic/defmethod* generic / [Number Color] 137 | [num {r :r g :g b :b}] 138 | (make-color ((generic/qsym generic /) num r) 139 | ((generic/qsym generic /) num g) 140 | ((generic/qsym generic /) num b))) 141 | 142 | -------------------------------------------------------------------------------- /src/cssgen/use.clj: -------------------------------------------------------------------------------- 1 | (ns cssgen.use) 2 | 3 | (defmacro css-ns [ns-name & forms] 4 | `(ns ~ns-name 5 | ~'(:refer-clojure :exclude [+ - * /]) 6 | ~'(:use cssgen cssgen.types 7 | [clojure.algo.generic.arithmetic :only (+ - * /)]) 8 | ~@forms)) 9 | -------------------------------------------------------------------------------- /test/operations.clj: -------------------------------------------------------------------------------- 1 | (ns operations 2 | (:refer-clojure :exclude [+ - * /]) 3 | (:use 4 | cssgen cssgen.types 5 | clojure.test 6 | [clojure.algo.generic.arithmetic :only (+ - * /)])) 7 | 8 | (def all-lengths [em ex px in cm mm pt pc % deg]) 9 | 10 | (deftest test-length-+ 11 | (doseq [unit all-lengths] 12 | (is (= (unit 3) 13 | (+ (unit 1) (unit 2)))))) 14 | 15 | (deftest test-length-unary- 16 | (doseq [unit all-lengths] 17 | (is (= (unit -2) 18 | (- (unit 2)))))) 19 | 20 | (deftest test-length-- 21 | (doseq [unit all-lengths] 22 | (is (= (unit 2) 23 | (- (unit 4) (unit 2)))))) 24 | 25 | (deftest test-length*number 26 | (doseq [unit all-lengths] 27 | (is (= (unit 6) 28 | (* (unit 3) 2))) 29 | (is (= (unit 6.0) 30 | (* (unit 3) 2.0))))) 31 | 32 | (deftest test-number*length 33 | (doseq [unit all-lengths] 34 | (is (= (unit 6) 35 | (* 2 (unit 3)))) 36 | (is (= (unit 6.0) 37 | (* 2.0 (unit 3)))))) 38 | 39 | 40 | (deftest test-length-divided-by-number 41 | (doseq [unit all-lengths] 42 | (is (= (unit 2) 43 | (/ (unit 4) 2))) 44 | (is (= (unit 2.0) 45 | (/ (unit 4) 2.0))))) 46 | 47 | 48 | (deftest color+color 49 | (are [a b res] (= res (+ a b)) 50 | (col :000) (col :abc) (col :abc) 51 | (col :111) (col :abc) (col :bcd) 52 | (col :abc) (col :111) (col :bcd) 53 | (col :aaa) (col :aaa) (col :fff))) 54 | 55 | (deftest color-color 56 | (are [a b res] (= res (- a b)) 57 | (col :abc) (col :000) (col :abc) 58 | (col :bcd) (col :abc) (col :111) 59 | (col :abc) (col :abc) (col :000) 60 | (col :aaa) (col :fff) (col :000))) 61 | 62 | (deftest color*color 63 | (are [a b res] (= res (* a b)) 64 | (col :abc) (col :000) (col :000) 65 | (col :bcd) (col :010101) (col :bcd) 66 | (col :567) (col :020202) (col :ace) 67 | (col :020304) (col :513) (col :a3c) 68 | (col :aaa) (col :fff) (col :fff))) 69 | 70 | (deftest color-divided-by-color 71 | (are [a b res] (= res (/ a b)) 72 | (col :abc) (col :010101) (col :abc) 73 | (col :804) (col :444) (col :020001))) 74 | 75 | (deftest color+number 76 | (are [a b res] (= res (+ a b)) 77 | (col :abc) 0 (col :abc) 78 | (col :abc) 2 (col :acbdce) 79 | (col :abc) 200 (col :fff))) 80 | 81 | (deftest number+color 82 | (are [a b res] (= res (+ a b)) 83 | 0 (col :abc) (col :abc) 84 | 2 (col :abc) (col :acbdce) 85 | 200 (col :abc) (col :fff))) 86 | 87 | (deftest color-number 88 | (are [a b res] (= res (- a b)) 89 | (col :abc) 0 (col :abc) 90 | (col :987) 2 (col :978675) 91 | (col :abc) 250 (col :000))) 92 | 93 | (deftest number-color 94 | (are [a b res] (= res (- a b)) 95 | 0 (col :abc) (col :000) 96 | 255 (col :010203) (col :fefdfc) 97 | 10 (col :000) (col :0a0a0a))) 98 | 99 | (deftest color*number 100 | (are [a b res] (= res (* a b)) 101 | (col :abc) 0 (col :000) 102 | (col :123) 2 (col :224466) 103 | (col :abc) 250 (col :fff))) 104 | 105 | (deftest number*color 106 | (are [a b res] (= res (* a b)) 107 | 0 (col :abc) (col :000) 108 | 2 (col :123) (col :224466) 109 | 250 (col :abc) (col :fff))) 110 | 111 | (deftest color-divided-by-number 112 | (are [a b res] (= res (/ a b)) 113 | (col :000) 10 (col :000) 114 | (col :abc) 1 (col :abc) 115 | (col :864) 2 (col :443322))) 116 | 117 | (deftest number-divided-by-color 118 | (are [a b res] (= res (/ a b)) 119 | 12 (col :010203) (col :0c0604) 120 | 0 (col :abc) (col :000))) 121 | -------------------------------------------------------------------------------- /test/rules.clj: -------------------------------------------------------------------------------- 1 | (ns rules 2 | (:use cssgen cssgen.types clojure.test)) 3 | 4 | (deftest single-prop-rules 5 | (are [the-rule result] (= result (css the-rule)) 6 | 7 | (rule "a" 8 | :color :#aaa) 9 | "a { 10 | color: #aaa; 11 | } 12 | " 13 | 14 | (rule "a" 15 | :color "#aaa") 16 | "a { 17 | color: #aaa; 18 | } 19 | " 20 | 21 | (rule "a" 22 | :color ($ :aaa)) 23 | "a { 24 | color: #AAAAAA; 25 | } 26 | " 27 | 28 | (rule "div.klass#id, tr > td" 29 | :padding [:1px :2px :5.5em :-3.3cm]) 30 | "div.klass#id, tr > td { 31 | padding: 1px 2px 5.5em -3.3cm; 32 | } 33 | ")) 34 | 35 | (deftest multiple-prop-rules 36 | (are [the-rule result] (= result (css the-rule)) 37 | 38 | (rule "a" 39 | :color "#aaa" 40 | :background-color :#fafbfc) 41 | "a { 42 | color: #aaa; 43 | background-color: #fafbfc; 44 | } 45 | " 46 | 47 | (rule "a" 48 | :color :#aaa :background-color (col "fff")) 49 | "a { 50 | color: #aaa; 51 | background-color: #FFFFFF; 52 | } 53 | ")) 54 | 55 | (deftest nested-rule 56 | (are [the-rule result] (= result (css the-rule)) 57 | 58 | (rule "tr" 59 | :background-color :#fff 60 | (rule "td" 61 | :color "black")) 62 | "tr { 63 | background-color: #fff; 64 | } 65 | tr td { 66 | color: black; 67 | } 68 | " 69 | 70 | (rule "tr" 71 | :background-color :#fff 72 | :color "black" 73 | (rule "td" 74 | :color "red" 75 | :width "50%")) 76 | "tr { 77 | background-color: #fff; 78 | color: black; 79 | } 80 | tr td { 81 | color: red; 82 | width: 50%; 83 | } 84 | ")) 85 | 86 | (def prop1 87 | (mixin :color :#fff 88 | :background-color "black")) 89 | 90 | (def prop2 91 | (mixin :width "100%" 92 | :display "block")) 93 | 94 | (def prop3 95 | (mixin :height :100px)) 96 | 97 | (deftest inner-prop 98 | (are [the-rule result] (= result (css the-rule)) 99 | (rule "tr" 100 | :padding 0 101 | prop1 prop2 prop3 102 | :border "none") 103 | "tr { 104 | padding: 0; 105 | color: #fff; 106 | background-color: black; 107 | width: 100%; 108 | display: block; 109 | height: 100px; 110 | border: none; 111 | } 112 | ")) 113 | 114 | (defn mixin1 [] 115 | (mixin 116 | :padding 0 117 | (rule "a" 118 | :color :blue))) 119 | 120 | (deftest multiple-rules-and-props 121 | (are [the-rule result] (= result (css the-rule)) 122 | (rule ".block, .group" 123 | (mixin1)) 124 | ".block, .group { 125 | padding: 0; 126 | } 127 | .block a, .group a { 128 | color: blue; 129 | } 130 | ")) 131 | 132 | (deftest mixin-with-nil 133 | (are [the-rule result] (= result (css the-rule)) 134 | (rule "a" 135 | (mixin 136 | :color "blue" 137 | nil 138 | :font-size :2mm)) 139 | "a { 140 | color: blue; 141 | font-size: 2mm; 142 | } 143 | ")) 144 | 145 | -------------------------------------------------------------------------------- /test/selectors.clj: -------------------------------------------------------------------------------- 1 | (ns selectors 2 | (:use cssgen clojure.test)) 3 | 4 | (defn- extract-selectors [rule] 5 | (map second (re-seq #"(?m)^(.*) \{" (css rule)))) 6 | 7 | (deftest nested-selectors 8 | (are [selectors the-rule] (= selectors (extract-selectors the-rule)) 9 | 10 | ["body"] (rule "body") 11 | ["body" "body table"] (rule "body" (rule "table")) 12 | ["body" "body table"] (rule "body" (mixin (rule "table"))) 13 | ["body" "body table"] (rule "body" (mixin (mixin (mixin (rule "table"))))) 14 | ["body" "body table" "body table tr"] (rule "body" (rule "table" (rule "tr"))) 15 | ["body" "body table" "body table tr"] (rule "body" (mixin (rule "table" (mixin (rule "tr"))))) 16 | 17 | ["a" "a:hover"] (rule "a" (rule "&:hover")) 18 | ["a" "a:hover"] (rule "a" (mixin (rule "&:hover"))) 19 | ["body a" "body a:hover, body a:visited"] (rule "body a" (rule "&:hover, &:visited")) 20 | ["body a" "body a:hover, body a:visited"] (rule "body a" (mixin (rule "&:hover, &:visited"))) 21 | ["a" "html a:hover"] (rule "a" (rule "html &:hover")) 22 | ["a" "html a:hover"] (rule "a" (mixin (rule "html &:hover"))) 23 | 24 | ["body" "body a, body input"] (rule "body" (rule "a, input")) 25 | ["body" "body a, body input"] (rule "body" (mixin (rule "a, input"))) 26 | ["body, #footer" "body a, #footer a"] (rule "body , #footer" (rule " a ")) 27 | ["body, #footer" "body a, #footer a"] (rule "body , #footer" (mixin (rule " a "))) 28 | ["body, #footer" "body a, body input, #footer a, #footer input"] (rule " body , #footer " (rule " a , input ")) 29 | ["body, #footer" "body a, body input, #footer a, #footer input"] (rule " body , #footer " (mixin (rule " a , input "))) 30 | 31 | ["body" "body a, body:hover"] (rule "body" (rule "a, &:hover")) 32 | ["body" "body a, body:hover"] (rule "body" (mixin (rule "a, &:hover"))) 33 | ["#foo, .bar" "#foo:hover, .bar:hover"] (rule "#foo, .bar" (rule "&:hover")) 34 | ["#foo, .bar" "#foo:hover, .bar:hover"] (rule "#foo, .bar" (mixin (rule "&:hover"))) 35 | ["#foo, .bar" "#foo:hover, #foo:visited, .bar:hover, .bar:visited"] (rule "#foo, .bar" (rule "&:hover, &:visited")) 36 | ["#foo, .bar" "#foo:hover, #foo:visited, .bar:hover, .bar:visited"] (rule "#foo, .bar" (mixin (rule "&:hover, &:visited"))) 37 | 38 | ["div" "div span" "div span a"] (rule "div" (rule "span" (rule "a"))) 39 | ["div" "div span" "div span a"] (rule "div" (mixin (rule "span" (mixin (rule "a"))))))) 40 | -------------------------------------------------------------------------------- /test/values.clj: -------------------------------------------------------------------------------- 1 | (ns values 2 | (:use cssgen cssgen.types clojure.test)) 3 | 4 | (deftest color-formats 5 | (is (not= (col :#aabbcc) ($ :#aabbcd))) 6 | (are [a b] (= a b) 7 | (col "#abc" ) ($ "#abc" ) 8 | (col "abc" ) ($ "abc" ) 9 | (col "#aabbcc") ($ "#aabbcc") 10 | (col "aabbcc" ) ($ "aabbcc" ) 11 | (col "#0bcdef") ($ "#0bcdef") 12 | (col "0bcdef" ) ($ "0bcdef" ) 13 | (col :0bcdef ) ($ :0bcdef ) 14 | (col :#0bcdef ) ($ :#0bcdef ) 15 | (col :#aaa ) ($ :#aaa ) 16 | (col :#abc ) (col :abc ) 17 | (col :#abc ) (col :aabbcc) 18 | (col 10 10 10) (col :0a0a0a) 19 | ($ 10 10 10) ($ :0a0a0a))) 20 | 21 | (def all-lengths [em ex px in cm mm pt pc %]) 22 | 23 | (deftest length-equality 24 | (doseq [unit all-lengths] 25 | (is (not= (unit 1) (unit 2))) 26 | (is (= (unit 1) (unit 1))))) 27 | 28 | 29 | (deftest representation 30 | (are [the-rule result] (= result (css the-rule)) 31 | 32 | (rule "a" :color :#aaa) 33 | "a { 34 | color: #aaa; 35 | } 36 | " 37 | 38 | (rule "a" :color ($ :010101)) 39 | "a { 40 | color: #010101; 41 | } 42 | " 43 | 44 | (rule "a" :color (col :aaa) :background-color ($ :1a2B3C)) 45 | "a { 46 | color: #AAAAAA; 47 | background-color: #1A2B3C; 48 | } 49 | " 50 | 51 | (rule "a" 52 | :width (em 1) 53 | :width (ex 1) 54 | :width (px 1) 55 | :width (in 1) 56 | :width (cm 1) 57 | :width (mm 1) 58 | :width (pt 1) 59 | :width (pc 1) 60 | :width (deg 1) 61 | :width (% 1)) 62 | "a { 63 | width: 1em; 64 | width: 1ex; 65 | width: 1px; 66 | width: 1in; 67 | width: 1cm; 68 | width: 1mm; 69 | width: 1pt; 70 | width: 1pc; 71 | width: 1deg; 72 | width: 1%; 73 | } 74 | " 75 | )) 76 | --------------------------------------------------------------------------------