KSS requires to provide section reference numbers in comments. It's OK but when I need to add another block in between of existing ones, this makes me shift all the sections with bigger numbers. Sometimes it's a lot of work. It would be nice to have a tool which makes this automatically.
This tool should go as a CLI and gulp-plugin.
In out project we came up with a gulp task. It can be run with parameters like gulp styleguide:new-section --name=NewSection --order=6.2.1
. It shift the sections after the given number and adds new section in the existing files. The code is shitty and does not consider many cases (like multiple line parameters). But I hope it will give you an idea how to move on.
'use strict';
var gulp;
var gutil = require('gulp-util');
var gonzales = require("gonzales-pe");
var minimist = require("minimist");
var through = require("through2");
module.exports = function registerTasks(g) {
gulp = g;
Object.keys(tasks).forEach(function(task) {
gulp.task(task, tasks[task]);
});
}
var tasks = {
'styleguide:new-section': newSection
}
function newSection() {
var args = minimist(process.argv.slice(2));
var params = {
name: args['name'] || args['n'],
order: args['order'] || args['o']
};
var sourcePath = "public_html/scss";
var source = sourcePath + "/**/*.scss";
if (!params.name) {
gutil.beep();
gutil.log(gutil.colors.red('Define name with --name=my-name'));
return;
}
if (!params.order) {
gutil.beep();
gutil.log(gutil.colors.red('Define name with --order=1.2.3'));
return;
}
params.order = params.order.toString();
// TODO: if order is not given, place into the last one
// TODO: Check ref number parameter with regexp
function getPreviousSection(section) {
var previousSectionArray = section.split('.');
previousSectionArray[previousSectionArray.length - 1] -= 1;
if (previousSectionArray[previousSectionArray.length - 1] === 0) {
previousSectionArray.pop();
}
return previousSectionArray.join('.');
}
function changeNumber(section) {
var filesBuffer = {};
var stream = through.obj(function(file, enc, cb) {
var content = file.contents.toString('utf8');
var syntax = 'scss';
var ast = gonzales.parse(content, { syntax: syntax });
var isKssMarkupBlock = /Styleguide [0-9]+/;
function isLess(left, right) {
left = left.split('.');
right = right.split('.');
var l = false;
for(var i = 0; i< Math.max(left.length, right.length); i++ ) {
if (parseInt(left[i]) < parseInt(right[i])) {
l = true;
break;
}
if (parseInt(left[i]) > parseInt(right[i])) {
l = false;
break;
}
}
return l;
}
function ifBelongsToParent(parentSection, section) {
var belongs = true;
parentSection = parentSection && parentSection.split('.');
section = section.split('.');
parentSection && parentSection.forEach(function(val, n) {
if (val !== section[n]) {
belongs = false;
}
});
return belongs;
}
function increaseSection(section, diff) {
section = section.split('.');
var last = section.pop();
section.push(parseInt(last) + diff);
return section.join('.');
}
function newStream(content) {
var stream = through();
stream.write(content);
return stream;
}
ast.map(function(declaration){
// Take only comments
if (
declaration.type !== 'commentSL' &&
declaration.type !== 'commentML'
) return;
// Skip everything which is not Styleguide reference
if (!isKssMarkupBlock.test(declaration.content))
return;
// Increase all the sections after the given one
var matchRegexp = /(Styleguide )([0-9]+(\.[0-9]+)*)/;
var match = declaration.content.match(matchRegexp);
var currentNumber = match[2];
// Skip sections with less reference number
if (isLess(currentNumber, section))
return;
// Skip everything which does not belong parent section
var parentSection = section.split('.');
parentSection.pop();
parentSection = parentSection.join('.');
if (!ifBelongsToParent(parentSection, currentNumber))
return;
// Skip parent section itself
if (parentSection === currentNumber)
return;
// Increase everything with +1
var newVal = increaseSection(currentNumber, 1);
declaration.content = declaration.content.replace(matchRegexp, '$1' + newVal);
});
var newContent = new Buffer(ast.toCSS(syntax));
file.contents = Buffer.concat([newContent]);
this.push(file);
cb();
});
return stream;
}
function insertSection(name, reference) {
var inserted = false;
var stream = through.obj(function(file, enc, cb) {
// Content of the new Section
var sectionContent =[
'// ' + params.name,
'//',
'// Styleguide ' + params.order
];
var previousSection = getPreviousSection(params.order);
// Paste new section after a blog of previous section
var content = file.contents.toString('utf8');
var syntax = 'scss';
var ast = gonzales.parse(content, { syntax: syntax });
var isKssMarkupBlock = /Styleguide [0-9]+/;
var commentBlockStarted = false;
var startLookForSection = false;
ast.map(function(declaration) {
if (inserted) {
return;
}
if (declaration.type === 'commentSL' && declaration.content === ' Styleguide ' + previousSection) {
startLookForSection = true;
return;
}
if (!startLookForSection) {
return;
}
if (declaration.type === 'commentSL' && commentBlockStarted === false) {
commentBlockStarted = declaration;
}
if (
declaration.type !== 'commentSL'
&& commentBlockStarted
&& declaration.content !== '\n'
&& declaration.content !== '\n\n'
) {
commentBlockStarted = false;
}
if (
commentBlockStarted &&
declaration.type === 'commentSL' &&
isKssMarkupBlock.test(declaration.content)
) {
// So, this was a KSS block, we need to place before commentBlockStarted
// TODO: rewrite normally when ast.insert is implemented
commentBlockStarted.content = '\n\n\n' + sectionContent.join('\n') + '\n\n//' + commentBlockStarted.content;
inserted = true;
}
});
var endOfFile = ''
if (inserted === false && startLookForSection === true) {
// need to add by the end of the file
endOfFile = '\n\n' + sectionContent.join('\n');
}
var newContent = new Buffer(ast.toCSS(syntax));
endOfFile = new Buffer(endOfFile);
file.contents = Buffer.concat([newContent, endOfFile]);
this.push(file);
cb();
});
return stream;
}
// Change all the numbers after giving one
gulp.src(source)
.pipe(changeNumber(params.order))
.pipe(insertSection(params.name, params.order))
.pipe(gulp.dest(sourcePath));
}