Skip to content

Commit

Permalink
Rewrite build.js processing.
Browse files Browse the repository at this point in the history
- Search through files via node-glob
- Use _.defaults for default options
- Custom filters are now more flexible, output relative
  to options.outputDir / --output-dir
- mui-icons no longer needs --file-suffix, _24px.svg -> .jsx
  happens in filter.
- pascalCase now capitalizes first letter of recursive directories
  and dashes.
  • Loading branch information
tony committed Jul 16, 2015
1 parent 9782c5a commit a001394
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 102 deletions.
3 changes: 1 addition & 2 deletions icon-builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ You can build your own SVG icons as well as collections like [game-icons](http:/
* `--inner-path` - "Reach into" subdirs, since libraries like material-design-icons
use arbitrary build directories to organize icons
e.g. "action/svg/production/icon_3d_rotation_24px.svg"
* `--file-suffix` - Filter only files ending with a suffix (pretty much only
for material-ui-icons)
* `--file-suffix` - Filter only files ending with a suffix

If you experience any issues building icons or would like a feature added,
[file and issue](https://github.com/callemall/material-ui/issues) and let us
Expand Down
142 changes: 76 additions & 66 deletions icon-builder/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,52 @@ var path = require('path');
var rimraf = require('rimraf');
var Mustache = require("mustache");
var _ = require('lodash');
var glob = require("glob");
var mkdirp = require("mkdirp");

const SVG_ICON_RELATIVE_REQUIRE = "require('../../svg-icon')"
, SVG_ICON_ABSOLUTE_REQUIRE = "require('material-ui/lib/svg-icon')"
, RENAME_FILTER_DEFAULT = './filters/rename/default'
, RENAME_FILTER_MUI = './filters/rename/material-design-icons';

const DEFAULT_OPTIONS = {
muiRequire: 'absolute',
glob: '/**/*.svg',
innerPath: '',
renameFilter: RENAME_FILTER_DEFAULT
}

function parseArgs() {
return require('yargs')
.usage('Build JSX components from SVG\'s.\nUsage: $0')
.demand('output-dir')
.describe('output-dir', 'Directory to output jsx components')
.demand('svg-dir')
.describe('svg-dir', 'SVG directory')
.describe('inner-path', '"Reach into" subdirs, since libraries like material-design-icons' +
' use arbitrary build directories to organize icons' +
' e.g. "action/svg/production/icon_3d_rotation_24px.svg"')
.describe('file-suffix', 'Filter only files ending with a suffix (pretty much only' +
' for material-ui-icons)')
.options('rename-filter', {
default: RENAME_FILTER_DEFAULT
})
.options('mui-require', {
demand: false,
default: 'absolute',
describe: 'Load material-ui dependencies (SvgIcon) relatively or absolutely. (absolute|relative). For material-ui distributions, relative, for anything else, you probably want absolute.',
type: 'string'
})
.describe('mui-icons-opts', 'Shortcut to use MUI icons options')
.boolean('mui-icons-opts')
.argv;
.usage('Build JSX components from SVG\'s.\nUsage: $0')
.demand('output-dir')
.describe('output-dir', 'Directory to output jsx components')
.demand('svg-dir')
.describe('svg-dir', 'SVG directory')
.describe('glob', 'Glob to match inside of --svg-dir. Default **/*.svg')
.describe('inner-path', '"Reach into" subdirs, since libraries like material-design-icons' +
' use arbitrary build directories to organize icons' +
' e.g. "action/svg/production/icon_3d_rotation_24px.svg"')
.describe('file-suffix', 'Filter only files ending with a suffix (pretty much only' +
' for material-ui-icons)')
.describe('rename-filter', 'Path to JS module used to rename destination filename and path. Default: ' + RENAME_FILTER_DEFAULT)
.options('mui-require', {
demand: false,
describe: 'Load material-ui dependencies (SvgIcon) relatively or absolutely. (absolute|relative). For material-ui distributions, relative, for anything else, you probably want absolute.',
type: 'string'
})
.describe('mui-icons-opts', 'Shortcut to use MUI icons options')
.boolean('mui-icons-opts')
.argv;
}

function main(options, cb) {
var originalWrite; // todo, add wiston / other logging tool

options = _.defaults(options, DEFAULT_OPTIONS);
if (options.disable_log) { // disable console.log opt, used for tests.
originalWrite = process.stdout.write;
process.stdout.write = function() {};
process.stdout.write = function() {};
}

rimraf.sync(options.outputDir); // Clean old files
Expand All @@ -65,9 +74,15 @@ function main(options, cb) {
}

fs.mkdirSync(options.outputDir);

dirs.forEach(function(dirName) {
processDir(dirName, options.svgDir, options.outputDir, options.innerPath, options.fileSuffix, renameFilter, options.muiRequire)
var files = glob.sync(path.join(options.svgDir, options.glob))
_.each(files, function(svgPath) {
var svgPathObj = path.parse(svgPath);
var innerPath = path.dirname(svgPath)
.replace(options.svgDir, "")
.replace(path.relative(process.cwd(), options.svgDir), ""); // for relative dirs
var destPath = renameFilter(svgPathObj, innerPath, options);

processFile(svgPath, destPath, options);
});

if (cb) {
Expand All @@ -79,45 +94,48 @@ function main(options, cb) {
}
}

function processDir(dirName, svgDir, outputDir, innerPath, fileSuffix, renameFilter, muiRequire) {
var newIconDirPath = path.join(outputDir, dirName);
var svgIconDirPath = path.join(svgDir, dirName, innerPath);
if (!fs.existsSync(svgIconDirPath)) { return false; }
if (!fs.lstatSync(svgIconDirPath).isDirectory()) { return false; }
var files = fs.readdirSync(svgIconDirPath);

rimraf.sync(newIconDirPath);
console.log('\n ' + dirName);
fs.mkdirSync(newIconDirPath);
/*
* @param {string} svgPath
* Absolute path to svg file to process.
*
* @param {string} destPath
* Path to jsx file relative to {options.outputDir}
*
* @param {object} options
*/
function processFile(svgPath, destPath, options) {
var outputFileDir = path.dirname(path.join(options.outputDir, destPath));

files.forEach(function(fileName) {
processFile(dirName, fileName, newIconDirPath, svgIconDirPath, fileSuffix, renameFilter, muiRequire);
});
if (!fs.existsSync(outputFileDir)) {
console.log("Making dir: " + outputFileDir);
mkdirp.sync(outputFileDir);
}
var fileString = getJsxString(svgPath, destPath, options);
var absDestPath = path.join(options.outputDir, destPath);
fs.writeFileSync(absDestPath, fileString);
}

function processFile(dirName, fileName, dirPath, svgDirPath, fileSuffix, renameFilter, muiRequire) {
var fullPath;
var svgFilePath = svgDirPath + '/' + fileName;

fileName = renameFilter(fileName, fileSuffix);
if (!fileName) return; // filter can return a falsey to skip
fullPath = path.join(dirPath, fileName);

//console.log('writing ' + newFile);
var fileString = getJsxString(dirName, fileName, svgFilePath, muiRequire);
fs.writeFileSync(fullPath, fileString);
/**
* Return Pascal-Cased classname.
*
* @param {string} svgPath
* @returns {string} class name
*/
function pascalCase(destPath) {
var splitregex = new RegExp("[" + path.sep + "-]+");
var parts = destPath.replace(".jsx", "").split(splitregex);
parts = _.map(parts, function(part) { return part.charAt(0).toUpperCase() + part.substring(1); });
var className = parts.join('');
return className;
}

function getJsxString(dirName, newFilename, svgFilePath, muiRequire, fileString) {
var className = newFilename.replace('.jsx', '');
className = dirName + '-' + className;
className = pascalCase(className);

console.log(' ' + className);
function getJsxString(svgPath, destPath, options) {
var className = pascalCase(destPath);

//var parser = new xml2js.Parser();
console.log(' ' + className);

var data = fs.readFileSync(svgFilePath, {encoding: 'utf8'});
var data = fs.readFileSync(svgPath, {encoding: 'utf8'});
var template = fs.readFileSync(path.join(__dirname, "tpl/SvgIcon.js"), {encoding: 'utf8'});
//Extract the paths from the svg string
var paths = data.slice(data.indexOf('>') + 1);
Expand All @@ -128,7 +146,7 @@ function getJsxString(dirName, newFilename, svgFilePath, muiRequire, fileString)

// Node acts wierd if we put this directly into string concatenation

var muiRequireStmt = muiRequire === "relative" ? SVG_ICON_RELATIVE_REQUIRE : SVG_ICON_ABSOLUTE_REQUIRE;
var muiRequireStmt = options.muiRequire === "relative" ? SVG_ICON_RELATIVE_REQUIRE : SVG_ICON_ABSOLUTE_REQUIRE;

return Mustache.render(
template, {
Expand All @@ -140,13 +158,6 @@ function getJsxString(dirName, newFilename, svgFilePath, muiRequire, fileString)

}

function pascalCase(str) {
str = str[0].toUpperCase() + str.slice(1);
return str.replace(/-(.)/g, function(match, group1) {
return group1.toUpperCase();
});
}

if (require.main === module) {
var argv = parseArgs();
main(argv);
Expand All @@ -155,7 +166,6 @@ if (require.main === module) {
module.exports = {
pascalCase: pascalCase,
getJsxString: getJsxString,
processDir: processDir,
processFile: processFile,
main: main,
SVG_ICON_RELATIVE_REQUIRE: SVG_ICON_RELATIVE_REQUIRE,
Expand Down
27 changes: 22 additions & 5 deletions icon-builder/filters/rename/default.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
function defaultFilter(fileName, fileSuffix) {
if (fileSuffix) {
fileName.replace(fileSuffix, ".svg");
/*
* Return path to write file to inside outputDir.
*
* @param {object} pathObj
* path objects from path.parse
*
* @param {string} innerPath
* Path (relative to options.svgDir) to svg file
* e.g. if svgFile was /home/user/icons/path/to/svg/file.svg
* options.svgDir is /home/user/icons/
* innerPath is path/to/svg
*
* @param {object} options
* @return {string} output file dest relative to outputDir
*/
function defaultDestRewriter(pathObj, innerPath, options) {
var path = require('path');
var fileName = pathObj.base;
if (options.fileSuffix) {
fileName.replace(options.fileSuffix, ".svg");
} else {
fileName = fileName.replace('.svg', '.jsx');
}
fileName = fileName.replace(/_/g, '-');
return fileName;
return path.join(innerPath, fileName);
}


module.exports = defaultFilter;
module.exports = defaultDestRewriter;
26 changes: 15 additions & 11 deletions icon-builder/filters/rename/material-design-icons.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
function myFilter(fileName, fileSuffix) {
if (fileSuffix && fileName.indexOf(fileSuffix, fileName.length - fileSuffix.length) !== -1) {
fileName = fileName.replace(fileSuffix, '.jsx');
fileName = fileName.slice(3);
fileName = fileName.replace(/_/g, '-');

if (fileName.indexOf('3d') === 0) {
fileName = 'three-d' + fileName.slice(2);
}
return fileName;
function myDestRewriter(pathObj, innerPath, options) {
var path = require('path');
var fileName = pathObj.base;

var rewrittenInnerPath = innerPath.replace('/svg/production', '');

fileName = fileName.replace('_24px.svg', '.jsx');
fileName = fileName.slice(3);
fileName = fileName.replace(/_/g, '-');

if (fileName.indexOf('3d') === 0) {
fileName = 'three-d' + fileName.slice(2);
}

return path.join(rewrittenInnerPath, fileName);
}

module.exports = myFilter;
module.exports = myDestRewriter;
3 changes: 2 additions & 1 deletion icon-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Material Design Svg Icons converted to React components.",
"scripts": {
"prebuild": "rm -rf js",
"createMuiIconsJsx": "node build.js --output-dir jsx --svg-dir ./node_modules/material-design-icons --inner-path /svg/production/ --file-suffix _24px.svg --mui-require relative --renameFilter ./filters/rename/material-design-icons.js",
"createMuiIconsJsx": "node build.js --output-dir jsx --svg-dir ./node_modules/material-design-icons --glob '/**/*_24px.svg' --mui-require relative --renameFilter ./filters/rename/material-design-icons.js",
"build": "npm run createMuiIconsJsx && babel --stage 1 ./jsx --out-dir ./js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand Down Expand Up @@ -36,6 +36,7 @@
"load-grunt-tasks": "^3.2.0",
"lodash": "^3.10.0",
"material-design-icons": "^2.0.0",
"mkdirp": "^0.5.1",
"mocha": "^2.2.5",
"mustache": "^2.1.2",
"rimraf": "^2.4.0",
Expand Down
32 changes: 15 additions & 17 deletions icon-builder/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ describe('builder', function() {
it('should be a function', function() {
assert.isFunction(builder.pascalCase);
});

it('should change capitalize dashes', function() {
assert.ok(builder.pascalCase("hi-world"), "HiWorld");
});

it('should capitalize based on environment path.sep', function() {
assert.ok(builder.pascalCase("this" + path.sep + "dir"), "ThisDir");
});
});

describe('#main', function() {
Expand All @@ -55,16 +63,6 @@ describe('builder', function() {
});
});

describe('#processDir', function() {
it('should have processDir', function() {
assert.ok(builder.hasOwnProperty('processDir'));
});

it('should be a function', function() {
assert.isFunction(builder.processDir);
});
});

describe('#processFile', function() {
it('should have processFile', function() {
assert.ok(builder.hasOwnProperty('processFile'));
Expand All @@ -81,7 +79,7 @@ describe('--output-dir', function() {
var options = {
svgDir: MUI_ICONS_SVG_DIR,
innerPath: "/svg/production/",
fileSuffix: "_24px.svg",
glob: '/**/*_24px.svg',
renameFilter: builder.RENAME_FILTER_MUI,
disable_log: DISABLE_LOG
}, tempPath;
Expand Down Expand Up @@ -109,6 +107,7 @@ describe('--output-dir', function() {
describe('--svg-dir, --innerPath, --fileSuffix', function() {
var options = {
svgDir: GAME_ICONS_SVG_DIR,
glob: "**/*.svg",
innerPath: "/dice/svg/000000/transparent/",
muiRequire: 'absolute',
renameFilter: builder.RENAME_FILTER_DEFAULT,
Expand All @@ -128,7 +127,8 @@ describe('--svg-dir, --innerPath, --fileSuffix', function() {
builder.main(options, function() {
assert.ok(fs.lstatSync(tempPath).isDirectory());
assert.ok(fs.lstatSync(path.join(tempPath, "delapouite")).isDirectory());
jsxExampleOutputPath = path.join(tempPath, 'delapouite', 'dice-six-faces-four.jsx');
jsxExampleOutputPath = path.join(tempPath, 'delapouite', 'dice', 'svg', '000000', 'transparent', 'dice-six-faces-four.jsx');
assert.ok(fs.existsSync(jsxExampleOutputPath));
data = fs.readFileSync(jsxExampleOutputPath, {encoding: 'utf8'});
assert.include(data, builder.SVG_ICON_ABSOLUTE_REQUIRE);
done();
Expand All @@ -140,7 +140,7 @@ describe('--mui-require', function() {
var options = {
svgDir: MUI_ICONS_SVG_DIR,
innerPath: "/svg/production/",
fileSuffix: "_24px.svg",
glob: '/**/*_24px.svg',
disable_log: DISABLE_LOG,
renameFilter: builder.RENAME_FILTER_MUI,
}, tempPath, jsxExampleOutputPath;
Expand Down Expand Up @@ -200,10 +200,10 @@ describe('Template rendering', function() {
var options = {
svgDir: MUI_ICONS_SVG_DIR,
innerPath: "/svg/production/",
fileSuffix: "_24px.svg",
glob: '/**/*_24px.svg',
renameFilter: builder.RENAME_FILTER_MUI,
muiRequire: 'relative',
disable_log: false
disable_log: DISABLE_LOG
}, tempPath;

before(function() {
Expand All @@ -226,8 +226,6 @@ describe('Template rendering', function() {

expected = fs.readFileSync(exampleFilePath, {encoding: 'utf8'});
result = fs.readFileSync(resultFilePath, {encoding: 'utf8'});
console.log(expected);
console.log(result);
assert.include(result, expected);
done();
});
Expand Down

0 comments on commit a001394

Please sign in to comment.