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

Commit 313b4d8

Browse files
committed
Support for custom functions
1 parent 8c641f4 commit 313b4d8

38 files changed

+2326
-205
lines changed

.travis.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ matrix:
1717
before_install:
1818
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
1919
- sudo apt-get update
20-
- sudo apt-get install gcc-4.8 g++-4.8
21-
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 20
22-
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 20
20+
- sudo apt-get install gcc-4.7 g++-4.7
21+
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.7 20
22+
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.7 20
2323
- g++ --version
2424
- sudo apt-get update -qq
2525
- git submodule update --init --recursive

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,68 @@ When returning or calling `done()` with `{ contents: "String" }`, the string val
8989

9090
`this` refers to a contextual scope for the immediate run of `sass.render` or `sass.renderSync`
9191

92+
### functions
93+
`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:
94+
95+
#### types.Number(value [, unit = ""])
96+
* `getValue()`/ `setValue(value)` : gets / sets the numerical portion of the number
97+
* `getUnit()` / `setUnit(unit)` : gets / sets the unit portion of the number
98+
99+
#### types.String(value)
100+
* `getValue()` / `setValue(value)` : gets / sets the enclosed string
101+
102+
#### types.Color(r, g, b [, a = 1.0]) or types.Color(argb)
103+
* `getR()` / `setR(value)` : red component (integer from `0` to `255`)
104+
* `getG()` / `setG(value)` : green component (integer from `0` to `255`)
105+
* `getB()` / `setB(value)` : blue component (integer from `0` to `255`)
106+
* `getA()` / `setA(value)` : alpha component (number from `0` to `1.0`)
107+
108+
Example:
109+
110+
```javascript
111+
var Color = require('node-sass').types.Color,
112+
c1 = new Color(255, 0, 0),
113+
c2 = new Color(0xff0088cc);
114+
```
115+
116+
#### types.Boolean(value)
117+
* `getValue()` : gets the enclosed boolean
118+
* `types.Boolean.TRUE` : Singleton instance of `types.Boolean` that holds "true"
119+
* `types.Boolean.FALSE` : Singleton instance of `types.Boolean` that holds "false"
120+
121+
#### types.List(length [, commaSeparator = true])
122+
* `getValue(index)` / `setValue(index, value)` : `value` must itself be an instance of one of the constructors in `sass.types`.
123+
* `getSeparator()` / `setSeparator(isComma)` : whether to use commas as a separator
124+
* `getLength()`
125+
126+
#### types.Map(length)
127+
* `getKey(index)` / `setKey(index, value)`
128+
* `getValue(index)` / `setValue(index, value)`
129+
* `getLength()`
130+
131+
#### types.Null()
132+
* `types.Null.NULL` : Singleton instance of `types.Null`.
133+
134+
#### Example
135+
136+
```javascript
137+
sass.renderSync({
138+
data: '#{headings(2,5)} { color: #08c; }',
139+
functions: {
140+
'headings($from: 0, $to: 6)': function(from, to) {
141+
var i, f = from.getValue(), t = to.getValue(),
142+
list = new sass.types.List(t - f + 1);
143+
144+
for (i = f; i <= t; i++) {
145+
list.setValue(i - f, new sass.types.String('h' + i));
146+
}
147+
148+
return list;
149+
}
150+
}
151+
});
152+
```
153+
92154
### includePaths
93155
Type: `Array<String>`
94156
Default: `[]`

binding.gyp

+40-29
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,19 @@
44
'target_name': 'binding',
55
'sources': [
66
'src/binding.cpp',
7-
'src/sass_context_wrapper.cpp'
7+
'src/create_string.cpp',
8+
'src/custom_function_bridge.cpp',
9+
'src/custom_importer_bridge.cpp',
10+
'src/sass_context_wrapper.cpp',
11+
'src/sass_types/boolean.cpp',
12+
'src/sass_types/color.cpp',
13+
'src/sass_types/error.cpp',
14+
'src/sass_types/factory.cpp',
15+
'src/sass_types/list.cpp',
16+
'src/sass_types/map.cpp',
17+
'src/sass_types/null.cpp',
18+
'src/sass_types/number.cpp',
19+
'src/sass_types/string.cpp'
820
],
921
'include_dirs': [
1022
'<!(node -e "require(\'nan\')")',
@@ -15,41 +27,39 @@
1527
'libsass.gyp:libsass',
1628
]
1729
}],
18-
['libsass_ext == "auto"', {
19-
'cflags_cc': [
20-
'<!(pkg-config --cflags libsass)',
21-
],
22-
'link_settings': {
23-
'ldflags': [
24-
'<!(pkg-config --libs-only-other --libs-only-L libsass)',
25-
],
26-
'libraries': [
27-
'<!(pkg-config --libs-only-l libsass)',
28-
],
29-
}
30-
}],
31-
['libsass_ext == "yes"', {
32-
'cflags_cc': [
33-
'<(libsass_cflags)',
34-
],
35-
'link_settings': {
36-
'ldflags': [
37-
'<(libsass_ldflags)',
38-
],
39-
'libraries': [
40-
'<(libsass_library)',
41-
],
42-
}
30+
['libsass_ext == "auto"', {
31+
'cflags_cc': [
32+
'<!(pkg-config --cflags libsass)',
33+
],
34+
'link_settings': {
35+
'ldflags': [
36+
'<!(pkg-config --libs-only-other --libs-only-L libsass)',
37+
],
38+
'libraries': [
39+
'<!(pkg-config --libs-only-l libsass)',
40+
],
41+
}
42+
}],
43+
['libsass_ext == "yes"', {
44+
'cflags_cc': [
45+
'<(libsass_cflags)',
46+
],
47+
'link_settings': {
48+
'ldflags': [
49+
'<(libsass_ldflags)',
50+
],
51+
'libraries': [
52+
'<(libsass_library)',
53+
],
54+
}
4355
}],
4456
['OS=="mac"', {
4557
'xcode_settings': {
4658
'OTHER_CPLUSPLUSFLAGS': [
4759
'-std=c++11',
4860
'-stdlib=libc++'
4961
],
50-
'OTHER_LDFLAGS': [
51-
'-stdlib=libc++'
52-
],
62+
'OTHER_LDFLAGS': [],
5363
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
5464
'GCC_ENABLE_CPP_RTTI': 'YES',
5565
'MACOSX_DEPLOYMENT_TARGET': '10.7'
@@ -73,6 +83,7 @@
7383
}],
7484
['OS!="win"', {
7585
'cflags_cc+': [
86+
'-fexceptions',
7687
'-std=c++0x'
7788
]
7889
}]

lib/index.js

+112-7
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,67 @@ function getOptions(options, cb) {
136136
return options;
137137
}
138138

139+
/**
140+
* Executes a callback and transforms any exception raised into a sass error
141+
*
142+
* @param {Function} callback
143+
* @param {Array} arguments
144+
* @api private
145+
*/
146+
147+
function tryCallback(callback, args) {
148+
try {
149+
return callback.apply(this, args);
150+
} catch (e) {
151+
if (typeof e === 'string') {
152+
return new binding.types.Error(e);
153+
} else if (e instanceof Error) {
154+
return new binding.types.Error(e.message);
155+
} else {
156+
return new binding.types.Error('An unexpected error occurred');
157+
}
158+
}
159+
}
160+
161+
/**
162+
* Normalizes the signature of custom functions to make it possible to just supply the
163+
* function name and have the signature default to `fn(...)`. The callback is adjusted
164+
* to transform the input sass list into discrete arguments.
165+
*
166+
* @param {String} signature
167+
* @param {Function} callback
168+
* @return {Object}
169+
* @api private
170+
*/
171+
172+
function normalizeFunctionSignature(signature, callback) {
173+
if (!/^\*|@warn|@error|@debug|\w+\(.*\)$/.test(signature)) {
174+
if (!/\w+/.test(signature)) {
175+
throw new Error('Invalid function signature format "' + signature + '"');
176+
}
177+
178+
return {
179+
signature: signature + '(...)',
180+
callback: function() {
181+
var args = Array.prototype.slice.call(arguments),
182+
list = args.shift(),
183+
i;
184+
185+
for (i = list.getLength() - 1; i >= 0; i--) {
186+
args.unshift(list.getValue(i));
187+
}
188+
189+
return callback.apply(this, args);
190+
}
191+
};
192+
}
193+
194+
return {
195+
signature: signature,
196+
callback: callback
197+
};
198+
}
199+
139200
/**
140201
* Render
141202
*
@@ -172,13 +233,9 @@ module.exports.render = function(options, cb) {
172233
var importer = options.importer;
173234

174235
if (importer) {
175-
options.importer = function(file, prev, key) {
236+
options.importer = function(file, prev, bridge) {
176237
function done(data) {
177-
console.log(data); // ugly hack
178-
binding.importedCallback({
179-
index: key,
180-
objectLiteral: data
181-
});
238+
bridge.success(data);
182239
}
183240

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

249+
var functions = options.functions;
250+
251+
if (functions) {
252+
options.functions = {};
253+
254+
Object.keys(functions).forEach(function(signature) {
255+
var cb = normalizeFunctionSignature(signature, functions[signature]);
256+
257+
options.functions[cb.signature] = function() {
258+
var args = Array.prototype.slice.call(arguments),
259+
bridge = args.pop();
260+
261+
function done(data) {
262+
bridge.success(data);
263+
}
264+
265+
var result = tryCallback(cb.callback, args.concat(done));
266+
267+
if (result) {
268+
done(result);
269+
}
270+
};
271+
});
272+
}
273+
192274
options.data ? binding.render(options) : binding.renderFile(options);
193275
};
194276

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

207289
if (importer) {
208290
options.importer = function(file, prev) {
209-
return { objectLiteral: importer.call(options.context, file, prev) };
291+
return importer.call(options.context, file, prev);
210292
};
211293
}
212294

295+
var functions = options.functions;
296+
297+
if (options.functions) {
298+
options.functions = {};
299+
300+
Object.keys(functions).forEach(function(signature) {
301+
var cb = normalizeFunctionSignature(signature, functions[signature]);
302+
303+
options.functions[cb.signature] = function() {
304+
return tryCallback(cb.callback, arguments);
305+
};
306+
});
307+
}
308+
213309
var status = options.data ? binding.renderSync(options) : binding.renderFileSync(options);
214310
var result = options.result;
215311

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

230326
module.exports.info = process.sass.versionInfo;
327+
328+
/**
329+
* Expose sass types
330+
*/
331+
332+
module.exports.types = binding.types;
333+
module.exports.TRUE = binding.types.Boolean.TRUE;
334+
module.exports.FALSE = binding.types.Boolean.FALSE;
335+
module.exports.NULL = binding.types.Null.NULL;

libsass.gyp

+2-4
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,12 @@
6161
'-std=c++11',
6262
'-stdlib=libc++'
6363
],
64-
'OTHER_LDFLAGS': [
65-
'-stdlib=libc++'
66-
],
64+
'OTHER_LDFLAGS': [],
6765
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
6866
'GCC_ENABLE_CPP_RTTI': 'YES',
6967
'MACOSX_DEPLOYMENT_TARGET': '10.7'
7068
}
71-
}],
69+
}],
7270
['OS=="win"', {
7371
'msvs_settings': {
7472
'VCCLCompilerTool': {

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"coverage": "node scripts/coverage.js",
2727
"install": "node scripts/install.js",
2828
"postinstall": "node scripts/build.js",
29-
"pretest": "node_modules/.bin/jshint bin lib test",
29+
"pretest": "node_modules/.bin/jshint bin lib scripts test",
3030
"test": "node_modules/.bin/mocha test"
3131
},
3232
"files": [

scripts/build.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function afterBuild(options) {
5353
*/
5454

5555
function build(options) {
56-
var arguments = [
56+
var args = [
5757
path.join('node_modules', 'pangyp', 'bin', 'node-gyp'),
5858
'rebuild',
5959
].concat(
@@ -63,9 +63,9 @@ function build(options) {
6363
})
6464
).concat(options.args);
6565

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

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

0 commit comments

Comments
 (0)