ember-rocks
Version:
An Em(ber) command line utility to help you build an ambitious web application
365 lines (318 loc) • 13.5 kB
JavaScript
;
var path = require('path');
var fs = require('fs');
var tildify = require('tildify');
var gulp = require('gulp');
var gutil = require('gulp-util');
var replace = require('gulp-replace');
var rename = require('gulp-rename');
var stringUtils = require('../utils/string');
// Add a test file template into the generating file list
function injectTestFile (srcPath, type) {
var injectTestGenerator = [{
type: type + '-test',
injection: true,
generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '-test.js',
generateUnitTest: true
}];
// flag `-T` or `--test`, will generate the unit test file
srcPath = srcPath.concat(injectTestGenerator);
// return the modified `srcPath` array
return srcPath;
}
// Add a template file template into the generating file list
function injectTemplateFile (srcPath, type) {
var injectTemplateGenerator = [{
type: 'template',
// injection: components is needed to setup the correct path: "templates/components/*.hbs"
injection: (type === 'component') ? 'components' : true,
generatorPath: path.join(__dirname, '..', 'skeletons/generators/template.js')
}];
srcPath = srcPath.concat(injectTemplateGenerator);
// return the modified `srcPath` array
return srcPath;
}
// Add a default type template into the generating file list
function injectDefaultTemplate (srcPath, type) {
var defaultTemplateGenerator = [{
type: type,
generatorPath: path.join(__dirname, '..', 'skeletons/generators', type) + '.js'
}];
srcPath = srcPath.concat(defaultTemplateGenerator);
// return the modified `srcPath` array
return srcPath;
}
function injectFiles (type, options) {
var srcPath = []; // the filePath/srcPath would be used to generate files
// check for the options mode, to generate an unit test file or not
if (options.test || false) {
srcPath = injectTestFile(srcPath, type);
}
// when `type` is `route` or `component`, will generate its template file
if (type === 'route' || type === 'component') {
srcPath = injectTemplateFile(srcPath, type);
}
// Default, it will generate whatever user inserts the `type`
srcPath = injectDefaultTemplate(srcPath, type);
return srcPath;
}
// Core function to generate the file from template, insert into the destination folder
function generatorEngine (type, srcPath, moduleName, fileName, ext, destPath) {
var namespace = stringUtils.classify(moduleName + '-' + type);
var dasherizeName = stringUtils.dasherize(moduleName);
var classifyName = stringUtils.classify(moduleName);
// __DASHERIZE_NAMESPACE__ mainly used in `-test` generator
// __CLASSIFY_NAMESPACE__ mainly used in regular generator
return gulp.src(srcPath)
.pipe(replace(/__NAMESPACE__/g, namespace))
.pipe(replace(/__DASHERIZE_NAMESPACE__/g, dasherizeName))
.pipe(replace(/__CLASSIFY_NAMESPACE__/g, classifyName))
.pipe(rename({
basename: fileName,
extname: ext
}))
.on('end', function () {
gutil.log(
gutil.colors.green('[-done:] Generate'),
gutil.colors.cyan(fileName + ext),
gutil.colors.green('at'),
gutil.colors.magenta(tildify(destPath))
);
})
.pipe(gulp.dest(destPath));
}
function generateFileTask (srcPath, moduleName, fileName, pathName, options) {
for (var i = 0, l = srcPath.length; i < l; i++) {
var _type = srcPath[i].type;
var dirName;
// Not asked by user input, create automatically by framework
var injection = srcPath[i].injection || false;
// original fileName, or if test is true, append "-test"
var finalFileName;
// used to build up the final output directory path "destPath"
var finalPath;
// final output directory path
var destPath;
// Update the direction name, if store, won't append 's'
dirName = (_type === 'store') ? _type : (_type.slice(-1) === 's') ? _type : _type + 's';
// when template is injected automatically, setup the right path "templates/components/"
dirName = (injection === 'components') ? dirName + '/' + injection : dirName;
// Figure out the type is testing generator
if (_type.indexOf('test') > -1) {
// Is it an Unit Test generator or Integration Test generator
if (_type.indexOf('-test') > -1) {
var typeArray = _type.split('-');
// if injection is true, the dirName has been setup already
// otherwise, we have to build up manually when user input directly
if (!!injection) {
dirName = typeArray[0] + 's';
} else {
dirName = 'tests/unit/' + typeArray[0] + 's';
}
} else {
dirName = dirName + '/integration';
}
}
finalPath = options.nestPath ? dirName + pathName : dirName;
if (_type.indexOf('test') > -1 && injection === false) {
// srcPath length is 1, no injection files, only ask for one test file generation
destPath = path.resolve('client') + '/' + finalPath;
finalFileName = fileName;
} else if (options.test && srcPath[i].generateUnitTest) {
// user pass "--test" flag, and inject test is true, generate an unit test file or not
destPath = path.resolve('client/tests/unit') + '/' + dirName;
finalFileName = fileName + '-test';
} else {
// generate an application development file
destPath = path.resolve('client/app') + '/' + finalPath;
finalFileName = fileName;
}
var ext = (_type === 'template') ? '.hbs' : '.js';
var fullFilePath = destPath + '/' + fileName + ext;
// if the file has existed in destination folder, exit the program. with Two exception,
// 1. injection file, if true & existed, handle the case in the next condition
// 2, generate a template, if existed, stop the template generating, won't exit program
if (checkFileExisted(fullFilePath, injection, fileName, ext, destPath)) {
continue;
}
generatorEngine(_type, srcPath[i].generatorPath, moduleName, finalFileName, ext, destPath);
}
}
function generateFile (type, moduleName, fileName, pathName, options) {
// an Array, contains a list of generating file info: path, generatorPath, type, etc
var srcPath = injectFiles(type, options);
generateFileTask(srcPath, moduleName, fileName, pathName, options);
}
// @describe generate an model,view,store,controller from base template
function runTasks (generator, options) {
var type = generator.type;
var name = generator.name;
var pathName = '';
var moduleName = '';
var fileName; // setup the fileName which used for rename module
// var srcPath = []; // the filePath/srcPath would be used to generate files
// based on the passing name arguments, to determine it is an nested folder structure
// or it is a simple file generation. assign a var `fileName` for current file name
if (name.indexOf('/') > -1) {
options.nestPath = true;
name = name.split('/');
fileName = name.pop();
} else {
options.nestPath = false;
fileName = name;
}
// component name has to be dash separated string
validComponentName(type, fileName, name[0], options);
// ignore the 'store' case, since it is already created
// create a folder if it is not existed in the "client/app/"
createFolderWhenMissing(type);
// Setup `pathName`
// `moduleName` would be used inside replacement of template placeholder
if (options.nestPath) {
// build up the nested path
for (var i = 0; i < name.length; i++) {
// 'component' and 'components' resolve as a 'app/templates/components/'
if (type === 'template' && name[0] === 'component') {
name[i] = 'components';
}
pathName += '/' + name[i];
moduleName += name[i] + '-';
}
// append fileName to the moduleName string
moduleName += fileName;
} else {
pathName += name;
moduleName = name;
}
// if type is test, or route-test or any sorts, it should append `-test` to the filename
fileName = (type.indexOf('test') > -1) ? fileName + '-test' : fileName;
generateFile(type, moduleName, fileName, pathName, options);
}
// Entry point of the generate command
function generate (generator, options) {
options = options || {};
// must be Ember Rocks project, user input must exist and is a string,
// generator must be in the right format separated by ":", otherwise, exit the program
validProjectAndValidUserInput(generator);
var validTypes = [
'adapter', 'component', 'controller', 'helper', 'initializer', 'mixin', 'model',
'route', 'serializer', 'template', 'transform', 'util', 'view',
'test', 'adapter-test', 'component-test', 'controller-test', 'helper-test',
'initializer-test', 'mixin-test', 'model-test', 'route-test', 'serializer-test',
'transform-test', 'util-test', 'view-test'
];
var generatorAndTasks = generator.split(':', 2);
var type = generatorAndTasks[0];
// type could be either route or routes
type = (type.slice(-1) === 's') ? type.substring(0, type.length - 1) : type;
var gen = {
type: type,
name: generatorAndTasks[1]
};
// must be a valid name, must be a valid type, otherwise, exit the program
validTypesAndValidName(gen, validTypes);
// only run tasks when it is a valid type and name
runTasks(gen, options);
}
module.exports = generate;
function validProjectAndValidUserInput (generator) {
// if the folder 'client/app' is not existed
// can assume that the project may not be created by Ember Rocks
if (!fs.existsSync('client') && !fs.existsSync('client/app')) {
gutil.log(gutil.colors.red('[-Error:] This project may not be created by \'Ember-Rocks\'\n'),
gutil.colors.red(
'[-Error:] `em new [dirName]` does not install the NPM packages dependencies correctly'));
exitProgram(1);
}
// Error out when user did not provide any argument
if (!generator || typeof generator !== 'string') {
gutil.log(gutil.colors.red('[-Error:] Missing type:name argument.'), 'ex: em new route:post');
gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
exitProgram();
}
// Check the fullname(generator) attribute is correct or not
var VALID_FULL_NAME_REGEXP = /^[^:]+.+:[^:]+$/;
if (!VALID_FULL_NAME_REGEXP.test(generator)) {
gutil.log(gutil.colors.red('[-Error:] Invalid argument, expected: `type:name` got: '),
gutil.colors.bold(generator));
gutil.log('[-Syntax:]', gutil.colors.cyan('type:name'), ' ex: em generate route:post');
gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
exitProgram();
}
}
function validTypesAndValidName (gen, validTypes) {
var type = gen.type;
var name = gen.name;
// Type must be in the `validTypes` array
if (validTypes.indexOf(type) > -1) {
// Name must be a valid string
if (name.length <= 0) {
gutil.log(gutil.colors.red('[-Error:] '), gutil.colors.cyan(name),
gutil.colors.red(' must be a valid string.'));
gutil.log(gutil.colors.red('[-Error:]'), 'See \'em generate --help\'');
exitProgram();
}
} else {
gutil.log(gutil.colors.red('[-Error:] '), gutil.colors.cyan(type),
gutil.colors.red(' is not a valid type.'));
gutil.log(gutil.colors.bold('[-note:] valid types are'),
gutil.colors.cyan(validTypes.join(', ')));
exitProgram();
}
}
function validComponentName (type, fileName, folderName, options) {
// Three error cases:
// case 1: `em g component:name` <= simple case
// case 2: `em g component:nested/name` <= nested case
// case 3: `em g template:component/name` <= name of nest path has to be dashized string
if (type === 'component' ||
type === 'template' && options.nestPath &&
folderName === 'components' || folderName === 'component') {
if (fileName.indexOf('-') === -1) {
gutil.log(gutil.colors.red('[-Error:] '), gutil.colors.cyan(fileName),
gutil.colors.red(' must be a dashize string. ex: my-component')
);
gutil.log(gutil.colors.red('[-Error:] Generate task has been canceled'));
exitProgram();
}
}
}
function createFolderWhenMissing (type) {
var typeFolder = path.resolve('client/app', type + 's');
// if client/app/[type](s) is not existed and it is not a test generator, simply create one
if (!fs.existsSync(typeFolder) && type.indexOf('test') === -1) {
fs.mkdirSync(typeFolder);
gutil.log(gutil.colors.gray('[-log:] Created a new folder at '),
gutil.colors.cyan('~/client/app/' + type + 's'));
}
}
function checkFileExisted (fullFilePath, injection, fileName, ext, destPath) {
if (fs.existsSync(fullFilePath)) {
if (!!injection) {
gutil.log(
gutil.colors.red('[-Warning:] '),
gutil.colors.cyan(fileName + ext),
gutil.colors.red('has existed at '),
gutil.colors.magenta(tildify(destPath))
);
// Does not continue to generate file, but won't stop the process
return true;
} else {
gutil.log(
gutil.colors.red('[-Error:] '),
gutil.colors.cyan(fileName + ext),
gutil.colors.red('has existed at '),
gutil.colors.magenta(tildify(destPath))
);
gutil.log(
gutil.colors.red('[-Error:] Generate task has been canceled')
);
// File is existed in the system, kill the process
exitProgram();
}
}
}
function exitProgram (errNumber) {
errNumber = errNumber || 0;
process.exit(errNumber);
}