Repository: leemalmac/frodo Branch: master Commit: 283c582d86df Files: 24 Total size: 36.2 KB Directory structure: gitextract_bs2hj3vx/ ├── .gitignore ├── README.md ├── bin/ │ └── frodo ├── lib/ │ ├── config.js │ ├── files.js │ ├── index.js │ ├── pluralize.js │ ├── routes.js │ └── templates/ │ ├── .gitignore │ ├── dynamic/ │ │ ├── controller │ │ └── model │ ├── index.js │ └── static/ │ ├── app/ │ │ ├── assets/ │ │ │ └── stylesheets/ │ │ │ └── application.css │ │ ├── controllers/ │ │ │ └── welcome_controller.js │ │ └── views/ │ │ ├── layouts/ │ │ │ └── application.pug │ │ └── welcome/ │ │ └── index.pug │ ├── config/ │ │ ├── application.js │ │ ├── database.js │ │ ├── globals.js │ │ └── routes.js │ ├── db/ │ │ ├── connection.js │ │ └── util.js │ └── index.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules/ ================================================ FILE: README.md ================================================ # Frodo Frodo is a Rails-like app generator for Node and Express. It was created to simplify working with projects based on the Express framework. Nowadays, many developers are familiar with the Ruby on Rails framework, which helps you generate a structure for your app. An app generated with Frodo will help you in the same way. **Motivation:** * I use Rails a lot and I love how Rails apps are organized * It can be difficult to start with a micro-framework like Express without a predefined structure. Frodo will help developers using Node/Express to create a new app as fast as possible. ## Documentation: #### Installation ```shell $ npm install -g frodojs ``` Done. Now you can easily invoke the Frodo command line tool. #### Usage: Frodo has a command line interface like Rails, but, for now, Frodo has support for a very limited set of actions. You are able to: ``` - create a new application - generate controller/model or scaffold ``` To create a new application use 'new' followed by application name: ```shell $ frodo new blog ``` This command will create a new project with a predefined folders structure. If you want to build an API, and you don't need views or static files, use the `--skipViews` optional argument. ```shell $ frodo new api --skipViews ``` **Generators** Like in Rails, Frodo can create a controller, a model, or a combination of both with a scaffold. Generating a controller ```shell $ frodo generate controller user [methods] ``` This command creates an empty controller in the app/controllers folder, which should look like this: ```javascript // users_controller.js var UsersController = (function () { return { } }()); module.exports = UsersController; ``` and corresponding static files users.css and users.js in app/assests/stylesheets and app/assets/javascripts respectively. You can also write a list of methods separated by whitespaces. ```shell $ frodo generate controller user new show ``` this produces: ```javascript // users_controller.js var UsersController = (function () { return { new: function (req, res) {}, show: function (req, res) {} } }()); ``` and creates corresponding the views. Generating a model: ```shell $ frodo generate model user name age:number ``` produces: ```javascript var mongoose = require('mongoose'), ObjectId = mongoose.Schema.Types.ObjectId, Mixed = Schema.Types.Mixed; var UserSchema = mongoose.Schema({ name: {type: String, required: true}, age: {type: Number, required: true}, created_at: {type: Number, required: true, default: new Date().getTime()}, updated_at: {type: Number, required: true, default: new Date().getTime()} }); var User = mongoose.model('User', UserSchema); module.exports = User; ``` Each property in a model generator may consist of 3 parts separated by a semicolon - 'name:type:required' ```shell $ frodo generate model user email:string:true ``` where property name is email, type string, and it is required field. Type and required are not mandatory parts and can be ommited. Available types: * String * Number * Date * Buffer * Boolean * Mixed * Objectid * Array At the moment, Frodo supports only mongoose for defining models. ================================================ FILE: bin/frodo ================================================ #! /usr/bin/env node var frodo = require('../lib/index.js'); ================================================ FILE: lib/config.js ================================================ module.exports = { appname: null, set: function (props) { for (var key in props) { this[key] = props[key]; } } }; ================================================ FILE: lib/files.js ================================================ /** * [exports description] * @type {Object} */ var fs = require('fs'), os = {path: require('path')}, config = require('./config'); /** * * @param {String} path [description] * @param {Object} locals * * @return {Boolean} */ function useTemplate(path, locals) { var fileName = path.split(config.appname).pop().replace('/', ''), template = os.path.join(__dirname, 'templates/static', fileName); if (fs.existsSync(template)) { fs.createReadStream(template).pipe(fs.createWriteStream(path)); return true; } }; module.exports = { createFile: function (path, content) { console.log('Creating file', path); if (useTemplate(path)) { return; } if (content) { fs.writeFileSync(path, content); } else { file = fs.openSync(path, 'w'); fs.closeSync(file); } }, /** * * @param {[type]} files [description] * @param {[type]} path [description] */ createFiles: function (files, path) { var file = null, self = this; for (var i = 0; i < files.length; i++) { file = files[i]; self.createFile(os.path.join(path, file)); } } }; ================================================ FILE: lib/index.js ================================================ /** * Frodo.js is a app generator for Express-based aplications. * Easily set structure in a json file and pass path for the file as command line argument */ String.prototype.capitalize = function() { return this.charAt(0).toUpperCase() + this.slice(1); } var fs = require('fs'), exec = require('child_process').execSync, /* path is actively used, so I made Python like name */ os = {path: require('path')}, pluralize = require('./pluralize'), generateRoutes = require('./routes'), config = require('./config'), files = require('./files'), templates = require('./templates'); var $WORKING_DIR = process.cwd(), $SKIP_VIEWS = false, $SKIP_ASSETS = false, $SCAFFOLD = false, $APP = os.path.join($WORKING_DIR, 'app'), $ASSETS = os.path.join($APP, 'assets'), $CONTROLLERS = os.path.join($APP, 'controllers'), $MODELS = os.path.join($APP, 'models'); $VIEWS = os.path.join($APP, 'views'), $VERSION = '0.6.0', $PREPROCESSORS = { views: 'pug', stylesheets: 'css', javascripts: 'js' }, $SCHEMA_TYPES = ['String', 'Number', 'Date', 'Buffer', 'Boolean', 'Mixed', 'ObjectId', 'Array']; var skipViewsItems = ['assets', 'views', 'vendor', 'public']; /** * Project structure */ var skeleton = { files: ['index.js', '.gitignore'], app: { assets: { images: {files: ['.keep']}, javascripts: {files: ['application.js']}, stylesheets: {files: ['application.css']} }, controllers: {files: ['welcome_controller.js']}, models: {files: ['.keep']}, views: { layouts: {files: ['application.pug']}, welcome: {files: ['index.pug']} }, helpers: {files: ['.keep']} }, bin: {files: ['.keep']}, config: { environments: {files: ['development.js', 'test.js', 'production.js']}, files: ['application.js', 'environment.js', 'database.js', 'routes.js', 'globals.js'] }, db: {files: ['.keep', 'connection.js', 'util.js']}, lib: {files: ['.keep']}, log: {files: ['.keep']}, middleware: { files: ['.keep'] }, public: { files: ['404.pug', '500.pug', 'favicon.ico', 'robots.txt'] }, test: { controllers: {files: ['.keep']}, models: {files: ['.keep']}, helpers: {files: ['.keep']} }, tmp: {files: ['.keep']}, vendor: { javascripts: {files: ['.keep']}, stylesheets: {files: ['.keep']} } }; /** * Checks if argument is object * * @method isObject * @param {Any} obj value to be checked * @return Boolean */ function isObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]'; } /** * Generates project folders structure */ function generateSkeleton(skeleton, path) { for (key in skeleton) { if (skeleton.hasOwnProperty(key)) { if ($SKIP_VIEWS && skipViewsItems.indexOf(key) >= 0) { continue; } if (isObject(skeleton[key])) { console.log('Creating folder', os.path.join(path, key)); fs.mkdirSync(os.path.join(path, key)); generateSkeleton(skeleton[key], os.path.join(path, key)); } else if (key === 'files') { files.createFiles(skeleton.files, path); } } } } function generatePackageJson(projectPath, appName) { var packageJson = '{\n'+ ' "name": "'+appName+'",\n'+ ' "version": "0.0.1",\n'+ ' "description": "",\n'+ ' "main": "index.js",\n'+ ' "license": "ISC"\n'+ '}'; files.createFile(os.path.join(projectPath, 'package.json'), packageJson) } function npmInit(projectPath) { console.log('Installing dependencies'); process.chdir(projectPath); console.log('Running: npm install express --save'); exec('npm install express --save'); console.log('Running: npm install body-parser --save'); exec('npm install body-parser --save'); console.log('Running: npm install mongoose --save'); exec('npm install mongoose --save'); console.log('Running: npm install async --save'); exec('npm install async --save'); if (!$SKIP_VIEWS) { console.log('Running: npm install pug --save'); exec('npm install pug --save'); } } /** * */ function getControllerContents (name, methods) { return templates.render('dynamic/controller', {methods: methods}); } /** * Generates a new controller. If $SKIP_VIEWS is false, generateViews will be executed. * Views folder name is first argument of the generateController function, views names the same as * methods. * * ! For now routes would not be generated automatically. This feature is on the road map * * @method generateController * @param {String} name controller name * @param {Array} methods a list of methods to be added to the controller */ function generateController(name, methods) { var pluralName = pluralize(name), fullName = pluralName+'_controller.js', contents = getControllerContents(pluralName, methods); files.createFile(os.path.join($CONTROLLERS, fullName), contents); if (!$SKIP_VIEWS && methods) { generateViews(pluralName, methods); } if (!$SKIP_ASSETS) { generateAssets(pluralName); } generateRoutes(pluralName, fullName, methods, $SCAFFOLD); } /** * Generates javascript and stylesheet files. * * @method generateAssets * * @param {String} name name of file as is. */ function generateAssets(name) { var js = os.path.join($ASSETS, 'javascripts', name+'.'+$PREPROCESSORS.javascripts), css = os.path.join($ASSETS, 'stylesheets', name+'.'+$PREPROCESSORS.stylesheets); files.createFile(js); files.createFile(css); } function getDefaultViewContent (viewPath) { return "extends ../layouts/application.pug\n\n" + "block content\n"+ " p You can find this view template at "+viewPath; } /** * Generates new views * * @method generateViews * * @param {String} dirName a directory for new views * @param {Array} methods a list of views to be generated */ function generateViews(dirName, views) { var viewsPath = os.path.join($VIEWS, dirName), view = null; /* If views directory does not exist - skip this step */ if (!fs.existsSync($VIEWS)) { return; } if (!fs.existsSync(os.path.join($VIEWS, dirName))) { fs.mkdirSync(os.path.join($VIEWS, dirName)); } for (var i = 0; i < views.length; i++) { /* Don't create views in the array if scaffold has been called */ if ($SCAFFOLD && ['create', 'update', 'delete'].indexOf(views[i]) >= 0 ) { continue; } view = views[i]+'.'+$PREPROCESSORS.views; var content = getDefaultViewContent(os.path.join(viewsPath, view)); files.createFile(os.path.join(viewsPath, view), content); } } /** * Generates mongoose compatible schema object. * * @method createMongooseSchema * * @param {Array} props - an array of model properties. Each item is a string 'name:type:required' * where only 'name' is required part. If type is omitted, then the default * value (String) will be used. */ function createMongooseSchema(options) { var modelName = options.name, nameCap = modelName.capitalize(), schemaName = nameCap+"Schema", propsRaw = options.props, props = [], splitted = null; propsRaw.forEach(function (val, i) { splitted = val.split(':'); var name = splitted[0], type = splitted[1], required = splitted[2]; if (splitted.length === 2 && type === 'true') { type = null; required = 'true'; } if (!type) { type = 'String'; } if ($SCHEMA_TYPES.indexOf(type.capitalize()) < 0) { console.error('Unknown type', type, 'for', name); process.exit(1); } props.push({name: name, type: type.capitalize(), required: required}); }); return templates.render('dynamic/model', {name: modelName, nameCap: nameCap, schemaName: schemaName, props: props}); } /** * Generates a new model * * @method generateModel * * @param {String} name model name * @prop {Array} props an array of properties. A property of collon seperated strings. * propName:propType:required:defaultValue. Example: login:string:required, * created_at:date:required:now * */ function generateModel(name, props) { var schema = createMongooseSchema({name: name, props: props}); /* If models directory does not exist - skip this step */ /* TODO: add logging here */ if (!fs.existsSync($MODELS)) { return; } files.createFile(os.path.join($MODELS, name+'.js'), schema); } /** * ------ THIS FEATURE NOT AVAILABLE -------- * Use your favorite preprocessors with frodo generators. In order to configure preprocessors * add module.exports.preprocessors into config/application. If preprocessors are not set frodo * will use default values: jade for views, css for stylesheets, js for scripts. If any value is * missed, frodo will use default for this as well. * * /!\ These settigns do not set your assets pipeline. Such a feature is on the roadmap. Don't forget * to set preprocessing of your assets. * * Example ('config/application.js'): * module.exports.preprocessors = { * views: 'jade', * stylesheets: 'scss', * javascripts: 'coffee' * } */ function setPreprocessors() { var preprocessors = null; try { preprocessors = require(os.path.join($WORKING_DIR, 'config/application')).preprocessors; } catch (e) { // console.log("Can't find config/application.js"); } if (!preprocessors) { return; } else { if (preprocessors.views) { $PREPROCESSORS.views = preprocessors.views; } if (preprocessors.stylesheets) { $PREPROCESSORS.stylesheets = preprocessors.stylesheets; } if (preprocessors.javascripts) { $PREPROCESSORS.javascripts = preprocessors.javascripts; } } } /** * Parses command line arguments and if they are correct, call required action. * * @method main */ function main () { var argv = process.argv; if (!fs.existsSync($ASSETS)) { $SKIP_ASSETS = true; } setPreprocessors(); if (argv.length === 2) { console.log('too few arguments'); return; } else if (argv[2] === 'new') { if (argv.length === 3) { console.log('Usage: frodo new project_name. Example: frodo new blog'); } else { config.set({appname: argv[3]}); var projectPath = os.path.join($WORKING_DIR, argv[3]); fs.mkdirSync(projectPath); if (argv.indexOf('--skipViews') >= 0) { $SKIP_VIEWS = true; } generateSkeleton(skeleton, projectPath); generatePackageJson(projectPath, argv[3]); npmInit(projectPath); } } else if (argv[2] === 'generate') { config.set({appname: process.cwd().split('/').pop()}); if (argv.length < 5) { console.log('Usage: frodo generate controller controller_name.'+ 'Example: frodo generate controller users'); return; } if (argv[3] === 'controller') { generateController(argv[4], argv.slice(5)); } else if (argv[3] === 'model') { generateModel(argv[4], argv.slice(5)); } else if (argv[3] === 'scaffold') { $SCAFFOLD = true; generateController(argv[4], ['index', 'show', 'new', 'edit', 'create', 'update', 'delete']); generateModel(argv[4], argv.slice(5)); } } else if (argv[2] === 'server') { try { exec('node index.js', {stdio: 'inherit'}); } catch (e) { /* TODO: add logging */ return; } } else { console.log('=> Unknown command "' + argv[2] + '"'); } } main(); ================================================ FILE: lib/pluralize.js ================================================ /** * Return a pluralized or singularized word based on the input string. * Author: Blake Embrey * Repository: https://github.com/blakeembrey/pluralize */ /* global define */ (function (root, pluralize) { /* istanbul ignore else */ if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { // Node. module.exports = pluralize() } else if (typeof define === 'function' && define.amd) { // AMD, registers as an anonymous module. define(function () { return pluralize() }) } else { // Browser global. root.pluralize = pluralize() } })(this, function () { // Rule storage - pluralize and singularize need to be run sequentially, // while other rules can be optimized using an object for instant lookups. var pluralRules = [] var singularRules = [] var uncountables = {} var irregularPlurals = {} var irregularSingles = {} /** * Title case a string. * * @param {string} str * @return {string} */ function toTitleCase (str) { return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase() } /** * Sanitize a pluralization rule to a usable regular expression. * * @param {(RegExp|string)} rule * @return {RegExp} */ function sanitizeRule (rule) { if (typeof rule === 'string') { return new RegExp('^' + rule + '$', 'i') } return rule } /** * Pass in a word token to produce a function that can replicate the case on * another word. * * @param {string} word * @param {string} token * @return {Function} */ function restoreCase (word, token) { // Upper cased words. E.g. "HELLO". if (word === word.toUpperCase()) { return token.toUpperCase() } // Title cased words. E.g. "Title". if (word[0] === word[0].toUpperCase()) { return toTitleCase(token) } // Lower cased words. E.g. "test". return token.toLowerCase() } /** * Interpolate a regexp string. * * @param {string} str * @param {Array} args * @return {string} */ function interpolate (str, args) { return str.replace(/\$(\d{1,2})/g, function (match, index) { return args[index] || '' }) } /** * Sanitize a word by passing in the word and sanitization rules. * * @param {String} word * @param {Array} collection * @return {String} */ function sanitizeWord (word, collection) { // Empty string or doesn't need fixing. if (!word.length || uncountables.hasOwnProperty(word)) { return word } var len = collection.length // Iterate over the sanitization rules and use the first one to match. while (len--) { var rule = collection[len] // If the rule passes, return the replacement. if (rule[0].test(word)) { return word.replace(rule[0], function (match, index, word) { var result = interpolate(rule[1], arguments) if (match === '') { return restoreCase(word[index - 1], result) } return restoreCase(match, result) }) } } return word } /** * Replace a word with the updated word. * * @param {Object} replaceMap * @param {Object} keepMap * @param {Array} rules * @return {Function} */ function replaceWord (replaceMap, keepMap, rules) { return function (word) { // Get the correct token and case restoration functions. var token = word.toLowerCase() // Check against the keep object map. if (keepMap.hasOwnProperty(token)) { return restoreCase(word, token) } // Check against the replacement map for a direct word replacement. if (replaceMap.hasOwnProperty(token)) { return restoreCase(word, replaceMap[token]) } // Run all the rules against the word. return sanitizeWord(word, rules) } } /** * Pluralize or singularize a word based on the passed in count. * * @param {String} word * @param {Number} count * @param {Boolean} inclusive * @return {String} */ function pluralize (word, count, inclusive) { var pluralized = count === 1 ? pluralize.singular(word) : pluralize.plural(word) return (inclusive ? count + ' ' : '') + pluralized } /** * Pluralize a word. * * @type {Function} */ pluralize.plural = replaceWord( irregularSingles, irregularPlurals, pluralRules ) /** * Singularize a word. * * @type {Function} */ pluralize.singular = replaceWord( irregularPlurals, irregularSingles, singularRules ) /** * Add a pluralization rule to the collection. * * @param {(string|RegExp)} rule * @param {string} replacement */ pluralize.addPluralRule = function (rule, replacement) { pluralRules.push([sanitizeRule(rule), replacement]) } /** * Add a singularization rule to the collection. * * @param {(string|RegExp)} rule * @param {string} replacement */ pluralize.addSingularRule = function (rule, replacement) { singularRules.push([sanitizeRule(rule), replacement]) } /** * Add an uncountable word rule. * * @param {(string|RegExp)} word */ pluralize.addUncountableRule = function (word) { if (typeof word === 'string') { uncountables[word.toLowerCase()] = true return } // Set singular and plural references for the word. pluralize.addPluralRule(word, '$0') pluralize.addSingularRule(word, '$0') } /** * Add an irregular word definition. * * @param {String} single * @param {String} plural */ pluralize.addIrregularRule = function (single, plural) { plural = plural.toLowerCase() single = single.toLowerCase() irregularSingles[single] = plural irregularPlurals[plural] = single } /** * Irregular rules. */ ;[ // Pronouns. ['I', 'we'], ['me', 'us'], ['he', 'they'], ['she', 'they'], ['them', 'them'], ['myself', 'ourselves'], ['yourself', 'yourselves'], ['itself', 'themselves'], ['herself', 'themselves'], ['himself', 'themselves'], ['themself', 'themselves'], ['this', 'these'], ['that', 'those'], // Words ending in with a consonant and `o`. ['echo', 'echoes'], ['dingo', 'dingoes'], ['volcano', 'volcanoes'], ['tornado', 'tornadoes'], ['torpedo', 'torpedoes'], // Ends with `us`. ['genus', 'genera'], ['viscus', 'viscera'], // Ends with `ma`. ['stigma', 'stigmata'], ['stoma', 'stomata'], ['dogma', 'dogmata'], ['lemma', 'lemmata'], ['schema', 'schemata'], ['anathema', 'anathemata'], // Other irregular rules. ['ox', 'oxen'], ['axe', 'axes'], ['die', 'dice'], ['yes', 'yeses'], ['foot', 'feet'], ['eave', 'eaves'], ['goose', 'geese'], ['tooth', 'teeth'], ['quiz', 'quizzes'], ['human', 'humans'], ['proof', 'proofs'], ['carve', 'carves'], ['valve', 'valves'], ['thief', 'thieves'], ['genie', 'genies'], ['groove', 'grooves'], ['pickaxe', 'pickaxes'], ['whiskey', 'whiskies'] ].forEach(function (rule) { return pluralize.addIrregularRule(rule[0], rule[1]) }) /** * Pluralization rules. */ ;[ [/s?$/i, 's'], [/([^aeiou]ese)$/i, '$1'], [/(ax|test)is$/i, '$1es'], [/(alias|[^aou]us|tlas|gas|ris)$/i, '$1es'], [/(e[mn]u)s?$/i, '$1s'], [/([^l]ias|[aeiou]las|[emjzr]as|[iu]am)$/i, '$1'], [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'], [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'], [/(seraph|cherub)(?:im)?$/i, '$1im'], [/(her|at|gr)o$/i, '$1oes'], [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'], [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'], [/sis$/i, 'ses'], [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'], [/([^aeiouy]|qu)y$/i, '$1ies'], [/([^ch][ieo][ln])ey$/i, '$1ies'], [/(x|ch|ss|sh|zz)$/i, '$1es'], [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'], [/(m|l)(?:ice|ouse)$/i, '$1ice'], [/(pe)(?:rson|ople)$/i, '$1ople'], [/(child)(?:ren)?$/i, '$1ren'], [/eaux$/i, '$0'], [/m[ae]n$/i, 'men'], ['thou', 'you'] ].forEach(function (rule) { return pluralize.addPluralRule(rule[0], rule[1]) }) /** * Singularization rules. */ ;[ [/s$/i, ''], [/(ss)$/i, '$1'], [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(?:sis|ses)$/i, '$1sis'], [/(^analy)(?:sis|ses)$/i, '$1sis'], [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'], [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'], [/([^aeiouy]|qu)ies$/i, '$1y'], [/(^[pl]|zomb|^(?:neck)?t|[aeo][lt]|cut)ies$/i, '$1ie'], [/([^c][eor]n|smil)ies$/i, '$1ey'], [/(m|l)ice$/i, '$1ouse'], [/(seraph|cherub)im$/i, '$1'], [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|tlas|gas|(?:her|at|gr)o|ris)(?:es)?$/i, '$1'], [/(e[mn]u)s?$/i, '$1'], [/(movie|twelve)s$/i, '$1'], [/(cris|test|diagnos)(?:is|es)$/i, '$1is'], [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'], [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'], [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'], [/(alumn|alg|vertebr)ae$/i, '$1a'], [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'], [/(matr|append)ices$/i, '$1ix'], [/(pe)(rson|ople)$/i, '$1rson'], [/(child)ren$/i, '$1'], [/(eau)x?$/i, '$1'], [/men$/i, 'man'] ].forEach(function (rule) { return pluralize.addSingularRule(rule[0], rule[1]) }) /** * Uncountable rules. */ ;[ // Singular words with no plurals. 'advice', 'agenda', 'bison', 'bream', 'buffalo', 'carp', 'chassis', 'cod', 'cooperation', 'corps', 'digestion', 'debris', 'diabetes', 'energy', 'equipment', 'elk', 'excretion', 'expertise', 'flounder', 'gallows', 'garbage', 'graffiti', 'headquarters', 'health', 'herpes', 'highjinks', 'homework', 'information', 'jeans', 'justice', 'kudos', 'labour', 'machinery', 'mackerel', 'media', 'mews', 'moose', 'news', 'pike', 'plankton', 'pliers', 'pollution', 'premises', 'rain', 'rice', 'salmon', 'scissors', 'series', 'sewage', 'shambles', 'shrimp', 'species', 'staff', 'swine', 'trout', 'tuna', 'whiting', 'wildebeest', 'wildlife', 'you', 'hello', 'auth', // Regexes. /pox$/i, // "chickpox", "smallpox" /ois$/i, /deer$/i, // "deer", "reindeer" /fish$/i, // "fish", "blowfish", "angelfish" /sheep$/i, /measles$/i, /[^aeiou]ese$/i // "chinese", "japanese" ].forEach(pluralize.addUncountableRule) return pluralize }) ================================================ FILE: lib/routes.js ================================================ /** * config/routes.js manipulations */ var fs = require('fs'), path = require('path'); var $ROUTES_FILE = path.join(process.cwd(), 'config/routes.js'); function findPlaceForRequire (lines, linesNum) { var lastRequireLineNum = 0, line = null; for (var i = 0; i < linesNum; i++) { line = lines[i]; if (line.indexOf('controller') >= 0) { lastRequireLineNum = i; } } return lastRequireLineNum; } function addNewRequire (lines, lastRequireLineNum, name, fullName) { var newLine = " "+name+" = require('../app/controllers/"+ fullName +"');" lines[lastRequireLineNum] = lines[lastRequireLineNum].replace(";", ","); lines.splice(lastRequireLineNum+1, 0, newLine); } function addRoutes (lines, controller, methods) { var method = null; for (var i = 0; i < methods.length; i++) { method = methods[i]; lines.push("app.get('/"+controller+"/"+method+"', "+controller+"."+method+");"); } } function addScaffoldRoutes (lines, controller) { lines.push("app.get('/"+controller+"', "+controller+".index);"); lines.push("app.get('/"+controller+"/new', "+controller+".new);"); lines.push("app.post('/"+controller+"/create', "+controller+".create);"); lines.push("app.get('/"+controller+"/:id/edit', "+controller+".edit);"); lines.push("app.get('/"+controller+"/:id', "+controller+".show);"); lines.push("app.put('/"+controller+"/:id', "+controller+".update);"); lines.push("app.delete('/"+controller+"/:id', "+controller+".delete);"); } module.exports = function (name, fullName, methods, scaffold) { var lines = fs.readFileSync($ROUTES_FILE, {encoding: 'utf-8'}).split('\n'), linesNum = lines.length; var lastRequireLineNum = findPlaceForRequire(lines, linesNum); /* Prevents addition of routes in any routes for a controller exists */ for (var i = 0; i < lines.length; i++) { if (lines[i].indexOf(fullName) >= 0) { return; } } addNewRequire(lines, lastRequireLineNum, name, fullName); if (scaffold) { addScaffoldRoutes(lines, name); } else if (methods) { addRoutes(lines, name, methods); } fs.writeFileSync($ROUTES_FILE, lines.join('\n'), {encoding: 'utf-8'}); } ================================================ FILE: lib/templates/.gitignore ================================================ # Ignore all logfiles and tempfiles. node_modules/ /log/* !/log/.keep /tmp !/tmp/.keep ================================================ FILE: lib/templates/dynamic/controller ================================================ module.exports = { {{#methods}} {{.}}: (req, res) => { // your code goes here }, {{/methods}} }; ================================================ FILE: lib/templates/dynamic/model ================================================ var mongoose = require('mongoose'), ObjectId = mongoose.Schema.Types.ObjectId, Mixed = mongoose.Schema.Types.Mixed; var {{schemaName}} = mongoose.Schema({ {{#props}} {{name}}: {type: {{type}}}, {{/props}} created_at: {type: Number, required: true, default: new Date().getTime()}, updated_at: {type: Number, required: true, default: new Date().getTime()} }); var {{nameCap}} = mongoose.model('{{nameCap}}', {{schemaName}}); module.exports = {{nameCap}}; ================================================ FILE: lib/templates/index.js ================================================ var fs = require('fs'), path = require('path'), Mustache = require('mustache'); /** * * @param {Object} options [description] * @return {String} [description] */ function renderController (options) { var _path = path.join(__dirname, 'dynamic/controller'), text = fs.readFileSync(_path, {encoding: 'utf8'}); return Mustache.render(text, {methods: options.methods}); } /** * * @param {Object} options [description] * @return {String} [description] */ function renderModel (options) { var _path = path.join(__dirname, 'dynamic/model'), text = fs.readFileSync(_path, {encoding: 'utf8'}); return Mustache.render(text, options); } module.exports = { /** * [render description] * @param {String} template [description] * @param {Object} options [description] * @return {String} [description] */ render(template, options) { switch (template) { case 'dynamic/controller': return renderController(options); break; case 'dynamic/model': return renderModel(options); break; default: console.log('Unknown template ' + template); return; } }, }; ================================================ FILE: lib/templates/static/app/assets/stylesheets/application.css ================================================ div#content {font-size: 16px;} section {margin-bottom: 20px;} section:last-child {margin-bottom: 0;} ================================================ FILE: lib/templates/static/app/controllers/welcome_controller.js ================================================ module.exports = { index: function (req, res) { res.render('welcome/index'); } }; ================================================ FILE: lib/templates/static/app/views/layouts/application.pug ================================================ doctype html html(lang="en") head title Welcome! link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css" rel="stylesheet") link(href="/assets/stylesheets/application.css" type="text/css" rel="stylesheet") script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js") script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js") script(src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js") script(src="/assets/javascripts/application.js") body div#header div#content block content div#footer ================================================ FILE: lib/templates/static/app/views/welcome/index.pug ================================================ extends ../layouts/application.pug block content div.fluid_container div.row div.col-md-8.col-md-offset-2 h1.text-center Welcome to the NodeJS world! section#intro | This page has been automaticaly created by a(href="https://www.npmjs.com/package/frodojs") frodojs |. Frodo is a Rails-like app generator for a(href="https://nodejs.org") Node | and a(href="http://expressjs.com/") Express | . It was created to simplify working with projects based on the Express framework. section#docs | First thing first - frodojs is not a framework, it is a tool to help you organize your | Node apps similar to Rails (if you want it:)). Frodo uses Express as the framework, | moreover it is pure Express withou any modifications, so you will be able to upgrage | instantly. See available documentation on the a(href="https://github.com/leemalmac/frodo") Github Page ================================================ FILE: lib/templates/static/config/application.js ================================================ var path = require('path'), express = require('express'), app = express(); global.ENV = process.env.NODE_ENV || 'development'; console.log('=> NODE_ENV:', ENV); app.set('view engine', 'pug'); app.set('views', path.join(ROOT, 'app/views')); app.use('/assets', express.static(path.join(ROOT, 'app/assets'))); /** * Add middleware here * * Example: * app.use(bodyParser.json()); * app.use('/public', express.static('public')); */ module.exports = app; ================================================ FILE: lib/templates/static/config/database.js ================================================ /* Database configuration file */ module.exports = { development: { servers: [['127.0.0.1', 27017]], database: '', user: '', password: '', replicaSet: null, } } ================================================ FILE: lib/templates/static/config/globals.js ================================================ /** * Define useful global veriables here */ global.ROOT = process.cwd(); ================================================ FILE: lib/templates/static/config/routes.js ================================================ /** * Require controllers and bound it to routes. * * Example: * var app = require('./application'), * users = require('../app/controllers/users_controller'); * * app.get('/users', users.index); * app.post('/users', users.create); * app.put('/users/:id', users.update); * app.delete('/users/:id, users.delete'); * */ var app = require('./application'), welcome = require('../app/controllers/welcome_controller'); app.get('/', welcome.index); ================================================ FILE: lib/templates/static/db/connection.js ================================================ var mongoose = require('mongoose'); /** * Require databse configuration depending on environment */ var conf = require('../config/database.js')[ENV], util = require('./util'), options = {useMongoClient: true}; mongoose.Promise = Promise; var connectionString = util.createConnectionString(conf); if (conf.replicaSet) { options.replset = conf.replicaSet; } mongoose.connect(connectionString, options); ================================================ FILE: lib/templates/static/db/util.js ================================================ /** * Creates mongodb's connection string */ var createConnectionString = function (conf) { 'mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]'; var str = 'mongodb://'; if (conf.user && conf.password) str = str+conf.user + ':' + conf.password + '@'; conf.servers.forEach(function (v, i, a) { var host = v[0], port = v[1]; str = str+host+':'+port; if (i < conf.servers.length-1) { str=str+','; } }); if (!conf.database || typeof conf.database !== "string" || conf.database.length === 0) { console.error('=> Database name should be a nonempty string'); process.exit(1); } str = str + '/' + conf.database; return str; }; module.exports = { createConnectionString: createConnectionString } ================================================ FILE: lib/templates/static/index.js ================================================ require('./config/globals'); var app = require('./config/application'); require('./db/connection'); require('./config/routes'); var server = app.listen(7000, function () { var host = server.address().address; var port = server.address().port; console.log('=> Express application starting on http://%s:%s', host, port); console.log('=> Ctrl-C to shutdown server'); }); ================================================ FILE: package.json ================================================ { "name": "frodojs", "version": "0.6.1", "description": "Frodo is a Rails-like app generator for Node and Express. It was created to simplify work with projects based on Express framework. Nowadays, many developers are familiar with Ruby on Rails framework, so app generated with Frodo will help you to kickstart faster.", "main": "frodo", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/leemalmac/frodo.git" }, "keywords": [ "node", "mean", "javascript", "rails" ], "author": "Nodari Lipartiya ", "license": "MIT", "bugs": { "url": "https://github.com/leemalmac/frodo/issues" }, "homepage": "https://github.com/leemalmac/frodo#readme", "dependencies": { "jade": "^1.11.0", "mustache": "^2.3.0" }, "bin": { "frodo": "./bin/frodo" } }