Basic project generator setup

TODO:

- App tests
- Generator tests
- Component generator

Signed-off-by: Simon Bailey <simon@newtriks.com>
This commit is contained in:
Simon Bailey 2014-01-07 20:00:49 +00:00
commit b1562cd74c
29 changed files with 17271 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
### SublimeText ###
*.sublime-workspace
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# App specific
node_modules/
temp/

21
.jshintrc Normal file
View File

@ -0,0 +1,21 @@
{
"node": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"white": true
}

8
.travis.yml Normal file
View File

@ -0,0 +1,8 @@
language: node_js
node_js:
- '0.8'
- '0.10'
before_install:
- currentfolder=${PWD##*/}
- if [ "$currentfolder" != 'generator-react-webpack' ]; then cd .. && eval "mv $currentfolder generator-react-webpack" && cd generator-react-webpack; fi

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright 2014 Simon Bailey
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# generator-react-webpack [![Build Status](https://secure.travis-ci.org/newtriks/generator-react-webpack.png?branch=master)](https://travis-ci.org/newtriks/generator-react-webpack)
A generator for [Yeoman](http://yeoman.io).
## Getting Started
### What is Yeoman?
Trick question. It's not a thing. It's this guy:
![](http://i.imgur.com/JHaAlBJ.png)
Basically, he wears a top hat, lives in your computer, and waits for you to tell him what kind of application you wish to create.
Not every new computer comes with a Yeoman pre-installed. He lives in the [npm](https://npmjs.org) package repository. You only have to ask for him once, then he packs up and moves into your hard drive. *Make sure you clean up, he likes new and shiny things.*
```
$ npm install -g yo
```
### Yeoman Generators
Yeoman travels light. He didn't pack any generators when he moved in. You can think of a generator like a plug-in. You get to choose what type of application you wish to create, such as a Backbone application or even a Chrome extension.
To install generator-react-webpack from npm, run:
```
$ npm install -g generator-react-webpack
```
Finally, initiate the generator:
```
$ yo react-webpack
```
### Getting To Know Yeoman
Yeoman has a heart of gold. He's a person with feelings and opinions, but he's very easy to work with. If you think he's too opinionated, he can be easily convinced.
If you'd like to get to know Yeoman better and meet some of his friends, [Grunt](http://gruntjs.com) and [Bower](http://bower.io), check out the complete [Getting Started Guide](https://github.com/yeoman/yeoman/wiki/Getting-Started).
## License
[MIT License](http://en.wikipedia.org/wiki/MIT_License)

74
app/index.js Normal file
View File

@ -0,0 +1,74 @@
'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var generalUtils = require('../util.js');
var ReactWebpackGenerator = module.exports = function ReactWebpackGenerator(args, options, config) {
yeoman.generators.Base.apply(this, arguments);
this.argument('appname', { type: String, required: false });
this.appname = this.appname || path.basename(process.cwd());
this.appname = this._.camelize(this._.slugify(this._.humanize(this.appname)));
this.scriptAppName = this._.capitalize(this.appname) + generalUtils.appName(this);
args = ['main'];
if (typeof this.env.options.appPath === 'undefined') {
this.env.options.appPath = this.env.options.appPath || 'src';
}
this.appPath = this.env.options.appPath;
this.hookFor('react-webpack:common', {
args: args
});
this.hookFor('react-webpack:main', {
args: args
});
this.on('end', function () {
this.installDependencies({ skipInstall: options['skip-install'] });
});
this.pkg = JSON.parse(this.readFileAsString(path.join(__dirname, '../package.json')));
};
util.inherits(ReactWebpackGenerator, yeoman.generators.Base);
ReactWebpackGenerator.prototype.welcome = function welcome() {
// welcome message
if (!this.options['skip-welcome-message']) {
console.log(this.yeoman);
console.log(
'Out of the box I include Webpack and some default React components.\n'
);
}
};
ReactWebpackGenerator.prototype.readIndex = function readIndex() {
this.indexFile = this.engine(this.read('../../templates/common/index.html'), this);
};
ReactWebpackGenerator.prototype.createIndexHtml = function createIndexHtml() {
this.indexFile = this.indexFile.replace(/&apos;/g, "'");
this.write(path.join(this.appPath, 'index.html'), this.indexFile);
};
ReactWebpackGenerator.prototype.packageFiles = function () {
this.template('../../templates/common/_package.json', 'package.json');
this.copy('../../templates/common/Gruntfile.js', 'Gruntfile.js');
};
ReactWebpackGenerator.prototype.styleFiles = function styleFiles() {
var mainFile = 'main.css';
var resetFile = 'reset.css';
this.copy('styles/' + mainFile, 'src/styles/' + mainFile);
this.copy('styles/' + resetFile, 'src/styles/' + resetFile);
};
ReactWebpackGenerator.prototype.imageFiles = function () {
this.sourceRoot(path.join(__dirname, 'templates'));
this.directory('images', 'src/images', true);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,41 @@
/* Stiziles */
html, body {
background: #222222;
}
/* main */
.main {
width: 100%;
height: 100%;
background: #222222;
color: #fff;
}
.main img {
width: 103px;
height: 89px;
margin-bottom: 10px;
text-align: center;
}
/* transitions */
.fade-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
}
.fade-enter.fade-enter-active {
opacity: 1;
}
.fade-leave {
opacity: 1;
transition: opacity .5s ease-in;
}
.fade-leave.fade-leave-active {
opacity: 0.01;
}

View File

@ -0,0 +1,48 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

17
common/index.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
var path = require('path');
var util = require('util');
var yeoman = require('yeoman-generator');
var CommonGenerator = module.exports = function CommonGenerator(args, options, config) {
yeoman.generators.NamedBase.apply(this, arguments);
};
util.inherits(CommonGenerator, yeoman.generators.NamedBase);
CommonGenerator.prototype.setupEnv = function setupEnv() {
// Copies the contents of the generator `templates`
// directory into your users new application path
this.sourceRoot(path.join(__dirname, '../templates/common'));
this.directory('root', '.', true);
};

13
main/index.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
var util = require('util');
var ScriptBase = require('../script-base.js');
var MainGenerator = module.exports = function MainGenerator(args, options, config) {
ScriptBase.apply(this, arguments);
};
util.inherits(MainGenerator, ScriptBase);
MainGenerator.prototype.createAppFile = function createAppFile() {
this.appTemplate('app', 'scripts/components/'+this.scriptAppName);
};

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "generator-react-webpack",
"version": "0.0.0",
"description": "A generator for Yeoman",
"keywords": [
"yeoman-generator"
],
"homepage": "https://github.com/newtriks/generator-react-webpack",
"bugs": "https://github.com/newtriks/generator-react-webpack/issues",
"author": {
"name": "Simon Bailey",
"email": "simon@newtriks.com",
"url": "https://github.com/newtriks"
},
"main": "app/index.js",
"repository": {
"type": "git",
"url": "git://github.com/newtriks/generator-react-webpack.git"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"yeoman-generator": "~0.14.0"
},
"devDependencies": {
"mocha": "~1.14.0"
},
"peerDependencies": {
"yo": ">=1.0.0"
},
"engines": {
"node": ">=0.8.0",
"npm": ">=1.2.10"
},
"licenses": [
{
"type": "MIT"
}
]
}

61
script-base.js Normal file
View File

@ -0,0 +1,61 @@
'use strict';
var util = require('util');
var path = require('path');
var yeoman = require('yeoman-generator');
var generalUtils = require('./util.js');
var Generator = module.exports = function Generator() {
yeoman.generators.NamedBase.apply(this, arguments);
// Add capitalize mixin
this._.mixin({ 'capitalize': generalUtils.capitalize });
this.appname = path.basename(process.cwd());
this.appname = this._.slugify(this._.humanize(this.appname));
this.scriptAppName = this._.capitalize(this.appname) + generalUtils.appName(this);
if (typeof this.env.options.appPath === 'undefined') {
this.env.options.appPath = this.env.options.appPath || 'src';
}
if (typeof this.env.options.testPath === 'undefined') {
this.env.options.testPath = this.env.options.testPath || 'test/spec';
}
var sourceRoot = '/templates/javascript';
this.scriptSuffix = '.js';
this.sourceRoot(path.join(__dirname, sourceRoot));
};
util.inherits(Generator, yeoman.generators.NamedBase);
Generator.prototype.appTemplate = function (src, dest) {
yeoman.generators.Base.prototype.template.apply(this, [
src + this.scriptSuffix,
path.join(this.env.options.appPath, dest) + this.scriptSuffix
]);
};
Generator.prototype.testTemplate = function (src, dest) {
yeoman.generators.Base.prototype.template.apply(this, [
src + this.scriptSuffix,
path.join(this.env.options.testPath, dest) + this.scriptSuffix
]);
};
Generator.prototype.htmlTemplate = function (src, dest) {
yeoman.generators.Base.prototype.template.apply(this, [
src,
path.join(this.env.options.appPath, dest.toLowerCase())
]);
};
Generator.prototype.generateSourceAndTest = function (appTemplate, testTemplate, targetDirectory, skipAdd) {
this.appTemplate(appTemplate, path.join('scripts', targetDirectory, this.name));
this.testTemplate(testTemplate, path.join(targetDirectory, this.name));
if (!skipAdd) {
this.addScriptToIndex(path.join(targetDirectory, this.name));
}
};

View File

@ -0,0 +1,137 @@
'use strict';
var LIVERELOAD_PORT = 35729;
var lrSnippet = require('connect-livereload')({
port: LIVERELOAD_PORT
});
var mountFolder = function (connect, dir) {
return connect.static(require('path').resolve(dir));
};
module.exports = function (grunt) {
// Let *load-grunt-tasks* require everything
require('load-grunt-tasks')(grunt);
// Read configuration from package.json
var pkgConfig = grunt.file.readJSON('package.json');
var jshintConfig = grunt.file.readJSON('.jshintrc');
var loaders = [{
test: /\.css$/,
loader: 'style!css'
}, {
test: /\.gif/,
loader: 'url-loader?limit=10000&minetype=image/gif'
}, {
test: /\.jpg/,
loader: 'url-loader?limit=10000&minetype=image/jpg'
}, {
test: /\.png/,
loader: 'url-loader?limit=10000&minetype=image/png'
}, {
test: /\.js$/,
loader: 'jsx-loader'
}];
grunt.initConfig({
pkg: pkgConfig,
webpack: {
development: {
entry: './<%= pkg.src %>/scripts/components/<%= pkg.mainInput %>.js',
output: {
path: '<%= pkg.src %>/scripts/',
filename: '<%= pkg.mainOutput %>.js'
},
debug: true,
cache: true,
stats: {
colors: true,
reasons: true
},
jshint: grunt.util._.merge(jshintConfig, {
emitErrors: false,
failOnHint: false
}),
module: {
preLoaders: [{
test: '\\.js$',
exclude: 'node_modules',
loader: 'jshint'
}],
loaders: loaders
}
}
},
watch: {
webpack: {
files: ['<%= pkg.src %>/scripts/{,*/}*.js',
'<%= pkg.src %>/styles/{,*/}*.css',
'!<%= pkg.src %>/scripts/<%= pkg.mainOutput %>.js'
],
tasks: ['webpack:development']
},
livereload: {
options: {
livereload: LIVERELOAD_PORT
},
files: [
'<%= pkg.src %>/{,*/}*.html',
'<%= pkg.src %>/scripts/<%= pkg.mainOutput %>.js'
]
}
},
connect: {
options: {
port: 8000,
// Change this to '0.0.0.0' to access the server from outside.
hostname: 'localhost'
},
livereload: {
options: {
middleware: function (connect) {
return [
lrSnippet,
mountFolder(connect, pkgConfig.src)
];
}
}
},
dist: {
options: {
middleware: function (connect) {
return [
mountFolder(connect, pkgConfig.dist)
];
}
}
}
},
open: {
server: {
url: 'http://localhost:<%= connect.options.port %>'
}
},
karma: {
unit: {
configFile: 'karma.conf.js'
}
}
});
grunt.registerTask('server', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
}
grunt.task.run([
'connect:livereload',
'webpack:development',
'open',
'watch'
]);
});
grunt.registerTask('test', ['karma']);
grunt.registerTask('build', []);
grunt.registerTask('default', []);
};

View File

@ -0,0 +1,36 @@
{
"name": "<%= _.slugify(appname) %>",
"version": "0.0.0",
"src": "src",
"test": "test",
"dist": "dist",
"mainInput": "<%= scriptAppName %>",
"mainOutput": "main",
"dependencies": {
"react": "~0.8.0"
},
"devDependencies": {
"grunt": "~0.4.2",
"load-grunt-tasks": "~0.2.1",
"grunt-contrib-watch": "~0.5.3",
"grunt-contrib-connect": "~0.6.0",
"connect-livereload": "~0.3.1",
"webpack": "~1.0.0-beta4",
"jsx-loader": "petehunt/jsx-loader",
"grunt-webpack": "~1.0.0",
"style-loader": "~0.6.0",
"url-loader": "~0.5.2",
"css-loader": "~0.6.6",
"karma-script-launcher": "~0.1.0",
"karma-chrome-launcher": "~0.1.2",
"karma-firefox-launcher": "~0.1.3",
"karma-jasmine": "~0.1.5",
"karma-phantomjs-launcher": "~0.1.1",
"karma": "~0.10.9",
"grunt-karma": "~0.6.2",
"karma-webpack-plugin": "~1.0.0",
"webpack-dev-server": "~1.0.2",
"grunt-open": "~0.2.2",
"jshint-loader": "~0.6.0"
}
}

View File

@ -0,0 +1,32 @@
### SublimeText ###
*.sublime-workspace
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# App specific
node_modules/
.tmp
/src/scripts/main.js

View File

@ -0,0 +1,19 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>
<!--[if lt IE 8]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div id="content">
<h1>If you can see this, something is broken (or JS is not enabled).</h1>
</div>
<script type="text/javascript" src="scripts/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,13 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -0,0 +1,27 @@
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": false,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": false,
"strict": true,
"trailing": true,
"smarttabs": true,
"white": true,
"newcap": false,
"globals": {
"React": true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,40 @@
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": false,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": false,
"strict": true,
"trailing": true,
"smarttabs": true,
"white": true,
"newcap": false,
"globals": {
"after": false,
"afterEach": false,
"react": false,
"before": false,
"beforeEach": false,
"browser": false,
"describe": false,
"expect": false,
"inject": false,
"it": false,
"spyOn": false,
"jasmine": false,
"spyOnConstructor": false,
"React": true
}
}

View File

@ -0,0 +1,34 @@
(function() {
var Ap = Array.prototype;
var slice = Ap.slice;
var Fp = Function.prototype;
if (!Fp.bind) {
// PhantomJS doesn't support Function.prototype.bind natively, so
// polyfill it whenever this module is required.
Fp.bind = function(context) {
var func = this;
var args = slice.call(arguments, 1);
function bound() {
var invokedAsConstructor = func.prototype && (this instanceof func);
return func.apply(
// Ignore the context parameter when invoking the bound function
// as a constructor. Note that this includes not only constructor
// invocations using the new keyword but also calls to base class
// constructors such as BaseClass.call(this, ...) or super(...).
!invokedAsConstructor && context || this,
args.concat(slice.call(arguments))
);
}
// The bound function must share the .prototype of the unbound
// function so that any object created by one constructor will count
// as an instance of both constructors.
bound.prototype = func.prototype;
return bound;
};
}
})();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
/**
* @jsx React.DOM
*/
'use strict';
var React = require('react/addons');
var ReactTransitionGroup = React.addons.TransitionGroup;
// CSS
require('../../styles/reset.css');
require('../../styles/main.css');
var imageURL = '../../images/yeoman.png';
var <%= scriptAppName %> = React.createClass({
/*jshint ignore:start */
render: function() {
return (
<div className='main'>
<ReactTransitionGroup transitionName="fade">
<img src={imageURL} />
</ReactTransitionGroup>
</div>
);
}
/*jshint ignore:end */
});
React.renderComponent(<<%= scriptAppName %> />, document.getElementById('content')); // jshint ignore:line
module.exports = <%= scriptAppName %>;

38
test/test-creation.js Normal file
View File

@ -0,0 +1,38 @@
/*global describe, beforeEach, it*/
'use strict';
var path = require('path');
var helpers = require('yeoman-generator').test;
describe('react-webpack generator', function () {
beforeEach(function (done) {
helpers.testDirectory(path.join(__dirname, 'temp'), function (err) {
if (err) {
return done(err);
}
this.app = helpers.createGenerator('react-webpack:app', [
'../../app'
]);
done();
}.bind(this));
});
it('creates expected files', function (done) {
var expected = [
// add files you expect to exist here.
'.jshintrc',
'.editorconfig'
];
helpers.mockPrompt(this.app, {
'someOption': true
});
this.app.options['skip-install'] = true;
this.app.run({}, function () {
helpers.assertFiles(expected);
done();
});
});
});

11
test/test-load.js Normal file
View File

@ -0,0 +1,11 @@
/*global describe, beforeEach, it*/
'use strict';
var assert = require('assert');
describe('react-webpack generator', function () {
it('can be imported without blowing up', function () {
var app = require('../app');
assert(app !== undefined);
});
});

80
util.js Normal file
View File

@ -0,0 +1,80 @@
'use strict';
var path = require('path');
var fs = require('fs');
module.exports = {
rewrite: rewrite,
rewriteFile: rewriteFile,
appName: appName,
capitalize: capitalize
};
function rewriteFile (args) {
args.path = args.path || process.cwd();
var fullPath = path.join(args.path, args.file);
args.haystack = fs.readFileSync(fullPath, 'utf8');
var body = rewrite(args);
fs.writeFileSync(fullPath, body);
}
function escapeRegExp (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
function rewrite (args) {
// check if splicable is already in the body text
var re = new RegExp(args.splicable.map(function (line) {
return '\s*' + escapeRegExp(line);
}).join('\n'));
if (re.test(args.haystack)) {
return args.haystack;
}
var lines = args.haystack.split('\n');
var otherwiseLineIndex = 0;
lines.forEach(function (line, i) {
if (line.indexOf(args.needle) !== -1) {
otherwiseLineIndex = i;
}
});
var spaces = 0;
while (lines[otherwiseLineIndex].charAt(spaces) === ' ') {
spaces += 1;
}
var spaceStr = '';
while ((spaces -= 1) >= 0) {
spaceStr += ' ';
}
lines.splice(otherwiseLineIndex, 0, args.splicable.map(function (line) {
return spaceStr + line;
}).join('\n'));
return lines.join('\n');
}
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
function appName(self) {
var counter = 0, suffix = self.options['app-suffix'];
// Have to check this because of generator bug #386
process.argv.forEach(function (val) {
if (val.indexOf('--app-suffix') > -1) {
counter++;
}
});
if (counter === 0 || (typeof suffix === 'boolean' && suffix)) {
suffix = 'App';
}
return suffix ? self._.classify(suffix) : '';
}