Source: apc-static/task/worker/generate_styleguide.js

/**
 * Generate style guide document wit KSS.
 * @see {@link http://github.com/hughsk/kss-node | kss}
 * @function task.worker.generateStyleguide
 * @param {object} config - Work configuration
 * @param {string|string[]} config.srcDir - Stylesheet source directory.
 * @param {string} config.destDir - Directory to create the document.
 * @param {object} config.aliases - Aliases to create inside the dest directory.
 * @param {function} callback - Callback when done.
 * @author Taka Okunishi
 *
 */
var path = require('path'),
    fs = require('fs'),
    os = require('os'),
    EOL = os.EOL,
    lib = require('../../lib'),
    file = lib.file,
    copyDir = file.copyDir,
    loadHbsTmpl = file.loadHbsTmpl,
    readdirRecursive = file.readdirRecursive,
    writeReadonlyFile = file.writeReadonlyFile,
    prepareCleanDir = file.prepareCleanDir,
    _unlinkFiles = require('./_unlink_files'),
    _generateStyleguideOverview = require('./_generate_styleguide_overview'),
    fork = require('child_process').fork,
    generateStyleGuideLess = require('./generate_styleguide_less'),
    _createAliases = require('./_create_aliases');

exports = module.exports = function (config, callback) {
    exports._prepareStyleguideWorkDir(function (err, workDir) {
        if (err) {
            callback(err);
            return;
        }
        var srcDir = [].concat(config.srcDir).map(function (srcDir) {
                return path.resolve(srcDir);
            }),
            destDir = path.resolve(config.destDir);
        var kssBin = path.resolve(require.resolve("kss"), '../bin/kss-node'),
            lessFiles = [],
            aliases = Object.keys(config.aliases || {}).map(function (name) {
                return {
                    from: path.resolve(config.aliases[name]),
                    to: path.resolve(destDir, name)
                };
            });
        var aliasNames = aliases.map(function (data) {
            return data.to;
        });
        _unlinkFiles(aliasNames, function (err) {
            if (err) {
                callback(err);
                return;
            }

            exports._prepareStyleguideTmpl(function (err, tmplDir) {
                if (err) {
                    callback(err);
                    return;
                }
                exports._concatFilesFlatten([], workDir, function (err) {
                    if (err) {
                        callback(err);
                        return;
                    }
                    exports._concatFilesFlattenInDir(srcDir, workDir, function (err) {
                        if (err) {
                            callback(err);
                            return;
                        }
                        var styleGuideLessFile = path.resolve(workDir, 'styleguide.less');
                        fs.readdir(workDir, function (err, workFiles) {
                            if (err) {
                                callback(err);
                                return;
                            }
                            generateStyleGuideLess({
                                dest: styleGuideLessFile,
                                imports: workFiles
                                    .filter(function (filename) {
                                        return path.extname(filename) === '.less';
                                    })
                                    .map(function (filename) {
                                        return path.resolve(workDir, filename);
                                    })
                            }, function (err) {
                                if (err) {
                                    callback(err);
                                    return;
                                }
                                lessFiles.push(styleGuideLessFile);
                                var overviewData = {},
                                    overviewFilename = path.resolve(workDir, 'styleguide.md');
                                _generateStyleguideOverview(overviewFilename, overviewData, function (err) {
                                        if (err) {
                                            callback(err);
                                            return;
                                        }
                                        var args = [
                                            workDir, destDir,
                                            '-t', path.resolve(tmplDir)
                                        ];
                                        lessFiles.forEach(function (filename) {
                                            args.push('--less');
                                            args.push(filename);
                                        });


                                        var kss = fork(kssBin, args);
                                        kss.on('exit', function () {
                                            _createAliases(aliases, function (err) {
                                                callback(err);
                                            });
                                        });

                                    }
                                );
                            });
                        });
                    });
                });
            });
        });
    });
};



/**
 * Copy a directory to flatten structure. All files to put direct under the destDir.
 * @param {string|string[]} srcDir
 * @param {string} destDir
 * @param {function} callback
 * @protected
 * @ignore
 */
exports._concatFilesFlattenInDir = function (srcDir, destDir, callback) {
    require('async').series(
        [].concat(srcDir).map(function (srcDir) {
            return function (callback) {
                readdirRecursive(srcDir, function (err, filenames) {
                    if (err) {
                        callback(err);
                        return;
                    }
                    filenames = filenames.map(function (filename) {
                        return path.resolve(srcDir, filename);
                    });
                    exports._concatFilesFlatten(filenames, destDir, callback);
                });
            };
        }),
        callback
    );
};

exports._concatFilesFlatten = function (src, destDir, callback) {
    var basename = path.basename(path.dirname(src[0])) + path.extname(src[0]);
    exports._newFilename(path.resolve(destDir, basename), function (dest) {
        require('./concat_files')({
            src: src,
            dest: dest,
            ignore: '.*'
        }, function (err) {
            if (err) {
                callback(err);
                return;
            }
            fs.exists(dest, function (exists) {
                if (!exists) {
                    callback();
                    return;
                }
                fs.readFile(dest, function (err, buffer) {
                    if (err) {
                        callback(err);
                        return;
                    }
                    var content = buffer.toString().split(EOL).map(function (line) {
                        if (!!line.match('@import')) {
                            var importPath = line.replace('@import', '').replace(/['";\s]/g, '');
                            return line.replace(importPath,
                                path.relative(
                                    path.dirname(dest),
                                    path.resolve(path.dirname(src[0]), importPath)
                                )
                            );
                        } else {
                            return line;
                        }
                    }).join(EOL);
                    writeReadonlyFile(dest, content, function (err) {
                        callback(err, dest);
                    });
                });
            });
        });
    });
};
/**
 * Make sure a filename is whole new.
 * @param filename
 * @param callback
 * @protected
 * @ignore
 */
exports._newFilename = function (filename, callback) {
    fs.exists(filename, function (exists) {
        if (exists) {
            var extname = path.extname(filename),
                basename = path.basename(filename, extname);
            var newFilename = path.join(path.dirname(filename), basename + '_' + extname);
            exports._newFilename(newFilename, callback);
        } else {
            callback(filename);
        }
    });
};

exports._prepareStyleguideWorkDir = function (callback) {
    var workDir = path.resolve(__dirname, '../../work/generate_style_guide_work');
    prepareCleanDir(workDir, function (err) {
        callback(err, workDir);
    });
};


exports._prepareStyleguideTmpl = function (callback) {
    var src = path.resolve(__dirname, '../../tmpl/_doc/styleguide'),
        dest = path.resolve(__dirname, '../../work/styleguide_tmpl');
    copyDir(src, dest, function (err) {
        if (err) {
            callback(err);
        }
        var meta = global.apemandata.meta,
            data = {
                __appname__: meta.name,
                __apeman_homepage__: require('apeman').pkg.homepage
            };

        readdirRecursive(dest, function (err, filenames) {
            if (err) {
                callback(err);
                return;
            }
            require('async').series(
                filenames
                    .map(function (filename) {
                        return path.resolve(dest, filename);
                    })
                    .map(function (filename) {
                        return function (callback) {
                            fs.readFile(filename, function (err, buffer) {
                                if (err) {
                                    callback(err);
                                } else {
                                    var content = buffer.toString();
                                    Object.keys(data).forEach(function (key) {
                                        var value = data[key];
                                        content = content.replace(new RegExp(key, 'g'), value);
                                    });
                                    fs.writeFile(filename, content, callback);
                                }
                            });
                        };
                    }),
                function (err) {
                    callback(err, dest);
                }
            );
        });
    });
};