diff --git a/lib/modules/parser.js b/lib/modules/parser.js
index d28ad58f..c04b9f78 100644
--- a/lib/modules/parser.js
+++ b/lib/modules/parser.js
@@ -75,6 +75,45 @@ module.exports.parseVariables = function(string, syntax) {
return out;
};
+// Parse Style variables to object
+module.exports.findVariables = function(string, syntax) {
+ syntax = syntax || 'scss';
+
+ var out = [],
+ ast = gonzales.srcToAST({
+ src: string,
+ syntax: syntax
+ });
+
+ gonzo.traverse(ast, [{
+ // Visitor for SASS and SCSS syntaxes
+ test: function(name, nodes) {
+ return name === 'value';
+ },
+ process: function(nodes) {
+ nodes.forEach(function(element) {
+ if (element[0] === 'variable' && element[1][0] === 'ident') {
+ out.push(element[1][1]);
+ }
+ });
+ }
+ }, {
+ // Visitor for LESS syntax
+ test: function(name, nodes) {
+ return name === 'value';
+ },
+ process: function(nodes) {
+ nodes.forEach(function(element) {
+ if (element[0] === 'atkeyword' && element[1][0] === 'ident') {
+ out.push(element[1][1]);
+ }
+ });
+ }
+ }]);
+
+ return out;
+};
+
// Modifies string so that variables passed in object are updated
module.exports.setVariables = function(string, syntax, variables) {
var sorted = [], lines = [],
diff --git a/lib/styleguide.js b/lib/styleguide.js
index 891bb830..f70ffb3e 100644
--- a/lib/styleguide.js
+++ b/lib/styleguide.js
@@ -122,8 +122,17 @@ module.exports = function(opt) {
// If settings file is found, generate settings object
if (opt.styleVariables) {
- var syntax = path.extname(opt.styleVariables).substring(1);
+ var syntax = path.extname(opt.styleVariables).substring(1),
+ variables;
+ // Parse variables from the defined file
styleguide.config.settings = parser.parseVariables(fs.readFileSync(opt.styleVariables, 'utf-8'), syntax);
+ // Go trough every styleguide style block and find used variables
+ styleguide.sections.forEach(function(section) {
+ if (section.css) {
+ section.variables = parser.findVariables(section.css, syntax);
+ }
+ return section;
+ });
}
// Create JSON containing KSS data
@@ -142,8 +151,8 @@ module.exports = function(opt) {
.pipe(pushAllFiles())
}
- opt.onCompileError = onCompileError;
// Preprocess all CSS files and combile then to a single file
+ opt.onCompileError = onCompileError;
preprocess.getStream(Object.keys(filesBuffer), opt)
.pipe(through.obj(function(file, enc, cb) {
diff --git a/package.json b/package.json
index 45618907..a9adeba3 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
"yargs": "^1.3.2"
},
"devDependencies": {
+ "async": "^0.9.0",
"chai": "^1.9.2",
"conventional-changelog": "git://github.com/sc5/conventional-changelog.git#features/sc-styleguide",
"exec-sync": "^0.1.6",
@@ -78,6 +79,7 @@
"karma-sinon-chai": "^0.2.0",
"main-bower-files": "^2.1.0",
"mocha": "^2.0.1",
+ "mocha-shared": "^0.2.0",
"multiline": "^1.0.1",
"sinon": "^1.11.1",
"sinon-chai": "^2.6.0",
diff --git a/test/markdown.js b/test/markdown.js
index d225bcf0..67753844 100644
--- a/test/markdown.js
+++ b/test/markdown.js
@@ -8,7 +8,6 @@ describe('Markdown', function() {
it('getRenderer if formed correctly', function() {
var renderer = markdown.getRenderer();
- console.log(renderer);
expect(renderer).to.be.an('object');
expect(renderer.heading).to.be.an('function');
expect(renderer.paragraph).to.be.an('function');
diff --git a/test/parser.js b/test/parser.js
index 21118e14..f17dcd3c 100644
--- a/test/parser.js
+++ b/test/parser.js
@@ -5,6 +5,78 @@ var gulp = require('gulp'),
parser = require('../lib/modules/parser');
describe('Parser', function() {
+ describe('variable finding', function() {
+ describe('SCSS syntax', function() {
+ it('should return all used variables', function() {
+ var str = multiline(function() {
+ /*
+ color: $mycolor1;
+ .testStyle {
+ border: 1px solid $mycolor2;
+ }
+ .testStyle2 {
+ background-color: $mycolor3;
+ }
+ */
+ }),
+ result = [
+ 'mycolor1', 'mycolor2', 'mycolor3'
+ ]
+ expect(parser.findVariables(str)).eql(result);
+ });
+
+ it('should not return new variable definitions', function() {
+ var str = multiline(function() {
+ /*
+ $mycolor: #00ff00;
+ .testStyle {
+ color: $mycolor2;
+ }
+ */
+ }),
+ result = [
+ 'mycolor2'
+ ]
+ expect(parser.findVariables(str)).eql(result);
+ });
+ });
+
+ describe('LESS syntax', function() {
+ it('should return all used variables', function() {
+ var str = multiline(function() {
+ /*
+ color: @mycolor1;
+ .testStyle {
+ border: 1px solid @mycolor2;
+ }
+ .testStyle2 {
+ background-color: @mycolor3;
+ }
+ */
+ }),
+ result = [
+ 'mycolor1', 'mycolor2', 'mycolor3'
+ ]
+ expect(parser.findVariables(str, 'less')).eql(result);
+ });
+
+ it('should not return new variable definitions', function() {
+ var str = multiline(function() {
+ /*
+ @mycolor: #00ff00;
+ .testStyle {
+ color: @mycolor2;
+ }
+ */
+ }),
+ result = [
+ 'mycolor2'
+ ]
+ expect(parser.findVariables(str, 'less')).eql(result);
+ });
+ });
+ });
+
describe('variable parser', function() {
describe('SCSS syntax', function() {
it('should parse basic variables', function() {
diff --git a/test/projects/less-project/source/styles/style.less b/test/projects/less-project/source/styles/style.less
index 1e2bae52..aa8d041c 100644
--- a/test/projects/less-project/source/styles/style.less
+++ b/test/projects/less-project/source/styles/style.less
@@ -27,3 +27,22 @@
// Styleguide 3.0
.test-css {color: purple;}
+
+// Section with multiple variables
+//
+// Markup:
+//
Section markup
+//
+// Styleguide 4.0
+
+.section1 {
+ color: @color-red;
+}
+
+.section2 {
+ background-color: @color-green;
+}
+
+.section3 {
+ border: 1px solid @color-blue;
+}
diff --git a/test/projects/scss-project/source/styles/style.scss b/test/projects/scss-project/source/styles/style.scss
index d06d1e37..bbd1220c 100644
--- a/test/projects/scss-project/source/styles/style.scss
+++ b/test/projects/scss-project/source/styles/style.scss
@@ -26,4 +26,23 @@
//
// Styleguide 3.0
-.test-css {color: purple;}
\ No newline at end of file
+.test-css {color: purple;}
+
+// Section with multiple variables
+//
+// Markup:
+// Section markup
+//
+// Styleguide 4.0
+
+.section1 {
+ color: $color-red;
+}
+
+.section2 {
+ background-color: $color-green;
+}
+
+.section3 {
+ border: 1px solid $color-blue;
+}
diff --git a/test/structure.js b/test/structure.js
index b122f553..b39398ed 100644
--- a/test/structure.js
+++ b/test/structure.js
@@ -31,7 +31,7 @@ beforeEach(function() {
function styleguideStream(source, config) {
return gulp.src(source || defaultSource)
- .pipe(styleguide(defaultConfig))
+ .pipe(styleguide(config || defaultConfig))
}
// This collector collects all files from the stream to the array passed as parameter
@@ -154,112 +154,162 @@ describe('overview.html', function() {
});
});
-['SCSS', 'LESS'].forEach(function(type) {
- var source,
- config;
+function sharedStyleguideCss() {
+ it('should exist', function() {
+ expect(this.styleguideFile).to.be.an('object');
+ });
+
+ it('should be processed correctly', function() {
+ expect(this.styleguideFile.contents.toString()).to.contain('.section {\n color: #ff0000;');
+ });
+};
+
+function sharedStyleguideJSON() {
+ it('should exist', function() {
+ expect(this.jsonData).to.be.an('object');
+ });
+
+ it('should contain correct title', function() {
+ expect(this.jsonData.config.title).to.eql('Test Styleguide');
+ });
+
+ it('should contain correct appRoot', function() {
+ expect(this.jsonData.config.appRoot).to.eql('/my-styleguide-book');
+ });
+
+ it('should contain extra heads in correct format', function() {
+ expect(this.jsonData.config.extraHead).to.eql(defaultConfig.extraHead[0] + '\n' + defaultConfig.extraHead[1]);
+ });
+
+ it('should contain all common classes', function() {
+ expect(this.jsonData.config.commonClass).to.eql(['custom-class-1', 'custom-class-2']);
+ });
+
+ it('should contain all style variables from defined file', function() {
+ var sassData = {
+ 'color-red': { value: '#ff0000', index: 0 },
+ 'color-green': { value: '#00ff00', index: 1 },
+ 'color-blue': { value: '#0000ff', index: 2 }
+ };
+ expect(this.jsonData.config.settings).to.eql(sassData);
+ });
+
+ it('should not reveal outputPath', function() {
+ expect(this.jsonData.config.outputPath).to.not.exist;
+ });
+
+ it('should have all the modifiers', function() {
+ expect(this.jsonData.sections[1].modifiers.length).to.eql(3)
+ });
+
+ // Markup
+
+ it('should print markup if defined', function() {
+ expect(this.jsonData.sections[0].markup).to.not.be.empty;
+ });
+
+ it('should not print empty markup', function() {
+ expect(this.jsonData.sections[2].markup).to.not.exist;
+ });
+
+ // Related CSS
+
+ it('should not print empty CSS', function() {
+ expect(this.jsonData.sections[1].css).to.not.exist;
+ });
+
+ it('should have section CSS', function() {
+ expect(this.jsonData.sections[2].css).to.eql('.test-css {color: purple;}');
+ });
+
+ // Related variables
+
+ it('should contain all related variables', function() {
+ var relatedVariables = ['color-red', 'color-green', 'color-blue'];
+ expect(this.jsonData.sections[3].variables).to.eql(relatedVariables)
+ });
+}
+
+describe('styleguide.css for SCSS project', function() {
+ beforeEach(function(done) {
+ var files = [],
+ _this = this,
+ source,
+ config;
- beforeEach(function() {
config = defaultConfig;
- if (type === 'SCSS') {
- config.styleVariables = './test/projects/scss-project/source/styles/_styleguide_variables.scss';
- source = './test/projects/scss-project/source/**/*.scss'
- } else if (type === 'LESS') {
- config.styleVariables = './test/projects/less-project/source/styles/_styleguide_variables.less';
- source = './test/projects/less-project/source/**/*.less'
- }
+ config.styleVariables = './test/projects/scss-project/source/styles/_styleguide_variables.scss';
+ source = './test/projects/scss-project/source/**/*.scss';
+ styleguideStream(source, config).pipe(
+ through.obj({objectMode: true}, collector(files), function(callback) {
+ _this.styleguideFile = findFile(files, 'styleguide.css');
+ done();
+ })
+ );
});
- describe('styleguide.css for ' + type + ' project', function() {
- var styleguideFile;
- this.timeout(5000);
-
- before(function(done) {
- var files = [];
- styleguideStream().pipe(
- through.obj({objectMode: true}, collector(files), function(callback) {
- styleguideFile = findFile(files, 'styleguide.css');
- done();
- })
- );
- });
-
- it('should exist', function() {
- expect(styleguideFile).to.be.an('object');
- });
-
- it('should be processed correctly', function() {
- expect(styleguideFile.contents.toString()).to.contain('.section {\n color: #ff0000; }');
- });
- });
-
- describe('styleguide.json for ' + type + ' project', function() {
- var jsonData;
- this.timeout(5000);
-
- before(function(done) {
- var files = [];
-
- styleguideStream(source).pipe(
- through.obj({objectMode: true}, collector(files), function(callback) {
- if (jsonData = findFile(files, 'styleguide.json')) {
- jsonData = JSON.parse(jsonData.contents);
- }
- done();
- })
- );
- });
-
- it('should exist', function() {
- expect(jsonData).to.be.an('object');
- });
-
- it('should contain correct title', function() {
- expect(jsonData.config.title).to.eql('Test Styleguide');
- });
-
- it('should contain correct appRoot', function() {
- expect(jsonData.config.appRoot).to.eql('/my-styleguide-book');
- });
-
- it('should contain extra heads in correct format', function() {
- expect(jsonData.config.extraHead).to.eql(defaultConfig.extraHead[0] + '\n' + defaultConfig.extraHead[1]);
- });
-
- it('should contain all common classes', function() {
- expect(jsonData.config.commonClass).to.eql(['custom-class-1', 'custom-class-2']);
- });
-
- it('should contain all ' + type + ' variables from defined file', function() {
- var sassData = {
- 'color-red': { value: '#ff0000', index: 0 },
- 'color-green': { value: '#00ff00', index: 1 },
- 'color-blue': { value: '#0000ff', index: 2 }
- };
- expect(jsonData.config.settings).to.eql(sassData);
- });
-
- it('should not reveal outputPath', function() {
- expect(jsonData.config.outputPath).to.not.exist;
- });
-
- it('should print markup if defined', function() {
- expect(jsonData.sections[0].markup).to.not.be.empty;
- });
-
- it('should not print empty markup', function() {
- expect(jsonData.sections[2].markup).to.not.exist;
- });
-
- it('should have all the modifiers', function() {
- expect(jsonData.sections[1].modifiers.length).to.eql(3)
- });
-
- it('should have section CSS', function() {
- expect(jsonData.sections[2].css).to.eql('.test-css {color: purple;}');
- });
-
- it('should not print empty markup', function() {
- expect(jsonData.sections[1].css).to.not.exist;
- });
+ sharedStyleguideCss();
+});
+
+describe('styleguide.css for LESS project', function() {
+ beforeEach(function(done) {
+ var files = [],
+ _this = this,
+ source,
+ config;
+
+ config = defaultConfig;
+ config.styleVariables = './test/projects/less-project/source/styles/_styleguide_variables.less';
+ source = './test/projects/less-project/source/**/*.less';
+ styleguideStream(source, config).pipe(
+ through.obj({objectMode: true}, collector(files), function(callback) {
+ _this.styleguideFile = findFile(files, 'styleguide.css');
+ done();
+ })
+ );
});
+
+ sharedStyleguideCss();
+});
+
+describe('styleguide.json for SCSS project', function() {
+ beforeEach(function(done) {
+ var files = [],
+ _this = this,
+ source,
+ config;
+
+ config = defaultConfig;
+ config.styleVariables = './test/projects/scss-project/source/styles/_styleguide_variables.scss';
+ source = './test/projects/scss-project/source/**/*.scss';
+ styleguideStream(source, config).pipe(
+ through.obj({objectMode: true}, collector(files), function(callback) {
+ _this.jsonData = JSON.parse(findFile(files, 'styleguide.json').contents);
+ done();
+ })
+ );
+ });
+
+ sharedStyleguideJSON();
+});
+
+describe('styleguide.json for LESS project', function() {
+ beforeEach(function(done) {
+ var files = [],
+ _this = this,
+ source,
+ config;
+
+ config = defaultConfig;
+ config.styleVariables = './test/projects/less-project/source/styles/_styleguide_variables.less';
+ source = './test/projects/less-project/source/**/*.less';
+ styleguideStream(source, config).pipe(
+ through.obj({objectMode: true}, collector(files), function(callback) {
+ _this.jsonData = JSON.parse(findFile(files, 'styleguide.json').contents);
+ done();
+ })
+ );
+ });
+
+ sharedStyleguideJSON();
});