Blob Blame History Raw
#|
Copyright 2017 Dmitry Arkhipov
Distributed under the Boost Software License, Version 1.0. (See
accompanying file LICENSE_1_0.txt or copy at
http://www.boost.org/LICENSE_1_0.txt)
|#

import common ;
import feature ;
import generators ;
import modules ;
import sequence ;
import toolset ;
import "class" : new ;

#| tag::doc[]

= Sass

This tool converts SASS and SCSS files into CSS. This tool explicitly supports
both the version written in C (sassc) and the original Ruby implementation
(scss) but other variants might also work.  In addition to tool-specific
features, described in this section, the tool recognizes features `<flags>`
and `<include>`.

|# # end::doc[]

feature.feature sass : : implicit propagated symmetric ;

#| tag::doc[]

== Feature: `sass-style`

Sets the output style. Available values are

* `nested`: each property is put on its own line, rules are indented based on
  how deeply they are nested;
* `expanded`: each property is put on its own line, rules are not indented;
* `compact`: each rule is put on a single line, nested rules occupy adjacent
  lines, while groups of unrelated rules are separated by newlines;
* `compressed`: takes minimum amount of space: all unnecessary whitespace is
  removed, property values are compressed to have minimal representation.

The feature is `optional` and is not `propagated` to dependent targets. If no
style is specified, then, if property set contains property `<optimization>on`,
`compressed` style is selected.  Otherwise, `nested` style is selected.

|# # end::doc[]

feature.subfeature sass
    : style
    : nested expanded compact compressed
    : optional
    ;

#| tag::doc[]

== Feature: `sass-line-numbers`

Enables emitting comments showing original line numbers for rules. This can be
useful for debugging a stylesheet. Available values are `on` and `off`. The
feature is `optional` and is not `propagated` to dependent targets. If no value
for this feature is specified, then one is copied from the feature
`debug-symbols`.

|# end::doc[]

feature.subfeature sass : line-numbers : on off : optional ;

#| tag::doc[]

== Initialization

To use the `sass` tool you need to declare it in a configuration file with the
`using` rule. The initialization takes the following arguments:

* `command`: the command, with any extra arguments, to execute.

For example you could insert the following in your `user-config.jam`:

```
using sass : /usr/local/bin/psass -p2 ; # Perl libsass-based version
```

If no `command` is given, `sassc` is tried, after which `scss` is tried.

|# # end::doc[]

rule init ( command * )
{
    if ! $(.initialized)
    {
        # Setup only if we were called via "using .. ;"
        .initialized = true ;

        # Register generators
        generators.register [ new sass-generator sass.convert : SASS : CSS ] ;
    }

    # Setting up command
    if ! $(command)
    {
        # If none was specified by the user, first try sassc, then scss
        SASS = [ common.find-tool sassc ] ;
        SASS ?= [ common.find-tool scss ] ;
    }
    else
    {
        # Otherwise we attempt to resolve each component of the command to
        # account for script interpreter wrappers.
        SASS = [ sequence.transform maybe-find-tool : $(command) ] ;
    }
}

class sass-generator : generator
{
    import property-set ;

    rule run ( project name ? : property-set : sources + )
    {
        local style = [ $(property-set).get <sass-style> ] ;
        local line-numbers = [ $(property-set).get <sass-line-numbers> ] ;

        # Only one source file is sensible; we accept only sass and scss files
        if ( ! $(sources[2]) ) && ( [ $(sources[1]).type ] in SASS )
        {
            # If no output name was given, guess it from sources
            if ! $(name)
            {
                name = [ generator.determine-output-name $(sources) ] ;
            }

            # If output style was not given, then it is determined by
            # <optimization> feature
            if ! $(style)
            {
                switch [ $(property-set).get <optimization> ]
                {
                    case "off" : style = nested ;
                    case *     : style = compressed ;
                }
            }

            # If line-numbers feature wasn't specified, copy it from
            # <debug-symbols>
            line-numbers ?= [ $(property-set).get <debug-symbols> ] ;
        }

        # We build a reduced property set so that we are not toolset dependent.
        local raw-set
            = <sass-style>$(style)
              <sass-line-numbers>$(line-numbers)
            ;
        raw-set +=
            [ sequence.filter recognized-feature : [ $(property-set).raw ] ] ;
        raw-set = [ feature.expand-composites $(raw-set) ] ;
        raw-set += [ $(property-set).incidental ] ;
        property-set = [ property-set.create $(raw-set) ] ;
        return
            [ generator.run $(project) $(name)
            : $(property-set)
            : $(sources)
            ] ;
    }

    local rule recognized-feature ( feature )
    {
        local result ;
        if $(feature:G) in <include> <flags>
        {
          result = true ;
        }
        return $(result) ;
    }
}

_ = " " ;
toolset.flags sass STYLE : <sass-style> ;
toolset.flags sass LINE_NUMBERS <sass-line-numbers>on  : --line-numbers ;
toolset.flags sass INCLUDES : <include> ;
toolset.flags sass FLAGS : <flags> ;

actions convert
{
  "$(SASS)" -t$(_)"$(STYLE)" $(LINE_NUMBERS) -I$(_)"$(INCLUDES)" $(FLAGS) "$(>)" $(_)"$(<)"
}

local rule maybe-find-tool ( command )
{
    local tool = [ common.find-tool $(command) ] ;
    tool ?= $(command) ;
    return $(tool) ;
}