From 400648db0351a1210f1e118a9724354e829aa228 Mon Sep 17 00:00:00 2001 From: cueedee Date: Wed, 15 Apr 2015 21:39:32 +0200 Subject: [PATCH] feat(generators): add answers validation and laundering, normalize across subgenerators, tweak and normalize phrasings --- generators/app/index.js | 37 +++++++++---- generators/app/templates/README.md | 4 +- generators/app/templates/_package.json | 2 +- .../app/templates/demo/_index.template.html | 2 +- .../app/templates/src/_index.template.html | 2 +- generators/collection/index.js | 54 +++++++++++++------ .../collection/templates/collection.coffee | 16 +++--- generators/model/index.js | 38 ++++++++----- generators/model/templates/model.coffee | 6 +-- generators/view/index.js | 54 ++++++++++--------- generators/view/templates/view.coffee | 12 ++--- generators/view/templates/view.sass | 2 +- lib/youtil.js | 32 +++++++++++ package.json | 2 +- 14 files changed, 173 insertions(+), 90 deletions(-) create mode 100644 lib/youtil.js diff --git a/generators/app/index.js b/generators/app/index.js index 9375e807..5f1f9175 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -4,10 +4,14 @@ // Yeoman bat app generator. // -var generators = require( 'yeoman-generator' ) -, yosay = require( 'yosay' ) +var generators = require( 'yeoman-generator' ) +, yosay = require( 'yosay' ) +, youtil = require( './../../lib/youtil.js' ) +, _ = require( 'lodash' ) ; +var clean = require( 'underscore.string/clean' ); + // Use different delimiters when our template itself is meant to be a template or template-like. // var tpl_tpl_settings = @@ -34,38 +38,51 @@ module.exports = generators.Base.extend( { askSomeQuestions: function () { + /* jshint laxbreak: true */ + var done = this.async(); // Have Yeoman greet the user. // - this.log( yosay( 'Welcome to the BAT generator! (Backbone Application Template) \n Powered by Marviq' ) ); + this.log( yosay( + 'Welcome to the BAT generator! (Backbone Application Template)\n' + + 'Powered by marviq' + )); // Ask the user for the webapp details // var prompts = [ { name: 'packageName' - , message: 'What is the name of this webapp?' + , message: 'What is the package name of this webapp?' , default: this.appname + , validate: youtil.isNpmName } , { name: 'packageDescription' , message: 'What is the purpose (description) of this webapp?' + , validate: youtil.isNonBlank + , filter: youtil.sentencify } , { name: 'authorName' , message: 'What is your name?' , default: this.user.git.name() + , validate: youtil.isNonBlank + , filter: clean } , { name: 'authorEmail' - , message: 'What is your email?' + , message: 'What is your email address?' , default: this.user.git.email() + , validate: youtil.isNonBlank + , filter: _.trim } , { name: 'authorUrl' - , message: 'If any, by what url would you like to be known?' + , message: 'If any, by what URL would you like to be known?' , default: '' + , filter: _.trim } , { name: 'multiLanguage' @@ -94,13 +111,11 @@ module.exports = generators.Base.extend( this.packageName = answers.packageName; this.packageDescription = answers.packageDescription; this.authorName = answers.authorName; - this.authorEmail = answers.authorEmail.trim(); - this.authorUrl = answers.authorUrl.trim(); + this.authorEmail = answers.authorEmail; + this.authorUrl = answers.authorUrl; this.ie8 = answers.ie8; this.demo = answers.demo; - this.i18n = answers.multiLanguage || answers.demo; - done(); }.bind( this ) @@ -110,6 +125,8 @@ module.exports = generators.Base.extend( , configuring: function () { + this.i18n = this.multiLanguage || this.demo; + // // Save a '.yo-rc.json' config file. // At the very least this marks your project root for sub-generators. diff --git a/generators/app/templates/README.md b/generators/app/templates/README.md index d0f59ab1..27c52883 100644 --- a/generators/app/templates/README.md +++ b/generators/app/templates/README.md @@ -1,4 +1,4 @@ -<%= _.slugify(packageName) %> +<%= packageName %> ====================== ## introduction @@ -7,7 +7,7 @@ ## installation ```bash -$ npm install <%= _.slugify(packageName) %> --save +$ npm install <%= packageName %> --save ``` ## usage diff --git a/generators/app/templates/_package.json b/generators/app/templates/_package.json index 2a73b4cc..add51eaa 100644 --- a/generators/app/templates/_package.json +++ b/generators/app/templates/_package.json @@ -22,7 +22,7 @@ } ], "main": "dist/app/bundle.js", - "name": "<%= _.slugify(packageName) %>", + "name": "<%= packageName %>", "private": true, "scripts": { "prepublish": "command -v grunt > /dev/null || { echo >&2 'It appears that \"grunt\" is not installed. Consider running \"npm install grunt --save-dev\" first.'; exit ; } && grunt", diff --git a/generators/app/templates/demo/_index.template.html b/generators/app/templates/demo/_index.template.html index 0f1d4d43..7594c40e 100644 --- a/generators/app/templates/demo/_index.template.html +++ b/generators/app/templates/demo/_index.template.html @@ -9,7 +9,7 @@ - #- _.slugify(packageName) -# + #- packageName -# diff --git a/generators/app/templates/src/_index.template.html b/generators/app/templates/src/_index.template.html index 8bd111fc..d8354b52 100644 --- a/generators/app/templates/src/_index.template.html +++ b/generators/app/templates/src/_index.template.html @@ -27,7 +27,7 @@ - #- _.slugify(packageName) -# + #- packageName -# #% if ( ie8 ) { %#<% diff --git a/generators/collection/index.js b/generators/collection/index.js index c130cbf6..bb746f9e 100644 --- a/generators/collection/index.js +++ b/generators/collection/index.js @@ -4,12 +4,14 @@ // Yeoman bat:collection sub-generator. // -var generators = require( 'yeoman-generator' ) -, yosay = require( 'yosay' ) -, varname = require( 'varname' ) -, _ = require( 'lodash' ) +var generators = require( 'yeoman-generator' ) +, yosay = require( 'yosay' ) +, youtil = require( './../../lib/youtil.js' ) +, _ = require( 'lodash' ) ; +var decapitalize = require( 'underscore.string/decapitalize' ); + var CollectionGenerator = generators.Base.extend( { initializing: function () @@ -32,12 +34,18 @@ var CollectionGenerator = generators.Base.extend( var prompts = [ { name: 'collectionName' - , message: 'What\'s the name of this collection you so desire? ( use camelcasing! )' + , message: 'What is the name of this collection you so desire?' + , validate: youtil.isIdentifier + , filter: function ( value ) + { + return decapitalize( _.trim( value ).replace( /collection$/i, '' )); + } } , { name: 'description' - , message: 'What\'s the description for this collection?' - , default: 'No description' + , message: 'What is the purpose (description) of this collection?' + , validate: youtil.isNonBlank + , filter: youtil.sentencify } , { type: 'confirm' @@ -79,8 +87,13 @@ var CollectionGenerator = generators.Base.extend( var prompts = [ { name: 'modelName' - , message: 'Whats the model name for this collection ( use camelcasing! )' + , message: 'What is the model name for this collection?' , default: modelName + , validate: youtil.isIdentifier + , filter: function ( value ) + { + return decapitalize( _.trim( value ).replace( /model$/i, '' )); + } } , { type: 'confirm' @@ -104,20 +117,27 @@ var CollectionGenerator = generators.Base.extend( } } + , configuring: function () + { + var collectionName = this.collectionName + , modelName = this.modelName + ; + + this.className = _.capitalize( collectionName ) + 'Collection'; + this.fileBase = _.kebabCase( _.deburr( collectionName )); + + this.modelClassName = _.capitalize( modelName ) + 'Model'; + this.modelFileName = _.kebabCase( _.deburr( modelName )) + '.coffee'; + } + , writing: { createCollection: function () { - // Create the needed variables - this.modelClass = this.modelName.charAt(0).toUpperCase() + this.modelName.slice(1); - this.className = this.collectionName.charAt(0).toUpperCase() + this.collectionName.slice(1); - this.modelFileName = varname.dash( this.modelName ); - this.fileName = varname.dash( this.collectionName ); - - // Create the collection - this.template( 'collection.coffee', 'src/collections/' + this.fileName + '.coffee' ); + // Create the collection + this.template( 'collection.coffee', 'src/collections/' + this.fileBase + '.coffee' ); - // Create the model if needed + // Create the model if needed if ( this.createModel === true ) { this.invoke( diff --git a/generators/collection/templates/collection.coffee b/generators/collection/templates/collection.coffee index bce79dda..afefb88f 100644 --- a/generators/collection/templates/collection.coffee +++ b/generators/collection/templates/collection.coffee @@ -2,17 +2,17 @@ if typeof exports is 'object' module.exports = factory( require( 'backbone' ) - require( './../models/<%= modelFileName %>.coffee' ) + require( './../models/<%= modelFileName %>' ) ) else if typeof define is 'function' and define.amd define( [ 'backbone' - './../models/<%= modelFileName %>.coffee' + './../models/<%= modelFileName %>' ], factory ) )(( Backbone - <%= modelClass %>Model + <%= modelClassName %> ) -> ###* @@ -26,28 +26,28 @@ ###*<% if ( description ) { %> # <%= description %> #<% } %> - # @class <%= className %>Collection + # @class <%= className %> # @extends Backbone.Collection<% if ( singleton ) { %> # @static<% } else { %> # @constructor<% } %> ### - class <%= className %>Collection extends Backbone.Collection + class <%= className %> extends Backbone.Collection ###* # @property model # - # @default <%= modelClass %>Model + # @default <%= modelClassName %> # @type Class # @static # @final ### - model: <%= modelClass %>Model<% if ( singleton ) { %> + model: <%= modelClassName %><% if ( singleton ) { %> ## Export singleton ## - return new <%= className %>Collection()<% } %> + return new <%= className %>()<% } %> ) diff --git a/generators/model/index.js b/generators/model/index.js index d0790a08..152ab340 100644 --- a/generators/model/index.js +++ b/generators/model/index.js @@ -4,12 +4,14 @@ // Yeoman bat:model sub-generator. // -var generators = require( 'yeoman-generator' ) -, yosay = require( 'yosay' ) -, varname = require( 'varname' ) -, _ = require( 'lodash' ) +var generators = require( 'yeoman-generator' ) +, yosay = require( 'yosay' ) +, youtil = require( './../../lib/youtil.js' ) +, _ = require( 'lodash' ) ; +var decapitalize = require( 'underscore.string/decapitalize' ); + var ModelGenerator = generators.Base.extend( { constructor: function ( args, options ) @@ -59,12 +61,18 @@ var ModelGenerator = generators.Base.extend( var prompts = [ { name: 'modelName' - , message: 'What\'s the name of this model you so desire? ( use camelcasing! )' + , message: 'What is the name of this model you so desire?' + , validate: youtil.isIdentifier + , filter: function ( value ) + { + return decapitalize( _.trim( value ).replace( /model$/i, '' )); + } } , { name: 'description' - , message: 'What\'s the description for this model?' - , default: 'No description' + , message: 'What is the purpose (description) of this model?' + , validate: youtil.isNonBlank + , filter: youtil.sentencify } , { type: 'confirm' @@ -94,17 +102,19 @@ var ModelGenerator = generators.Base.extend( } } + , configuring: function () + { + var modelName = this.modelName; + + this.className = _.capitalize( modelName ) + 'Model'; + this.fileBase = _.kebabCase( _.deburr( modelName )); + } + , writing: { createModel: function () { - this.fileName = varname.dash( this.modelName ); - - // Class names start with a capital by convention - // - this.className = this.modelName.charAt( 0 ).toUpperCase() + this.modelName.slice( 1 ); - - this.template( 'model.coffee', 'src/models/' + this.fileName + '.coffee' ); + this.template( 'model.coffee', 'src/models/' + this.fileBase + '.coffee' ); } } } diff --git a/generators/model/templates/model.coffee b/generators/model/templates/model.coffee index b42698d3..0aaee4f1 100644 --- a/generators/model/templates/model.coffee +++ b/generators/model/templates/model.coffee @@ -23,17 +23,17 @@ ###*<% if ( description ) { %> # <%= description %> #<% } %> - # @class <%= className %>Model + # @class <%= className %> # @extends Backbone.Model<% if ( singleton ) { %> # @static<% } else { %> # @constructor<% } %> ### - class <%= className %>Model extends Backbone.Model<% if ( singleton ) { %> + class <%= className %> extends Backbone.Model<% if ( singleton ) { %> ## Export singleton ## - return new <%= className %>Model()<% } %> + return new <%= className %>()<% } %> ) diff --git a/generators/view/index.js b/generators/view/index.js index e4ccc191..6b9d2cd6 100644 --- a/generators/view/index.js +++ b/generators/view/index.js @@ -4,12 +4,14 @@ // Yeoman bat:view sub-generator. // -var generators = require( 'yeoman-generator' ) -, yosay = require( 'yosay' ) -, varname = require( 'varname' ) -, _ = require( 'lodash' ) +var generators = require( 'yeoman-generator' ) +, yosay = require( 'yosay' ) +, youtil = require( './../../lib/youtil.js' ) +, _ = require( 'lodash' ) ; +var decapitalize = require( 'underscore.string/decapitalize' ); + var ViewGenerator = generators.Base.extend( { initializing: function () @@ -32,13 +34,19 @@ var ViewGenerator = generators.Base.extend( var prompts = [ { name: 'viewName' - , message: 'What\'s the name of this view you so desire? ( use camelcasing! )' + , message: 'What is the name of this view you so desire?' + , validate: youtil.isIdentifier + , filter: function ( value ) + { + return decapitalize( _.trim( value ).replace( /view$/i, '' )); + } } , { name: 'description' - , message: 'What\'s the description for this view?' - , default: 'No description given....' - } + , message: 'What is the purpose (description) of this view?' + , validate: youtil.isNonBlank + , filter: youtil.sentencify + } , { type: 'confirm' , name: 'sassFile' @@ -57,17 +65,6 @@ var ViewGenerator = generators.Base.extend( { this.viewName = answers.viewName; this.description = answers.description; - - // Convert the filename to dashes instead of camel casing - // - this.fileName = varname.dash( this.viewName ); - - // Classnames are uppercase by convention - // - this.className = answers.viewName.charAt( 0 ).toUpperCase() + answers.viewName.slice( 1 ); - - // Whether the user wants a sass file or not - // this.sassFile = answers.sassFile; done(); @@ -77,14 +74,21 @@ var ViewGenerator = generators.Base.extend( } } + , configuring: function () + { + var viewName = this.viewName; + + this.className = _.capitalize( viewName ) + 'View'; + this.cssClassName = _.kebabCase( viewName ) + '-view'; + this.fileBase = _.kebabCase( _.deburr( viewName )); + } + , writing: { createView: function () { - // Create the views coffee file and handlebars template file - // - this.template( 'view.hbs', 'src/views/' + this.fileName + '.hbs' ); - this.template( 'view.coffee', 'src/views/' + this.fileName + '.coffee' ); + this.template( 'view.hbs', 'src/views/' + this.fileBase + '.hbs' ); + this.template( 'view.coffee', 'src/views/' + this.fileBase + '.coffee' ); // Check if a sass file should be created for this view // @@ -96,13 +100,13 @@ var ViewGenerator = generators.Base.extend( // Create the sass file with the same name as the view // - this.template( 'view.sass', 'src/sass/views/_' + this.fileName + '.sass' ); + this.template( 'view.sass', 'src/sass/views/_' + this.fileBase + '.sass' ); // Read in the _views.sass file so we can add the import statement // for the newly created sass file // var views = this.readFileAsString( 'src/sass/_views.sass' ) - , insert = '@import "views/_' + this.fileName + '"'; + , insert = '@import "views/_' + this.fileBase + '"'; // Check if there isn't already in import for this file // just in case.... diff --git a/generators/view/templates/view.coffee b/generators/view/templates/view.coffee index 5bd4e5fc..c284eb6e 100644 --- a/generators/view/templates/view.coffee +++ b/generators/view/templates/view.coffee @@ -2,12 +2,12 @@ if typeof exports is 'object' module.exports = factory( require( 'backbone' ) - require( './<%= fileName %>.hbs' ) + require( './<%= fileBase %>.hbs' ) ) else if typeof define is 'function' and define.amd define( [ 'backbone' - './<%= fileName %>.hbs' + './<%= fileBase %>.hbs' ], factory ) )(( @@ -26,12 +26,12 @@ ###*<% if ( description ) { %> # <%= description %> #<% } %> - # @class <%= className %>View + # @class <%= className %> # @extends Backbone.View # @constructor ### - class <%= className %>View extends Backbone.View + class <%= className %> extends Backbone.View ###* # Expose this view's name to the router. @@ -52,13 +52,13 @@ # # @property className # - # @default '<%= fileName %>-view' + # @default '<%= cssClassName %>' # @type String # @static # @final ### - className: '<%= fileName %>-view' + className: '<%= cssClassName %>' ###* diff --git a/generators/view/templates/view.sass b/generators/view/templates/view.sass index 1cad537f..6af5a9e3 100644 --- a/generators/view/templates/view.sass +++ b/generators/view/templates/view.sass @@ -1,2 +1,2 @@ -.<%= fileName %>-view +.<%= cssClassName %> // Yeoman was here..... diff --git a/lib/youtil.js b/lib/youtil.js new file mode 100644 index 00000000..cc0f0313 --- /dev/null +++ b/lib/youtil.js @@ -0,0 +1,32 @@ +'use strict'; + +var capitalize = require( 'underscore.string/capitalize' ) +, clean = require( 'underscore.string/clean' ) +; + +module.exports = + { + isIdentifier: function ( value ) + { + return /^[$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*$/.test( value.trim() ); + } + + , isNonBlank: function ( value ) + { + return /\S/.test( value ); + } + + // https://docs.npmjs.com/files/package.json#name + // https://github.com/npm/normalize-package-data/blob/v2.0.0/lib/fixer.js#L304 + // + , isNpmName: function ( value ) + { + return value === encodeURIComponent( value ) && value === value.toLowerCase(); + } + + , sentencify: function ( value ) + { + return capitalize( clean( value )).replace( /([^!?.,:;])$/, '$1.' ); + } + } +; diff --git a/package.json b/package.json index d205c953..39462ef4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ }, "dependencies": { "chalk": "^1.0.0", - "varname": "^1.0.2", + "underscore.string": "^3.0.3", "yeoman-generator": "^0.18.10", "yosay": "^1.0.3" },