diff --git a/.travis.yml b/.travis.yml index 2cdacafbd..31fe87b41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,7 @@ language: node_js node_js: - '0.10' - '0.8' +before_install: + - currentfolder=${PWD##*/} + - if [ "$currentfolder" != 'generator-angular' ]; then cd .. && eval "mv $currentfolder generator-angular" && cd generator-angular; fi + diff --git a/app/index.js b/app/index.js index 62c73537d..3809d4162 100644 --- a/app/index.js +++ b/app/index.js @@ -45,6 +45,21 @@ var Generator = module.exports = function Generator(args, options) { this.env.options.coffee = this.options.coffee; } + if (typeof this.env.options.jade === 'undefined') { + this.option('jade', { + desc: 'Generate Jade templates instead of HTML' + }); + + // attempt to detect if user is using jade or not + // if cml arg provided, use that; else look for the existence of cs + if (!this.options.jade && + this.expandFiles(path.join(this.appPath, '/**/*.jade'), {}).length > 0) { + this.options.jade = true; + } + + this.env.options.jade = this.options.jade; + } + if (typeof this.env.options.minsafe === 'undefined') { this.option('minsafe', { desc: 'Generate AngularJS minification safe code' @@ -174,11 +189,72 @@ Generator.prototype.askForModules = function askForModules() { }; Generator.prototype.readIndex = function readIndex() { - this.indexFile = this.engine(this.read('../../templates/common/index.html'), this); + var extension = this.env.options.jade ? "jade" : "html"; + this.indexFile = this.engine(this.read('../../templates/common/index.' + extension), this); }; +function spacePrefix(jade, block){ + var prefix; + jade.split('\n').forEach( function (line) { if( line.indexOf(block)> -1 ) { + prefix = line.split("/")[0]; + }}); + return prefix; +} + +function generateJadeBlock(blockType, optimizedPath, filesBlock, searchPath, prefix) { + var blockStart, blockEnd; + var blockSearchPath = ''; + + if (searchPath !== undefined) { + if (util.isArray(searchPath)) { + searchPath = '{' + searchPath.join(',') + '}'; + } + blockSearchPath = '(' + searchPath + ')'; + } + + blockStart = '\n' + prefix + '// build:' + blockType + blockSearchPath + ' ' + optimizedPath + ' \n'; + blockEnd = prefix + '// endbuild\n' + prefix; + return blockStart + filesBlock + blockEnd; +} + +function appendJade(jade, tag, blocks){ + var mark = "//- build:" + tag, + position = jade.indexOf(mark); + return [jade.slice(0, position), blocks, jade.slice(position)].join(''); +} + +function appendFilesToJade(jadeOrOptions, fileType, optimizedPath, sourceFileList, attrs, searchPath) { + var blocks, updatedContent, prefix, jade, files = ''; + jade = jadeOrOptions; + if (typeof jadeOrOptions === 'object') { + jade = jadeOrOptions.html; + fileType = jadeOrOptions.fileType; + optimizedPath = jadeOrOptions.optimizedPath; + sourceFileList = jadeOrOptions.sourceFileList; + attrs = jadeOrOptions.attrs; + searchPath = jadeOrOptions.searchPath; + } + if (fileType === 'js') { + prefix = spacePrefix(jade, "build:body"); + sourceFileList.forEach(function (el) { + files += prefix + 'script(' + (attrs||'') + 'src="' + el + '")\n'; + }); + blocks = generateJadeBlock('js', optimizedPath, files, searchPath, prefix); + updatedContent = appendJade(jade, 'body', blocks); + } else if (fileType === 'css') { + prefix = spacePrefix(jade, "build:head"); + sourceFileList.forEach(function (el) { + files += prefix + 'link(' + (attrs||'') + 'rel="stylesheet", href="' + el + '")\n'; + }); + blocks = generateJadeBlock('css', optimizedPath, files, searchPath, prefix); + updatedContent = appendJade(jade, 'head', blocks); + } + return updatedContent; +} + // Waiting a more flexible solution for #138 Generator.prototype.bootstrapFiles = function bootstrapFiles() { + var filesToAppend; var sass = this.compassBootstrap; var files = []; var source = 'styles/' + ( sass ? 's' : '' ) + 'css/'; @@ -197,7 +273,7 @@ Generator.prototype.bootstrapFiles = function bootstrapFiles() { this.copy(source + file, 'app/styles/' + file); }.bind(this)); - this.indexFile = this.appendFiles({ + filesToAppend = { html: this.indexFile, fileType: 'css', optimizedPath: 'styles/main.css', @@ -205,16 +281,28 @@ Generator.prototype.bootstrapFiles = function bootstrapFiles() { return 'styles/' + file.replace('.scss', '.css'); }), searchPath: '.tmp' - }); + }; + + if (this.env.options.jade) { + this.indexFile = appendFilesToJade(filesToAppend); + } else { + this.indexFile = this.appendFiles(filesToAppend); + } }; + +function appendScriptsJade(jade, optimizedPath, sourceFileList, attrs) { + return appendFilesToJade(jade, 'js', optimizedPath, sourceFileList, attrs); +} + Generator.prototype.bootstrapJS = function bootstrapJS() { + var list; if (!this.bootstrap) { return; // Skip if disabled. } // Wire Twitter Bootstrap plugins - this.indexFile = this.appendScripts(this.indexFile, 'scripts/plugins.js', [ + list = [ 'bower_components/sass-bootstrap/js/affix.js', 'bower_components/sass-bootstrap/js/alert.js', 'bower_components/sass-bootstrap/js/button.js', @@ -226,8 +314,15 @@ Generator.prototype.bootstrapJS = function bootstrapJS() { 'bower_components/sass-bootstrap/js/scrollspy.js', 'bower_components/sass-bootstrap/js/tab.js', 'bower_components/sass-bootstrap/js/tooltip.js', - 'bower_components/sass-bootstrap/js/transition.js', - ]); + 'bower_components/sass-bootstrap/js/transition.js' + ]; + + if (this.env.options.jade) { + this.indexFile = appendScriptsJade(this.indexFile, 'scripts/plugins.js', list); + } else { + this.indexFile = this.appendScripts(this.indexFile, 'scripts/plugins.js', list); + } + }; Generator.prototype.extraModules = function extraModules() { @@ -245,23 +340,37 @@ Generator.prototype.extraModules = function extraModules() { } if (modules.length) { - this.indexFile = this.appendScripts(this.indexFile, 'scripts/modules.js', - modules); + if (this.env.options.jade) { + this.indexFile = appendScriptsJade(this.indexFile, 'scripts/plugins.js', modules); + } else { + this.indexFile = this.appendScripts(this.indexFile, 'scripts/plugins.js', modules); + } } }; Generator.prototype.appJs = function appJs() { - this.indexFile = this.appendFiles({ - html: this.indexFile, - fileType: 'js', - optimizedPath: 'scripts/scripts.js', - sourceFileList: ['scripts/app.js', 'scripts/controllers/main.js'], - searchPath: ['.tmp', 'app'] - }); + if (this.env.options.jade) { + this.indexFile = appendFilesToJade({ + html: this.indexFile, + fileType: 'js', + optimizedPath: 'scripts/scripts.js', + sourceFileList: ['scripts/app.js'], + searchPath: ['.tmp', 'app'] + }); + } else { + this.indexFile = this.appendFiles({ + html: this.indexFile, + fileType: 'js', + optimizedPath: 'scripts/scripts.js', + sourceFileList: ['scripts/app.js', 'scripts/controllers/main.js'], + searchPath: ['.tmp', 'app'] + }); + } }; Generator.prototype.createIndexHtml = function createIndexHtml() { - this.write(path.join(this.appPath, 'index.html'), this.indexFile); + var indexFile = 'index.' + (this.env.options.jade ? 'jade' : 'html'); + this.write(path.join(this.appPath, indexFile), this.indexFile); }; Generator.prototype.packageFiles = function () { @@ -269,3 +378,8 @@ Generator.prototype.packageFiles = function () { this.template('../../templates/common/_package.json', 'package.json'); this.template('../../templates/common/Gruntfile.js', 'Gruntfile.js'); }; + +Generator.prototype.addMainView = function addMainView() { + var mainFile = 'main.' + (this.env.options.jade ? 'jade' : 'html'); + this.copy('../../templates/common/' + mainFile, 'app/views/' + mainFile); +}; diff --git a/readme.md b/readme.md index 64594454a..c5a37570f 100644 --- a/readme.md +++ b/readme.md @@ -172,6 +172,18 @@ angular.module('myMod').config(function ($provide) { ## Options In general, these options can be applied to any generator, though they only affect generators that produce scripts. +### Jade +For generators that output html, the `--jade` option will output Jade instead of html files. + +For example: +```bash +yo angular:view user --jade + +Produces `app/scripts/views/user.jade`: +```jade +p This is the user view. +``` + ### CoffeeScript For generators that output scripts, the `--coffee` option will output CoffeeScript instead of JavaScript. diff --git a/script-base.js b/script-base.js index 989cb1789..bd5f31e21 100644 --- a/script-base.js +++ b/script-base.js @@ -46,6 +46,20 @@ var Generator = module.exports = function Generator() { this.env.options.coffee = this.options.coffee; } + this.env.options.jade = this.options.jade; + if (typeof this.env.options.jade === 'undefined') { + this.option('jade'); + + // attempt to detect if user is using jade or not + // if cml arg provided, use that; else look for the existence of cs + if (!this.options.jade && + this.expandFiles(path.join(this.env.options.appPath, '/**/*.jade'), {}).length > 0) { + this.options.jade = true; + } + + this.env.options.jade = this.options.jade; + } + if (typeof this.env.options.minsafe === 'undefined') { this.option('minsafe'); this.env.options.minsafe = this.options.minsafe; @@ -89,9 +103,25 @@ Generator.prototype.htmlTemplate = function (src, dest) { ]); }; -Generator.prototype.addScriptToIndex = function (script) { +function addScriptToIndexJade(script, env) { try { - var appPath = this.env.options.appPath; + var appPath = env.options.appPath; + var fullPath = path.join(appPath, 'index.jade'); + angularUtils.rewriteFile({ + file: fullPath, + needle: '// endbuild', + splicable: [ + 'script(src="scripts/' + script + '.js")' + ] + }); + } catch (e) { + console.log('\nUnable to find '.yellow + fullPath + '. Reference to '.yellow + script + '.js ' + 'not added.\n'.yellow); + } +} + +function addScriptToIndexHtml(script, env) { + try { + var appPath = env.options.appPath; var fullPath = path.join(appPath, 'index.html'); angularUtils.rewriteFile({ file: fullPath, @@ -103,6 +133,14 @@ Generator.prototype.addScriptToIndex = function (script) { } catch (e) { console.log('\nUnable to find '.yellow + fullPath + '. Reference to '.yellow + script + '.js ' + 'not added.\n'.yellow); } +} + +Generator.prototype.addScriptToIndex = function (script) { + if (this.env.options.jade) { + addScriptToIndexJade(script, this.env); + } else { + addScriptToIndexHtml(script, this.env); + } }; Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, targetDirectory, skipAdd) { @@ -111,4 +149,4 @@ Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, if (!skipAdd) { this.addScriptToIndex(path.join(targetDirectory, this.name)); } -}; +}; \ No newline at end of file diff --git a/templates/coffeescript-min/spec/service.coffee b/templates/coffeescript-min/spec/service.coffee index 9eae32b3f..07291d18a 100644 --- a/templates/coffeescript-min/spec/service.coffee +++ b/templates/coffeescript-min/spec/service.coffee @@ -3,7 +3,7 @@ describe 'Service: <%= classedName %>', () -> # load the service's module - beforeEach module '<%= scriptAppName %>App' + beforeEach module '<%= scriptAppName %>' # instantiate service <%= classedName %> = {} diff --git a/templates/coffeescript/spec/service.coffee b/templates/coffeescript/spec/service.coffee index 9eae32b3f..07291d18a 100644 --- a/templates/coffeescript/spec/service.coffee +++ b/templates/coffeescript/spec/service.coffee @@ -3,7 +3,7 @@ describe 'Service: <%= classedName %>', () -> # load the service's module - beforeEach module '<%= scriptAppName %>App' + beforeEach module '<%= scriptAppName %>' # instantiate service <%= classedName %> = {} diff --git a/templates/common/Gruntfile.js b/templates/common/Gruntfile.js index bd2734c9c..de61b6346 100644 --- a/templates/common/Gruntfile.js +++ b/templates/common/Gruntfile.js @@ -30,6 +30,10 @@ module.exports = function (grunt) { files: ['<%%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], tasks: ['compass:server', 'autoprefixer'] },<% } %> + jade: { + files: ['<%%= yeoman.app %>/**/*.jade'], + tasks: ['jade'] + }, styles: { files: ['<%%= yeoman.app %>/styles/{,*/}*.css'], tasks: ['copy:styles', 'autoprefixer'] @@ -174,6 +178,28 @@ module.exports = function (grunt) { } } }, + jade: { + index: { + files: { + '<%%= yeoman.app %>/': ['<%%= yeoman.app %>/index.jade'] + }, + options: { + basePath: '<%%= yeoman.app %>/', + client: false, + pretty: true + } + }, + views: { + files: { + '<%%= yeoman.app %>/views/': ['<%%= yeoman.app %>/views/**/*.jade'] + }, + options: { + basePath: '<%%= yeoman.app %>/views', + client: false, + pretty: true + } + } + }, useminPrepare: { html: '<%%= yeoman.app %>/index.html', options: { @@ -274,16 +300,19 @@ module.exports = function (grunt) { }, concurrent: { server: [ + 'jade', 'coffee:dist',<% if (compassBootstrap) { %> 'compass:server',<% } %> 'copy:styles' ], test: [ + 'jade', 'coffee',<% if (compassBootstrap) { %> 'compass',<% } %> 'copy:styles' ], dist: [ + 'jade', 'coffee',<% if (compassBootstrap) { %> 'compass:dist',<% } %> 'copy:styles', diff --git a/templates/common/_package.json b/templates/common/_package.json index bc03d0841..86c75b297 100644 --- a/templates/common/_package.json +++ b/templates/common/_package.json @@ -25,6 +25,7 @@ "grunt-google-cdn": "~0.2.0", "grunt-ngmin": "~0.0.2", "time-grunt": "~0.1.0", + "grunt-jade": "~0.4.0", "jshint-stylish": "~0.1.3" }, "engines": { diff --git a/templates/common/index.jade b/templates/common/index.jade new file mode 100644 index 000000000..a52ec7705 --- /dev/null +++ b/templates/common/index.jade @@ -0,0 +1,41 @@ +!!! +// [if lt IE 7]> App") + //[if lt IE 7]> +
You are using an outdated browser. Please upgrade your browser to improve your experience.
+ + + + view. \ No newline at end of file diff --git a/templates/javascript-min/spec/service.js b/templates/javascript-min/spec/service.js index 93e717916..d41939590 100644 --- a/templates/javascript-min/spec/service.js +++ b/templates/javascript-min/spec/service.js @@ -3,7 +3,7 @@ describe('Service: <%= classedName %>', function () { // load the service's module - beforeEach(module('<%= scriptAppName %>App')); + beforeEach(module('<%= scriptAppName %>')); // instantiate service var <%= classedName %>; diff --git a/templates/javascript/spec/service.js b/templates/javascript/spec/service.js index c4a6898a2..729509e3e 100644 --- a/templates/javascript/spec/service.js +++ b/templates/javascript/spec/service.js @@ -3,7 +3,7 @@ describe('Service: <%= classedName %>', function () { // load the service's module - beforeEach(module('<%= scriptAppName %>App')); + beforeEach(module('<%= scriptAppName %>')); // instantiate service var <%= classedName %>; diff --git a/test/test-file-creation.js b/test/test-file-creation.js index 38906ddba..56bc6bbf4 100644 --- a/test/test-file-creation.js +++ b/test/test-file-creation.js @@ -101,6 +101,35 @@ describe('Angular generator', function () { }); }); + it('creates jade files', function (done) { + var expected = ['app/.htaccess', + 'app/404.html', + 'app/favicon.ico', + 'app/robots.txt', + 'app/styles/main.css', + 'app/views/main.jade', + ['.bowerrc', /"directory": "app\/bower_components"/], + 'Gruntfile.js', + 'package.json', + ['bower.json', /"name":\s+"temp"/], + 'app/scripts/app.js', + 'app/index.jade', + 'app/scripts/controllers/main.js', + 'test/spec/controllers/main.js' + ]; + helpers.mockPrompt(angular, { + bootstrap: true, + compassBoostrap: true, + modules: [] + }); + + angular.env.options.jade = true; + angular.run([], function () { + helpers.assertFiles(expected); + done(); + }); + }); + /** * Generic test function that can be used to cover the scenarios where a generator is creating both a source file * and a test file. The function will run the respective generator, and then check for the existence of the two @@ -168,7 +197,7 @@ describe('Angular generator', function () { describe('Service', function () { function serviceTest (generatorType, nameFn, done) { generatorTest(generatorType, 'service', 'services', nameFn, _.classify, '', done); - }; + } it('should generate a new constant', function (done) { serviceTest('constant', _.camelize, done); @@ -212,6 +241,27 @@ describe('Angular generator', function () { }); }); + it('should generate a new jade view', function (done) { + var angularView; + var deps = ['../../view']; + angularView = helpers.createGenerator('angular:view', deps, ['foo']); + + helpers.mockPrompt(angular, { + bootstrap: true, + compassBoostrap: true, + modules: [] + }); + angularView.env.options.jade = true; + angular.run([], function (){ + angularView.run([], function () { + helpers.assertFiles([ + ['app/views/foo.jade'] + ]); + done(); + }); + }); + }); + it('should generate a new view in subdirectories', function (done) { var angularView; var deps = ['../../view']; diff --git a/view/index.js b/view/index.js index 6de914e8a..d184b3c97 100644 --- a/view/index.js +++ b/view/index.js @@ -1,11 +1,11 @@ 'use strict'; var path = require('path'); var util = require('util'); -var yeoman = require('yeoman-generator'); +var ScriptBase = require('../script-base.js'); var Generator = module.exports = function Generator() { - yeoman.generators.NamedBase.apply(this, arguments); + ScriptBase.apply(this, arguments); this.sourceRoot(path.join(__dirname, '../templates')); if (typeof this.env.options.appPath === 'undefined') { @@ -16,8 +16,9 @@ var Generator = module.exports = function Generator() { } }; -util.inherits(Generator, yeoman.generators.NamedBase); +util.inherits(Generator, ScriptBase); Generator.prototype.createViewFiles = function createViewFiles() { - this.template('common/view.html', path.join(this.env.options.appPath, 'views', this.name + '.html')); + var extension = this.env.options.jade ? '.jade' : '.html'; + this.template('common/view' + extension, path.join(this.env.options.appPath, 'views', this.name + extension)); };