Skip to content

Commit

Permalink
init (0.1.0)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaddr committed Jun 28, 2010
0 parents commit 04c7cdd
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
lib
pom.xml
*.jar
136 changes: 136 additions & 0 deletions README.markdown
@@ -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.
5 changes: 5 additions & 0 deletions project.clj
@@ -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"]])
54 changes: 54 additions & 0 deletions src/gaka/core.clj
@@ -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))))
62 changes: 62 additions & 0 deletions test/gaka/core_test.clj
@@ -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"))

0 comments on commit 04c7cdd

Please sign in to comment.