DSS: Dynamic Style Sheets


Description

CSS was a huge step forward in styling websites. It’s much simpler to be able to define styles in one place, and have them applied to the whole website. It’s much closer to the DRY philosophy that good programmers try to follow. Unfortunately, within a .css file, there’s often unnecessary repetition and redundancy (see what I did there?). DSS tries to fix that.

DSS is a style sheet preprocessor. It’s input is a CSS-like file with extra DSS “directives” (at-rules) to define classes (not the html kind) and constant definitions, or include other style sheets. It also supports calculated values and conditional rules.

DSS is written in Java, and uses the Coco/R compiler generator to parse its input. It’s licensed under the MIT License, so you can use it pretty much however you want to.

Download

Get the code from GitHub

Documentation

Hahaha… you weren’t really expecting documentation were you? OK, it is on the TODO list. For now, here’s an example file that shows the basic usage.

@define { /* Define some constants */
    foreColor: black;
    backColor: white;
    width: 42%;
}

@class error<size; style:solid> { /* Define an error class that rules can
                                     "apply". Any declarations inside the class
                                     will be included in the ruleset. Classes
                                     are similar to dynamic mixins in less. They
                                     can have parameters (with optional default
                                     values) that are referenced using
                                     param(name) */
    border: param(size) param(style) #800000;
    background-color: #F00;
    font-weight: bold;
}

@include url(layout.dss); /* Include and process layout.dss. Like @import, but
                             it's processed by DSS and the result is inserted
                             into the output. */

html {  
    color: const(foreColor);      /* Reference the named constants */
    background-color: @backColor; /* Alternate syntax for constants or class
                                     parameters (parameters have priority) */
}

@media print {
    @class error { /* Override the error class inside the @media rule */
        apply: error<size:1pt>; /* Extend the original error class */
        background-color: white;      /* Override the original border style */
    }

    @define global {
        width: 42px; /* "global" forces the constants to be available in the
                        global scope (but only after this point in the file),
                        not just inside the "@media print" rule. */
    }

    div.error {
        apply: error; /* We overrode the first error class with a new one that
                         doesn't need any arguments. */
        margin: 1em 0;
    }

    span.error {
        apply: error;
    }
}

div.error {  
    apply: error<size: 1px; style: dotted>; /* This is the first error
                                               class, since we are outside the
                                               "@media print" rule */
    margin: 1em 0;
}

span.error {  
    apply: error<1px>; /* Still the first error class of course. Since
                          the "style" parameter has a default value, we can skip
                          it. Arguments can be passed by position or by name. */
}

@if (ie5 || ie6) { /* ie6 would be a constant defined by the program (see the -d
                      option). Anything except "0" (any unit), "false",
                      or "no" is true. "||" means "OR", && (AND) "!" (NOT),
                      and "^" (XOR) are also supported. */
    @class has-layout {
        zoom: 1;
    }
}
@else {            /* Yay! */
    @class has-layout {
    }
}

.box { /* a 100 pixel box (including padding/border) */
    @define {
        width: 100px;
        pad: 10px;
        border: 1px;
    }
    border: const(border) solid;
    padding: const(pad);
    /* Calculate the width based on the outside width, padding, and border */
    width: @calc(const(width) - 2 * const(pad) - 2 * const(border));
    /* This next line is an equivalent, but shorter version: */
    width: [@width - 2 * @pad - 2 * @border];

    p { /* Rule sets can nest. Selectors can start with a '>', '+', or '~' for
           child, next sibling, and sibling relationships. The leading
           combinator applies to each sub-selector, for example:
               p {
                   > a, span {...}
               }
           would expand to:
               p > a, p > span {...}
           NOT
               p > a, p span {...}
           so you don't need to repeat the '>' symbol.
        */
        margin: 0;
        padding: prop(margin); /* Set the padding to be the same as the margin */
    }
    a {
        text-decoration: none;
        &:hover { /* Use "&" as a placeholder for the parent selector (a in this
                     case) to squeeze selectors together. Just ":hover" would
                     default to the descendant selector ("a :hover") which probably
                     isn't what you want.
                  */
            text-decoration: underline;
        }
    }
}

Usage

Although DSS is intended to be used as a library, it can also be run from a command line.

The basic usage is:

java -jar path/to/dss.jar /path/to/dss/file.dss

Without any additional arguments, it will print the resulting CSS file to stdout. You can also specify an output file, to save directly to another file.

The file path can also be a URL. URLs must be absolute (including “http:”, “file:”, etc.).

Options

-v (--version)
Show version information and exit.
--help
Show help text and exit.
-c (--compress)
Compress the CSS output. This option removes unnecessary whitespace from the output. In the future it may optimize other things, like converting declarations to their short-hand forms.
-d (--define) definition
Define a constant in the global namespace. The syntax is exactly the same as a CSS property, but without the semicolon, i.e. “property-name: value...“. You can have as many definitions as you want (and the maximum command-line length allows, of course).
-o output
File to save the output to.
-w (--watch)
Keep DSS running and automatically re-process the file when it changes.
--debug
Keep the DSS directives in the output. This is mostly for my use in development to make sure everything is parsed correctly.
-t (--test)
Run a series of tests. Instead of the normal file argument, you should give it a directory containing .dss files, and corresponding .css files with the expected output. For example, if the directory contains a “test.dss” file, it should also have a “test.css” file with the exact (byte for byte) output expected from processing “test.dss”.
--color
Color-code the results from the self-test. The output uses ANSI escape codes, which your console should support.