Skip to content

Commit

Permalink
icon-builder tests, grunt, fix mui#1127 mui#1126 mui#1125 mui#1139
Browse files Browse the repository at this point in the history
- Add grunt, mocha.
- Modularize functions in build.
- Tests for functions and types.
- Grunt watch for rerunning tests on file save.
- Wrap argv in main() + only run when executing as a script.
- main() now accepts parameterized object literal for simulating args.
- mui#1139 split file renaming into module / argument.
  • Loading branch information
tony committed Jul 13, 2015
1 parent 51cb691 commit a8ac948
Show file tree
Hide file tree
Showing 49 changed files with 467 additions and 94 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');
};
207 changes: 115 additions & 92 deletions icon-builder/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,85 +11,104 @@
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');

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

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;
}

function main(options, cb) {
var originalWrite; // todo, add wiston / other logging tool
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);
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);

dirs.forEach(function(dirName) {
processDir(dirName, argv.svgDir, argv.outputDir, argv.innerPath, argv.fileSuffix, argv.muiRequire)
processDir(dirName, options.svgDir, options.outputDir, options.innerPath, options.fileSuffix, renameFilter, options.muiRequire)
});
});

function processDir(dirName, svgDir, outputDir, innerPath, fileSuffix, muiRequire) {
if (cb) {
cb();
}

if (options.disable_log) { // bring back stdout
process.stdout.write = originalWrite;
}
}

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; }
try {
var files = fs.readdirSync(svgIconDirPath);
var files = fs.readdirSync(svgIconDirPath);

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

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

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

function processFile(dirName, fileName, dirPath, svgDirPath, fileSuffix, muiRequire) {
//Only process 24px files
function processFile(dirName, fileName, dirPath, svgDirPath, fileSuffix, renameFilter, muiRequire) {
var fullPath;
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;
}
}
newFile = path.join(dirPath, fileName);

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

//console.log('writing ' + newFile);
getJsxString(dirName, fileName, svgFilePath, muiRequire, function(fileString) {
fs.writeFileSync(newFile, fileString);
});
var fileString = getJsxString(dirName, fileName, svgFilePath, muiRequire);
fs.writeFileSync(fullPath, fileString);
}

function getJsxString(dirName, newFilename, svgFilePath, muiRequire, callback) {
function getJsxString(dirName, newFilename, svgFilePath, muiRequire, fileString) {
var className = newFilename.replace('.jsx', '');
className = dirName + '-' + className;
className = pascalCase(className);
Expand All @@ -98,40 +117,27 @@ function getJsxString(dirName, newFilename, svgFilePath, muiRequire, callback) {

//var parser = new xml2js.Parser();

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"', '');

// 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";

callback(
"let React = require('react');\n" +
muiRequireStmt +
var data = fs.readFileSync(svgFilePath, {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 = muiRequire === "relative" ? SVG_ICON_RELATIVE_REQUIRE : SVG_ICON_ABSOLUTE_REQUIRE;

"});\n\n" +

"module.exports = " + className + ";"
);
return Mustache.render(
template, {
muiRequireStmt: muiRequireStmt,
paths: paths,
className: className
}
);

});
}

function pascalCase(str) {
Expand All @@ -140,3 +146,20 @@ function pascalCase(str) {
return group1.toUpperCase();
});
}

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

module.exports = {
pascalCase: pascalCase,
getJsxString: getJsxString,
processDir: processDir,
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
}
12 changes: 12 additions & 0 deletions icon-builder/filters/rename/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function defaultFilter(fileName, fileSuffix) {
if (fileSuffix) {
fileName.replace(fileSuffix, ".svg");
} else {
fileName = fileName.replace('.svg', '.jsx');
}
fileName = fileName.replace(/_/g, '-');
return fileName;
}


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

module.exports = myFilter;
14 changes: 12 additions & 2 deletions 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",
"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",
"build": "npm run createMuiIconsJsx && babel --stage 1 ./jsx --out-dir ./js",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -28,7 +28,17 @@
"peerDependencies": {},
"devDependencies": {
"babel": "^5.6.1",
"chai": "^3.0.0",
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-mocha-test": "^0.12.7",
"load-grunt-tasks": "^3.2.0",
"lodash": "^3.10.0",
"material-design-icons": "^2.0.0",
"rimraf": "^2.4.0"
"mocha": "^2.2.5",
"mustache": "^2.1.2",
"rimraf": "^2.4.0",
"temp": "^0.8.3"
}
}
7 changes: 7 additions & 0 deletions icon-builder/test/fixtures/game-icons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Game Icons Test Fixtures
========================

Source: http://game-icons.net/
Date: July 7, 2015
License: [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
Extracted from SVG (B/T) set.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit a8ac948

Please sign in to comment.