Skip to content

Commit

Permalink
Merge pull request #1130 from tony/icon-builder-test
Browse files Browse the repository at this point in the history
#1127 #1126 #1125 #1139 Icon builder tests, grunt, templating
  • Loading branch information
hai-cea committed Jul 16, 2015
2 parents 053bd54 + 440b567 commit 0004660
Show file tree
Hide file tree
Showing 51 changed files with 576 additions and 113 deletions.
39 changes: 39 additions & 0 deletions icon-builder/Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module.exports = function(grunt) {

grunt.loadNpmTasks('grunt-mocha-test');
grunt.loadNpmTasks('grunt-contrib-watch');

grunt.initConfig({
mochaTest: {
test: {
options: {
reporter: 'spec',
clearRequireCache: true
},
src: ['test/*.js']
},
},

watch: {
js: {
options: {
spawn: false,
},
files: ['build.js', 'test/*.js'],
tasks: ['default']
}
}
});

// On watch events, if the changed file is a test file then configure mochaTest to only
// run the tests from that file. Otherwise run all the tests
var defaultTestSrc = grunt.config('mochaTest.test.src');
grunt.event.on('watch', function(action, filepath) {
grunt.config('mochaTest.test.src', defaultTestSrc);
if (filepath.match('test/')) {
grunt.config('mochaTest.test.src', filepath);
}
});

grunt.registerTask('default', 'mochaTest');
};
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
251 changes: 142 additions & 109 deletions icon-builder/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,132 +11,165 @@
var fs = require('fs');
var path = require('path');
var rimraf = require('rimraf');
var argv = 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('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;

//Clean old files
rimraf(argv.outputDir, function() {
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('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() {};
}

rimraf.sync(options.outputDir); // Clean old files
console.log('** Starting Build');
//Process each folder
var dirs = fs.readdirSync(argv.svgDir);
fs.mkdirSync(argv.outputDir);
dirs.forEach(function(dirName) {
processDir(dirName, argv.svgDir, argv.outputDir, argv.innerPath, argv.fileSuffix, argv.muiRequire)
var dirs = fs.readdirSync(options.svgDir);

var renameFilter = options.renameFilter;
if (_.isString(renameFilter)) {
renameFilter = require(renameFilter);
}
if (!_.isFunction(renameFilter)) {
throw Error("renameFilter must be a function");
}

fs.mkdirSync(options.outputDir);
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);
});
});

function processDir(dirName, svgDir, outputDir, innerPath, fileSuffix, 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; }
try {
var files = fs.readdirSync(svgIconDirPath);

rimraf(newIconDirPath, function() {
console.log('\n ' + dirName);
fs.mkdirSync(newIconDirPath);

files.forEach(function(fileName) {
processFile(dirName, fileName, newIconDirPath, svgIconDirPath, fileSuffix, muiRequire);
});
});

} catch (err) {
throw (err);

if (cb) {
cb();
}
}

function processFile(dirName, fileName, dirPath, svgDirPath, fileSuffix, muiRequire) {
//Only process 24px files
var svgFilePath = svgDirPath + '/' + fileName;
var newFile;
if (fileSuffix) {
if (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);
}
} else {
return;
}
if (options.disable_log) { // bring back stdout
process.stdout.write = originalWrite;
}
newFile = path.join(dirPath, fileName);
}

//console.log('writing ' + newFile);
getJsxString(dirName, fileName, svgFilePath, muiRequire, function(fileString) {
fs.writeFileSync(newFile, fileString);
});
/*
* @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));

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 getJsxString(dirName, newFilename, svgFilePath, muiRequire, callback) {
var className = newFilename.replace('.jsx', '');
className = dirName + '-' + className;
className = pascalCase(className);

console.log(' ' + className);

//var parser = new xml2js.Parser();
/**
* 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;
}

fs.readFile(svgFilePath, {encoding: 'utf8'}, function(err, data) {
if (err) {
throw err;
}
//Extract the paths from the svg string
var paths = data.slice(data.indexOf('>') + 1);
paths = paths.slice(0, -6);
//clean xml paths
paths = paths.replace('xlink:href="#a"', '');
paths = paths.replace('xlink:href="#c"', '');
function getJsxString(svgPath, destPath, options) {
var className = pascalCase(destPath);

// Node acts wierd if we put this directly into string concatenation
var muiRequireStmt = muiRequire === "relative" ? "let SvgIcon = require('../../svg-icon');\n\n" : "let SvgIcon = require('material-ui/lib/svg-icon');\n\n";
console.log(' ' + className);

callback(
"let React = require('react');\n" +
muiRequireStmt +
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);
paths = paths.slice(0, -6);
//clean xml paths
paths = paths.replace('xlink:href="#a"', '');
paths = paths.replace('xlink:href="#c"', '');

"let " + className + " = React.createClass({\n\n" +
// Node acts wierd if we put this directly into string concatenation

" render() {\n" +
" return (\n" +
" <SvgIcon {...this.props}>\n" +
" " + paths + "\n" +
" </SvgIcon>\n" +
" );\n" +
" }\n\n" +
var muiRequireStmt = options.muiRequire === "relative" ? SVG_ICON_RELATIVE_REQUIRE : SVG_ICON_ABSOLUTE_REQUIRE;

"});\n\n" +
return Mustache.render(
template, {
muiRequireStmt: muiRequireStmt,
paths: paths,
className: className
}
);

"module.exports = " + className + ";"
);
}

});
if (require.main === module) {
var argv = parseArgs();
main(argv);
}

function pascalCase(str) {
str = str[0].toUpperCase() + str.slice(1);
return str.replace(/-(.)/g, function(match, group1) {
return group1.toUpperCase();
});
module.exports = {
pascalCase: pascalCase,
getJsxString: getJsxString,
processFile: processFile,
main: main,
SVG_ICON_RELATIVE_REQUIRE: SVG_ICON_RELATIVE_REQUIRE,
SVG_ICON_ABSOLUTE_REQUIRE: SVG_ICON_ABSOLUTE_REQUIRE,
RENAME_FILTER_DEFAULT: RENAME_FILTER_DEFAULT,
RENAME_FILTER_MUI: RENAME_FILTER_MUI
}
29 changes: 29 additions & 0 deletions icon-builder/filters/rename/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 path.join(innerPath, fileName);
}


module.exports = defaultDestRewriter;
18 changes: 18 additions & 0 deletions icon-builder/filters/rename/material-design-icons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 = myDestRewriter;
Loading

0 comments on commit 0004660

Please sign in to comment.