Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Commit

Permalink
Merge pull request #644 from matryo/custom_functions
Browse files Browse the repository at this point in the history
Custom functions
  • Loading branch information
am11 committed Mar 23, 2015
2 parents f956476 + 07a9540 commit 9f62c26
Show file tree
Hide file tree
Showing 38 changed files with 2,330 additions and 205 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ matrix:
before_install:
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update
- sudo apt-get install gcc-4.8 g++-4.8
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 20
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 20
- sudo apt-get install gcc-4.7 g++-4.7
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.7 20
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.7 20
- g++ --version
- sudo apt-get update -qq
- git submodule update --init --recursive
Expand Down
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,68 @@ When returning or calling `done()` with `{ contents: "String" }`, the string val

Starting from v3.0.0, `this` refers to a contextual scope for the immediate run of `sass.render` or `sass.renderSync`

### functions
`functions` is an `Object` that holds a collection of custom functions that may be invoked by the sass files being compiled. They may take zero or more input parameters and must return a value either synchronously (`return ...;`) or asynchronously (`done();`). Those parameters will be instances of one of the constructors contained in the `require('node-sass').types` hash. The return value must be of one of these types as well. See the list of available types below:

#### types.Number(value [, unit = ""])
* `getValue()`/ `setValue(value)` : gets / sets the numerical portion of the number
* `getUnit()` / `setUnit(unit)` : gets / sets the unit portion of the number

#### types.String(value)
* `getValue()` / `setValue(value)` : gets / sets the enclosed string

#### types.Color(r, g, b [, a = 1.0]) or types.Color(argb)
* `getR()` / `setR(value)` : red component (integer from `0` to `255`)
* `getG()` / `setG(value)` : green component (integer from `0` to `255`)
* `getB()` / `setB(value)` : blue component (integer from `0` to `255`)
* `getA()` / `setA(value)` : alpha component (number from `0` to `1.0`)

Example:

```javascript
var Color = require('node-sass').types.Color,
c1 = new Color(255, 0, 0),
c2 = new Color(0xff0088cc);
```

#### types.Boolean(value)
* `getValue()` : gets the enclosed boolean
* `types.Boolean.TRUE` : Singleton instance of `types.Boolean` that holds "true"
* `types.Boolean.FALSE` : Singleton instance of `types.Boolean` that holds "false"

#### types.List(length [, commaSeparator = true])
* `getValue(index)` / `setValue(index, value)` : `value` must itself be an instance of one of the constructors in `sass.types`.
* `getSeparator()` / `setSeparator(isComma)` : whether to use commas as a separator
* `getLength()`

#### types.Map(length)
* `getKey(index)` / `setKey(index, value)`
* `getValue(index)` / `setValue(index, value)`
* `getLength()`

#### types.Null()
* `types.Null.NULL` : Singleton instance of `types.Null`.

#### Example

```javascript
sass.renderSync({
data: '#{headings(2,5)} { color: #08c; }',
functions: {
'headings($from: 0, $to: 6)': function(from, to) {
var i, f = from.getValue(), t = to.getValue(),
list = new sass.types.List(t - f + 1);

for (i = f; i <= t; i++) {
list.setValue(i - f, new sass.types.String('h' + i));
}

return list;
}
}
});
```

