Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

init (0.1.0)

  • Loading branch information...
commit 04c7cdd9e148b310744df39b8fcb981c1019e80a 0 parents
Brian Carper authored
3  .gitignore
@@ -0,0 +1,3 @@
+lib
+pom.xml
+*.jar
136 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 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 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 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"))
Please sign in to comment.
Something went wrong with that request. Please try again.