Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 04c7cdd
Showing
5 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
lib | ||
pom.xml | ||
*.jar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# gaka 0.1.0 | ||
by [Brian Carper](http://briancarper.net) | ||
|
||
Gaka is a CSS-generating library for Clojure inspired partly by | ||
[Sass](http://sass-lang.com/) and similar to | ||
[Hiccup](http://github.com/weavejester/hiccup). | ||
|
||
## Features | ||
|
||
* Simple | ||
* Indented output | ||
* Selector nesting | ||
* "Mixins" | ||
|
||
## Purpose | ||
|
||
CSS syntax is verbose, with lots of curly braces and semi-colons. Writing CSS | ||
as s-expressions is a way to ensure you have proper syntax, because the program | ||
handles the syntax for you. And it lets you write CSS very quickly using an | ||
editor that's good at manipulating s-expressions. | ||
|
||
CSS has a lot of repetition in selectors. `body #content div a {...}` etc. | ||
You can remove most of this verbosity via nesting. S-expressions are a great | ||
way to express that nesting. (Sass uses indentation for the same purpose.) | ||
|
||
CSS rules in Gaka are just vectors of keywords and strings and numbers, which | ||
means you can easily generate and manipulate them programatically. | ||
|
||
## Example | ||
|
||
Rules are vectors, where the first element is a selector and the rest are | ||
either key/value pairs, or sub-rules. | ||
|
||
user> (require '(gaka [core :as gaka])) | ||
nil | ||
user> (def rules [:div#foo | ||
:margin "0px" | ||
[:span.bar | ||
:color "black" | ||
:font-weight "bold" | ||
[:a:hover | ||
:text-decoration "none"]]]) | ||
#'user/rules | ||
user> (println (gaka/css rules)) | ||
div#foo { | ||
margin: 0px;} | ||
|
||
div#foo span.bar { | ||
color: black; | ||
font-weight: bold;} | ||
|
||
div#foo span.bar a:hover { | ||
text-decoration: none;} | ||
|
||
|
||
nil | ||
user> (binding [gaka/*print-indent* false] | ||
(println (gaka/css rules))) | ||
div#foo { | ||
margin: 0px;} | ||
|
||
div#foo span.bar { | ||
color: black; | ||
font-weight: bold;} | ||
|
||
div#foo span.bar a:hover { | ||
text-decoration: none;} | ||
|
||
|
||
nil | ||
user> (gaka/save-css "foo.css" rules) | ||
nil | ||
|
||
Anything in a seq (e.g. a list) will be flattened into the surrounding context, | ||
which lets you have "mixins". | ||
|
||
user> (def standard-attrs (list :margin 0 :padding 0 :font-size "12px")) | ||
#'user/standard-attrs | ||
user> (println (gaka/css [:div standard-attrs :color "red"])) | ||
div { | ||
margin: 0; | ||
padding: 0; | ||
font-size: 12px; | ||
color: red;} | ||
user> (defn color [x] (list :color x)) | ||
#'user/color | ||
user> (println (gaka/css [:div (color "red")])) | ||
div { | ||
color: red;} | ||
|
||
If you want a fancy selector or attribute that doesn't work as a keyword, use a | ||
string. | ||
|
||
user> (println (gaka/css ["input[type=text]" :font-family "\"Bitstream Vera Sans\", monospace"])) | ||
input[type=text] { | ||
font-family: "Bitstream Vera Sans", monospace;} | ||
|
||
An easy way to compile your CSS to a file and make sure it's always up-to-date | ||
is to throw a `save-css` call at the bottom of your source file. | ||
|
||
(ns my-site.css | ||
(:require (gaka [core :as gaka) | ||
|
||
(def rules [...]) | ||
|
||
(save-css "public/css/style.css" rules) | ||
|
||
Now every time you re-compile this file (for example, `C-c C-k` in | ||
Slime/Emacs), a static CSS file in `public/css` will be generated or updated. | ||
This is the prefered way to serve CSS files for a web app (to avoid | ||
re-compiling your CSS on every request, which is probably pointless). | ||
|
||
That's about it. | ||
|
||
## Limitations | ||
|
||
Gaka currently outputs less-than-optimal CSS under certain circumstances and | ||
errs on the side of verbosity to preserve correctness. | ||
|
||
Gaka doesn't validate your CSS or check your spelling. | ||
|
||
Gaka makes no attempt to be fast. You should compile your CSS, save it to a | ||
file and serve it statically. | ||
|
||
I wrote this in one afternoon while eating a tasty ham and turky sandwich. | ||
Bugs are likely. | ||
|
||
## Install | ||
|
||
To fetch from CLojars (via Leningen) put this in your project.clj: | ||
|
||
[gaka "0.1.0"] | ||
|
||
## License | ||
|
||
Eclipse Public License 1.0, see http://opensource.org/licenses/eclipse-1.0.php. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
(defproject gaka/gaka "0.1.0" | ||
:description "A CSS-generating library for Clojure" | ||
:dependencies [[org.clojure/clojure "1.2.0-master-SNAPSHOT"] | ||
[org.clojure/clojure-contrib "1.2.0-SNAPSHOT"]] | ||
:dev-dependencies [[swank-clojure "1.2.1"]]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
(ns gaka.core | ||
(:require (clojure.contrib [string :as s]) | ||
(clojure.java [io :as io]))) | ||
|
||
(def *context* []) | ||
(def *print-indent* true) | ||
|
||
(defn make-rule [selector keyvals] | ||
{:selector selector | ||
:keyvals keyvals}) | ||
|
||
(defn indent [n] | ||
(when *print-indent* | ||
(s/repeat n " "))) | ||
|
||
(defn render-val [x] | ||
(cond (number? x) (str x) | ||
:else (name x))) | ||
|
||
(defn render-keyval [n [key val]] | ||
(when-not val | ||
(throw (IllegalArgumentException. (str "Missing value for key " (pr-str key) ".")))) | ||
(let [indent (indent n)] | ||
(str indent (name key) ": " (render-val val) ";"))) | ||
|
||
(defn render-rule [{:keys [selector keyvals]}] | ||
(let [indent (indent (dec (count selector)))] | ||
(str indent (s/join " " (map name selector)) " {\n" | ||
(s/join "\n" (map #(render-keyval (count selector) %) | ||
(partition-all 2 keyvals))) | ||
"}\n\n"))) | ||
|
||
(declare compile-rule) | ||
(defn compile* [rules [selector & xs]] | ||
(reduce (fn [rules selector] | ||
(binding [*context* (conj *context* selector)] | ||
(let [subrules (filter vector? xs) | ||
keyvals (flatten (remove vector? xs)) | ||
rules (conj rules (make-rule *context* keyvals))] | ||
(reduce (fn [rs x] | ||
(compile* rs x)) | ||
rules subrules)))) | ||
rules (s/split #"\s*,\s*" (name selector)))) | ||
|
||
(defn css [& rules] | ||
(let [rules (filter (complement empty?) rules)] | ||
(if-not (seq rules) | ||
"" | ||
(let [rules (reduce compile* [] rules)] | ||
(s/map-str render-rule rules))))) | ||
|
||
(defn save-css [filename & rules] | ||
(with-open [out (io/writer filename)] | ||
(.write out (apply css rules)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
(ns gaka.core-test | ||
(:use [gaka.core] :reload-all) | ||
(:use [clojure.test])) | ||
|
||
(defmacro =? [& body] | ||
`(are [x# y#] (= x# y#) | ||
~@body)) | ||
|
||
(deftest test-compile* | ||
(=? (compile* [] [:a]) | ||
[{:selector ["a"] | ||
:keyvals []}] | ||
|
||
(compile* [] [:a :color :red]) | ||
[{:selector ["a"] | ||
:keyvals [:color :red]}] | ||
|
||
(compile* [] [:a [:img :border :none]]) | ||
[{:selector ["a"] | ||
:keyvals []} | ||
{:selector ["a" "img"] | ||
:keyvals [:border :none]}] | ||
|
||
(compile* [] [:div [:a [:img :border :none]]]) | ||
[{:selector ["div"] | ||
:keyvals []} | ||
{:selector ["div" "a"] | ||
:keyvals []} | ||
{:selector ["div" "a" "img"] | ||
:keyvals [:border :none]}] | ||
|
||
(compile* [] [:div (list :border :none)]) | ||
[{:selector ["div"] | ||
:keyvals [:border :none]}] | ||
|
||
(compile* [] [:div ["a, img" :border :none]]) | ||
[{:selector ["div"] | ||
:keyvals []} | ||
{:selector ["div" "a"] | ||
:keyvals [:border :none]} | ||
{:selector ["div" "img"] | ||
:keyvals [:border :none]}])) | ||
|
||
(deftest test-render-rule | ||
(=? (render-rule {:selector ["a"] :keyvals [:color :red]}) | ||
"a {\n color: red;}\n\n" | ||
|
||
(render-rule {:selector ["a"] :keyvals [:color :red :border :none]}) | ||
"a {\n color: red;\n border: none;}\n\n" | ||
|
||
(render-rule {:selector ["a" "img"] :keyvals [:border :none]}) | ||
" a img {\n border: none;}\n\n")) | ||
|
||
(deftest test-css | ||
(=? (css nil) | ||
"" | ||
|
||
(css [:a :color :red [:img :border :none]]) | ||
"a {\n color: red;}\n\n a img {\n border: none;}\n\n" | ||
|
||
(css [:a :color :red [:img :border :none] :font-style :italic]) | ||
"a {\n color: red;\n font-style: italic;}\n\n a img {\n border: none;}\n\n")) |