This is a read-only archive!

Introducing Gaka

The CSS for my blog is now being generated via gaka, a CSS-generating library I wrote this afternoon. It's extremely simple, but it got the job done for me. I turned around 600 lines of CSS into around 250 lines of Clojure without much effort. It looks like this:

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;}

Gaka is partly inspired by Sass, which I found very pleasant to work with recently. And it's partly inspired by Hiccup, which is a delicious way to generate HTML in Clojure.

There's more info and more examples on github.

June 28, 2010 @ 10:59 AM PDT
Cateogory: Programming

13 Comments

brandon
Quoth brandon on June 28, 2010 @ 12:19 PM PDT

i think it would be more natural to have the attributes be in curly braces like clojure's html rendering libraries. although i haven't seen any css libraries in clojure, so i don't know if your way is common.

Brian
Quoth Brian on June 28, 2010 @ 12:47 PM PDT

That would be my first choice also, but hash-maps are unordered, and order is significant in CSS.

brandon
Quoth brandon on June 28, 2010 @ 2:00 PM PDT

good point. i suppose i didn't think of that. probably because (at least in my css) the order doesn't matter for the attributes, because i don't override the same attribute within any one block.

div#container{  
margin:2px 1px 1px 1px;  
color:#000000;  
margin-bottom:5px;}

i guess i don't see where order is important unless you're doing something like that (which is awkward anyway).

Brian
Quoth Brian on June 28, 2010 @ 2:36 PM PDT

It is ugly, but I do it. :(

border: 0;
border-bottom: 1px black solid;

There's even more of a chance of this showing up if you're generating CSS programatically, e.g. using mixins or calling functions that might return things that conflict with your literal rules. It is valid CSS to repeat rules, as far as I know.

Keeping it flat also lets you do things like this:

[:div
  :margin 0
  [:a :color "red"]
  :padding 0]

Currently Gaka will handle that OK, though it's arguably even uglier than your example.

I would've loved to have used hash-maps otherwise.

Scott
Quoth Scott on June 28, 2010 @ 9:56 PM PDT

This was already done two months ago, see cssgen.

bhenry
Quoth bhenry on June 28, 2010 @ 11:52 PM PDT

bummer. didn't i read something about sorted maps in clojure? or are those just disguised lists that would be slower?

Raynes
Quoth Raynes on June 29, 2010 @ 7:31 AM PDT

http://github.com/programble/csslj

A friend of mine seems to have done pretty much exactly what you have done a while back. I believe csslj is going to be put into Hiccup itself eventually.

Brian
Quoth Brian on June 29, 2010 @ 8:21 AM PDT

@Scott: Yeah, I saw cssgen, but I didn't quite like the interface.

@bhenry: I'm unaware of sorted maps. Whatever I use, it would be nice (mandatory) that it has a nice reader syntax. Otherwise, no sense in using a DSL.

@Raynes: Wish I would've found that a week ago. Google gives me cssgen as a search result, but csslj is nowhere to be found. Github's search is completely useless.

I don't like how csslj uses maps, for the same reason as above. Order is not preserved.

brandon
Quoth brandon on June 30, 2010 @ 10:30 AM PDT

http://richhickey.github.com/clojure/clojure.core-api.html#clojure.core/array-map

you would have to modify your compile* function (at least i think that's where it would go) to create the map with array-map instead of a hash map. I'm not certain of how you would implement it or that it would even be possible to do with a friendly syntax. but it is something to consider, and although the documentation states that array maps should only be used on small maps, i think a set of css attributes would definitely qualify as small enough.

Steve
Quoth Steve on July 29, 2010 @ 5:59 AM PDT

Heya Brian, I've forked gaka on github (http://github.com/purcell/gaka) and added support for attribute maps in addition to your existing syntax, so you can either supply flattened attrs (e.g. [:mydiv :color "#fff"]) or a hash (e.g. [:mydiv {:color "#fff"}]) or both (e.g. [:mydiv :border 1 {:color "#fff"}]). This seems like the best of both worlds to me, and degrades gracefully to a format compatible with csslj.

I've also added an inline-css function, which formats sets of attributes suitably for use in HTML style= attributes.

Feel free to pull the changes into your repo if they make any sense to you.

Brian
Quoth Brian on July 29, 2010 @ 6:16 AM PDT

Actually I just finished pushing your changes to github about 12 seconds ago. Thanks!

I also worked out a (hopefully) better "flatten" to work around a limitation I hit using mixins in some of my other code.

Scott
Quoth Scott on November 23, 2010 @ 11:12 AM PST

Is there a builtin for handling attribute values that can be collections (converting them to strings)?

cssgen:

 (defn foo [& [x y radius color :as args]]
   (mixin :box-shadow args))

gaka:

 (defn foo [& [x y radius color :as args]]
   (list :box-shadow (apply str (interpose " " (map name args)))))

Is there a builtin to do nesting where indirection is neeeded and the sub element doesn't have a space between itself and the superelement?

gaka:

 (defn foo [obj]
   (css [obj
         ...
         [".class"
          ...]]
        [(str obj ".class")
         ...]
        [(str obj ":hover")
         ...]))

cssgen:

 (css (rule obj
        ...
        (rule "& .class"
          ...)
        (rule "&.class"
          ...)
        (rule "&:hover"
          ...)))
Brian
Quoth Brian on November 24, 2010 @ 6:16 AM PST

No to both. Good ideas though, I'll work on those.