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