### includePaths
Type: `Array<String>`
Default: `[]`
Expand Down
69 changes: 40 additions & 29 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
'target_name': 'binding',
'sources': [
'src/binding.cpp',
'src/sass_context_wrapper.cpp'
'src/create_string.cpp',
'src/custom_function_bridge.cpp',
'src/custom_importer_bridge.cpp',
'src/sass_context_wrapper.cpp',
'src/sass_types/boolean.cpp',
'src/sass_types/color.cpp',
'src/sass_types/error.cpp',
'src/sass_types/factory.cpp',
'src/sass_types/list.cpp',
'src/sass_types/map.cpp',
'src/sass_types/null.cpp',
'src/sass_types/number.cpp',
'src/sass_types/string.cpp'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")',
Expand All @@ -15,41 +27,39 @@
'libsass.gyp:libsass',
]
}],
['libsass_ext == "auto"', {
'cflags_cc': [
'<!(pkg-config --cflags libsass)',
],
'link_settings': {
'ldflags': [
'<!(pkg-config --libs-only-other --libs-only-L libsass)',
],
'libraries': [
'<!(pkg-config --libs-only-l libsass)',
],
}
}],
['libsass_ext == "yes"', {
'cflags_cc': [
'<(libsass_cflags)',
],
'link_settings': {
'ldflags': [
'<(libsass_ldflags)',
],
'libraries': [
'<(libsass_library)',
],
}
['libsass_ext == "auto"', {
'cflags_cc': [
'<!(pkg-config --cflags libsass)',
],
'link_settings': {
'ldflags': [
'<!(pkg-config --libs-only-other --libs-only-L libsass)',
],
'libraries': [
'<!(pkg-config --libs-only-l libsass)',
],
}
}],
['libsass_ext == "yes"', {
'cflags_cc': [
'<(libsass_cflags)',
],
'link_settings': {
'ldflags': [
'<(libsass_ldflags)',
],
'libraries': [
'<(libsass_library)',
],
}
}],
['OS=="mac"', {
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [
'-std=c++11',
'-stdlib=libc++'
],
'OTHER_LDFLAGS': [
'-stdlib=libc++'
],
'OTHER_LDFLAGS': [],
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES',
'MACOSX_DEPLOYMENT_TARGET': '10.7'
Expand All @@ -73,6 +83,7 @@
}],
['OS!="win"', {
'cflags_cc+': [
'-fexceptions',
'-std=c++0x'
]
}]
Expand Down
119 changes: 112 additions & 7 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,67 @@ function getOptions(options, cb) {
return options;
}

/**
* Executes a callback and transforms any exception raised into a sass error
*
* @param {Function} callback
* @param {Array} arguments
* @api private
*/

function tryCallback(callback, args) {
try {
return callback.apply(this, args);
} catch (e) {
if (typeof e === 'string') {
return new binding.types.Error(e);
} else if (e instanceof Error) {
return new binding.types.Error(e.message);
} else {
return new binding.types.Error('An unexpected error occurred');
}
}
}

/**
* Normalizes the signature of custom functions to make it possible to just supply the
* function name and have the signature default to `fn(...)`. The callback is adjusted
* to transform the input sass list into discrete arguments.
*
* @param {String} signature
* @param {Function} callback
* @return {Object}
* @api private
*/

function normalizeFunctionSignature(signature, callback) {
if (!/^\*|@warn|@error|@debug|\w+\(.*\)$/.test(signature)) {
if (!/\w+/.test(signature)) {
throw new Error('Invalid function signature format "' + signature + '"');
}

return {
signature: signature + '(...)',
callback: function() {
var args = Array.prototype.slice.call(arguments),
list = args.shift(),
i;

for (i = list.getLength() - 1; i >= 0; i--) {
args.unshift(list.getValue(i));
}

return callback.apply(this, args);
}
};
}

return {
signature: signature,
callback: callback
};
}

/**
* Render
*
Expand Down Expand Up @@ -172,13 +233,9 @@ module.exports.render = function(options, cb) {
var importer = options.importer;

if (importer) {
options.importer = function(file, prev, key) {
options.importer = function(file, prev, bridge) {
function done(data) {
console.log(data); // ugly hack
binding.importedCallback({
index: key,
objectLiteral: data
});
bridge.success(data);
}

var result = importer.call(options.context, file, prev, done);
Expand All @@ -189,6 +246,31 @@ module.exports.render = function(options, cb) {
};
}

var functions = options.functions;

if (functions) {
options.functions = {};

Object.keys(functions).forEach(function(signature) {
var cb = normalizeFunctionSignature(signature, functions[signature]);

options.functions[cb.signature] = function() {
var args = Array.prototype.slice.call(arguments),
bridge = args.pop();

function done(data) {
bridge.success(data);
}

var result = tryCallback(cb.callback, args.concat(done));

if (result) {
done(result);
}
};
});
}

options.data ? binding.render(options) : binding.renderFile(options);
};

Expand All @@ -206,10 +288,24 @@ module.exports.renderSync = function(options) {

if (importer) {
options.importer = function(file, prev) {
return { objectLiteral: importer.call(options.context, file, prev) };
return importer.call(options.context, file, prev);
};
}

var functions = options.functions;

if (options.functions) {
options.functions = {};

Object.keys(functions).forEach(function(signature) {
var cb = normalizeFunctionSignature(signature, functions[signature]);

options.functions[cb.signature] = function() {
return tryCallback(cb.callback, arguments);
};
});
}

var status = options.data ? binding.renderSync(options) : binding.renderFileSync(options);
var result = options.result;

Expand All @@ -228,3 +324,12 @@ module.exports.renderSync = function(options) {
*/

module.exports.info = process.sass.versionInfo;

/**
* Expose sass types
*/

module.exports.types = binding.types;
module.exports.TRUE = binding.types.Boolean.TRUE;
module.exports.FALSE = binding.types.Boolean.FALSE;
module.exports.NULL = binding.types.Null.NULL;
6 changes: 2 additions & 4 deletions libsass.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,12 @@
'-std=c++11',
'-stdlib=libc++'
],
'OTHER_LDFLAGS': [
'-stdlib=libc++'
],
'OTHER_LDFLAGS': [],
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES',
'MACOSX_DEPLOYMENT_TARGET': '10.7'
}
}],
}],
['OS=="win"', {
'msvs_settings': {
'VCCLCompilerTool': {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"coverage": "node scripts/coverage.js",
"install": "node scripts/install.js",
"postinstall": "node scripts/build.js",
"pretest": "node_modules/.bin/jshint bin lib test",
"pretest": "node_modules/.bin/jshint bin lib scripts test",
"test": "node_modules/.bin/mocha test"
},
"files": [
Expand Down
6 changes: 3 additions & 3 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function afterBuild(options) {
*/

function build(options) {
var arguments = [
var args = [
path.join('node_modules', 'pangyp', 'bin', 'node-gyp'),
'rebuild',
].concat(
Expand All @@ -63,9 +63,9 @@ function build(options) {
})
).concat(options.args);

console.log(['Building:', process.sass.runtime.execPath].concat(arguments).join(' '));
console.log(['Building:', process.sass.runtime.execPath].concat(args).join(' '));

var proc = spawn(process.sass.runtime.execPath, arguments, {
var proc = spawn(process.sass.runtime.execPath, args, {
stdio: [0, 1, 2]
});

Expand Down
Loading

0 comments on commit 9f62c26

Please sign in to comment.