This commit is contained in:
Danni committed 2026-09-20 13:07:50 +07:00
1 parent 620fc5bda1
commit bd3ef9f279
779 files changed
+202868

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
*.swp
.DS_Store
node_modules
+77
View File
@@ -0,0 +1,77 @@
# scss-js
## A JavaScript implementation of SCSS (Sassy CSS)
## API
### Connect Compiler
var sys = require('sys'),
fs = require('fs'),
compiler = require('../src/scssCompiler');
fs.readFile(__dirname + '/blog.scss', function(err, scssFile) {
compiler.compile(scssFile.toString(), function(err, css) {
if(err) {
sys.puts(sys.inspect(err));
} else {
sys.puts(css);
}
});
});
The compile function takes a string and returns another string with the file's css.
## Where Does the Project Stand?
### What's Usable?
* CSS Selectors
* Nested Rules
* Variables
* Fixed Mixins
### What Needs Work?
* CSS Selectors
* Variable and Mixin names
### What's Unimplemented?
* Parameterized Mixins
* Functions
## Next Release (0.2.0)
Full CSS Selector implementation
## Special Thanks
I would like to give special thanks to [Hampton Catlin](http://hamptoncatlin.com),
[Nathan Weizenbaum](http://nex-3.com), and [Chris Eppstein](http://acts-as-architect.blogspot.com)
for starting and continuing the haml and sass project. I'd also like to thank the rest of the people
that have contributed to [Sass](http://sass-lang.com/) and I hope I can catch up to your
awesomeness.
## Modified MIT License
Copyright (c) 2010 Brian Mavity
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Except as contained in this notice, the name(s) of the above copyright
holders shall not be used in advertising or otherwise to promote the sale,
use or other dealings in this Software without prior written authorization.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+36
View File
@@ -0,0 +1,36 @@
$mainColor: green;
@mixin a_mixin {
color: $mainColor;
div {
float: left;
}
}
@mixin child {
@include a_mixin;
background-color: $mainColor;
a {
text-decoration: none;
}
}
#main {
margin: 0 5.625em;
h5, h6 {
font-size: 1em;
line-height: 1.25em;
}
.post {
text-align: left;
ul {
list-style-position: inside;
list-style-type: circle;
margin: 0.625em;
}
}
.post-dash {
@include child;
color: red;
}
}
+13
View File
@@ -0,0 +1,13 @@
var sys = require('sys'),
fs = require('fs'),
compiler = require('../src/scssCompiler');
fs.readFile(__dirname + '/blog.scss', function(err, scssFile) {
compiler.compile(scssFile.toString(), function(err, css) {
if(err) {
sys.puts(sys.inspect(err));
} else {
sys.puts(css);
}
});
});
+43
View File
@@ -0,0 +1,43 @@
var sys = require('sys'),
fs = require('fs'),
ometa = require('ometa'),
createdParser;
var getParser = function(callback) {
if(!createdParser) {
fs.readFile(__dirname + '/../src/cssSelector.ometa', function(err, contents) {
if(err) {
callback(err);
} else {
ometa.createParser(contents.toString(), function(err, parser) {
if(err) {
callback(err);
} else {
createdParser = parser;
callback(null, createdParser);
}
});
}
});
} else {
callback(null, createdParser);
}
};
var parse = function(scssFile, callback) {
getParser(function(err, parser) {
if(err) {
callback(err);
} else {
parser.parse(scssFile, 'selectors_group', function(err, css) {
sys.puts(css.toString());
});
}
});
};
module.exports.parse = parse;
parse(':not(h1, h2, h3)', function(err) {
sys.puts(sys.inspect(err, true, null));
});
+1
View File
@@ -0,0 +1 @@
$b : #234567; div { height:#d3e115; .error { background: $b; color:green; }} a:hover{color: $b; }
+15
View File
@@ -0,0 +1,15 @@
var sys = require('sys')
, fs = require('fs')
, path = require('path')
, scss = require('../')
;
fs.readFile(path.join(__dirname, 'blog.scss'), function(err, scssFile) {
scss.parse(scssFile.toString(), function(err, css) {
if(err) {
sys.puts(sys.inspect(err));
} else {
sys.puts(css);
}
});
});
+13
View File
@@ -0,0 +1,13 @@
{
"name": "scss",
"description": "JavaScript Implementation of SCSS (Sassy CSS)",
"version": "0.2.4",
"contributors": [
{ "name": "Brian Mavity", "email": "[email protected]" }
],
"main": "./src/index.js",
"engines": { "node": ">= 0.2.0" },
"dependencies": {
"ometa": "0.2.2"
}
}
+257
View File
@@ -0,0 +1,257 @@
var vows = require('vows'),
assert = require('assert'),
selectorGrammar = require(__dirname + '/grammarInvoker');
var shouldParse = function() {
var selector,
context = {
topic: function() {
selector = this.context.name;
selectorGrammar.parse(selector, this.callback);
}
};
context['should parse selector'] = function(err, parsedSelector) {
if(err) {
assert.fail(err.inner.toString());
} else {
assert.equal(parsedSelector, selector);
}
};
return context;
};
var shouldParseTo = function(expectedSelector) {
var context = {
topic: function() {
var selector = this.context.name;
selectorGrammar.parse(selector, this.callback);
}
};
context['should parse selector'] = function(err, parsedSelector) {
if(err) {
assert.fail(err.inner.toString());
} else {
assert.equal(parsedSelector, expectedSelector);
}
};
return context;
};
vows.describe('Summarized Selectors').addBatch({
// Taken from http://www.w3.org/TR/css3-selectors/#selectors
'*': shouldParse(),
'E': shouldParse(),
'E[foo]': shouldParse(),
'E[foo="bar"]': shouldParse(),
'E[foo~="bar"]': shouldParse(),
'E[foo^="bar"]': shouldParse(),
'E[foo$="bar"]': shouldParse(),
'E[foo*="bar"]': shouldParse(),
'E[foo|="en"]': shouldParse(),
'E:root': shouldParse(),
'E:nth-child(n)': shouldParse(),
'E:nth-last-child(n)': shouldParse(),
'E:nth-of-type(n)': shouldParse(),
'E:nth-last-of-type(n)': shouldParse(),
'E:first-child': shouldParse(),
'E:last-child': shouldParse(),
'E:first-of-type': shouldParse(),
'E:last-of-type': shouldParse(),
'E:only-child': shouldParse(),
'E:only-of-type': shouldParse(),
'E:empty': shouldParse(),
'E:link': shouldParse(),
'E:visited': shouldParse(),
'E:active': shouldParse(),
'E:hover': shouldParse(),
'E:focus': shouldParse(),
'E:target': shouldParse(),
'E:lang(fr)': shouldParse(),
'E:enabled': shouldParse(),
'E:disabled': shouldParse(),
'E:checked': shouldParse(),
'E::first-line': shouldParse(),
'E::first-letter': shouldParse(),
'E::before': shouldParse(),
'E::after': shouldParse(),
'E.warning': shouldParse(),
'E#myid': shouldParse(),
'E:not(s)': shouldParse(),
'E F': shouldParse(),
'E > F': shouldParse(),
'E + F': shouldParse(),
'E ~ F': shouldParse()
}).run();
vows.describe('Lonely Selectors').addBatch({
// Taken from http://www.w3.org/TR/css3-selectors/#selectors,
// but without the element names
'[foo]': shouldParse(),
'[foo="bar"]': shouldParse(),
'[foo~="bar"]': shouldParse(),
'[foo^="bar"]': shouldParse(),
'[foo$="bar"]': shouldParse(),
'[foo*="bar"]': shouldParse(),
'[foo|="en"]': shouldParse(),
':root': shouldParse(),
':nth-child(n)': shouldParse(),
':nth-last-child(n)': shouldParse(),
':nth-of-type(n)': shouldParse(),
':nth-last-of-type(n)': shouldParse(),
':first-child': shouldParse(),
':last-child': shouldParse(),
':first-of-type': shouldParse(),
':last-of-type': shouldParse(),
':only-child': shouldParse(),
':only-of-type': shouldParse(),
':empty': shouldParse(),
':link': shouldParse(),
':visited': shouldParse(),
':active': shouldParse(),
':hover': shouldParse(),
':focus': shouldParse(),
':target': shouldParse(),
':lang(fr)': shouldParse(),
':enabled': shouldParse(),
':disabled': shouldParse(),
':checked': shouldParse(),
'::first-line': shouldParse(),
'::first-letter': shouldParse(),
'::before': shouldParse(),
'::after': shouldParse(),
'.warning': shouldParse(),
'#myid': shouldParse(),
':not(s)': shouldParse()
}).run();
vows.describe('Attribute Selectors with Identifiers').addBatch({
'[foo~=bar]': shouldParse(),
'[foo^=bar]': shouldParse(),
'[foo$=bar]': shouldParse(),
'[foo*=bar]': shouldParse(),
'[foo|=en]': shouldParse()
}).run();
vows.describe('Nth Selectors').addBatch({
':nth-child(-n)': shouldParse(),
':nth-child(+n)': shouldParse(),
':nth-child(even)': shouldParse(),
':nth-child(odd)': shouldParse(),
':nth-child(50)': shouldParse(),
':nth-child(-50)': shouldParse(),
':nth-child(+50)': shouldParse(),
':nth-child(2n+3)': shouldParse(),
':nth-child(2n-3)': shouldParse(),
':nth-child(+2n-3)': shouldParse(),
':nth-child(-2n+3)': shouldParse(),
':nth-child(-2n+ 3)': shouldParse(),
':nth-child(-2n+ 3)': shouldParse(),
':nth-child(-2n+ 3)': shouldParse(),
':nth-child( 2n + 3 )': shouldParseTo(':nth-child(2n + 3)')
}).run();
vows.describe('Negation Selectors').addBatch({
':not(foo|bar)': shouldParse(),
':not(*|bar)': shouldParse(),
':not(foo|*)': shouldParse(),
':not(*|*)': shouldParse(),
':not(#blah)': shouldParse(),
':not(.blah)': shouldParse(),
':not([foo])': shouldParse(),
':not([foo^="bar"])': shouldParse(),
':not([baz|foo~="bar"])': shouldParse(),
':not(:hover)': shouldParse(),
':not(:nth-child(2n + 3))': shouldParse(),
// Not technically allowed, but what the heck
':not(:not(#foo))': shouldParse(),
':not(a#foo.bar)': shouldParse(),
':not(#foo .bar > baz)': shouldParse(),
':not(h1, h2, h3)': shouldParse()
}).run();
vows.describe('moz-any Selector').addBatch({
':-moz-any(h1, h2, h3)': shouldParse(),
':-moz-any(.foo)': shouldParse(),
':-moz-any(foo bar, .baz > .bang)': shouldParse()
}).run();
vows.describe('Namespaced Selectors').addBatch({
'foo|E': shouldParse(),
'*|E': shouldParse(),
'foo|*': shouldParse(),
'*|*': shouldParse()
}).run();
vows.describe('Namespaced Attribute Selectors').addBatch({
'[foo|bar=baz]': shouldParse(),
'[*|bar=baz]': shouldParse(),
'[foo|bar|=baz]': shouldParse()
}).run();
vows.describe('Comma Selectors').addBatch({
'E, F': shouldParse(),
'E F, G H': shouldParse(),
'E > F, G > H': shouldParse()
}).run();
vows.describe('Selectors with Newlines').addBatch({
"E,\nF": shouldParse(),
"E\nF": shouldParse(),
"E, F\nG, H": shouldParse()
}).run();
vows.describe('Expression Fallback Selectors').addBatch({
'0%': shouldParse(),
'60%': shouldParse(),
'100%': shouldParse(),
'12px': shouldParse(),
'"foo"': shouldParse()
}).run();
vows.describe('Functional Pseudo Selectors').addBatch({
':foo("bar")': shouldParse(),
':foo(bar)': shouldParse(),
':foo(12px)': shouldParse(),
':foo(+)': shouldParse(),
':foo(-)': shouldParse(),
':foo(+"bar")': shouldParse(),
':foo(-++--baz-"bar"12px)': shouldParse()
}).run();
vows.describe('Selector Hacks').addBatch({
'> E': shouldParse(),
'+ E': shouldParse(),
'~ E': shouldParse(),
'> > E': shouldParse(),
'>> E': shouldParseTo('> > E'),
'E*': shouldParse(),
'E*.foo': shouldParse(),
'E*:hover': shouldParse()
}).run();
+83
View File
@@ -0,0 +1,83 @@
var sys = require('sys'),
fs = require('fs'),
ometa = require('ometa'),
creatingParser = false;
var fixReadFile = function(rawFile) {
return rawFile.slice(1).replace(/\\n/g, '\n');
};
var createParser = function(parserInitializer) {
if(!creatingParser) {
creatingParser = true;
//fs.readFile(__dirname + '/../src/cssSelector.ometa', 'utf8', function(err, contents) {
fs.readFile(__dirname + '/../../ometa-js/newLineGrammar.ometa', 'utf8', function(err, contents) {
if(err) {
parserInitializer.setParser(err);
} else {
ometa.createParser(fixReadFile(contents), parserInitializer.setParser);
}
creatingParser = false;
});
}
};
var parserInitializer = (function() {
var createdParser,
parserErr,
queuedCallbacks = [],
that = {};
that.setParser = function(err, parser) {
parserErr = err;
createdParser = parser;
queuedCallbacks.forEach(function(callback) {
callback(parserErr, createdParser);
});
queuedCallbacks = [];
};
that.addCallback = function(callback) {
if(parserErr || createdParser) {
callback(parserErr, createdParser);
} else {
queuedCallbacks.push(callback);
createParser(that);
}
};
that.reset = function() {
createdParser = null;
};
return that;
})();
var parse = function(selector, callback) {
parserInitializer.addCallback(function(err, parser) {
if(err) {
callback(err);
} else {
parser.parse(selector, 'selectors_group', function(err, css) {
if(err) {
callback(err);
} else {
//callback(null, css.toString());
callback(null, css.join(''));
}
});
}
});
};
module.exports.parse = parse;
module.exports.reset = parserInitializer.reset;
var ssss = 'a,\nb';
console.log(ssss);
parse(ssss, function(err, shiznit) {
if(err) {
console.log(err);
}
console.log(shiznit);
});
+20
View File
@@ -0,0 +1,20 @@
var scss = require('./index');
var compiler = (function() {
return {
match: /\.css$/,
ext: '.scss',
compile: function(str, fn) {
scss.parse(str, function(err, css) {
if(err) {
fn(err);
} else {
fn(null, css);
}
});
}
};
})();
module.exports = compiler;
+162
View File
@@ -0,0 +1,162 @@
ometa CssSelector <: Parser {
crChar = '\r',
ffChar = '\f',
nlChar = '\n',
tabChar = '\t',
lineEnding = crChar | ffChar | nlChar,
tabOrLineEnding = tabChar | lineEnding,
ident = '-' nmstart:s nmchar*:cs -> { '-' + s + cs.join('') }
| nmstart:s nmchar*:cs -> { s + cs.join('') },
name = nmchar+:n -> { n.join('') },
nmstart = ('_' | letter | nonascii | escape):n -> { n },
nonascii = '',
unicode = '',
escape = unicode | '',
nmchar = '_' | '-' | letterOrDigit | nonascii | escape,
num = digit+: d -> d.join('')
| digit* '.' digit+,
string = (string1 | string2):s -> { s },
//string1 = '\"' (~(lineEnding | '\"') | '\\' nl | nonascii | escape)*:s '\"' -> { '\"' + s.join('') + '\"' },
string1 = '"' letter*:s '"' -> { '"' + s.join('') + '"' },
string2 = '\'' (~(lineEnding | '\'') | '\\' nl | nonascii | escape)*:s '\'' -> { '\'' + s.join('') + '\'' },
//invalid = invalid1 | invalid2,
//invalid1 = '\"' (~(lineEnding | '\"') | '\\' nl | nonascii | escape)*,
//invalid2 = '\'' (~(lineEnding | '\'') | '\\' nl | nonascii | escape)*,
nl = crChar nlChar
| lineEnding,
D = 'd' | 'D',
E = 'e' | 'E',
N = 'n' | 'N',
O = 'o' | 'O',
T = 't' | 'T',
V = 'v' | 'V',
S = ' ' -> { ' ' }
| '\n' -> { '\n' },
INCLUDES = '~' '=' -> { '~=' },
DASHMATCH = '|' '=' -> { '|=' },
PREFIXMATCH = '^' '=' -> { '^=' },
SUFFIXMATCH = '$' '=' -> { '$=' },
SUBSTRINGMATCH = '*' '=' -> { '*=' },
IDENT = ident,
STRING = string:s -> { s },
FUNCTION = ident:i '(' -> { i + '(' },
NUMBER = num:n -> { n },
HASH = '#' name:n -> { '#' + n },
PLUS = S+ '+' -> { ' +' }
| '+' -> { '+' },
// First line of next selector is a Css Hack
GREATER = '>' '>' -> { '> >' }
| S+ '>' -> { ' >' }
| '>' -> { '>' },
COMMA = S+ ',' -> { ' ,' }
| ',' -> { ',' },
TILDE = S+ '~' -> { ' ~' }
| '~' -> { '~' },
NOT = ':' N O T '(' -> { ':not(' },
ATKEYWORD = '@' ident,
INVALID = invalid,
PERCENTAGE = num:n '%' -> { n + '%' },
DIMENSION = num:n ident:i -> { n + i },
CDO = '<' '!' '-' '-',
CDC = '-' '-' '>',
selectors_group = selector:pre comma_separated_selector* -> { self.addSelector(pre); self },
comma_separated_selector = COMMA:com S*:spacing selector:sel -> { self.addCommaSeparatedSelector(com + spacing.join(''), sel) },
selector = simple_selector_sequence:sim (combined_sequence)*:additional -> { sim + additional.join('') }
// Css Hack
| combined_sequence*:comb -> { comb.join('') },
combinator = PLUS:p S+ -> { p + ' ' }
| PLUS:p -> { p }
| GREATER:g S+ -> { g + ' ' }
| GREATER:g -> { g }
| TILDE:t S+ -> { t + ' ' }
| TILDE:t -> { t }
| S+:spacing -> { spacing.join('') },
combined_sequence = combinator+:comb simple_selector_sequence:sel -> { comb.join('') + sel },
non_namespaced_selector = (HASH | class | attrib | negation | pseudo):sel -> { sel },
simple_selector_sequence = namespace_prefix:pre '*' non_namespaced_selector*:post -> { pre + '*' + post.join('') }
| namespace_prefix:pre element_name:ele non_namespaced_selector*:post -> { pre + ele + post.join('') }
| '*' non_namespaced_selector*:post -> { '*' + post.join('') }
| element_name:ele non_namespaced_selector*:post -> { ele + post.join('') }
| non_namespaced_selector+:sels -> { sels.join('') }
// Css Hack
| expression:ex -> { ex },
namespace_prefix = ('*' | IDENT):pre '|' -> { pre + '|' }
| '|' -> { '|' },
// First line of the next selector is a Css Hack
element_name = IDENT:i '*' -> { i + '*' }
| IDENT:i -> { i },
class = '.' IDENT:i -> { '.' + i },
attrib = '[' S* possible_namespaced_attrib:att ']' -> { '[' + att + ']' },
possible_namespaced_attrib = namespace_prefix:pre ident_with_possible_postfix:post -> { pre + post }
| ident_with_possible_postfix:post -> { post },
ident_with_possible_postfix = IDENT:left S* attrib_match:match S* (IDENT | STRING):right S* -> { left + match + right }
| IDENT:i S* -> { i },
attrib_match = (PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | equals_match | INCLUDES | DASHMATCH):m -> { m },
equals_match = '=' -> { '=' },
pseudo = ':' ':' (functional_pseudo | IDENT):i -> { '::' + i }
| ':' (functional_pseudo | IDENT):i -> { ':' + i },
functional_pseudo = FUNCTION:f S* full_expression:e ')' -> { f + e + ')' }
// Css Hack for :-moz-any(...)
| FUNCTION:f S* selectors_group:sel ')' -> { f + sel.toString() + ')' },
expression_content = (PLUS | '-' | PERCENTAGE | DIMENSION | NUMBER | STRING | IDENT):e -> { e },
expression = expression_content:ec S+ expression:e -> { ec + ' ' + e }
| expression_content:ec expression:e -> { ec + e }
| expression_content:ec S* -> { ec },
full_expression = (expression)+:ea -> { ea.join('') },
negation = NOT:n S* negation_arg:na S* ')' -> { n + na + ')' },
//negation_arg = (type_selector | universal | HASH | class | attrib | pseudo):na -> { na }
//Technically not allowed, but here for scss compatibility
negation_arg = selectors_group:sg -> { sg.toString() }
}
CssSelector.initialize = function() {
var hasBeenStringified = false,
self = this;
this.selector;
this.selectors = [];
this.commaSeparatedSelectors = [];
var resetFields = function() {
self.selector = null;
self.selectors = [];
self.commaSeparatedSelectors = [];
hasBeenStrigified = false;
};
this.addSelector = function(sel) {
if(hasBeenStringified) {
resetFields();
}
this.selector = sel;
};
this.addCommaSeparatedSelector = function(commaAndSpacing, selector) {
if(hasBeenStringified) {
resetFields();
}
this.selectors.push(selector);
this.commaSeparatedSelectors.push({
commaAndSpacing: commaAndSpacing,
selector: selector
});
};
this.toString = function() {
hasBeenStringified = true;
return this.selector + this.commaSeparatedSelectors.map(function(csSel) {
return csSel.commaAndSpacing + csSel.selector;
}).join('');
};
};
CssSelector
+55
View File
@@ -0,0 +1,55 @@
var sys = require('sys')
, fs = require('fs')
, path = require('path')
, ometa = require('ometa')
, rule = 'scssFile'
, createdParser
;
var fixReadFile = function(rawFile) {
return rawFile.slice(1).replace(/\\n/g, '\n');
};
var getParser = function(callback) {
if(!createdParser) {
fs.readFile(path.join(__dirname, '/CssSelector.ometa'), 'utf8', function(err, cssSelectorContents) {
var selectorFile = fixReadFile(cssSelectorContents);
ometa.createParser(selectorFile, function(err, cssSelectorParser) {
fs.readFile(path.join(__dirname, '/scss.ometa'), 'utf8', function(err, contents) {
var contentFile = fixReadFile(contents);
if(err) {
callback(err);
} else {
ometa.createParser(contentFile, function(err, parser) {
if(err) {
callback(err);
} else {
createdParser = parser;
callback(null, createdParser);
}
});
}
});
});
});
} else {
callback(null, createdParser);
}
};
var parse = function(scssFile, callback) {
getParser(function(err, parser) {
if(err) {
callback(err);
} else {
parser.parse(scssFile, rule, function(err, css) {
require(__dirname + '/prettyPrint').render(css, function(inner, printed) {
callback(err, printed);
});
});
}
});
};
module.exports.parse = parse;
+87
View File
@@ -0,0 +1,87 @@
function renderProperty(fileObj, propertyObj) {
var getPropertyValue = function(val) {
if(val.indexOf('$') == 0) {
return fileObj.getValue(val.replace('$', ''));
}
return val;
};
return propertyObj.name + ': ' + getPropertyValue(propertyObj.val) + ';';
};
function renderBlock(fileObj, blockObj, scope) {
var selector = blockObj.selector,
scopeIndent = (scope && scope.indent) || '',
currentIndent = scopeIndent + ' ',
paddedScopeSelector = (scope && scope.selector) ? scope.selector + ' ' : '',
currentSelector = paddedScopeSelector + selector,
selectors = blockObj.selectors,
properties = blockObj.properties,
blocks = blockObj.blocks,
includes = blockObj.includes,
css = scopeIndent + currentSelector;
var expandIncludes = function(includeNames) {
includeNames.forEach(function(includeName) {
var mixin = fileObj.getMixin(includeName);
//Why doesn't .concat() work?
//properties.concat(mixin.properties);
//blocks.concat(mixin.blocks);
mixin.properties.forEach(function(property) {
properties.push(property);
});
mixin.blocks.forEach(function(block) {
blocks.push(block);
});
expandIncludes(mixin.includes);
});
};
expandIncludes(includes);
selectors.forEach(function(sel) {
css += ',\n' + scopeIndent + paddedScopeSelector + sel;
});
css += ' {\n';
properties.forEach(function(property) {
css += currentIndent + renderProperty(fileObj, property) + '\n';
});
css += scopeIndent + '}\n';
blocks.forEach(function(block) {
css += renderBlock(fileObj, block, {
indent: currentIndent,
selector: currentSelector
});
});
return css;
};
function renderFile(fileObj) {
var blocks = fileObj.blocks,
css = '';
blocks.forEach(function(block) {
css += renderBlock(fileObj, block);
css += '\n';
});
return css;
};
function render(cssObj, callback) {
var css;
try {
css = renderFile(cssObj);
callback(null, css)
}
catch(ex) {
callback(ex);
}
};
module.exports.render = render;
+92
View File
@@ -0,0 +1,92 @@
ometa Scss <: Parser {
selector = foreign(CssSelector, 'selectors_group'):sel -> { self.addSelector(sel) },
propertyValue = '$' letterOrDigit+:val -> ('$' + val.join(''))
| ('#' | '.' | ',' | '-' | space | letterOrDigit)+:val -> val.join(''),
property = spaces ('-' | letter)+:prop ":" spaces propertyValue:val ";" -> { self.addProperty({ name: prop.join(''), val: val }); },
blockBody = "{" (mixinInclude | property | scssBlock)* "}" -> { self.endBlock(); },
scssBlock = spaces selector blockBody,
scssVariable = "$" letter+:name ":" spaces propertyValue:val ";" -> { self.addVariable({ name: name.join(''), val: val }); },
mixinName = spaces ('-' | '_' | letterOrDigit)+:name -> name.join(''),
mixinInclude = "@include" mixinName:name ";" -> { self.addInclude(name); },
mixinDeclaration = "@mixin" mixinName:name -> { self.addMixin(name); },
scssMixin = mixinDeclaration blockBody,
scssFile = (scssVariable | scssMixin | scssBlock)* -> { self.getFile(); }
}
Scss.initialize = function() {
var blocks = [],
mixins = {},
variables = {},
file,
currentBlock;
var createBlock = function() {
var parentBlock = currentBlock;
return {
blocks: [],
includes: [],
properties: [],
end: function() {
currentBlock = parentBlock;
}
};
};
this.addMixin = function(mixinName) {
var block = createBlock();
mixins[mixinName] = block;
currentBlock = block;
};
this.addProperty = function(prop) {
currentBlock.properties.push(prop);
};
this.addInclude = function(include) {
currentBlock.includes.push(include);
};
this.addSelector = function(sel) {
var block;
if(sel.selector) {
block = createBlock();
block.selector = sel.selector;
block.selectors = sel.selectors;
currentBlock.blocks.push(block);
currentBlock = block;
}
};
this.endBlock = function() {
currentBlock.end();
};
this.addVariable = function(variable) {
variables[variable.name] = variable.val;
};
this.getFile = function() {
return file;
};
file = {
blocks: [],
end: function() {
currentBlock = file;
},
getMixin: function(mixinName) {
return mixins[mixinName];
},
getValue: function(variableName) {
return variables[variableName];
}
};
currentBlock = file;
};
Scss