├── mockApp ├── config │ ├── production.json │ ├── test.json │ ├── config.json │ └── development.json └── src │ ├── Package3 │ └── index.js │ ├── Package │ ├── Controller │ │ ├── AnotherController.js │ │ └── PackageController.js │ └── routes.js │ └── Package2 │ ├── Controller │ ├── Package2Controller.js │ └── RestController.js │ └── routes.js ├── .gitignore ├── bin ├── files │ ├── config │ │ ├── test.template │ │ ├── development.template │ │ ├── production.template │ │ └── config.template │ ├── bowerrc.template │ ├── views │ │ ├── index.ejs.template │ │ ├── index.jade.template │ │ ├── layout.jade.template │ │ ├── layout.ejs.template │ │ ├── index.hjs.template │ │ └── index.soy.template │ ├── bower.template │ ├── public │ │ └── css │ │ │ ├── stylus.template │ │ │ ├── css.template │ │ │ └── less.template │ ├── package │ │ ├── routes.template │ │ ├── model.template │ │ ├── controller.template │ │ ├── tests │ │ │ ├── model.template │ │ │ ├── controller.template │ │ │ └── restcontroller.template │ │ └── restcontroller.template │ ├── setup.template │ ├── package.template │ ├── app.template │ └── gruntfile.template ├── rode ├── rode-generate ├── rode-new └── helpers │ └── utils ├── examples └── simple-blog │ ├── config │ ├── test.json │ ├── development.json │ ├── production.json │ └── config.json │ ├── .bowerrc │ ├── .gitignore │ ├── bower.json │ ├── public │ └── css │ │ └── style.css │ ├── views │ ├── Post │ │ ├── index.jade │ │ └── add.jade │ ├── layout.jade │ └── Main │ │ └── index.jade │ ├── src │ ├── Main │ │ ├── routes.js │ │ ├── Controller │ │ │ └── MainController.js │ │ └── Tests │ │ │ └── Controller │ │ │ └── MainControllerTest.js │ └── Post │ │ ├── routes.js │ │ ├── Controller │ │ └── PostController.js │ │ ├── Tests │ │ ├── Controller │ │ │ └── PostControllerTest.js │ │ └── Model │ │ │ └── PostTest.js │ │ └── Model │ │ └── Post.js │ ├── README.md │ ├── setup.js │ ├── package.json │ ├── app.js │ └── Gruntfile.js ├── src ├── Error │ ├── FileExistsError.js │ └── InvalidParamsError.js ├── MVC │ ├── Model.js │ ├── EjsViewEngine.js │ ├── Tests │ │ ├── ViewEngineTest.js │ │ ├── ModelTest.js │ │ └── ObservableTest.js │ ├── ViewEngine.js │ ├── SoyViewEngine.js │ └── Observable.js ├── DB │ ├── AbstractDB.js │ ├── Tests │ │ └── MongoTest.js │ └── Mongo.js ├── Util │ ├── Tests │ │ ├── TemplateTest.js │ │ └── ListTest.js │ ├── Template.js │ └── List.js ├── Package │ ├── Tests │ │ ├── PackageListTest.js │ │ └── PackageTest.js │ ├── PackageList.js │ └── Package.js ├── Config │ ├── Config.js │ └── Tests │ │ └── ConfigTest.js ├── Server │ ├── Tests │ │ └── ServerTest.js │ └── Server.js ├── Router │ ├── Tests │ │ └── RouterTest.js │ └── Router.js ├── Core │ ├── Core.js │ └── Tests │ │ └── CoreTest.js └── Generator │ ├── Tests │ ├── ProjectGeneratorTest.js │ └── PackageGeneratorTest.js │ ├── ProjectGenerator.js │ └── PackageGenerator.js ├── index.js ├── Gruntfile.js ├── LICENSE ├── loader.js ├── package.json └── README.md /mockApp/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log 4 | tmp -------------------------------------------------------------------------------- /bin/files/config/test.template: -------------------------------------------------------------------------------- 1 | { 2 | "port": 5000 3 | } -------------------------------------------------------------------------------- /bin/files/config/development.template: -------------------------------------------------------------------------------- 1 | { 2 | "port": 3000 3 | } -------------------------------------------------------------------------------- /bin/files/config/production.template: -------------------------------------------------------------------------------- 1 | { 2 | "port": 4000 3 | } -------------------------------------------------------------------------------- /examples/simple-blog/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 5000 3 | } -------------------------------------------------------------------------------- /bin/files/bowerrc.template: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/vendor" 3 | } -------------------------------------------------------------------------------- /examples/simple-blog/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/vendor" 3 | } -------------------------------------------------------------------------------- /examples/simple-blog/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log -------------------------------------------------------------------------------- /examples/simple-blog/config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 3000 3 | } -------------------------------------------------------------------------------- /examples/simple-blog/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 4000 3 | } -------------------------------------------------------------------------------- /mockApp/config/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "fake_option": 654321, 3 | "another_option": "abcdef" 4 | } -------------------------------------------------------------------------------- /mockApp/src/Package3/index.js: -------------------------------------------------------------------------------- 1 | console.log('This package does not use rode.js framework!'); 2 | -------------------------------------------------------------------------------- /bin/files/views/index.ejs.template: -------------------------------------------------------------------------------- 1 | <% layout('../layout') -%> 2 |

<%= title %>

3 |

Welcome to <%= title %>

-------------------------------------------------------------------------------- /bin/files/views/index.jade.template: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} -------------------------------------------------------------------------------- /bin/files/bower.template: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /bin/files/public/css/stylus.template: -------------------------------------------------------------------------------- 1 | body 2 | padding: 50px 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif 4 | a 5 | color: #00B7FF -------------------------------------------------------------------------------- /examples/simple-blog/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-blog", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "bootstrap": "~3.1.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bin/files/public/css/css.template: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /mockApp/src/Package/Controller/AnotherController.js: -------------------------------------------------------------------------------- 1 | export class AnotherController { 2 | 3 | index(req, res) { 4 | res.send('[POST] /another'); 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /bin/files/public/css/less.template: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /bin/files/views/layout.jade.template: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/css/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /examples/simple-blog/public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /src/Error/FileExistsError.js: -------------------------------------------------------------------------------- 1 | export class FileExistsError extends Error { 2 | 3 | constructor(message) { 4 | this.name = 'FileExistsError'; 5 | this.message = message; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/Error/InvalidParamsError.js: -------------------------------------------------------------------------------- 1 | export class InvalidParamsError extends Error { 2 | 3 | constructor(message) { 4 | this.name = 'InvalidParamsError'; 5 | this.message = message; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /bin/files/views/layout.ejs.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | <%-body -%> 9 | 10 | -------------------------------------------------------------------------------- /mockApp/src/Package/Controller/PackageController.js: -------------------------------------------------------------------------------- 1 | export class PackageController { 2 | 3 | index(req, res) { 4 | res.send('[GET] /'); 5 | } 6 | 7 | sayHello(req, res) { 8 | res.send('[GET] /hello'); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /examples/simple-blog/views/Post/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1= post.title 5 | div.container 6 | div(class='panel panel-default') 7 | div.panel-body 8 | p #{post.body} 9 | div.panel-footer #{post.tags} -------------------------------------------------------------------------------- /mockApp/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "fake_option": 123456, 3 | "fake_array": [ 4 | { 5 | "item1": 1, 6 | "item2": 2, 7 | "item3": 3 8 | }, 9 | { 10 | "item1": 4, 11 | "item2": 5, 12 | "item3": 6 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /bin/files/views/index.hjs.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | 6 | 7 | 8 |

{{ title }}

9 |

Welcome to {{ title }}

10 | 11 | -------------------------------------------------------------------------------- /examples/simple-blog/src/Main/routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'rode/loader'; 2 | 3 | var router = new Router(); 4 | router.base = '/'; 5 | 6 | /** 7 | * [GET] / 8 | * Calls MainController.index 9 | */ 10 | router.add({ 11 | action: 'index' 12 | }); 13 | 14 | export {router}; -------------------------------------------------------------------------------- /bin/files/package/routes.template: -------------------------------------------------------------------------------- 1 | import { Router } from 'rode/loader'; 2 | 3 | var router = new Router();{{ restApi }} 4 | router.base = '/'; 5 | 6 | /** 7 | * [GET] / 8 | * Calls {{ package }}Controller.index 9 | */ 10 | router.add({ 11 | action: 'index' 12 | }); 13 | 14 | export {router}; -------------------------------------------------------------------------------- /mockApp/src/Package2/Controller/Package2Controller.js: -------------------------------------------------------------------------------- 1 | export class Package2Controller { 2 | 3 | index() { 4 | return [ 5 | // Middleware 6 | (req, res, next) => { 7 | next(); 8 | }, 9 | (req, res) => { 10 | res.send('[GET] /'); 11 | } 12 | ]; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /mockApp/src/Package2/routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from '../../../loader'; 2 | 3 | var router = new Router(); 4 | router.restApi = '/api/'; 5 | router.base = '/pack2'; 6 | 7 | /** 8 | * [GET] /pack2 9 | * Calls Package2Controller.index 10 | */ 11 | router.add({ 12 | action: 'index' 13 | }); 14 | 15 | export {router}; -------------------------------------------------------------------------------- /examples/simple-blog/README.md: -------------------------------------------------------------------------------- 1 | Example of a simple blog using [rode.js](https://github.com/codexar/rode) and [mongoose](http://mongoosejs.com) 2 | ==== 3 | 4 | Install example dependencies: 5 | 6 | $ npm install 7 | $ bower install 8 | 9 | Run the blog: 10 | 11 | $ node app 12 | 13 | Run the tests: 14 | 15 | $ grunt test -------------------------------------------------------------------------------- /bin/files/package/model.template: -------------------------------------------------------------------------------- 1 | import { Model } from 'rode/loader'; 2 | 3 | /** 4 | * {{ model | capitalize }} Model 5 | */ 6 | export class {{ model | capitalize }} extends Model { 7 | 8 | /** 9 | * Sample method, converts a string to upper case 10 | */ 11 | sampleMethod(str) { 12 | return str.toUpperCase(); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /bin/rode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander'); 4 | var pkg = require('../package.json'); 5 | 6 | program 7 | .version(pkg.version) 8 | .command('new [options] [dir]', 'Create a new project') 9 | .command('generate ', 'Create a new component (package, controller, model)') 10 | .parse(process.argv); 11 | 12 | -------------------------------------------------------------------------------- /bin/files/config/config.template: -------------------------------------------------------------------------------- 1 | { 2 | "baseUri": "http://localhost", 3 | "srcPath": "src", 4 | "statics": { 5 | "path": "public", 6 | "images": "images", 7 | "js": "js", 8 | "css": "css" 9 | }, 10 | "css": "{{ cssTemplate | toLowerCase }}", 11 | "views": { 12 | "path": "views", 13 | "engine": "{{ viewEngine | toLowerCase }}" 14 | } 15 | } -------------------------------------------------------------------------------- /bin/files/package/controller.template: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * {{ controller | capitalize }} Controller 4 | */ 5 | export class {{ controller | capitalize }}Controller { 6 | 7 | /** 8 | * Index Action 9 | */ 10 | index(req, res) { 11 | res.render('{{ package }}/index', { 12 | {{ templateAction }}title: 'Index of {{ package }}' 13 | }); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /bin/files/setup.template: -------------------------------------------------------------------------------- 1 | var traceur = require('traceur'); 2 | 3 | /********************************************* 4 | * WARNING: This is file does not support ES6 5 | *********************************************/ 6 | 7 | // Config which files will be compiled with ES6 8 | traceur.require.makeDefault(function(filename) { 9 | return filename.indexOf('node_modules') === -1; 10 | }); -------------------------------------------------------------------------------- /examples/simple-blog/setup.js: -------------------------------------------------------------------------------- 1 | var traceur = require('traceur'); 2 | 3 | /********************************************* 4 | * WARNING: This is file does not support ES6 5 | *********************************************/ 6 | 7 | // Config which files will be compiled with ES6 8 | traceur.require.makeDefault(function(filename) { 9 | return filename.indexOf('node_modules') === -1; 10 | }); -------------------------------------------------------------------------------- /bin/files/views/index.soy.template: -------------------------------------------------------------------------------- 1 | {namespace {{ package }}.index} 2 | 3 | /** 4 | * @param title 5 | */ 6 | {template .index} 7 | 8 | 9 | 10 | {$title} 11 | 12 | 13 | 14 |

{$title}

15 |

Welcome to {$title}

16 | 17 | 18 | {/template} -------------------------------------------------------------------------------- /mockApp/config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUri": "http://localhost", 3 | "port": 3000, 4 | "srcPath": "src", 5 | "statics": { 6 | "path": "public", 7 | "images": "images", 8 | "js": "js", 9 | "css": "css" 10 | }, 11 | "css": "less", 12 | "views": { 13 | "path": "views", 14 | "engine": "ejs" 15 | }, 16 | "bodyParser": true, 17 | "useErrorHandler": false 18 | } -------------------------------------------------------------------------------- /src/MVC/Model.js: -------------------------------------------------------------------------------- 1 | import { Observable } from './Observable'; 2 | 3 | export class Model extends Observable { 4 | 5 | /** 6 | * @param [data] 7 | */ 8 | constructor(data) { 9 | super(); 10 | 11 | // load the data on the model 12 | for (var key in data) { 13 | if (data.hasOwnProperty(key)) { 14 | this[key] = data[key]; 15 | } 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/MVC/EjsViewEngine.js: -------------------------------------------------------------------------------- 1 | var ejs = require('ejs-locals'); 2 | import { ViewEngine } from './ViewEngine'; 3 | 4 | export class EjsViewEngine extends ViewEngine { 5 | 6 | constructor(app, views) { 7 | super(app, views); 8 | } 9 | 10 | /** 11 | * Config ejs views using ejs-locals module 12 | */ 13 | config() { 14 | super.config(); 15 | this.app.engine('ejs', ejs); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /examples/simple-blog/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUri": "http://localhost", 3 | "srcPath": "src", 4 | "statics": { 5 | "path": "public", 6 | "images": "images", 7 | "js": "js", 8 | "css": "css" 9 | }, 10 | "css": "css", 11 | "views": { 12 | "path": "views", 13 | "engine": "jade" 14 | }, 15 | "mongo": { 16 | "uri": "mongodb://localhost/example_simple_blog" 17 | } 18 | } -------------------------------------------------------------------------------- /examples/simple-blog/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/vendor/bootstrap/dist/css/bootstrap.min.css') 6 | link(rel='stylesheet', href='/css/style.css') 7 | body 8 | header 9 | nav 10 | a(class='btn btn-primary', href='/') Home 11 | | . 12 | a(class='btn btn-primary', href='/post/add') Add Post 13 | block content -------------------------------------------------------------------------------- /examples/simple-blog/src/Post/routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'rode/loader'; 2 | 3 | var router = new Router(); 4 | router.base = '/post/'; 5 | 6 | /** 7 | * [GET] / 8 | * Calls PostController.index 9 | */ 10 | router 11 | .add({ 12 | action: 'showAddPost', 13 | pattern: 'add' 14 | }) 15 | .add({ 16 | action: 'addPost', 17 | pattern: 'add', 18 | method: 'post' 19 | }) 20 | .add({ 21 | action: 'showPost', 22 | pattern: ':id' 23 | }); 24 | 25 | export {router}; -------------------------------------------------------------------------------- /src/DB/AbstractDB.js: -------------------------------------------------------------------------------- 1 | export class AbstractDB { 2 | 3 | constructor(uri, options = {}) { 4 | this.uri = uri; 5 | this.options = options; 6 | this.isOpen = false; 7 | } 8 | 9 | /** 10 | * Overwrite this method with specific database connection 11 | */ 12 | connect() { 13 | throw new Error('Connect not implemented'); 14 | } 15 | 16 | /** 17 | * Overwrite this method with specific database close connection 18 | */ 19 | close() { 20 | throw new Error('Close not implemented'); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /bin/files/package/tests/model.template: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { {{ model | capitalize }} } from '../../Model/{{ model }}'; 3 | 4 | describe('{{ model | capitalize }}', () => { 5 | var {{ model | camelize }}; 6 | 7 | beforeEach(() => {{ model | camelize }} = new {{ model | capitalize }}); 8 | 9 | /** 10 | * Test if a string is converted to upper case 11 | */ 12 | it('should convert a string in upper case', () => { 13 | expect({{ model | camelize }}.sampleMethod('test')).to.be('TEST'); 14 | }); 15 | 16 | }); -------------------------------------------------------------------------------- /bin/files/package.template: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "rode": "{{ acceptedVersion }}", 6 | "underscore": "1.x.x", 7 | "{{ css }}": "*", 8 | "traceur": "0.0.32", 9 | "es6-shim": "0.10.x", 10 | "es6-module-loader": "0.6.x", 11 | "prfun": "0.1.x" 12 | }, 13 | "devDependencies": { 14 | "expect.js": "0.3.x", 15 | "grunt-mocha-test": "0.x.x", 16 | "grunt": "0.x.x" 17 | }, 18 | "main": "app", 19 | "scripts": { 20 | "test": "grunt test" 21 | }, 22 | "engines": { 23 | "node": ">= 0.8.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // NOTE: This is the only file that does not support ES6 // 2 | 3 | var traceur = require('traceur'); 4 | var path = require('path'); 5 | require('es6-shim'); 6 | require('es6-module-loader'); 7 | require('prfun'); 8 | 9 | // base path 10 | global.__rodeBase = __dirname; 11 | 12 | // ES6 files 13 | traceur.require.makeDefault(function(filename) { 14 | // all the files outside the node_modules directory will use ES6 15 | return filename.startsWith(__dirname) && !filename.startsWith(path.join(__dirname, 'node_modules')); 16 | }); 17 | 18 | // export rode.js 19 | module.exports = require('./loader').rode; -------------------------------------------------------------------------------- /mockApp/src/Package/routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from '../../../loader'; 2 | 3 | var router = new Router(); 4 | router.base = '/'; 5 | 6 | /** 7 | * [GET] / 8 | * Calls PackageController.index 9 | */ 10 | router.add({ 11 | action: 'index' 12 | }); 13 | 14 | /** 15 | * [GET] /hello 16 | * Calls PackageController.sayHello 17 | */ 18 | router.add({ 19 | pattern: 'hello', 20 | action: 'sayHello' 21 | }); 22 | 23 | /** 24 | * [POST] /another 25 | * Calls AnotherController.index 26 | */ 27 | router.add({ 28 | controller: 'Another', 29 | pattern: 'another', 30 | action: 'index', 31 | method: 'post' 32 | }); 33 | 34 | export {router}; -------------------------------------------------------------------------------- /bin/files/app.template: -------------------------------------------------------------------------------- 1 | var rode = require('rode'); 2 | require('./setup'); 3 | 4 | /********************************************* 5 | * WARNING: This is file does not support ES6 6 | *********************************************/ 7 | 8 | /** 9 | * [Optional] Config your app using Express 10 | * 11 | * @param app = express() 12 | */ 13 | rode.config(function (app) { 14 | {{ sessions }} 15 | }); 16 | 17 | /** 18 | * Run the server (by default on localhost:3000) 19 | */ 20 | rode.run() 21 | .then(function () { 22 | console.log('Server started on ' + rode.env + '!'); 23 | }) 24 | .catch(function (err) { 25 | console.error(err); 26 | }); -------------------------------------------------------------------------------- /examples/simple-blog/views/Post/add.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | header 5 | h1= title 6 | div.container 7 | form.col-lg-6(method='POST') 8 | div.form-group 9 | label(for='title') Title 10 | input.form-control#title(name='title', placeholder='Inser Title', required) 11 | div.form-group 12 | label(for='body') Body 13 | textarea.form-control#body(name='body', placeholder='Insert Body', required) 14 | div.form-group 15 | label(for='tabs') Tags (separated with commas) 16 | input.form-control#tags(name='tags', placeholder='Insert tags') 17 | button(class='btn btn-primary', type='submit') Submit -------------------------------------------------------------------------------- /examples/simple-blog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-blog", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "body-parser": "^1.0.2", 6 | "css": "*", 7 | "es6-module-loader": "0.5.x", 8 | "es6-shim": "0.10.x", 9 | "mongoose": "^3.8.8", 10 | "prfun": "0.1.x", 11 | "rode": "0.3.x", 12 | "traceur": "0.0.32", 13 | "underscore": "1.x.x" 14 | }, 15 | "devDependencies": { 16 | "expect.js": "0.3.x", 17 | "grunt": "0.x.x", 18 | "grunt-mocha-test": "0.x.x", 19 | "sinon": "^1.9.1" 20 | }, 21 | "main": "app", 22 | "scripts": { 23 | "test": "grunt test" 24 | }, 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/simple-blog/app.js: -------------------------------------------------------------------------------- 1 | require('./setup'); 2 | var rode = require('rode'); 3 | var bodyParser = require('body-parser'); 4 | 5 | /********************************************* 6 | * WARNING: This is file does not support ES6 7 | *********************************************/ 8 | 9 | /** 10 | * [Optional] Config your app using Express 11 | * 12 | * @param app = express() 13 | */ 14 | rode.config(function (app) { 15 | app.use(bodyParser()); 16 | }); 17 | 18 | /** 19 | * Run the server (by default on localhost:3000) 20 | */ 21 | rode.run() 22 | .then(function () { 23 | console.log('Server started on ' + rode.env + '!'); 24 | }) 25 | .catch(function (err) { 26 | console.error(err); 27 | }); -------------------------------------------------------------------------------- /mockApp/src/Package2/Controller/RestController.js: -------------------------------------------------------------------------------- 1 | export class RestController { 2 | 3 | get(req, res) { 4 | res.send('[GET] /api/pack2'); 5 | } 6 | 7 | post(req, res) { 8 | res.send('[POST] /api/pack2'); 9 | } 10 | 11 | getProducts(req, res) { 12 | res.send('[GET] /api/pack2/products'); 13 | } 14 | 15 | postProducts(req, res) { 16 | res.send('[POST] /api/pack2/products'); 17 | } 18 | 19 | getProductsById(req, res) { 20 | res.send('[GET] /api/pack2/products/:id'); 21 | } 22 | 23 | putProductsById(req, res) { 24 | res.send('[PUT] /api/pack2/products/:id') 25 | } 26 | 27 | deleteProductsById(req, res) { 28 | res.send('[DELETE] /api/pack2/products/:id'); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /examples/simple-blog/views/Main/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | h1= title 5 | div.container 6 | 7 | each post in posts 8 | div.panel.panel-default 9 | div.panel-heading #{post.title} 10 | div.panel-body 11 | p #{post.body} 12 | div.panel-footer #{post.tags} 13 | 14 | ul.pagination 15 | li(class=currentPage > 1 ? '' : 'disabled') 16 | a(href='?page=' + (currentPage - 1)) « 17 | - for (var i = 1; i <= pages; i++) { 18 | li(class=currentPage == i ? 'active' : '') 19 | a(href='?page=' + i) #{i} 20 | - } 21 | li(class=currentPage < pages ? '' : 'disabled') 22 | a(href='?page=' + (++currentPage)) » -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | var index = require('./index'); 2 | 3 | module.exports = function (grunt) { 4 | 5 | grunt.loadNpmTasks('grunt-mocha-test'); 6 | 7 | // Project configuration. 8 | grunt.initConfig({ 9 | // Configure a mochaTest task 10 | mochaTest: { 11 | test: { 12 | options: { 13 | reporter: 'spec' 14 | }, 15 | src: [ 16 | 'src/**/Tests/**/*.js' 17 | ] 18 | } 19 | } 20 | }); 21 | 22 | // Run tests // 23 | grunt.registerTask('test', [ 24 | 'mochaTest' 25 | ]); 26 | 27 | // Build tasks // 28 | grunt.registerTask('build', [ 29 | 30 | ]); 31 | 32 | // Default tasks // 33 | grunt.registerTask('default', [ 34 | 'test', 35 | 'build' 36 | ]); 37 | }; -------------------------------------------------------------------------------- /src/MVC/Tests/ViewEngineTest.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('expect.js'); 3 | import { ViewEngine } from '../ViewEngine'; 4 | 5 | describe('ViewEngine', () => { 6 | var viewEngine; 7 | var fakeExpressApp; 8 | var views; 9 | 10 | /** 11 | * Create a viewEngine with a fake Express app 12 | */ 13 | beforeEach(() => { 14 | fakeExpressApp = new Map(); 15 | views = { 16 | path: path.join(__dirname, 'views'), 17 | engine: 'jade' 18 | }; 19 | viewEngine = new ViewEngine(fakeExpressApp, views); 20 | }); 21 | 22 | /** 23 | * Test if the view engine configs express 24 | */ 25 | it('should config view engine on express', () => { 26 | viewEngine.config(); 27 | expect(fakeExpressApp.get('views')).to.be(views.path); 28 | expect(fakeExpressApp.get('view engine')).to.be(views.engine); 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /examples/simple-blog/src/Main/Controller/MainController.js: -------------------------------------------------------------------------------- 1 | import { Post } from '../../Post/Model/Post'; 2 | 3 | /** 4 | * Main Controller 5 | */ 6 | export class MainController { 7 | 8 | /** 9 | * Index Action 10 | */ 11 | index(req, res) { 12 | var postsPerPage = 10; 13 | var currentPage = req.param('page') || 1; 14 | var skipPosts = (currentPage - 1) * postsPerPage; 15 | Promise 16 | .all([ 17 | Post.findLatestPosted(skipPosts, postsPerPage), 18 | Post.count() 19 | ]) 20 | .spread((posts, count) => { 21 | res.render('Main/index', { 22 | title: 'Blog Index', 23 | posts: posts, 24 | pages: Math.ceil(count / postsPerPage), 25 | currentPage: Number(currentPage) 26 | }); 27 | }) 28 | .catch(err => res.send(500, `Unexpected Error: ${err}`)); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /examples/simple-blog/Gruntfile.js: -------------------------------------------------------------------------------- 1 | require('./setup'); 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.loadNpmTasks('grunt-mocha-test'); 6 | 7 | // Project configuration. 8 | grunt.initConfig({ 9 | // Configure a mochaTest task 10 | mochaTest: { 11 | test: { 12 | options: { 13 | reporter: 'spec' 14 | }, 15 | src: [ 16 | 'src/**/Tests/**/*.js' 17 | ] 18 | } 19 | } 20 | }); 21 | 22 | grunt.registerTask('startTestEnvironment', 'Test Environment', function() { 23 | process.env.NODE_ENV = 'test'; 24 | require('rode'); 25 | }); 26 | 27 | // Run tests // 28 | grunt.registerTask('test', [ 29 | 'startTestEnvironment', 30 | 'mochaTest' 31 | ]); 32 | 33 | // Build tasks // 34 | grunt.registerTask('build', [ 35 | 36 | ]); 37 | 38 | // Default tasks // 39 | grunt.registerTask('default', [ 40 | 'test', 41 | 'build' 42 | ]); 43 | 44 | }; -------------------------------------------------------------------------------- /bin/files/package/tests/controller.template: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { {{ controller | capitalize }}Controller } from '../../Controller/{{ controller }}Controller'; 3 | 4 | describe('{{ controller | capitalize }}Controller', () => { 5 | var {{ controller | camelize }}Controller; 6 | 7 | beforeEach(() => {{ controller | camelize }}Controller = new {{ controller | capitalize }}Controller); 8 | 9 | /** 10 | * Test if {{ controller | camelize }}Controller.index render a view correctly 11 | */ 12 | it('should render a view with the title "Index of {{ package }}"', done => { 13 | var req = {}; 14 | var res = { 15 | render(view, data) { 16 | expect(view).to.be('{{ package }}/index'); 17 | expect(data).to.be.an(Object); 18 | expect(data.title).to.be('Index of {{ package }}'); 19 | done(); 20 | } 21 | }; 22 | {{ controller | camelize }}Controller.index(req, res); 23 | }); 24 | 25 | }); -------------------------------------------------------------------------------- /src/Util/Tests/TemplateTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { Template } from '../Template'; 3 | 4 | describe('Template', () => { 5 | 6 | /** 7 | * Test if the template system works correctly 8 | */ 9 | it('should render the variables inside a template', () => { 10 | var str = 'Hello {{ name }}! This is a {{ thing | toLowerCase }}.'; 11 | var template = new Template(str); 12 | var vars = { 13 | name: 'World', 14 | thing: 'TEST' 15 | }; 16 | str = template.render(vars); 17 | expect(str).to.be('Hello World! This is a test.'); 18 | }); 19 | 20 | /** 21 | * Test if the template does not render undefined variables 22 | */ 23 | it('should not render something if the variable does not exist', () => { 24 | var str = 'This {{ variable }} does not exist'; 25 | var template = new Template(str); 26 | str = template.render({}); 27 | expect(str).to.be('This does not exist'); 28 | }); 29 | 30 | }); -------------------------------------------------------------------------------- /bin/files/gruntfile.template: -------------------------------------------------------------------------------- 1 | require('./setup'); 2 | 3 | module.exports = function(grunt) { 4 | 5 | grunt.loadNpmTasks('grunt-mocha-test'); 6 | 7 | // Project configuration. 8 | grunt.initConfig({ 9 | // Configure a mochaTest task 10 | mochaTest: { 11 | test: { 12 | options: { 13 | reporter: 'spec' 14 | }, 15 | src: [ 16 | 'src/**/Tests/**/*.js' 17 | ] 18 | } 19 | } 20 | }); 21 | 22 | // Configure tests // 23 | grunt.registerTask('startTestEnvironment', 'Test Environment', function() { 24 | process.env.NODE_ENV = 'test'; 25 | require('rode'); 26 | }); 27 | 28 | // Run tests // 29 | grunt.registerTask('test', [ 30 | 'startTestEnvironment', 31 | 'mochaTest' 32 | ]); 33 | 34 | // Build tasks // 35 | grunt.registerTask('build', [ 36 | 37 | ]); 38 | 39 | // Default tasks // 40 | grunt.registerTask('default', [ 41 | 'test', 42 | 'build' 43 | ]); 44 | 45 | }; -------------------------------------------------------------------------------- /src/DB/Tests/MongoTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { Mongo } from '../Mongo'; 3 | 4 | describe('Mongo', () => { 5 | var fakeUri = 'mongodb://localhost/rode_tests'; 6 | var mongo = new Mongo(fakeUri, {}); 7 | 8 | /** 9 | * Test if a mongodb instance can be opened on 'mongodb://localhost/rode_tests' 10 | */ 11 | it('should connect to a mongodb instance', done => { 12 | mongo.connect() 13 | .then(connection => { 14 | expect(mongo.isOpen).to.be(true); 15 | expect(connection).to.be.an(Object); 16 | expect(connection.host).to.be('localhost'); 17 | expect(connection.name).to.be('rode_tests'); 18 | }) 19 | .catch(err => expect().fail(err)) 20 | .done(done); 21 | }); 22 | 23 | /** 24 | * Test if the existent mongodb instance can be closed 25 | */ 26 | it('should close the mongodb instance', () => { 27 | mongo.close(); 28 | expect(mongo.isOpen).to.be(false); 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /src/MVC/Tests/ModelTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { Model } from '../Model'; 3 | 4 | describe('Model', () => { 5 | 6 | /** 7 | * Test if extends to Observable 8 | */ 9 | it('should be observable', () => { 10 | expect(Model.isObservable).to.be(true); 11 | }); 12 | 13 | describe('Constructor', () => { 14 | 15 | it('should load an object passed on the constructor into the model instance', () => { 16 | var data = { 17 | name: 'Test', 18 | val: 'value' 19 | }; 20 | var model = new Model(data); 21 | expect(model.name).to.be(data.name); 22 | expect(model.val).to.be(data.val); 23 | model.val = 'new value'; 24 | expect(model.val).to.be('new value'); 25 | }); 26 | 27 | it('should create a empty model if no parameters are given', () => { 28 | var model = new Model(); 29 | model.name = 'Test'; 30 | expect(model).to.be.a(Model); 31 | expect(model.name).to.be('Test'); 32 | }); 33 | 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /src/MVC/ViewEngine.js: -------------------------------------------------------------------------------- 1 | export class ViewEngine { 2 | 3 | constructor(app, views) { 4 | this.app = app; 5 | this.views = views; 6 | } 7 | 8 | /** 9 | * Config view engine 10 | */ 11 | config() { 12 | this.app.set('views', this.views.path || ''); 13 | this.app.set('view engine', this.views.engine || 'jade'); 14 | } 15 | 16 | /** 17 | * Compile views 18 | * This is useful only for views that need to be compiled 19 | * 20 | * @return {Promise} 21 | */ 22 | compile() { 23 | return new Promise(resolve => resolve()); 24 | } 25 | 26 | /** 27 | * Create a new instance of the specific view engine class 28 | * 29 | * @param app 30 | * @param views 31 | * @return {ViewEngine} 32 | */ 33 | static createInstance(app, views) { 34 | switch (views.engine) { 35 | case 'ejs': 36 | return new (require('./EjsViewEngine').EjsViewEngine)(app, views); 37 | case 'soy': 38 | return new (require('./SoyViewEngine').SoyViewEngine)(app, views); 39 | default: 40 | return new ViewEngine(app, views); 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /bin/files/package/tests/restcontroller.template: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { RestController } from '../../Controller/RestController'; 3 | 4 | describe('RestController', () => { 5 | var restController; 6 | 7 | beforeEach(() => restController = new RestController); 8 | 9 | /** 10 | * Test if RestController.get response correctly 11 | */ 12 | it('should respond to GET "/api/{{ package | toLowerCase }}" with "[GET] /api/{{ package | toLowerCase }}"', done => { 13 | var req = {}; 14 | var res = { 15 | send(data) { 16 | expect(data).to.be('[GET] /api/{{ package | toLowerCase }}'); 17 | done(); 18 | } 19 | }; 20 | restController.get(req, res); 21 | }); 22 | 23 | /** 24 | * Test if RestController.post response correctly 25 | */ 26 | it('should respond to POST "/api/{{ package | toLowerCase }}" with "[POST] /api/{{ package | toLowerCase }}"', done => { 27 | var req = {}; 28 | var res = { 29 | send(data) { 30 | expect(data).to.be('[POST] /api/{{ package | toLowerCase }}'); 31 | done(); 32 | } 33 | }; 34 | restController.post(req, res); 35 | }); 36 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Mariano Pardo (Codexar) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /bin/rode-generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var commander = require('commander'); 4 | var rode = require('../index'); 5 | var utils = require('./helpers/utils').utils; 6 | var FileExistsError = require('../loader').FileExistsError; 7 | 8 | commander._name = 'rode generate'; 9 | commander 10 | .usage(' ') 11 | .option('-r, --rest', 'create a REST api for the package or controller') 12 | .option('-f, --force', 'force on existent package') 13 | .parse(process.argv); 14 | 15 | // Path 16 | var path = utils.extractPath(commander.args) || '.'; 17 | var rootPath = utils.findRootPath(path); 18 | var component = commander.args.shift(); 19 | var names = commander.args; 20 | var rest = !!commander.rest; 21 | var force = !!commander.force; 22 | 23 | if (!names.length) { 24 | utils.exit('Error: The name for the new component is not defined'); 25 | } 26 | 27 | for (var i = 0, len = names.length; i < len; i++) { 28 | try { 29 | utils.generateComponent(component, names[i], rootPath, rest, force); 30 | } catch (e) { 31 | if (e instanceof FileExistsError) { 32 | utils.exit(e.message + ' already exists, use --force to overwrite it.'); 33 | } else { 34 | utils.exit(e); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /examples/simple-blog/src/Post/Controller/PostController.js: -------------------------------------------------------------------------------- 1 | import { Post } from '../Model/Post'; 2 | 3 | /** 4 | * Post Controller 5 | */ 6 | export class PostController { 7 | 8 | /** 9 | * Show a single post 10 | */ 11 | showPost(req, res) { 12 | var postId = req.param('id'); 13 | Post.findById(postId) 14 | .then(post => { 15 | if (post) { 16 | res.render('Post/index', { 17 | post: post 18 | }); 19 | } else { 20 | res.send(404, 'Post not found!'); 21 | } 22 | }) 23 | .catch(err => res.send(500, `Unexpected Error: ${err}`)); 24 | } 25 | 26 | /** 27 | * Show the add post form 28 | */ 29 | showAddPost(req, res) { 30 | res.render('Post/add', { 31 | title: 'Add Post' 32 | }); 33 | } 34 | 35 | /** 36 | * Add a new post 37 | */ 38 | addPost(req, res) { 39 | var postData = { 40 | title: req.param('title'), 41 | body: req.param('body'), 42 | tags: req.param('tags') 43 | }; 44 | var post = new Post(postData); 45 | post.save() 46 | .then(() => res.redirect(`/post/${post._id}`)) 47 | .catch(err => res.send(500, `Unexpected Error: ${err}`)); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /loader.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | import { Core } from './src/Core/Core'; 4 | import { PackageList } from './src/Package/PackageList'; 5 | import { Package } from './src/Package/Package'; 6 | import { Router } from './src/Router/Router'; 7 | import { List } from './src/Util/List'; 8 | import { Template } from './src/Util/Template'; 9 | import { Observable } from './src/MVC/Observable'; 10 | import { Model } from './src/MVC/Model'; 11 | import { ViewEngine } from './src/MVC/ViewEngine'; 12 | import { InvalidParamsError } from './src/Error/InvalidParamsError'; 13 | import { FileExistsError } from './src/Error/FileExistsError'; 14 | 15 | // Find the app root path 16 | var rootPath = path.resolve(__dirname, '../../'); 17 | var packageJson = path.resolve(rootPath, 'package.json'); 18 | if (!fs.existsSync(packageJson)) { 19 | rootPath = __dirname; 20 | } 21 | 22 | var core = new Core(rootPath); 23 | core.packageList = new PackageList; 24 | 25 | // for singletons export the instance 26 | export var rode = core; 27 | export var packageList = core.packageList; 28 | export var db = core.db; 29 | 30 | // export all the public classes 31 | export {Package}; 32 | export {Router}; 33 | export {List}; 34 | export {Template}; 35 | export {Observable}; 36 | export {Model}; 37 | export {ViewEngine}; 38 | 39 | // export common errors 40 | export {InvalidParamsError}; 41 | export {FileExistsError}; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rode", 3 | "description": "Packet-Oriented Framework for ES6 and Express.", 4 | "version": "0.3.4", 5 | "author": "Mariano Pardo (Codexar)", 6 | "dependencies": { 7 | "commander": "1.x.x", 8 | "ejs-locals": "1.0.x", 9 | "es6-module-loader": "0.6.x", 10 | "es6-shim": "0.10.x", 11 | "express": "4.x.x", 12 | "extfs": "0.x.x", 13 | "hjs": "0.0.x", 14 | "jade": "1.x.x", 15 | "less-middleware": "1.0.x", 16 | "mkdirp": "0.5.x", 17 | "mongoose": "3.8.x", 18 | "prfun": "0.1.x", 19 | "request": "2.34.x", 20 | "soynode": "0.3.x", 21 | "string": "1.8.x", 22 | "stylus": "0.x.x", 23 | "traceur": "0.0.32", 24 | "underscore": "1.x.x" 25 | }, 26 | "devDependencies": { 27 | "expect.js": "0.3.x", 28 | "grunt-mocha-test": "0.x.x", 29 | "grunt": "0.x.x" 30 | }, 31 | "repository": "git://github.com/codexar/rode", 32 | "main": "index", 33 | "scripts": { 34 | "prepublish": "npm prune" 35 | }, 36 | "bin": { 37 | "rode": "./bin/rode", 38 | "rode-generate": "./bin/rode-generate", 39 | "rode-new": "./bin/rode-new" 40 | }, 41 | "keywords": [ 42 | "framework", 43 | "boilerplate", 44 | "mvc", 45 | "express", 46 | "es6", 47 | "rest", 48 | "restful", 49 | "model", 50 | "controller" 51 | ], 52 | "engines": { 53 | "node": ">= 0.8.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Package/Tests/PackageListTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { PackageList } from '../PackageList'; 3 | import { Package } from '../Package'; 4 | import { List } from '../../Util/List'; 5 | 6 | describe('PackageList', () => { 7 | var packageList; 8 | 9 | beforeEach(() => packageList = new PackageList); 10 | 11 | describe('Get all the packages', () => { 12 | 13 | /** 14 | * Test if the list of packages is a valid one 15 | * 16 | * @param {List.} packs 17 | */ 18 | var testPackages = function (packs) { 19 | expect(packs).to.be.a(List); 20 | expect(packs).to.have.length(3); 21 | expect(packs.get(0)).to.be.a(Package); 22 | 23 | // Check if the list has 'Package' and 'Package2' 24 | var hasPackage = packs.some(pack => pack.name === 'Package'); 25 | var hasPackage2 = packs.some(pack => pack.name === 'Package2'); 26 | expect(hasPackage).to.be(true); 27 | expect(hasPackage2).to.be(true); 28 | }; 29 | 30 | it('should find the list of packages async', done => { 31 | packageList.getAll() 32 | .then(packs => { 33 | testPackages(packs); 34 | done(); 35 | }) 36 | .catch(err => { 37 | expect().fail(err); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('should find the list of packages sync', () => { 43 | var packs = packageList.getAllSync(); 44 | testPackages(packs); 45 | }); 46 | 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /src/DB/Mongo.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('extfs'); 3 | var mongoose = require('mongoose'); 4 | import { AbstractDB } from './AbstractDB'; 5 | import { Core } from '../Core/Core'; 6 | 7 | export class Mongo extends AbstractDB { 8 | 9 | constructor(uri, options) { 10 | super(uri, options); 11 | } 12 | 13 | /** 14 | * Connect to mongodb 15 | * 16 | * @return {Promise} 17 | */ 18 | connect() { 19 | this._linkMongoose(); 20 | return new Promise((resolve, reject) => { 21 | mongoose.connect(this.uri, this.options); 22 | this.connection = mongoose.connection; 23 | this.connection.on('connected', () => { 24 | this.isOpen = true; 25 | resolve(this.connection) 26 | }); 27 | this.connection.on('error', reject); 28 | }); 29 | } 30 | 31 | /** 32 | * Close connection with mongodb 33 | */ 34 | close() { 35 | this.isOpen = false; 36 | return this.connection.close(); 37 | } 38 | 39 | /** 40 | * If the app has mongoose installed, replace its mongoose module with the mongoose module of rode 41 | * 42 | * @private 43 | */ 44 | _linkMongoose() { 45 | var mongoosePath = path.join(__rodeBase, 'node_modules', 'mongoose'); 46 | var appMongoosePath = path.join(Core.instance.getPath('node_modules'), 'mongoose'); 47 | if (fs.existsSync(mongoosePath) && fs.existsSync(appMongoosePath)) { 48 | fs.removeSync(appMongoosePath); 49 | fs.symlinkSync(mongoosePath, appMongoosePath); 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /bin/rode-new: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var commander = require('commander'); 5 | var fs = require('extfs'); 6 | var rode = require('../index'); 7 | var utils = require('./helpers/utils').utils; 8 | var relativePath; 9 | var projectPath; 10 | 11 | // CLI 12 | commander._name = 'rode new'; 13 | commander 14 | .usage('[options] [dir]') 15 | .option('-s, --sessions', 'add session support') 16 | .option('-v --view ', 'add view support (jade|ejs|hogan|soy) (defaults to jade)') 17 | .option('-c, --css ', 'add stylesheet support (less|stylus) (defaults to plain css)') 18 | .parse(process.argv); 19 | 20 | relativePath = commander.args.shift() || '.'; 21 | projectPath = path.resolve(relativePath); 22 | 23 | fs.isEmpty(projectPath, function (empty) { 24 | if (empty) { 25 | utils.generateProject(projectPath, commander.view, commander.css, commander.sessions); 26 | writeInstructions(); 27 | } else { 28 | commander.confirm('Destination is not empty, continue? (yes/no) ', function (ok) { 29 | if (ok) { 30 | process.stdin.destroy(); 31 | utils.generateProject(projectPath, commander.view, commander.css, commander.sessions); 32 | writeInstructions(); 33 | } else { 34 | console.log('Bye!'); 35 | utils.exit(); 36 | } 37 | }); 38 | } 39 | }); 40 | 41 | function writeInstructions () { 42 | console.log(); 43 | console.log(' Install the dependencies:'); 44 | console.log(' cd ' + relativePath + ' && npm install'); 45 | console.log(); 46 | console.log(' Run the app:'); 47 | console.log(' node app'); 48 | console.log(); 49 | }; -------------------------------------------------------------------------------- /src/Package/PackageList.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('extfs'); 3 | var _ = require('underscore'); 4 | import { Package } from './Package'; 5 | import { Core } from '../Core/Core'; 6 | import { List } from '../Util/List'; 7 | 8 | export class PackageList { 9 | 10 | constructor() { 11 | this.list = new List; 12 | } 13 | 14 | /** 15 | * Promise a list with the packages of the app 16 | * 17 | * @return {Promise.} 18 | */ 19 | getAll() { 20 | return new Promise((resolve, reject) => { 21 | if (!this.list.isEmpty()) { 22 | resolve(this.list); 23 | return; 24 | } 25 | var srcPath = Core.instance.getPath('src'); 26 | fs.getDirs(srcPath, (err, files) => { 27 | if (err) { 28 | reject(err); 29 | return; 30 | } 31 | files.forEach(file => this._addPackageToList(file, srcPath)); 32 | resolve(this.list); 33 | }); 34 | }); 35 | } 36 | 37 | /** 38 | * Returns a list with the packages of the app 39 | * 40 | * @return {List.} 41 | */ 42 | getAllSync() { 43 | if (!this.list.isEmpty()) { 44 | return this.list; 45 | } 46 | var srcPath = Core.instance.getPath('src'); 47 | var packs = fs.getDirsSync(srcPath); 48 | packs.forEach(file => this._addPackageToList(file, srcPath)); 49 | return this.list; 50 | } 51 | 52 | /** 53 | * Add a package to the list 54 | * 55 | * @param {string} file 56 | * @param {string} srcPath 57 | * @private 58 | */ 59 | _addPackageToList(file, srcPath) { 60 | var filePath = path.join(srcPath, file); 61 | this.list.add(new Package(file, filePath)); 62 | } 63 | } -------------------------------------------------------------------------------- /src/MVC/SoyViewEngine.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var os = require('os'); 3 | var soynode = require('soynode'); 4 | import { ViewEngine } from './ViewEngine'; 5 | 6 | export class SoyViewEngine extends ViewEngine { 7 | 8 | constructor(app, views) { 9 | super(app, views); 10 | } 11 | 12 | /** 13 | * Config Google Closure Templates 14 | * 15 | * @link http://www.technology-ebay.de/the-teams/mobile-de/blog/node-js-with-express-and-closure-templates.html 16 | */ 17 | config() { 18 | var soyRenderer; 19 | soynode.setOptions({ 20 | outputDir: os.tmpdir(), 21 | uniqueDir: true, 22 | allowDynamicRecompile: true, 23 | eraseTemporaryFiles: true 24 | }); 25 | this.app.set('view engine', '.soy'); 26 | soyRenderer = function(_path, options, callback) { 27 | var templatePath = _path.replace(path.normalize(this.root + '/'), ''); 28 | templatePath = templatePath.replace('.soy', path.sep + options.template); 29 | templatePath = templatePath.split(path.sep).join('.'); 30 | callback(null, soynode.render(templatePath, options)); 31 | }; 32 | this.app.engine('.soy', soyRenderer); 33 | this.app.use(function(req, res, next) { 34 | res.locals.soynode = soynode; 35 | next(); 36 | }); 37 | } 38 | 39 | /** 40 | * Compile Google Closure Templates 41 | * 42 | * @link http://www.technology-ebay.de/the-teams/mobile-de/blog/node-js-with-express-and-closure-templates.html 43 | * @return {Promise} 44 | */ 45 | compile() { 46 | return new Promise((resolve, reject) => { 47 | soynode.compileTemplates(this.views.path, err => { 48 | if (err) { 49 | return reject(err); 50 | } 51 | resolve(); 52 | }); 53 | }); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/Config/Config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var _ = require('underscore'); 4 | 5 | export class Config { 6 | 7 | constructor(rootPath, env = false) { 8 | var configPath = path.join(rootPath, 'config'); 9 | var file = `${configPath}/config.json`; 10 | this.options = {}; 11 | this._loadFile(file); 12 | 13 | // load environment config 14 | if (env) { 15 | var envFile = `${configPath}/${env}.json`; 16 | this._loadFile(envFile); 17 | } 18 | } 19 | 20 | /** 21 | * Extend the current configuration with a new json file 22 | * 23 | * @param {string} file 24 | * @private 25 | */ 26 | _loadFile(file) { 27 | var json; 28 | try { 29 | json = fs.readFileSync(file); 30 | json = JSON.parse(json); 31 | } catch(e) { 32 | console.error(`${file} does not exists.`); 33 | json = {}; 34 | } 35 | _.extend(this.options, json); 36 | } 37 | 38 | /** 39 | * Returns an option value 40 | * 41 | * @param {string} key 42 | * @return {*} 43 | */ 44 | get(key) { 45 | return this.options[key]; 46 | } 47 | 48 | /** 49 | * Set an option value 50 | * 51 | * @param {string} key 52 | * @param value 53 | */ 54 | set(key, value) { 55 | this.options[key] = value; 56 | } 57 | 58 | /** 59 | * Check if options has a key 60 | * 61 | * @param {string} key 62 | * @return {boolean} 63 | */ 64 | has(key) { 65 | return !!this.options[key]; 66 | } 67 | 68 | /** 69 | * Remove an option key 70 | * 71 | * @param {string} key 72 | */ 73 | remove(key) { 74 | delete this.options[key]; 75 | } 76 | 77 | /** 78 | * Remove all the options 79 | */ 80 | clear() { 81 | this.options = {}; 82 | } 83 | } -------------------------------------------------------------------------------- /src/Util/Template.js: -------------------------------------------------------------------------------- 1 | var S = require('string'); 2 | 3 | export class Template { 4 | 5 | constructor(str) { 6 | this.str = str; 7 | } 8 | 9 | /** 10 | * Render the variables between double curly braces 11 | * 12 | * i.e: {{ name }} -> vars['name'] 13 | * {{ name | toLowerCase }} -> vars['name'].toLowerCase() 14 | * 15 | * @param vars 16 | * @returns {string} 17 | * @private 18 | */ 19 | render(vars) { 20 | var regex = /\{{2}([^}]+)\}{2}/g; 21 | this.str = this.str.replace(regex, (match, value) => { 22 | var parts = value.split('|'); 23 | var result; 24 | value = parts[0].trim(); 25 | result = vars[value] || ''; 26 | if (parts[1] && parts[1].trim()) { 27 | result = result[parts[1].trim()](); 28 | } 29 | return result; 30 | }); 31 | return this.str; 32 | } 33 | 34 | /** 35 | * Helper for strings in templates, extends from string.js 36 | * 37 | * @param {string} value 38 | * @private 39 | */ 40 | static ExtendString(value) { 41 | this.setValue(value); 42 | 43 | /** 44 | * Returns the string with the first letter in upper case 45 | * 46 | * @return {Template.ExtendString} 47 | */ 48 | this.capitalize = function () { 49 | return new Template.ExtendString(value[0].toUpperCase() + value.slice(1)); 50 | }; 51 | 52 | /** 53 | * Returns the string with the first letter in lower case 54 | * 55 | * @return {Template.ExtendString} 56 | */ 57 | this.camelize = function () { 58 | return new Template.ExtendString(value[0].toLowerCase() + value.slice(1)); 59 | }; 60 | } 61 | } 62 | 63 | // _templateVar extends from string.js 64 | Template.ExtendString.prototype = S(); 65 | Template.ExtendString.prototype.constructor = Template.ExtendString; -------------------------------------------------------------------------------- /examples/simple-blog/src/Main/Tests/Controller/MainControllerTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var sinon = require('sinon'); 3 | import { MainController } from '../../Controller/MainController'; 4 | import { Post } from '../../../Post/Model/Post'; 5 | 6 | describe('MainController', () => { 7 | var mainController; 8 | 9 | beforeEach(() => mainController = new MainController); 10 | 11 | afterEach(() => { 12 | Post.findLatestPosted.restore(); 13 | Post.count.restore(); 14 | }); 15 | 16 | /** 17 | * Test if mainController.index render a view correctly 18 | */ 19 | it('should render a view with the latest post', done => { 20 | var fakePosts = [ 21 | new Post({ 22 | _id: 'fake_id', 23 | title: 'Fake Post', 24 | body: 'Fake Body' 25 | }), 26 | new Post({ 27 | _id: 'fake_id_2', 28 | title: 'Fake Post 2', 29 | body: 'Fake Body 2' 30 | }) 31 | ]; 32 | 33 | // Create a spy on Post.findLatestPosted() 34 | sinon.stub(Post, 'findLatestPosted', (skip, limit) => { 35 | return new Promise(resolve => { 36 | resolve(fakePosts); 37 | }); 38 | }); 39 | 40 | // Create a spy on Post.count() 41 | sinon.stub(Post, 'count', () => { 42 | return new Promise(resolve => { 43 | resolve(2); 44 | }); 45 | }); 46 | 47 | var req = { 48 | param() { 49 | return 1; 50 | } 51 | }; 52 | var res = { 53 | render(view, data) { 54 | expect(view).to.be('Main/index'); 55 | expect(data.title).to.be('Blog Index'); 56 | expect(data.posts).to.be.an(Array); 57 | expect(data.posts.length).to.be(2); 58 | expect(data.currentPage).to.be(1); 59 | done(); 60 | } 61 | }; 62 | mainController.index(req, res); 63 | }); 64 | 65 | }); -------------------------------------------------------------------------------- /src/Package/Package.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | export class Package { 5 | 6 | constructor(name, packagePath) { 7 | this.name = name; 8 | this.path = packagePath; 9 | } 10 | 11 | /** 12 | * Returns the path of a file inside of this package 13 | * 14 | * @param [file] 15 | * @return {string} 16 | */ 17 | getPath(file) { 18 | if (!file) { 19 | return this.path; 20 | } 21 | return path.join(this.path, file); 22 | } 23 | 24 | /** 25 | * Returns a controller by name 26 | * 27 | * @param {string} name 28 | * @return {exports} 29 | */ 30 | getController(name = this.name) { 31 | var ctrlPath = this.getPath(`Controller/${name}Controller`); 32 | return require(ctrlPath)[`${name}Controller`]; 33 | } 34 | 35 | /** 36 | * Returns the path of the routes file of the packages 37 | * 38 | * @return {string} 39 | */ 40 | get routesPath() { 41 | return this.getPath('routes.js'); 42 | } 43 | 44 | /** 45 | * Returns the router defined in the routes file 46 | * 47 | * @return {Router} 48 | */ 49 | get router() { 50 | var router; 51 | try { 52 | router = require(this.routesPath).router; 53 | router.pack = this; 54 | } catch (e) { } 55 | return router; 56 | } 57 | 58 | /** 59 | * Check if the package exists 60 | * 61 | * @param {string} [resourcePath] 62 | * @return {Promise} 63 | */ 64 | exists(resourcePath) { 65 | if (resourcePath) { 66 | resourcePath = path.join(this.path, resourcePath); 67 | } else { 68 | resourcePath = this.path; 69 | } 70 | return new Promise(resolve => fs.exists(resourcePath, resolve)); 71 | } 72 | 73 | /** 74 | * Check if the package exists synchronously 75 | * 76 | * @param {string} [resourcePath] 77 | * @return {boolean} 78 | */ 79 | existsSync(resourcePath) { 80 | if (resourcePath) { 81 | resourcePath = path.join(this.path, resourcePath); 82 | } else { 83 | resourcePath = this.path; 84 | } 85 | return fs.existsSync(resourcePath); 86 | } 87 | } -------------------------------------------------------------------------------- /bin/files/package/restcontroller.template: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * REST Controller 4 | */ 5 | export class RestController { 6 | 7 | /** 8 | * [GET] /api/{{ package | toLowerCase }} 9 | */ 10 | get(req, res) { 11 | res.send('[GET] /api/{{ package | toLowerCase }}'); 12 | } 13 | 14 | /** 15 | * [POST] /api/{{ package | toLowerCase }} 16 | */ 17 | post(req, res) { 18 | res.send('[POST] /api/{{ package | toLowerCase }}'); 19 | } 20 | 21 | /** 22 | * [GET] /api/{{ package | toLowerCase }}/sponsored 23 | */ 24 | getSponsored(req, res) { 25 | res.send('[GET] /api/{{ package | toLowerCase }}/sponsored'); 26 | } 27 | 28 | /** 29 | * [POST] /api/{{ package | toLowerCase }}/sponsored 30 | */ 31 | postSponsored(req, res) { 32 | res.send('[POST] /api/{{ package | toLowerCase }}/sponsored'); 33 | } 34 | 35 | /** 36 | * [PUT] /api/{{ package | toLowerCase }}/sponsored/today 37 | */ 38 | putSponsoredToday(req, res) { 39 | res.send('[PUT] /api/{{ package | toLowerCase }}/sponsored/today'); 40 | } 41 | 42 | /** 43 | * [GET] /api/{{ package | toLowerCase }}/:id 44 | */ 45 | getById(req, res) { 46 | var {{ package | camelize }}Id = req.params.id; 47 | res.send('[GET] /api/{{ package | toLowerCase }}/' + {{ package | camelize }}Id); 48 | } 49 | 50 | /** 51 | * [PUT] /api/{{ package | toLowerCase }}/:id 52 | */ 53 | putById(req, res) { 54 | var {{ package | camelize }}Id = req.params.id; 55 | res.send('[PUT] /api/{{ package | toLowerCase }}/' + {{ package | camelize }}Id); 56 | } 57 | 58 | /** 59 | * [GET] /api/{{ package | toLowerCase }}/:id/comments 60 | */ 61 | getByIdComments(req, res) { 62 | var {{ package | camelize }}Id = req.params.id; 63 | res.send('[GET] /api/{{ package | toLowerCase }}/' + {{ package | camelize }}Id + '/comments'); 64 | } 65 | 66 | /** 67 | * [DELETE] /api/{{ package | toLowerCase }}/:id/comments/:id2 68 | */ 69 | deleteByIdCommentsById(req, res) { 70 | var {{ package | camelize }}Id = req.params.id, 71 | commentId = req.params.id2; 72 | res.send('[DELETE] /api/{{ package | toLowerCase }}/' + {{ package | camelize }}Id + '/comments/' + commentId); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /examples/simple-blog/src/Post/Tests/Controller/PostControllerTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var sinon = require('sinon'); 3 | import { PostController } from '../../Controller/PostController'; 4 | import { Post } from '../../Model/Post'; 5 | 6 | describe('PostController', () => { 7 | var postController; 8 | 9 | beforeEach(() => postController = new PostController); 10 | 11 | /** 12 | * Test if postController.showPost render a view correctly 13 | */ 14 | it('should render a view with the specified post', done => { 15 | var fakePost = new Post({ 16 | _id: 1, 17 | title: 'fake title', 18 | body: 'fake body' 19 | }); 20 | var req = { 21 | param() { 22 | return 1; 23 | } 24 | }; 25 | var res = { 26 | render(view, data) { 27 | expect(view).to.be('Post/index'); 28 | expect(data).to.be.an(Object); 29 | expect(data.post).to.be(fakePost); 30 | Post.findById.restore(); 31 | done(); 32 | } 33 | }; 34 | 35 | // Create a spy on Post.findById() 36 | sinon.stub(Post, 'findById', () => { 37 | return new Promise(resolve => { 38 | resolve(fakePost); 39 | }); 40 | }); 41 | 42 | postController.showPost(req, res); 43 | }); 44 | 45 | /** 46 | * Test if postController.showAddPost render a view correctly 47 | */ 48 | it('should render the add post form', done => { 49 | var req = {}; 50 | var res = { 51 | render(view, data) { 52 | expect(view).to.be('Post/add'); 53 | expect(data).to.be.an(Object); 54 | expect(data.title).to.be('Add Post'); 55 | done(); 56 | } 57 | }; 58 | postController.showAddPost(req, res); 59 | }); 60 | 61 | /** 62 | * Test if postController.addPost add a post and redirect to it 63 | */ 64 | it('should receive a new post and redirect to it', done => { 65 | var fakePost = new Post({ 66 | title: 'fake title', 67 | body: 'fake body' 68 | }); 69 | var req = { 70 | param() { 71 | return ''; 72 | } 73 | }; 74 | var res = { 75 | redirect(page) { 76 | expect(page).to.be('/post/fake_id'); 77 | Post.prototype.save.restore(); 78 | done(); 79 | } 80 | }; 81 | 82 | // Create a spy on Post.findById() 83 | sinon.stub(Post.prototype, 'save', function () { 84 | 85 | return new Promise(resolve => { 86 | this._id = 'fake_id'; 87 | resolve(fakePost); 88 | }); 89 | }); 90 | 91 | postController.addPost(req, res); 92 | }); 93 | 94 | }); -------------------------------------------------------------------------------- /src/Config/Tests/ConfigTest.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('expect.js'); 3 | import { Config } from '../Config'; 4 | 5 | describe('Config', () => { 6 | // mock app with .json options 7 | var fakePath = path.join(__rodeBase, 'mockApp'); 8 | 9 | it('should allow to be newed', () => { 10 | var config = new Config(fakePath); 11 | expect(config).to.be.a(Config); 12 | }); 13 | 14 | it('should load config.json file in their options', () => { 15 | var config = new Config(fakePath); 16 | expect(config.options).to.be.an(Object); 17 | expect(config.options.fake_option).to.be(123456); 18 | expect(config.options.fake_array).to.be.an(Array); 19 | expect(config.options.fake_array[0]).to.be.an(Object); 20 | expect(config.options.fake_array[1]).to.be.an(Object); 21 | expect(config.options.fake_array[0].item1).to.be(1); 22 | expect(config.options.fake_array[0].item2).to.be(2); 23 | expect(config.options.fake_array[0].item3).to.be(3); 24 | expect(config.options.fake_array[1].item1).to.be(4); 25 | expect(config.options.fake_array[1].item2).to.be(5); 26 | expect(config.options.fake_array[1].item3).to.be(6); 27 | }); 28 | 29 | it('should load environment json options without remove config.json', () => { 30 | var config = new Config(fakePath, 'test'); 31 | expect(config.options).to.be.an(Object); 32 | expect(config.options.fake_option).to.be(654321); 33 | expect(config.options.another_option).to.be('abcdef'); 34 | }); 35 | 36 | describe('CRUD operations', () => { 37 | var config; 38 | 39 | /** 40 | * Create a config instance with ./mock/config/config.json options 41 | */ 42 | beforeEach(() => config = new Config(fakePath)); 43 | 44 | it('should get an element by key', () => { 45 | expect(config.get('fake_option')).to.be(123456); 46 | expect(config.get('NONEXISTENT OPTION')).to.be(undefined); 47 | }); 48 | 49 | it('should set an element by key', () => { 50 | config.set('my_option', 'my value'); 51 | config.set('fake_option', 'changed value'); 52 | expect(config.get('my_option')).to.be('my value'); 53 | expect(config.get('fake_option')).to.be('changed value'); 54 | }); 55 | 56 | it('should if config contains an option', () => { 57 | expect(config.has('fake_option')).to.be(true); 58 | expect(config.has('NONEXISTENT OPTION')).to.be(false); 59 | }); 60 | 61 | it('should remove an element by key', () => { 62 | config.remove('fake_option'); 63 | expect(config.has('fake_option')).to.be(false); 64 | expect(config.get('fake_option')).to.be(undefined); 65 | expect(() => config.remove('NONEXISTENT OPTION')).to.not.throwError(); 66 | }); 67 | 68 | it('should remove all the options', () => { 69 | config.clear(); 70 | expect(config.options).to.be.empty(); 71 | }); 72 | }); 73 | 74 | }); -------------------------------------------------------------------------------- /src/MVC/Observable.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | 3 | export class Observable { 4 | 5 | constructor() { 6 | this.__events__ = new EventEmitter; 7 | } 8 | 9 | /** 10 | * Execute each of the listeners in order with the supplied arguments 11 | * 12 | * @returns {Observable} 13 | */ 14 | trigger() { 15 | this.__events__.emit(...arguments); 16 | return this; 17 | } 18 | 19 | /** 20 | * Alias for trigger 21 | * 22 | * @returns {Observable} 23 | */ 24 | emit() { 25 | return this.trigger(...arguments); 26 | } 27 | 28 | /** 29 | * Adds a listener to the end of the listeners array for the specified event 30 | * 31 | * @param {string} event 32 | * @param {Function} listener 33 | * @returns {Observable} 34 | */ 35 | on(event, listener) { 36 | this.__events__.on(event, listener); 37 | return this; 38 | } 39 | 40 | /** 41 | * Remove a listener from the listener array for the specified event 42 | * 43 | * @param {string} [event] 44 | * @param {Function} [listener] 45 | * @returns {Observable} 46 | */ 47 | off(event, listener) { 48 | if (!listener) { 49 | return this.removeAllListeners(event); 50 | } 51 | this.__events__.removeListener(event, listener); 52 | return this; 53 | } 54 | 55 | /** 56 | * Adds a one time listener for the event 57 | * 58 | * @returns {Observable} 59 | */ 60 | once() { 61 | this.__events__.once(...arguments); 62 | return this; 63 | } 64 | 65 | /** 66 | * Removes all listeners, or those of the specified event 67 | * 68 | * @param {string} [event] 69 | * @returns {Observable} 70 | */ 71 | removeAllListeners(event) { 72 | this.__events__.removeAllListeners(event); 73 | return this; 74 | } 75 | 76 | /** 77 | * By default will print a warning if more than 10 listeners are added for a particular event 78 | * Set to zero for unlimited 79 | * 80 | * @param {number} n 81 | * @returns {Observable} 82 | */ 83 | setMaxListeners(n) { 84 | this.__events__.setMaxListeners(n); 85 | return this; 86 | } 87 | 88 | /** 89 | * Returns an array of listeners for the specified event 90 | * 91 | * @param {string} event 92 | * @returns {Array} 93 | */ 94 | listeners(event) { 95 | return this.__events__.listeners(event); 96 | } 97 | 98 | /** 99 | * Returns the number of listeners for the specified event 100 | * 101 | * @param {string} event 102 | * @returns {number} 103 | */ 104 | listenerCount(event) { 105 | return EventEmitter.listenerCount(this.__events__, event); 106 | } 107 | 108 | /** 109 | * Returns if the class is observable 110 | * 111 | * @returns {boolean} 112 | */ 113 | static get isObservable() { 114 | return true; 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /src/Server/Tests/ServerTest.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('expect.js'); 3 | import { Server } from '../Server'; 4 | import { Core } from '../../Core/Core'; 5 | import { Package } from '../../Package/Package'; 6 | import { List } from '../../Util/List'; 7 | 8 | describe('Server', () => { 9 | var server; 10 | 11 | beforeEach(() => server = new Server); 12 | 13 | describe('Config', () => { 14 | 15 | it('should configure express', () => { 16 | var fakePath = path.join(__rodeBase, 'mockApp'); 17 | var core = new Core(fakePath); 18 | server.config(core); 19 | expect(server.app.get('port')).to.be(3000); 20 | expect(core.options.get('baseUri')).to.be('http://localhost'); 21 | expect(core.options.get('host')).to.be('localhost'); 22 | }); 23 | 24 | }); 25 | 26 | describe('Routing', () => { 27 | 28 | /** 29 | * Test if the routes of a router were added to express 30 | * 31 | * @param {Router} router 32 | */ 33 | var testExpressRoutes = function (router) { 34 | router.forEach(route => { 35 | var routePath; 36 | var exists; 37 | if (route.isRestful) { 38 | routePath = path.join(router.restApi, router.base, route.pattern); 39 | } else { 40 | routePath = path.join(router.base, route.pattern); 41 | } 42 | exists = server.app._router.stack.some(stack => { 43 | if (!stack || !stack.route) { 44 | return false; 45 | } 46 | return stack.route.methods[route.method] && routePath === stack.route.path; 47 | }); 48 | expect(exists).to.be(true); 49 | }); 50 | }; 51 | 52 | it('should add the routes of a router to express', () => { 53 | var packName = 'Package'; 54 | var packPath = path.join(__rodeBase, `mockApp/src/${packName}`); 55 | var pack = new Package(packName, packPath); 56 | var router = pack.router; 57 | server.addRoutes(router); 58 | testExpressRoutes(router); 59 | }); 60 | 61 | it('should add the routes of a REST router to express', () => { 62 | var packName = 'Package2'; 63 | var packPath = path.join(__rodeBase, `mockApp/src/${packName}`); 64 | var pack = new Package(packName, packPath); 65 | var router = pack.router; 66 | server.addRoutes(router); 67 | expect(router.getAll().some(route => route.controller === 'Rest')).to.be(true); 68 | testExpressRoutes(router); 69 | }); 70 | 71 | }); 72 | 73 | describe('Running and stopping the server', () => { 74 | var server = new Server; 75 | var fakePath = path.join(__rodeBase, 'mockApp'); 76 | var core = new Core(fakePath); 77 | 78 | it('should run the server on specified port', done => { 79 | server.config(core); 80 | server.run() 81 | .then(() => { 82 | expect(server.isRunning).to.be(true); 83 | done(); 84 | }) 85 | .done(); 86 | }); 87 | 88 | it('should stop the server', () => { 89 | server.stop(); 90 | expect(server.isRunning).to.be(false); 91 | }); 92 | 93 | }); 94 | 95 | }); 96 | 97 | -------------------------------------------------------------------------------- /src/Router/Tests/RouterTest.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('expect.js'); 3 | import { Router } from '../Router'; 4 | import { Package } from '../../Package/Package'; 5 | 6 | describe('Router', () => { 7 | var router; 8 | var route1 = { 9 | action: 'index', 10 | pattern: '/', 11 | method: 'get' 12 | }; 13 | var route2 = { 14 | action: 'menu', 15 | pattern: '/menu', 16 | method: 'get' 17 | }; 18 | 19 | beforeEach(() => router = new Router); 20 | 21 | it('should allow to add new routes', () => { 22 | router.add(route1); 23 | router.add(route2); 24 | expect(router).to.have.length(2); 25 | expect(router.get(0)).to.be(route1); 26 | expect(router.get(1)).to.be(route2); 27 | }); 28 | 29 | describe('filter', () => { 30 | 31 | it('should filter a route by predicate', () => { 32 | router.add(route1); 33 | router.add(route2); 34 | var result = router.filter(route => route.action === 'index'); 35 | expect(result[0]).to.be(route1); 36 | }); 37 | 38 | it('should filter a route by action', () => { 39 | router.add(route1); 40 | router.add(route2); 41 | var result = router.filterByAction('menu'); 42 | expect(result[0]).to.be(route2); 43 | }); 44 | 45 | it('should filter a route by pattern', () => { 46 | router.add(route1); 47 | router.add(route2); 48 | var result = router.filterByPattern('/menu'); 49 | expect(result[0]).to.be(route2); 50 | }); 51 | 52 | }); 53 | 54 | describe('find', () => { 55 | 56 | it('should find a route by predicate', () => { 57 | router.add(route1); 58 | router.add(route2); 59 | var result = router.find(route => route.action === 'index'); 60 | expect(result).to.be(route1); 61 | }); 62 | 63 | it('should find a route by action', () => { 64 | router.add(route1); 65 | router.add(route2); 66 | var result = router.findByAction('index'); 67 | expect(result).to.be(route1); 68 | }); 69 | 70 | it('should find a route by action', () => { 71 | router.add(route1); 72 | router.add(route2); 73 | var result = router.findByPattern('/'); 74 | expect(result).to.be(route1); 75 | }); 76 | 77 | }); 78 | 79 | describe('rest routes', () => { 80 | 81 | it('should return the rest routes', () => { 82 | var packPath = path.join(__rodeBase, 'mockApp/src/Package2'); 83 | var pack = new Package('Package2', packPath); 84 | var packRouter = pack.router; 85 | expect(packRouter.restRoutes).to.be.an(Array); 86 | packRouter.restRoutes.forEach(route => { 87 | expect(route.controller).to.be.ok(); 88 | expect(route.action).to.be.ok(); 89 | expect(route.method).to.be.ok(); 90 | }); 91 | }); 92 | 93 | it('should not break if the package has no rest api', () => { 94 | var packPath = path.join(__rodeBase, 'mockApp/src/Package'); 95 | var pack = new Package('Package2', packPath); 96 | var packRouter = pack.router; 97 | expect(packRouter.restRoutes).to.be.an(Array); 98 | expect(packRouter.restRoutes).to.be.empty(); 99 | }); 100 | 101 | }); 102 | }); -------------------------------------------------------------------------------- /src/Core/Core.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var _ = require('underscore'); 3 | import { Config } from '../Config/Config'; 4 | import { Server } from '../Server/Server'; 5 | import { InvalidParamsError } from '../Error/InvalidParamsError'; 6 | 7 | export class Core { 8 | 9 | constructor(rootPath, env = process.env.NODE_ENV || 'development') { 10 | if (!rootPath) { 11 | throw new InvalidParamsError('[Core] Path is required!'); 12 | } 13 | this.path = rootPath; 14 | this.env = env; 15 | this.options = new Config(this.path, this.env); 16 | this.server = new Server; 17 | Core.instance = this; 18 | } 19 | 20 | /** 21 | * Config express server before start the app 22 | * 23 | * @param {Function} callback 24 | */ 25 | config(callback) { 26 | if (_.isFunction(callback)) { 27 | callback.call(this, this.server.app); 28 | } else { 29 | throw new InvalidParamsError('rode.config first parameter should be a function'); 30 | } 31 | } 32 | 33 | /** 34 | * Run the app 35 | * 36 | * @return {Promise} 37 | */ 38 | run() { 39 | return this.server.config(this) 40 | .then(() => this.packageList.getAll()) 41 | .then(packs => this.server.createRoutes(packs)) 42 | .then(() => this.server.configViews(this.options.get('views'))) 43 | .then(() => this.server.run()); 44 | } 45 | 46 | /** 47 | * Stop the app 48 | * 49 | * @returns {Promise} 50 | */ 51 | stop() { 52 | return this.server.stop(); 53 | } 54 | 55 | /** 56 | * Returns the path of a resource 57 | * 58 | * @param {string} [name] 59 | * @return {string} 60 | */ 61 | getPath(name) { 62 | var statics = this.options.get('statics') || {}; 63 | _.defaults(statics, { 64 | path: 'public', 65 | images: 'images', 66 | js: 'js', 67 | css: 'css' 68 | }); 69 | 70 | var _path; 71 | switch (name.toLowerCase()) { 72 | case 'src': 73 | return path.join(this.path, this.options.get('srcPath') || 'src'); 74 | 75 | case 'views': 76 | if (this.options.has('views')) { 77 | _path = this.options.get('views').path; 78 | } 79 | return path.join(this.path, _path || 'views'); 80 | 81 | case 'statics': case 'public': 82 | return path.join(this.path, statics.path); 83 | 84 | case 'images': 85 | return path.join(this.path, statics.path, statics.images); 86 | 87 | case 'js': case 'javascript': 88 | return path.join(this.path, statics.path, statics.js); 89 | 90 | case 'css': case 'stylesheets': 91 | return path.join(this.path, statics.path, statics.css); 92 | 93 | case 'node_modules': 94 | return path.join(this.path, 'node_modules'); 95 | 96 | default: 97 | return this.path; 98 | } 99 | } 100 | 101 | /** 102 | * Returns express app 103 | * 104 | * @return {*} 105 | */ 106 | get app() { 107 | return this.server.app; 108 | } 109 | 110 | /** 111 | * Returns express 112 | * 113 | * @return {*} 114 | */ 115 | get express() { 116 | return this.server.express; 117 | } 118 | 119 | /** 120 | * Return the last instance of this class 121 | * 122 | * @return {Core} 123 | */ 124 | static get instance() { 125 | return this._instance; 126 | } 127 | 128 | /** 129 | * Set the last instance of this class 130 | * 131 | * @param {Core} instance 132 | */ 133 | static set instance(instance) { 134 | this._instance = instance; 135 | } 136 | } -------------------------------------------------------------------------------- /src/Generator/Tests/ProjectGeneratorTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var path = require('path'); 3 | var fs = require('extfs'); 4 | import { ProjectGenerator } from '../ProjectGenerator'; 5 | 6 | describe('ProjectGenerator', () => { 7 | var projectName = 'FakeProject'; 8 | var projectPath = path.join(__rodeBase, `tmp/${projectName}`); 9 | var filesPath = path.join(__rodeBase, 'bin/files'); 10 | var acceptedVersion = '0.3.x'; 11 | var projectGenerator; 12 | 13 | /** 14 | * Creates a new Package and PackageGenerator 15 | */ 16 | beforeEach(() => projectGenerator = new ProjectGenerator(projectPath, filesPath, acceptedVersion)); 17 | 18 | /** 19 | * Removes the content of the temporal folder 20 | */ 21 | afterEach(() => { 22 | if (fs.existsSync(projectPath)) { 23 | fs.removeSync(projectPath); 24 | } 25 | }); 26 | 27 | /** 28 | * Test if the configuration files are created correctly 29 | */ 30 | it('should generate the configuration files', () => { 31 | projectGenerator.createConfigs('jade', 'css'); 32 | expect(fs.existsSync(projectPath)).to.be(true); 33 | expect(fs.existsSync(path.join(projectPath, 'config/config.json'))).to.be(true); 34 | expect(fs.existsSync(path.join(projectPath, 'config/development.json'))).to.be(true); 35 | expect(fs.existsSync(path.join(projectPath, 'config/production.json'))).to.be(true); 36 | expect(fs.existsSync(path.join(projectPath, 'config/test.json'))).to.be(true); 37 | }); 38 | 39 | /** 40 | * Test if the files of the main package are created correctly 41 | */ 42 | it('should generate the main package', () => { 43 | projectGenerator.createMainPackage(); 44 | expect(fs.existsSync(projectPath)).to.be(true); 45 | expect(fs.existsSync(path.join(projectPath, 'src/Main/Controller/MainController.js'))).to.be(true); 46 | expect(fs.existsSync(path.join(projectPath, 'src/Main/Model/Main.js'))).to.be(true); 47 | }); 48 | 49 | /** 50 | * Test if the main files are created correctly 51 | */ 52 | it('should generate the main files', () => { 53 | projectGenerator.createMainFiles(); 54 | expect(fs.existsSync(projectPath)).to.be(true); 55 | expect(fs.existsSync(path.join(projectPath, 'app.js'))).to.be(true); 56 | expect(fs.existsSync(path.join(projectPath, 'setup.js'))).to.be(true); 57 | }); 58 | 59 | /** 60 | * Test if the style files are created correctly 61 | */ 62 | it('should generate the style files', () => { 63 | projectGenerator.createStyles(); 64 | expect(fs.existsSync(projectPath)).to.be(true); 65 | expect(fs.existsSync(path.join(projectPath, 'public/css/style.css'))).to.be(true); 66 | }); 67 | 68 | /** 69 | * Test if package.json is created correctly 70 | */ 71 | it('should generate package.json', () => { 72 | projectGenerator.createPackageJSON(); 73 | expect(fs.existsSync(projectPath)).to.be(true); 74 | expect(fs.existsSync(path.join(projectPath, 'package.json'))).to.be(true); 75 | }); 76 | 77 | /** 78 | * Test if Gruntfile.js is created correctly 79 | */ 80 | it('should generate Gruntfile.js', () => { 81 | projectGenerator.createGruntFiles(); 82 | expect(fs.existsSync(projectPath)).to.be(true); 83 | expect(fs.existsSync(path.join(projectPath, 'Gruntfile.js'))).to.be(true); 84 | }); 85 | 86 | /** 87 | * Test if bower files are created correctly 88 | */ 89 | it('should generate Bower files', () => { 90 | projectGenerator.createBowerFiles(); 91 | expect(fs.existsSync(projectPath)).to.be(true); 92 | expect(fs.existsSync(path.join(projectPath, 'bower.json'))).to.be(true); 93 | expect(fs.existsSync(path.join(projectPath, '.bowerrc'))).to.be(true); 94 | }); 95 | 96 | }); -------------------------------------------------------------------------------- /examples/simple-blog/src/Post/Model/Post.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | import { Model } from 'rode/loader'; 3 | 4 | /*********************************** 5 | * NOTE: 6 | * You could export directly the mongoose schema and work with mongoose objects, and this example will have the 7 | * same results, but is a good practice to not expose the mongoose schema outside of this model. 8 | ***********************************/ 9 | 10 | /** 11 | * Post Model 12 | */ 13 | export class Post extends Model { 14 | 15 | constructor(data) { 16 | super(data); 17 | } 18 | 19 | /** 20 | * Store the model 21 | * 22 | * @return {Promise} 23 | */ 24 | save() { 25 | // validate the tags 26 | if (!Array.isArray(this.tags)) { 27 | this.tags = this.tags.split(','); 28 | } 29 | this.tags = this.tags.map(tag => tag.trim()); 30 | 31 | this.posted = Date.now(); 32 | return new Promise((resolve, reject) => { 33 | var doc = new Post._mongooseModel(this); 34 | doc.save(err => { 35 | if (err) return reject(err); 36 | this._loadObject(doc._doc); 37 | resolve(); 38 | }); 39 | }); 40 | } 41 | 42 | /** 43 | * Load an object on the model 44 | * 45 | * @param obj 46 | * @private 47 | */ 48 | _loadObject(obj) { 49 | for (var key in obj) { 50 | if (obj.hasOwnProperty(key)) { 51 | this[key] = obj[key]; 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Promise a post with the specific id 58 | * 59 | * @param id 60 | * @return {Promise.} 61 | */ 62 | static findById(id) { 63 | return new Promise((resolve, reject) => { 64 | Post._mongooseModel 65 | .findById(id) 66 | .exec((err, doc) => { 67 | if (err) return reject(err); 68 | resolve(doc && new Post(doc._doc)); 69 | }); 70 | }); 71 | } 72 | 73 | /** 74 | * Promise an array of posts order by posted time 75 | * 76 | * @param {number} [skip=0] 77 | * @param {number} [limit=10] 78 | * @return {Promise.} 79 | */ 80 | static findLatestPosted(skip = 0, limit = 10) { 81 | return new Promise((resolve, reject) => { 82 | Post._mongooseModel 83 | .find({}, {}, { 84 | skip: skip, 85 | limit: limit, 86 | sort: { 87 | posted: -1 88 | } 89 | }) 90 | .exec((err, docs) => { 91 | var posts = []; 92 | if (err) return reject(err); 93 | docs.forEach(doc => posts.push(new Post(doc._doc))); 94 | resolve(posts); 95 | }); 96 | }); 97 | } 98 | 99 | /** 100 | * Promise the number of posts 101 | * 102 | * @return {Promise} 103 | */ 104 | static count() { 105 | return new Promise((resolve, reject) => { 106 | Post._mongooseModel 107 | .count() 108 | .exec((err, count) => { 109 | if (err) return reject(err); 110 | resolve(count); 111 | }); 112 | }); 113 | } 114 | 115 | /** 116 | * Returns the mongoose schema for this model 117 | * 118 | * @private 119 | */ 120 | static get _schema() { 121 | return new mongoose.Schema({ 122 | title: String, 123 | body: String, 124 | tags: [ String ], 125 | posted: Date 126 | }); 127 | } 128 | 129 | /** 130 | * Returns the mongoose model and ensure that will be compiled once 131 | * 132 | * @private 133 | */ 134 | static get _mongooseModel() { 135 | if (!this.__mongooseModel) { 136 | this.__mongooseModel = mongoose.model('Post', Post._schema); 137 | } 138 | return this.__mongooseModel; 139 | } 140 | } -------------------------------------------------------------------------------- /src/Package/Tests/PackageTest.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('expect.js'); 3 | import { Package } from '../Package'; 4 | import { Router } from '../../Router/Router'; 5 | 6 | describe('Package', () => { 7 | var packName = 'Package'; 8 | var packPath = path.join(__rodeBase, `mockApp/src/${packName}`); 9 | var pack; 10 | 11 | beforeEach(() => pack = new Package(packName, packPath)); 12 | 13 | describe('get file paths', () => { 14 | 15 | it('should return the package path if is called without parameters', () => { 16 | expect(pack.getPath()).to.be(packPath); 17 | }); 18 | 19 | it('should return the path of the file specified', () => { 20 | expect(pack.getPath('routes.js')).to.be(path.join(packPath, 'routes.js')); 21 | expect(pack.getPath('Controller/PackageController.js')).to.be(path.join(packPath, 'Controller/PackageController.js')); 22 | expect(pack.getPath('SOME TEXT')).to.be(path.join(packPath, 'SOME TEXT')); 23 | }); 24 | 25 | }); 26 | 27 | describe('get controllers', () => { 28 | 29 | it('should return the default controller if is called without parameters', () => { 30 | var controller = pack.getController(); 31 | expect(controller.name).to.be(`${pack.name}Controller`); 32 | }); 33 | 34 | it('should return the controller specified', () => { 35 | var controller = pack.getController('Another'); 36 | expect(controller.name).to.be('AnotherController'); 37 | }); 38 | 39 | it('should throw an error if the controller not exists', () => { 40 | expect(() => pack.getController('nonExistent')).to.throwError(); 41 | }); 42 | 43 | }); 44 | 45 | describe('routes', () => { 46 | 47 | it('should return the path to routes file', () => { 48 | expect(pack.routesPath).to.be(path.join(packPath, 'routes.js')); 49 | }); 50 | 51 | it('should return the router of the package', () => { 52 | var router = pack.router; 53 | expect(router).to.be.a(Router); 54 | expect(router.pack).to.be(pack); 55 | }); 56 | 57 | it('should not throw an error if the package not have routes file', () => { 58 | var pack3Name = 'Package3'; 59 | var pack3Path = path.join(__rodeBase, `mockApp/src/${packName}`); 60 | var pack3 = new Package(pack3Name, pack3Path); 61 | expect(() => pack3.router).to.not.throwError(); 62 | }); 63 | }); 64 | 65 | describe('exists', () => { 66 | var pack2 = new Package('not exists', '/this_package_not_exists'); 67 | 68 | /** 69 | * Test if exists works correctly 70 | */ 71 | it('should check if a package exists', done => { 72 | var count = 0; 73 | var expected = 2; 74 | var checkDone = function () { 75 | if (++count === expected) { 76 | done(); 77 | } 78 | }; 79 | pack.exists() 80 | .then(exists => { 81 | expect(exists).to.be(true); 82 | checkDone(); 83 | }); 84 | pack2.exists() 85 | .then(exists => { 86 | expect(exists).to.be(false); 87 | checkDone(); 88 | }); 89 | }); 90 | 91 | /** 92 | * Test if existsSync works correctly 93 | */ 94 | it('should check if a package exists synchronously', () => { 95 | expect(pack.existsSync()).to.be(true); 96 | expect(pack2.existsSync()).to.be(false); 97 | }); 98 | 99 | /** 100 | * Test if exists works with specific resources 101 | */ 102 | it('should check if a resource exists', done => { 103 | var count = 0; 104 | var expected = 2; 105 | var checkDone = function () { 106 | if (++count === expected) { 107 | done(); 108 | } 109 | }; 110 | pack.exists('Controller/AnotherController.js') 111 | .then(exists => { 112 | expect(exists).to.be(true); 113 | checkDone(); 114 | }); 115 | pack.exists('NON EXISTENT RESOURCE') 116 | .then(exists => { 117 | expect(exists).to.be(false); 118 | checkDone(); 119 | }); 120 | }); 121 | 122 | /** 123 | * Test if exists works with specific resources synchronously 124 | */ 125 | it('should check if a resource exists synchronously', () => { 126 | expect(pack.existsSync('Controller/AnotherController.js')).to.be(true); 127 | expect(pack.existsSync('NON EXISTENT RESOURCE')).to.be(false); 128 | }); 129 | 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /src/MVC/Tests/ObservableTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { Observable } from '../Observable'; 3 | 4 | describe('Observable', () => { 5 | var eventKey = 'event'; 6 | var observable; 7 | 8 | beforeEach(function () { 9 | observable = new Observable(); 10 | }); 11 | 12 | /** 13 | * Add listeners 14 | */ 15 | describe('on', () => { 16 | 17 | /** 18 | * Test if an event listener is registered 19 | */ 20 | it('should register an event listener', () => { 21 | observable.on(eventKey, val => console.log); 22 | expect(observable.listenerCount(eventKey)).to.be(1); 23 | }); 24 | 25 | /** 26 | * Test if multiple events are registered 27 | */ 28 | it('should register multiple listeners for an event', () => { 29 | observable.on(eventKey, val => console.log); 30 | observable.on(eventKey, val => console.info); 31 | observable.on(eventKey, val => console.warn); 32 | expect(observable.listenerCount(eventKey)).to.be(3); 33 | }); 34 | 35 | }); 36 | 37 | /** 38 | * Remove listeners 39 | */ 40 | describe('off', () => { 41 | 42 | /** 43 | * Test if an event listener is removed 44 | */ 45 | it('should remove a specific listener of an event', () => { 46 | var callback = function () {}; 47 | observable.on(eventKey, callback); 48 | observable.off(eventKey, callback); 49 | expect(observable.listenerCount(eventKey)).to.be(0); 50 | }); 51 | 52 | /** 53 | * Test if all the event listeners are removed 54 | */ 55 | it('should remove all the listeners of an event', () => { 56 | observable.on(eventKey, console.log); 57 | observable.on(eventKey, console.info); 58 | observable.on(eventKey, console.warn); 59 | observable.off(eventKey); 60 | expect(observable.listenerCount(eventKey)).to.be(0); 61 | }); 62 | 63 | }); 64 | 65 | /** 66 | * Trigger events 67 | */ 68 | describe('trigger', () => { 69 | 70 | /** 71 | * Test if a listener is triggered 72 | */ 73 | it('should trigger an event', () => { 74 | var called = false; 75 | observable.on(eventKey, () => called = true); 76 | observable.trigger(eventKey); 77 | expect(called).to.be(true); 78 | }); 79 | 80 | /** 81 | * Test if a listener is triggered, and receive all the parameters 82 | */ 83 | it('should trigger an event with multiple parameters', () => { 84 | var called = false; 85 | observable.on(eventKey, (val1, val2, val3) => { 86 | expect(val1).to.be('value 1'); 87 | expect(val2).to.be('value 2'); 88 | expect(val3).to.be('value 3'); 89 | called = true; 90 | }); 91 | observable.trigger(eventKey, 'value 1', 'value 2', 'value 3'); 92 | expect(called).to.be(true); 93 | }); 94 | 95 | /** 96 | * Test if a listener is triggered multiple times 97 | */ 98 | it('should call the listener function once per event triggered', () => { 99 | var called = 0; 100 | observable.on(eventKey, () => called++); 101 | observable.trigger(eventKey); 102 | observable.trigger(eventKey); 103 | observable.trigger(eventKey); 104 | expect(called).to.be(3); 105 | }); 106 | 107 | /** 108 | * Test if a listener registered with "once" only is triggered once 109 | */ 110 | it('should trigger an event once', () => { 111 | var called = 0; 112 | observable.once(eventKey, val => called++); 113 | observable.trigger(eventKey); 114 | observable.trigger(eventKey); 115 | observable.trigger(eventKey); 116 | expect(called).to.be(1); 117 | }); 118 | 119 | /** 120 | * Test if listeners without events throw an error 121 | */ 122 | it('should not throw an error if there are no listeners for an event', () => { 123 | expect(() => observable.trigger(eventKey)).to.not.throwError(); 124 | }); 125 | 126 | }); 127 | 128 | /** 129 | * Test if has static isObservable equals to true 130 | */ 131 | it('should be observable', () => { 132 | expect(Observable.isObservable).to.be(true); 133 | }); 134 | 135 | /** 136 | * Test isolation between multiple instances 137 | */ 138 | it('should be executed isolated from other instances', () => { 139 | var observable2 = new Observable(); 140 | var called = 0; 141 | var called2 = 0; 142 | observable.on(eventKey, () => called++); 143 | observable2.on(eventKey, () => called2++); 144 | observable.trigger(eventKey); 145 | observable.trigger(eventKey); 146 | observable.trigger(eventKey); 147 | observable2.trigger(eventKey); 148 | expect(called).to.be(3); 149 | expect(called2).to.be(1); 150 | }); 151 | }); -------------------------------------------------------------------------------- /src/Server/Server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var http = require('http'); 3 | var url = require('url'); 4 | var express = require('express'); 5 | var _ = require('underscore'); 6 | import { ViewEngine } from '../MVC/ViewEngine'; 7 | 8 | export class Server { 9 | 10 | constructor() { 11 | this.express = express; 12 | this.app = express(); 13 | this.isRunning = false; 14 | } 15 | 16 | /** 17 | * Run the app 18 | * 19 | * @return {Promise} 20 | */ 21 | run() { 22 | return new Promise(resolve => { 23 | if (this.isRunning) { 24 | resolve(); 25 | return; 26 | } 27 | this.server = http.createServer(this.app) 28 | .listen(this.app.get('port'), () => { 29 | this.isRunning = true; 30 | resolve(); 31 | }); 32 | }); 33 | } 34 | 35 | /** 36 | * Stop the app 37 | */ 38 | stop() { 39 | if (!this.isRunning) { 40 | resolve(); 41 | return; 42 | } 43 | this.server.close(); 44 | this.isRunning = false; 45 | } 46 | 47 | /** 48 | * Apply all the configurations to the server 49 | * 50 | * @param {Core} core 51 | */ 52 | config(core) { 53 | var config = core.options; 54 | 55 | // Config Port 56 | var port = process.env.PORT || config.get('port') || 3000; 57 | config.set('port', port); 58 | this.app.set('port', port); 59 | 60 | // Config Host 61 | if (config.has('baseUri') && !config.has('host')) { 62 | var host = url.parse(config.get('baseUri')).host; 63 | if (host.startsWith('www.')) { 64 | host = host.replace('www.', ''); 65 | } 66 | config.set('host', host); 67 | } 68 | 69 | // Config Favicon 70 | if (config.has('favicon')) { 71 | this.app.use(config.get('favicon')); 72 | } 73 | 74 | // Config Logger 75 | if (config.has('logger')) { 76 | this.app.use(config.get('logger')); 77 | } 78 | 79 | // Config CSS 80 | switch (config.get('css')) { 81 | case 'less': 82 | this.app.use(require('less-middleware')( 83 | core.getPath('statics'), { 84 | compress: core.env === 'production' 85 | } 86 | )); 87 | break; 88 | case 'stylus': 89 | this.app.use(require('stylus').middleware(core.getPath('statics'))); 90 | } 91 | 92 | // Config Statics 93 | this.app.use(this.express.static(core.getPath('statics'))); 94 | 95 | // Config database 96 | if (config.has('mongo') && config.get('mongo').uri) { 97 | core.db = new (require('../DB/Mongo').Mongo)(config.get('mongo').uri, config.get('mongo').options); 98 | return core.db.connect(); 99 | } 100 | 101 | // Ensure that the response is a promise 102 | return new Promise(resolve => resolve()); 103 | } 104 | 105 | /** 106 | * Config the view engine 107 | * 108 | * @return {Promise} 109 | */ 110 | configViews(views) { 111 | var viewEngine = ViewEngine.createInstance(this.app, views); 112 | viewEngine.config(); 113 | return viewEngine.compile(); 114 | } 115 | 116 | /** 117 | * Create app routes on express 118 | * 119 | * @param {List.} packages 120 | */ 121 | createRoutes(packages) { 122 | packages.forEach(pack => this.addRoutes(pack.router)); 123 | } 124 | 125 | /** 126 | * Add all the routes of a Router to express 127 | * 128 | * @param {Router} router 129 | */ 130 | addRoutes(router) { 131 | if (!router) { 132 | return; 133 | } 134 | router.forEach(route => { 135 | this.count++; 136 | var controller = router.pack.getController(route.controller); 137 | var controllerInstance = new controller; 138 | var call = controllerInstance[route.action]; 139 | var pattern = path.join(router.base, route.pattern); 140 | 141 | // Add api prefix to REST routes 142 | if (route.isRestful) { 143 | pattern = path.join(router.restApi, pattern); 144 | } 145 | 146 | // Fix routes patterns across the different OS. 147 | if (pattern.indexOf('\\') !== -1) { 148 | pattern = S(pattern).replaceAll('\\', '/').s; 149 | } 150 | while (pattern.indexOf('//') !== -1) { 151 | pattern = S(pattern).replaceAll('//', '/').s; 152 | } 153 | if (pattern.endsWith('/')) { 154 | pattern = pattern.substring(0, pattern.length - 1); 155 | } 156 | 157 | // If call has not arguments, should be a function with middlewares 158 | if (!call.length) { 159 | var middlewares = call(); 160 | call = middlewares.pop(); 161 | this.app[route.method.toLowerCase()](pattern || '/', ...middlewares); 162 | } 163 | 164 | // Send the route to express 165 | if (!_.isFunction(call)) { 166 | throw new TypeError(`Package ${router.name}: Route action should be a Function. 167 | You need to implement the method '${route.action}' on '${controller.name}'`); 168 | } 169 | 170 | this.app[route.method](pattern || '/', call); 171 | }); 172 | } 173 | } -------------------------------------------------------------------------------- /bin/helpers/utils: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('extfs'); 3 | import { Package } from '../../src/Package/Package'; 4 | import { PackageGenerator } from '../../src/Generator/PackageGenerator'; 5 | import { ProjectGenerator } from '../../src/Generator/ProjectGenerator'; 6 | 7 | export var utils = { 8 | 9 | /** 10 | * Generate components 11 | * 12 | * @param {string} component 13 | * @param {string} name 14 | * @param {string} rootPath 15 | * @param {boolean} [rest] 16 | * @param {boolean} [force] 17 | */ 18 | generateComponent(component, name, rootPath, rest = false, force = false) { 19 | var filesPath = path.join(__dirname, '../files'); 20 | var config = this.getConfig(rootPath); 21 | var _package; 22 | var packageName; 23 | var packagePath; 24 | var packageGenerator; 25 | var viewsPath; 26 | 27 | // find the package name 28 | if (component.toLowerCase() === 'package') { 29 | packageName = name; 30 | } else if (~component.indexOf(':')) { 31 | packageName = component.split(':')[0]; 32 | component = component.split(':')[1]; 33 | } else { 34 | this.exit(`The component ${component} is not valid`); 35 | } 36 | 37 | packagePath = path.join(rootPath, config.srcPath || 'src', packageName); 38 | _package = new Package(packageName, packagePath); 39 | 40 | // If we are not generating a package, this should exist 41 | if (component.toLowerCase() !== 'package' && !_package.existsSync()) { 42 | this.exit(`The package '${packageName}' does not exist`); 43 | } 44 | 45 | // Config package generator 46 | viewsPath = path.join(rootPath, config.views ? config.views.path : 'views'); 47 | packageGenerator = new PackageGenerator(_package, viewsPath, filesPath); 48 | packageGenerator.viewEngine = config.views ? config.views.engine : ''; 49 | packageGenerator.addRest = rest; 50 | packageGenerator.force = force; 51 | 52 | switch (component.toLowerCase()) { 53 | case 'package': 54 | packageGenerator.create(); 55 | break; 56 | case 'controller': 57 | packageGenerator.createController(name); 58 | break; 59 | case 'model': 60 | packageGenerator.createModel(name); 61 | break; 62 | case 'routes': 63 | packageGenerator.createRoutes(); 64 | break; 65 | default: 66 | this.exit(`The component ${component} is not valid`); 67 | } 68 | }, 69 | 70 | /** 71 | * Generate a new project 72 | * 73 | * @param {string} projectPath 74 | * @param {string} viewEngine 75 | * @param {string} cssTemplate 76 | * @param {boolean} sessions 77 | */ 78 | generateProject(projectPath, viewEngine, cssTemplate, sessions = false) { 79 | var filesPath = path.join(__dirname, '../files'); 80 | var packageJson = require('../../package.json'); 81 | var version = packageJson.version.split('.'); 82 | var acceptedVersion = version[0] + '.' + version[1] + '.x'; 83 | var projectGenerator = new ProjectGenerator(projectPath, filesPath, acceptedVersion); 84 | projectGenerator.viewEngine = viewEngine; 85 | projectGenerator.cssTemplate = cssTemplate; 86 | projectGenerator.sessions = sessions; 87 | projectGenerator.create(); 88 | }, 89 | 90 | /** 91 | * Extract the path from an array 92 | * 93 | * @param array 94 | * @returns {string} path 95 | */ 96 | extractPath(array) { 97 | var count = array.length; 98 | for (var i = 0; i < count; i++) { 99 | if (array[i][0] === '/') { 100 | return array.splice(i, 1)[0]; 101 | } 102 | } 103 | return null; 104 | }, 105 | 106 | /** 107 | * Find root path for app 108 | * 109 | * @param {string} [currentPath] 110 | * @returns {string} path 111 | */ 112 | findRootPath(currentPath = __dirname) { 113 | var err = false; 114 | while (currentPath !== '/') { 115 | currentPath = path.resolve(currentPath); 116 | if (this.isRootPath(currentPath)) { 117 | return currentPath; 118 | } 119 | currentPath = path.join(currentPath, '../'); 120 | } 121 | return null; 122 | }, 123 | 124 | /** 125 | * Check if path is the root path of the app 126 | * 127 | * @param {string} currentPath 128 | * @returns {boolean} 129 | */ 130 | isRootPath(currentPath) { 131 | var files = fs.readdirSync(currentPath); 132 | return files.indexOf('package.json') !== -1 && files.indexOf('config') !== -1; 133 | }, 134 | 135 | /** 136 | * Get the config options from config.json 137 | * 138 | * @param {string} rootPath 139 | */ 140 | getConfig(rootPath) { 141 | var configPath = path.join(rootPath, 'config/config.json'); 142 | return require(configPath); 143 | }, 144 | 145 | /** 146 | * Set error when no installation is detected 147 | * 148 | * @param {string} currentPath 149 | */ 150 | errorNotRode(currentPath) { 151 | this.exit(`Can not find a rode.js installation in ${path.resolve(currentPath)}`); 152 | }, 153 | 154 | /** 155 | * Finish the process 156 | * 157 | * @param [error] if is defined, shows the error 158 | */ 159 | exit(error) { 160 | if (error) { 161 | console.error(` \x1b[31m${error}\x1b[0m`); 162 | } 163 | process.exit(1); 164 | } 165 | }; -------------------------------------------------------------------------------- /src/Generator/ProjectGenerator.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var mkdirp = require('mkdirp'); 4 | import { PackageGenerator } from './PackageGenerator'; 5 | import { Package } from '../Package/Package'; 6 | import { Template } from '../Util/Template'; 7 | 8 | export class ProjectGenerator { 9 | 10 | constructor(projectPath, filesPath, acceptedVersion) { 11 | this.projectPath = projectPath; 12 | this.filesPath = filesPath; 13 | this.acceptedVersion = acceptedVersion; 14 | var packagePath = path.join(projectPath, 'src/Main'); 15 | var viewsPath = path.join(projectPath, 'views'); 16 | this.mainPackage = new Package('Main', packagePath); 17 | this.packageGenerator = new PackageGenerator(this.mainPackage, viewsPath, filesPath); 18 | } 19 | 20 | /** 21 | * Creates a new project 22 | */ 23 | create() { 24 | this.createMainFiles(this.sessions); 25 | this.createMainPackage(); 26 | this.createConfigs(this.viewEngine, this.cssTemplate); 27 | this.createScripts(); 28 | this.createStyles(this.cssTemplate); 29 | this.createPackageJSON(this.cssTemplate); 30 | this.createGruntFiles(); 31 | this.createBowerFiles(); 32 | } 33 | 34 | /** 35 | * Creates app.js and setup.js files 36 | */ 37 | createMainFiles(sessions = false) { 38 | var appTemplate = new Template(this._getTemplate('app')); 39 | var templateVars = { 40 | sessions: sessions ? 'app.use(express.cookieParser(\'your secret here\'));' : '' 41 | }; 42 | this._write('app.js', appTemplate.render(templateVars)); 43 | this._write('setup.js', this._getTemplate('setup')); 44 | } 45 | 46 | /** 47 | * Creates the Main package 48 | */ 49 | createMainPackage() { 50 | this.packageGenerator.viewEngine = this.viewEngine; 51 | this.packageGenerator.addLayout = true; 52 | this.packageGenerator.force = true; 53 | this.packageGenerator.create(); 54 | } 55 | 56 | /** 57 | * Creates the configuration files 58 | * 59 | * @param {string} [viewEngine] 60 | * @param {string} [cssTemplate] 61 | */ 62 | createConfigs(viewEngine = 'jade', cssTemplate = 'css') { 63 | var configTemplate = new Template(this._getTemplate('config/config')); 64 | var developmentTemplate = new Template(this._getTemplate('config/development')); 65 | var productionTemplate = new Template(this._getTemplate('config/production')); 66 | var testTemplate = new Template(this._getTemplate('config/test')); 67 | var templateVars = { 68 | cssTemplate: cssTemplate, 69 | viewEngine: viewEngine 70 | }; 71 | this._write('config/config.json', configTemplate.render(templateVars)); 72 | this._write('config/development.json', developmentTemplate.render(templateVars)); 73 | this._write('config/production.json', productionTemplate.render(templateVars)); 74 | this._write('config/test.json', testTemplate.render(templateVars)); 75 | } 76 | 77 | /** 78 | * Creates the folder for scripts 79 | */ 80 | createScripts() { 81 | this._mkdir('public/js'); 82 | } 83 | 84 | /** 85 | * Creates the files for the styles 86 | * 87 | * @param {string} [cssTemplate] 88 | */ 89 | createStyles(cssTemplate = 'css') { 90 | var styles = { 91 | css: 'css', 92 | less: 'less', 93 | stylus: 'styl' 94 | }; 95 | if (!styles[cssTemplate]) { 96 | throw new Error(`Style "${cssTemplate}" not yet supported.`); 97 | } 98 | this._write(`public/css/style.${styles[cssTemplate]}`, this._getTemplate(`public/css/${cssTemplate}`)); 99 | } 100 | 101 | /** 102 | * Creates the package.json file 103 | * 104 | * @param {string} [cssTemplate] 105 | */ 106 | createPackageJSON(cssTemplate = 'css') { 107 | var packageTemplate = new Template(this._getTemplate('package')); 108 | var templateVars = { 109 | acceptedVersion: this.acceptedVersion, 110 | css: cssTemplate 111 | }; 112 | this._write('package.json', packageTemplate.render(templateVars)); 113 | } 114 | 115 | /** 116 | * Create the Gruntfile.js file 117 | */ 118 | createGruntFiles() { 119 | this._write('Gruntfile.js', this._getTemplate('gruntfile')); 120 | } 121 | 122 | /** 123 | * Creates all the bower files 124 | */ 125 | createBowerFiles() { 126 | this._write('bower.json', this._getTemplate('bower')); 127 | this._write('.bowerrc', this._getTemplate('bowerrc')); 128 | } 129 | 130 | /** 131 | * Creates a file inside the package 132 | * 133 | * @param {string} filePath 134 | * @param {string} str 135 | */ 136 | _write(filePath, str) { 137 | if (!filePath.startsWith('/')) { 138 | filePath = path.join(this.projectPath, filePath); 139 | } 140 | this._mkdir(path.dirname(filePath)); 141 | fs.writeFile(filePath, str); 142 | console.log(` \x1b[36mcreated\x1b[0m : ${filePath}`); 143 | } 144 | 145 | /** 146 | * Creates a directory 147 | * 148 | * @param {string} dirPath 149 | * @private 150 | */ 151 | _mkdir(dirPath) { 152 | if (!dirPath.startsWith('/')) { 153 | dirPath = path.join(this.projectPath, dirPath); 154 | } 155 | mkdirp.sync(dirPath, '0755'); 156 | } 157 | 158 | /** 159 | * Returns the content of a template file 160 | * 161 | * @param {string} relativePath 162 | * @return {string} 163 | * @private 164 | */ 165 | _getTemplate(relativePath) { 166 | return fs.readFileSync(path.join(this.filesPath, `${relativePath}.template`)) 167 | .toString(); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/Router/Router.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var _ = require('underscore'); 3 | var S = require('string'); 4 | import { List } from '../Util/List'; 5 | import { InvalidParamsError } from '../Error/InvalidParamsError'; 6 | 7 | export class Router { 8 | 9 | constructor() { 10 | this.routes = new List; 11 | } 12 | 13 | /** 14 | * Add a new route to the router 15 | * 16 | * @param route 17 | * @return {Router} 18 | * @throws InvalidParamsError 19 | */ 20 | add(route) { 21 | if (!route.action) { 22 | throw new InvalidParamsError('Route requires an action'); 23 | } 24 | 25 | // default parameters 26 | route = _.defaults(route, { 27 | pattern: '', 28 | method: 'get' 29 | }); 30 | 31 | this.routes.add(route); 32 | return this; 33 | } 34 | 35 | /** 36 | * Find all the routes that match a predicate 37 | * 38 | * @param {Function} predicate 39 | * @return {List} 40 | */ 41 | filter(predicate) { 42 | return this.routes.filter(predicate); 43 | } 44 | 45 | /** 46 | * Find all the routes by action 47 | * 48 | * @param {string} action 49 | * @return {List} 50 | */ 51 | filterByAction(action) { 52 | return this.filter(route => action === route.action); 53 | } 54 | 55 | /** 56 | * Find all the routes by pattern 57 | * 58 | * @param {string} pattern 59 | * @return {List} 60 | */ 61 | filterByPattern(pattern) { 62 | return this.filter(route => pattern === route.pattern); 63 | } 64 | 65 | /** 66 | * Find the first route that match a predicate 67 | * 68 | * @param {Function} predicate 69 | * @return {*} 70 | */ 71 | find(predicate) { 72 | return this.routes.find(predicate); 73 | } 74 | 75 | /** 76 | * Find the first route that match an action 77 | * 78 | * @param {string} action 79 | * @return {*} 80 | */ 81 | findByAction(action) { 82 | return this.find(route => action === route.action); 83 | } 84 | 85 | /** 86 | * Find the first route that match a pattern 87 | * 88 | * @param {string} pattern 89 | * @return {*} 90 | */ 91 | findByPattern(pattern) { 92 | return this.find(route => pattern === route.pattern); 93 | } 94 | 95 | /** 96 | * Remove all the routes that match the predicate 97 | * 98 | * @param {Function} predicate 99 | */ 100 | remove(predicate) { 101 | var removes = this.filter(predicate); 102 | removes.forEach(route => this.routes.removeElement(route)); 103 | } 104 | 105 | /** 106 | * Remove the first route that match the predicate 107 | * 108 | * @param {Function} predicate 109 | */ 110 | removeOne(predicate) { 111 | var remove = this.find(predicate); 112 | this.routes.removeElement(remove); 113 | } 114 | 115 | /** 116 | * Iterate normal and rest routes and invoke `fn(el, i)` 117 | * 118 | * @param {Function} callback 119 | * @return {*} 120 | */ 121 | forEach(callback) { 122 | return this.getAll().forEach(callback); 123 | } 124 | 125 | /** 126 | * Returns a route by index 127 | * 128 | * @param {number} index 129 | * @return {*} 130 | */ 131 | get(index) { 132 | return this.routes[index]; 133 | } 134 | 135 | /** 136 | * Returns all the routes 137 | * 138 | * @returns {List} 139 | */ 140 | getAll() { 141 | var list = new List; 142 | list.add(this.routes); 143 | list.add(this.restRoutes); 144 | return list; 145 | } 146 | 147 | /** 148 | * Transform the REST method name to an url 149 | * 150 | * @param {Array} parts 151 | * @returns {string} 152 | */ 153 | static transformRestRoute(parts) { 154 | var routePath = '', 155 | ids = 0; 156 | if (!_.isArray(parts)) { 157 | return ''; 158 | } 159 | parts.forEach(part => { 160 | if (part === 'Byid') { 161 | routePath += ++ids > 1 ? `:id${ids}/` : ':id/'; 162 | } else { 163 | routePath += `${part}/`; 164 | } 165 | }); 166 | return routePath; 167 | } 168 | 169 | /** 170 | * Returns the list of REST routes 171 | * 172 | * @return {Array} 173 | */ 174 | get restRoutes() { 175 | if (!this.isRestful) { 176 | return []; 177 | } 178 | 179 | var controllerName = this.restController || 'Rest'; 180 | var controller = this.pack.getController(controllerName); 181 | var routes = [], 182 | routePath, 183 | method, 184 | parts; 185 | 186 | // loop over each action 187 | for (var action in controller.prototype) { 188 | method = action.match(/[a-z]+/)[0]; 189 | parts = S(action).replaceAll('ById','Byid').s.match(/[A-Z][a-z]+/g); 190 | routePath = Router.transformRestRoute(parts); 191 | if (routePath.endsWith('/')) { 192 | routePath = routePath.substring(0, routePath.length - 1); 193 | } 194 | routes.push({ 195 | controller: controllerName, 196 | pattern: routePath, 197 | action: action, 198 | method: method, 199 | isRestful: true 200 | }); 201 | } 202 | 203 | return routes; 204 | } 205 | 206 | /** 207 | * Returns the package name 208 | * 209 | * @return {string} 210 | */ 211 | get name() { 212 | return this.pack.name; 213 | } 214 | 215 | /** 216 | * Returns the number of routes 217 | * 218 | * @return {number} 219 | */ 220 | get length() { 221 | return this.routes.length; 222 | } 223 | 224 | /** 225 | * Returns if the route is restful 226 | * 227 | * @return {boolean} 228 | */ 229 | get isRestful() { 230 | return !!this.restApi; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Generator/Tests/PackageGeneratorTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var path = require('path'); 3 | var fs = require('extfs'); 4 | import { PackageGenerator } from '../PackageGenerator'; 5 | import { Package } from '../../Package/Package'; 6 | 7 | describe('PackageGenerator', () => { 8 | 9 | describe('Generate Components', () => { 10 | var packageName = 'FakePackage'; 11 | var packagePath = path.join(__rodeBase, `tmp/${packageName}`); 12 | var viewsPath = path.join(__rodeBase, 'tmp/views/'); 13 | var filesPath = path.join(__rodeBase, 'bin/files'); 14 | var _package; 15 | var packageGenerator; 16 | 17 | /** 18 | * Creates a new Package and PackageGenerator 19 | */ 20 | beforeEach(() => { 21 | _package = new Package(packageName, packagePath); 22 | packageGenerator = new PackageGenerator(_package, viewsPath, filesPath); 23 | }); 24 | 25 | /** 26 | * Removes the content of the temporal folder 27 | */ 28 | afterEach(() => { 29 | if (fs.existsSync(packagePath)) { 30 | fs.removeSync(packagePath); 31 | } 32 | if (fs.existsSync(viewsPath)) { 33 | fs.removeSync(viewsPath); 34 | } 35 | }); 36 | 37 | /** 38 | * Test if the files of a new controller are created correctly 39 | */ 40 | it('should generate a new controller', () => { 41 | var controllerName = 'fakeController'; 42 | packageGenerator.createController(controllerName); 43 | expect(fs.existsSync(_package.path)).to.be(true); 44 | expect(fs.existsSync(_package.getPath(`Controller/${controllerName}.js`))).to.be(true); 45 | expect(fs.existsSync(_package.getPath(`Tests/Controller/${controllerName}Test.js`))).to.be(true); 46 | }); 47 | 48 | /** 49 | * Test if the files of a new REST controller are created correctly 50 | */ 51 | it('should generate a new REST controller', () => { 52 | packageGenerator.createRestController(); 53 | expect(fs.existsSync(_package.path)).to.be(true); 54 | expect(fs.existsSync(_package.getPath(`Controller/RestController.js`))).to.be(true); 55 | expect(fs.existsSync(_package.getPath(`Tests/Controller/RestControllerTest.js`))).to.be(true); 56 | }); 57 | 58 | /** 59 | * Test if the files of a new model are created correctly 60 | */ 61 | it('should generate a new model', () => { 62 | var modelName = 'fake'; 63 | packageGenerator.createModel(modelName); 64 | expect(fs.existsSync(_package.path)).to.be(true); 65 | expect(fs.existsSync(_package.getPath(`Model/${modelName}.js`))).to.be(true); 66 | expect(fs.existsSync(_package.getPath(`Tests/Model/${modelName}Test.js`))).to.be(true); 67 | }); 68 | 69 | /** 70 | * Test if the routes file is created correctly 71 | */ 72 | it('should generate the routes file', () => { 73 | packageGenerator.createRoutes(); 74 | expect(fs.existsSync(_package.path)).to.be(true); 75 | }); 76 | 77 | /** 78 | * Test if the jade views are created correctly 79 | */ 80 | it('should generate the "jade" views', () => { 81 | packageGenerator.createViews('jade'); 82 | expect(fs.existsSync(path.join(viewsPath, `${_package.name}/index.jade`))).to.be(true); 83 | }); 84 | 85 | /** 86 | * Test if the jade layout is created correctly 87 | */ 88 | it('should generate the "jade" layout', () => { 89 | packageGenerator.createViews('jade', true); 90 | expect(fs.existsSync(path.join(viewsPath, `layout.jade`))).to.be(true); 91 | }); 92 | 93 | /** 94 | * Test if the ejs views are created correctly 95 | */ 96 | it('should generate the "ejs" views', () => { 97 | packageGenerator.createViews('ejs'); 98 | expect(fs.existsSync(path.join(viewsPath, `${_package.name}/index.ejs`))).to.be(true); 99 | }); 100 | 101 | /** 102 | * Test if the ejs layout is created correctly 103 | */ 104 | it('should generate the "ejs" layout', () => { 105 | packageGenerator.createViews('ejs', true); 106 | expect(fs.existsSync(path.join(viewsPath, `layout.ejs`))).to.be(true); 107 | }); 108 | 109 | /** 110 | * Test if the hjs views are created correctly 111 | */ 112 | it('should generate the "hjs" views', () => { 113 | packageGenerator.createViews('hjs'); 114 | expect(fs.existsSync(path.join(viewsPath, `${_package.name}/index.hjs`))).to.be(true); 115 | }); 116 | 117 | /** 118 | * Test if the soy views are created correctly 119 | */ 120 | it('should generate the "soy" views', () => { 121 | packageGenerator.createViews('soy'); 122 | expect(fs.existsSync(path.join(viewsPath, `${_package.name}/index.soy`))).to.be(true); 123 | }); 124 | 125 | /** 126 | * Test if the files of a new package are created correctly 127 | */ 128 | it('should create a package files', () => { 129 | packageGenerator.create(); 130 | expect(fs.existsSync(_package.path)).to.be(true); 131 | expect(fs.existsSync(_package.getPath(`Controller/${_package.name}Controller.js`))).to.be(true); 132 | expect(fs.existsSync(_package.getPath(`Model/${_package.name}.js`))).to.be(true); 133 | expect(fs.existsSync(_package.getPath(`Tests/Controller/${_package.name}ControllerTest.js`))).to.be(true); 134 | expect(fs.existsSync(_package.getPath(`Tests/Model/${_package.name}Test.js`))).to.be(true); 135 | expect(fs.existsSync(_package.getPath(`routes.js`))).to.be(true); 136 | 137 | // by default the views use "jade" as their view engine 138 | expect(fs.existsSync(path.join(viewsPath, `${_package.name}/index.jade`))).to.be(true); 139 | }); 140 | 141 | }); 142 | 143 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rode.js 2 | ==== 3 | 4 | Packet-Oriented Framework for [ES6](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts) and [Express](http://expressjs.com). 5 | 6 | ##Table of Contents: 7 | 8 | - [ECMAScript 6](#es6) 9 | - [Getting started](#getting-started) 10 | - [Packages](#packages) 11 | - [Models](#models) 12 | - [Controllers](#controllers) 13 | - [Routes](#routes) 14 | - [Middleware on Routes](#middleware-routes) 15 | - [Restful APIs](#restful-apis) 16 | - [Templates engines](#templates-engines) 17 | - [Tests](#tests) 18 | 19 | 20 | ## ECMAScript 6 21 | 22 | rode.js framework is a stable way to use [ES6](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts) today. 23 | [ES6](http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts) support is provided by [traceur](https://npmjs.org/package/traceur), [es6-shim](https://npmjs.org/package/es6-shim) and [es6-module-loader](https://npmjs.org/package/es6-module-loader). 24 | 25 | rode.js is based on [Express](http://expressjs.com), but we not rewrite it, you can still using all the [features of Express](http://expressjs.com/4x/api.html). 26 | 27 | 28 | ## Getting started 29 | 30 | Install rode.js globally and generate a new project: 31 | 32 | # npm install -g rode 33 | $ rode new --view ejs --css less --sessions myProject 34 | 35 | Install project dependencies: 36 | 37 | $ npm install 38 | 39 | Start the server: 40 | 41 | $ node app 42 | 43 | Usage of `rode new`: 44 | 45 | Usage: rode new [options] [dir] 46 | 47 | Options: 48 | 49 | -h, --help output usage information 50 | -V, --version output the version number 51 | -v, --view add view engine support (jade|ejs|hogan|soy) (defaults to jade) 52 | -c, --css add stylesheet support (less|stylus|css) (defaults to plain css) 53 | 54 | 55 | ## Packages 56 | 57 | A package (or bundle) is a component of your application with its own MVC. 58 | 59 | You can simply create a new package with the command: 60 | 61 | $ rode generate PackageName 62 | 63 | Usage: rode generate package 64 | 65 | Options: 66 | 67 | -h, --help output usage information 68 | -r, --rest config rest api for this package 69 | -f, --force force on existing package 70 | 71 | 72 | ## Models 73 | 74 | In a model you should add all your business logic: 75 | 76 | ```js 77 | import { Model } from 'rode/loader'; 78 | 79 | export class MyModel extends Model { 80 | 81 | /** 82 | * Sample method, converts a string to upper case 83 | */ 84 | sampleMethod(str) { 85 | return str.toUpperCase(); 86 | } 87 | 88 | } 89 | ``` 90 | 91 | You can generate a Model `myModel` for a package `User` with the command: 92 | 93 | $ rode generate User:model myModel 94 | 95 | 96 | ## Controllers 97 | 98 | Controllers and Routes works together to facilitate the routing of the application. 99 | 100 | A controller looks like: 101 | 102 | ```js 103 | export class HelloController { 104 | 105 | /** 106 | * sayHello Action 107 | */ 108 | sayHello(req, res) { 109 | res.render('index', { 110 | title: 'Hello World!' 111 | }); 112 | } 113 | 114 | } 115 | ``` 116 | 117 | You can generate a Controller `myController` for a package `User` with the command: 118 | 119 | $ rode generate User:controller myController 120 | 121 | 122 | ## Routes 123 | 124 | A route for the above controller looks like: 125 | 126 | ```js 127 | import { Router } from 'rode/loader'; 128 | var router = new Router(); 129 | 130 | /** 131 | * [GET] / 132 | * Calls HelloController.sayHello 133 | */ 134 | router.add({ 135 | controller: 'Hello', // defaults 'Main' 136 | pattern: '/hello', 137 | action: 'sayHello', 138 | method: 'GET' // defaults 'GET' 139 | }); 140 | 141 | export {router}; 142 | ``` 143 | 144 | 145 | ## Middleware on Routes 146 | 147 | Here's an example of how to define a middleware on routes: 148 | 149 | ```js 150 | export class UserController { 151 | showPrivateData() { 152 | return [ 153 | (req, res, next) => { 154 | // Check permissions 155 | next(); 156 | }, 157 | (req, res) => { 158 | // Show Private Data 159 | } 160 | ]; 161 | } 162 | } 163 | ``` 164 | 165 | 166 | ## Restful APIs 167 | 168 | Make a Restful API can not be more easy. 169 | 170 | Create your REST package with the command: 171 | 172 | $ rode generate package PackageName --rest 173 | 174 | Or add `router.restApi = '/api/products';` on the `routes.js` of any package. 175 | 176 | Now you should create methods on your `RestController.js` following simple naming conventions. 177 | 178 | Here are some examples: 179 | 180 | ```js 181 | // [GET] /api/products 182 | get(req, res) { ... } 183 | 184 | // [POST] /api/products 185 | post(req, res) { ... } 186 | 187 | // [GET] /api/products/sponsored 188 | getSponsored(req, res) { ... } 189 | 190 | // [PUT] /api/products/sponsored 191 | putSponsored(req, res) { ... } 192 | 193 | // [POST] /api/products/sponsored/today 194 | postSponsoredToday(req, res) { ... } 195 | 196 | // [GET] /api/products/:id 197 | getById(req, res) { ... } 198 | 199 | // [POST] /api/products/:id 200 | postById(req, res) { ... } 201 | 202 | // [DELETE] /api/products/:id 203 | deleteById(req, res) { ... } 204 | 205 | // [GET] /api/products/:id/comments 206 | getByIdComments(req, res) { ... } 207 | 208 | // [PUT] /api/products/:id/comments/:id2 209 | putByIdCommentsById(req, res) { ... } 210 | ``` 211 | 212 | You can create as many combinations as you like. 213 | Remember that each package can have its own `RestController.js` 214 | 215 | 216 | ## Templates engines 217 | 218 | rode.js supports all this templates engines: 219 | 220 | * [Jade](http://jade-lang.com/) (default template) 221 | * [Ejs](http://embeddedjs.com/) (using [ejs-locals](https://github.com/RandomEtc/ejs-locals)) 222 | * [Hogan.js](http://twitter.github.io/hogan.js/) 223 | * [Google Closure Templates](https://developers.google.com/closure/templates/) 224 | * Since you still can use express, you can use any template that express support 225 | 226 | 227 | ## Tests 228 | 229 | You can run the test with the command: 230 | 231 | $ grunt test 232 | 233 | 234 | ## License 235 | 236 | [MIT](https://github.com/codexar/rode/blob/master/LICENSE) -------------------------------------------------------------------------------- /src/Core/Tests/CoreTest.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var expect = require('expect.js'); 3 | var request = require('request'); 4 | import { Core } from '../Core'; 5 | import { PackageList } from '../../Package/PackageList'; 6 | 7 | describe('Core', () => { 8 | // mock app with 9 | var fakePath = path.join(__rodeBase, 'mockApp'); 10 | 11 | describe('constructor', () => { 12 | 13 | it('should allow to create new instances', () => { 14 | var core = new Core(fakePath); 15 | expect(core).to.be.a(Core); 16 | }); 17 | 18 | it('should allow to pass the environment on construction', () => { 19 | var core = new Core(fakePath, 'production'); 20 | expect(core.env).to.be('production'); 21 | }); 22 | 23 | it('should have default environment', () => { 24 | var core = new Core(fakePath); 25 | expect(core.env).to.be(process.env.NODE_ENV || 'development'); 26 | }); 27 | 28 | it('should throw an exception if path is not specified', () => { 29 | expect(() => new Core).to.throwError(); 30 | }); 31 | 32 | it('should store the last instance of Core as static property', () => { 33 | var core = new Core(fakePath); 34 | expect(Object.is(Core.instance, core)).to.be(true); 35 | }); 36 | 37 | }); 38 | 39 | describe('config', () => { 40 | var core; 41 | 42 | beforeEach(() => core = new Core(fakePath)); 43 | 44 | it('should allow to configure the app', done => { 45 | core.config(app => { 46 | expect(app).to.be.an(Object); 47 | expect(app.use).to.be.a(Function); 48 | done(); 49 | }); 50 | }); 51 | 52 | it('should throw an error if a callback is not passed', () => { 53 | expect(core.config).to.throwError(); 54 | }); 55 | 56 | }); 57 | 58 | describe('get paths', () => { 59 | var core = new Core(fakePath); 60 | 61 | it('should return src path', () => { 62 | var srcPath = core.getPath('src'); 63 | expect(srcPath).to.be(path.join(fakePath, 'src')); 64 | }); 65 | 66 | it('should return views path', () => { 67 | var viewsPath = core.getPath('views'); 68 | expect(viewsPath).to.be(path.join(fakePath, 'views')); 69 | }); 70 | 71 | it('should return statics/public path', () => { 72 | var staticsPath = core.getPath('statics'); 73 | var publicPath = core.getPath('public'); 74 | expect(staticsPath).to.be(publicPath); 75 | expect(publicPath).to.be(path.join(fakePath, 'public')); 76 | }); 77 | 78 | it('should return images path', () => { 79 | var imagesPath = core.getPath('images'); 80 | expect(imagesPath).to.be(path.join(fakePath, 'public/images')); 81 | }); 82 | 83 | it('should return js path', () => { 84 | var javascriptPath = core.getPath('javascript'); 85 | var jsPath = core.getPath('js'); 86 | expect(javascriptPath).to.be(jsPath); 87 | expect(jsPath).to.be(path.join(fakePath, 'public/js')); 88 | }); 89 | 90 | it('should return css path', () => { 91 | var stylesheetsPath = core.getPath('stylesheets'); 92 | var cssPath = core.getPath('css'); 93 | expect(stylesheetsPath).to.be(cssPath); 94 | expect(cssPath).to.be(path.join(fakePath, 'public/css')); 95 | }); 96 | 97 | it('should return node_modules path', () => { 98 | var rootPath = core.getPath('node_modules'); 99 | expect(rootPath).to.be(path.join(fakePath, 'node_modules')); 100 | }); 101 | 102 | it('should return root path', () => { 103 | var rootPath = core.getPath('root'); 104 | expect(rootPath).to.be(fakePath); 105 | }); 106 | }); 107 | 108 | describe('Run app', () => { 109 | var core = new Core(fakePath); 110 | core.packageList = new PackageList; 111 | var protocol; 112 | var host; 113 | var port; 114 | 115 | /** 116 | * Run the server and config protocol, host and port 117 | */ 118 | beforeEach(done => { 119 | core.run() 120 | .then(() => { 121 | protocol = 'http'; 122 | host = core.options.get('host'); 123 | port = core.options.get('port'); 124 | }) 125 | .done(done); 126 | }); 127 | 128 | /** 129 | * Stop the server 130 | */ 131 | afterEach(() => { 132 | if (core.server.isRunning) { 133 | core.stop(); 134 | } 135 | }); 136 | 137 | /** 138 | * Test if the host and port are set correctly 139 | */ 140 | it('should run the app on specified host and port', () => { 141 | expect(core.app.get('port')).to.be(core.options.get('port')); 142 | expect(core.options.get('port')).to.be(3000); 143 | expect(core.options.get('host')).to.be('localhost'); 144 | }); 145 | 146 | /** 147 | * Test if [GET] http://localhost:3000/ is accessible 148 | */ 149 | it('should respond to any route with method GET', done => { 150 | var options = { 151 | uri: `${protocol}://${host}:${port}` 152 | }; 153 | request(options, (err, res, body) => { 154 | expect(err).to.be(null); 155 | expect(body).to.be('[GET] /'); 156 | done(); 157 | }); 158 | }); 159 | 160 | /** 161 | * Test if [POST] http://localhost:3000/another is accessible 162 | */ 163 | it('should respond to any route with method POST', done => { 164 | var options = { 165 | uri: `${protocol}://${host}:${port}/another`, 166 | method: 'POST' 167 | }; 168 | request(options, (err, res, body) => { 169 | expect(err).to.be(null); 170 | expect(body).to.be('[POST] /another'); 171 | done(); 172 | }); 173 | }); 174 | 175 | /** 176 | * Test if [GET] http://localhost:3000/api/pack2 is accessible 177 | */ 178 | it('should respond to any REST route with method GET', done => { 179 | var options = { 180 | uri: `${protocol}://${host}:${port}/api/pack2` 181 | }; 182 | request(options, (err, res, body) => { 183 | expect(err).to.be(null); 184 | expect(body).to.be('[GET] /api/pack2'); 185 | done(); 186 | }); 187 | }); 188 | 189 | /** 190 | * Test if [POST] http://localhost:3000/api/pack2 is accessible 191 | */ 192 | it('should respond to any REST route with method POST', done => { 193 | var options = { 194 | uri: `${protocol}://${host}:${port}/api/pack2`, 195 | method: 'POST' 196 | }; 197 | request(options, (err, res, body) => { 198 | expect(err).to.be(null); 199 | expect(body).to.be('[POST] /api/pack2'); 200 | done(); 201 | }); 202 | }); 203 | 204 | }); 205 | 206 | }); 207 | -------------------------------------------------------------------------------- /src/Generator/PackageGenerator.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var mkdirp = require('mkdirp'); 3 | var fs = require('fs'); 4 | var os = require('os'); 5 | import { Package } from '../Package/Package'; 6 | import { FileExistsError } from '../Error/FileExistsError'; 7 | import { Template } from '../Util/Template'; 8 | 9 | export class PackageGenerator { 10 | 11 | /** 12 | * @param {Package} _package 13 | * @param {string} viewsPath 14 | * @param {string} filesPath 15 | */ 16 | constructor (_package, viewsPath, filesPath) { 17 | this.package = _package; 18 | this.viewsPath = viewsPath; 19 | this.filesPath = filesPath; 20 | } 21 | 22 | /** 23 | * Create a new package 24 | * 25 | * @throws FileExistsError 26 | */ 27 | create() { 28 | if (!this.force && this.package.existsSync()) { 29 | throw new FileExistsError(`Package: "${this.package.name}"`); 30 | } 31 | this.createController(); 32 | this.createModel(); 33 | this.createRoutes(); 34 | this.createViews(this.viewEngine, this.addLayout); 35 | } 36 | 37 | /** 38 | * Create a new controller 39 | * 40 | * @param {string} [name] 41 | * @throws FileExistsError 42 | */ 43 | createController(name = this.package.name) { 44 | var template = new Template(this._getTemplate('package/controller')); 45 | var testTemplate = new Template(this._getTemplate('package/tests/controller')); 46 | var templateVars = this._defaultTemplateVars; 47 | 48 | // avoid *ControllerController in the name of the controller 49 | if (name.toLowerCase().endsWith('controller')) { 50 | name = name.slice(0, -10); 51 | } 52 | 53 | if (!this.force && this.package.existsSync(`Controller/${name}Controller.js`)) { 54 | throw new FileExistsError(`Controller "${name}"`); 55 | } 56 | templateVars.controller = new Template.ExtendString(name); 57 | 58 | // soy's view engine needs a template on controller action 59 | if (this.viewEngine === 'soy') { 60 | templateVars.templateAction = `template: 'index',${os.EOL} `; 61 | } 62 | 63 | this._write(`Controller/${name}Controller.js`, template.render(templateVars)); 64 | this._write(`Tests/Controller/${name}ControllerTest.js`, testTemplate.render(templateVars)); 65 | 66 | // if REST api is set create the RestController 67 | if (this.addRest) { 68 | this.createRestController(); 69 | } 70 | } 71 | 72 | /** 73 | * Create a new REST controller 74 | * 75 | * @throws FileExistsError 76 | */ 77 | createRestController() { 78 | var template = new Template(this._getTemplate('package/restcontroller')); 79 | var testTemplate = new Template(this._getTemplate('package/tests/restcontroller')); 80 | var templateVars = this._defaultTemplateVars; 81 | if (!this.force && this.package.existsSync('Controller/RestController.js')) { 82 | throw new FileExistsError('Controller "RestController"'); 83 | } 84 | this._write(`Controller/RestController.js`, template.render(templateVars)); 85 | this._write(`Tests/Controller/RestControllerTest.js`, testTemplate.render(templateVars)); 86 | } 87 | 88 | /** 89 | * Create a new model 90 | * 91 | * @param {string} [name] 92 | * @throws FileExistsError 93 | */ 94 | createModel(name = this.package.name) { 95 | var template = new Template(this._getTemplate('package/model')); 96 | var testTemplate = new Template(this._getTemplate('package/tests/model')); 97 | var templateVars = this._defaultTemplateVars; 98 | if (!this.force && this.package.existsSync(`Model/${name}.js`)) { 99 | throw new FileExistsError(`Model "${name}"`); 100 | } 101 | templateVars.model = new Template.ExtendString(name); 102 | this._write(`Model/${name}.js`, template.render(templateVars)); 103 | this._write(`Tests/Model/${name}Test.js`, testTemplate.render(templateVars)); 104 | } 105 | 106 | /** 107 | * Create a new routes file 108 | * 109 | * @throws FileExistsError 110 | */ 111 | createRoutes() { 112 | var template = new Template(this._getTemplate('package/routes')); 113 | var templateVars = this._defaultTemplateVars; 114 | if (!this.force && this.package.existsSync('routes.js')) { 115 | throw new FileExistsError('"routes.js"'); 116 | } 117 | if (this.addRest) { 118 | templateVars.restApi = `${os.EOL}router.restApi = '/api/${this.package.name.toLowerCase()}';`; 119 | } 120 | this._write('routes.js', template.render(templateVars)); 121 | } 122 | 123 | /** 124 | * Create the views for the package 125 | * 126 | * @param {string} [engine] (jade|ejs|hjs|soy) 127 | * @param {boolean} [layout] if true, also create the layout 128 | */ 129 | createViews(engine = 'jade', layout = false) { 130 | var templateVars = this._defaultTemplateVars; 131 | var viewsPath = path.join(this.viewsPath, this.package.name); 132 | var templateIndex; 133 | var templateLayout; 134 | 135 | // Generate the files according the view engine 136 | switch (engine) { 137 | case 'jade': 138 | templateIndex = new Template(this._getTemplate('views/index.jade')); 139 | this._write(path.join(viewsPath, 'index.jade'), templateIndex.render(templateVars)); 140 | if (layout) { 141 | templateLayout = new Template(this._getTemplate('views/layout.jade')); 142 | this._write(path.join(this.viewsPath, 'layout.jade'), templateLayout.render(templateVars)); 143 | } 144 | break; 145 | case 'ejs': 146 | templateIndex = new Template(this._getTemplate('views/index.ejs')); 147 | this._write(path.join(viewsPath, 'index.ejs'), templateIndex.render(templateVars)); 148 | if (layout) { 149 | templateLayout = new Template(this._getTemplate('views/layout.ejs')); 150 | this._write(path.join(this.viewsPath, 'layout.ejs'), templateLayout.render(templateVars)); 151 | } 152 | break; 153 | case 'hjs': 154 | // do not render vars in .hjs templates 155 | templateIndex = this._getTemplate('views/index.hjs'); 156 | this._write(path.join(viewsPath, 'index.hjs'), templateIndex); 157 | break; 158 | case 'soy': 159 | templateIndex = new Template(this._getTemplate('views/index.soy')); 160 | this._write(path.join(viewsPath, 'index.soy'), templateIndex.render(templateVars)); 161 | break; 162 | default: 163 | throw new Error(`View engine "${engine}" not yet supported.`); 164 | } 165 | } 166 | 167 | /** 168 | * Create a file inside the package 169 | * 170 | * @param {string} filePath 171 | * @param {string} str 172 | */ 173 | _write(filePath, str) { 174 | if (!filePath.startsWith('/')) { 175 | filePath = path.join(this.package.path, filePath); 176 | } 177 | this._mkdir(path.dirname(filePath)); 178 | fs.writeFile(filePath, str); 179 | console.log(` \x1b[36mcreated\x1b[0m : ${filePath}`); 180 | } 181 | 182 | /** 183 | * Create a directory inside the package 184 | * 185 | * @param dirPath 186 | * @private 187 | */ 188 | _mkdir(dirPath) { 189 | if (!dirPath.startsWith('/')) { 190 | dirPath = path.join(this.package.path, dirPath); 191 | } 192 | mkdirp.sync(dirPath, '0755'); 193 | } 194 | 195 | /** 196 | * Returns the default variables for the templates 197 | * 198 | * @private 199 | */ 200 | get _defaultTemplateVars() { 201 | return { 202 | package: new Template.ExtendString(this.package.name) 203 | } 204 | } 205 | 206 | /** 207 | * Returns the content of a template file 208 | * 209 | * @param {string} relativePath 210 | * @return {string} 211 | * @private 212 | */ 213 | _getTemplate(relativePath) { 214 | return fs.readFileSync(path.join(this.filesPath, `${relativePath}.template`)) 215 | .toString(); 216 | } 217 | 218 | } -------------------------------------------------------------------------------- /src/Util/Tests/ListTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | import { List } from '../List'; 3 | 4 | describe('List', () => { 5 | 6 | it('should create a new empty list', () => { 7 | var list = new List; 8 | expect(list.isEmpty()).to.be(true); 9 | expect(list).to.have.length(0); 10 | }); 11 | 12 | describe('add()', () => { 13 | it('should allow to add new simple elements', () => { 14 | var list = new List; 15 | list.add(1); 16 | list.add(2); 17 | list.add(3); 18 | expect(list.isEmpty()).to.be(false); 19 | expect(list).to.have.length(3); 20 | }); 21 | 22 | it('should allow to add elements from an array', () => { 23 | var list = new List; 24 | list.add([1, 2, 3, 4]); 25 | list.add([5, 6]); 26 | list.add([7]); 27 | list.add(8); 28 | expect(list.isEmpty()).to.be(false); 29 | expect(list).to.have.length(8); 30 | }); 31 | 32 | it('should allow to add a list inside another list', () => { 33 | var list = new List; 34 | var list2 = new List; 35 | list.add([1, 2, 3]); 36 | list2.add([4, 5, 6]); 37 | expect(list).to.have.length(3); 38 | expect(list2).to.have.length(3); 39 | list.add(list2); 40 | expect(list).to.have.length(6); 41 | expect(list2).to.have.length(3); 42 | expect(list.first()).to.be(1); 43 | expect(list.last()).to.be(6); 44 | }); 45 | }); 46 | 47 | describe('get()', () => { 48 | it('should get elements by position', () => { 49 | var list = new List; 50 | list.add([1, 2, 3]); 51 | expect(list.get(0)).to.be(1); 52 | expect(list.get(1)).to.be(2); 53 | expect(list.get(2)).to.be(3); 54 | expect(list[0]).to.be(1); 55 | expect(list[1]).to.be(2); 56 | expect(list[2]).to.be(3); 57 | }); 58 | }); 59 | 60 | describe('first(), last() and initial()', () => { 61 | it('should get first, last and initial elements', () => { 62 | var list = new List; 63 | list.add([1, 2, 3]); 64 | expect(list.first()).to.be(1); 65 | expect(list.last()).to.be(3); 66 | expect(list.initial()).to.be.a(List); 67 | expect(list.initial().first()).to.be(1); 68 | expect(list.initial().last()).to.be(2); 69 | expect(list.initial(2).last()).to.be(1); 70 | }); 71 | }); 72 | 73 | describe('contains()', () => { 74 | it('should check if a list contains an element', () => { 75 | var list = new List; 76 | list.add([1, 2, 2, 3]); 77 | expect(list.contains(1)).to.be(true); 78 | expect(list.contains(2)).to.be(true); 79 | expect(list.contains(3)).to.be(true); 80 | expect(list.contains(4)).to.be(false); 81 | expect(list.contains('1')).to.be(false); 82 | expect(list.contains(true)).to.be(false); 83 | expect(list.contains([])).to.be(false); 84 | }); 85 | }); 86 | 87 | describe('indexOf()', () => { 88 | it('should returns the index of the first occurrence of an element', () => { 89 | var list = new List; 90 | list.add([1, 2, 2, 3]); 91 | expect(list.indexOf(1)).to.be(0); 92 | expect(list.indexOf(2)).to.be(1); 93 | expect(list.indexOf(3)).to.be(3); 94 | expect(list.indexOf(5)).to.be(-1); 95 | }); 96 | }); 97 | 98 | describe('lastIndexOf()', () => { 99 | it('should returns the index of the last occurrence of an element', () => { 100 | var list = new List; 101 | list.add([1, 2, 2, 3]); 102 | expect(list.lastIndexOf(1)).to.be(0); 103 | expect(list.lastIndexOf(2)).to.be(2); 104 | expect(list.lastIndexOf(3)).to.be(3); 105 | expect(list.lastIndexOf(5)).to.be(-1); 106 | }); 107 | }); 108 | 109 | describe('equals()', () => { 110 | it('should check if two lists are equals', () => { 111 | var list = new List; 112 | var list2 = new List; 113 | list.add([1, 2, 3]); 114 | list2.add([1, 2, 3]); 115 | expect(list.equals(list2)).to.be(true); 116 | list2.replaceAll([1, 1, 1]); 117 | expect(list2.equals(list)).to.be(false); 118 | }); 119 | 120 | it('should check if a list is equal to an array', () => { 121 | var list = new List; 122 | list.add([1, 2, 3]); 123 | expect(list.equals([1, 2, 3])).to.be(true); 124 | expect(list.equals([1, 1, 1])).to.be(false); 125 | }); 126 | }); 127 | 128 | describe('clear()', () => { 129 | it('should remove all the elements from the list', () => { 130 | var list = new List; 131 | list.add([1, 2, 3]); 132 | expect(list.isEmpty()).to.be(false); 133 | expect(list).to.have.length(3); 134 | list.clear(); 135 | expect(list.isEmpty()).to.be(true); 136 | expect(list).to.have.length(0); 137 | }); 138 | }); 139 | 140 | describe('remove(), removeElement() and removeAll()', () => { 141 | it('should remove an element at the specified position in the list', () => { 142 | var list = new List; 143 | list.add([1, 2, 3, 4, 5]); 144 | list.remove(0); // remove 1 145 | list.remove(2); // remove 4 146 | list.remove(99); // there is not an element in this position 147 | expect(list).to.have.length(3); 148 | expect(list.first()).to.be(2); 149 | expect(list.last()).to.be(5); 150 | expect(list.contains(1)).to.be(false); 151 | expect(list.contains(2)).to.be(true); 152 | expect(list.contains(3)).to.be(true); 153 | expect(list.contains(4)).to.be(false); 154 | expect(list.contains(5)).to.be(true); 155 | }); 156 | 157 | it('should remove the first occurrence of an specified element', () => { 158 | var list = new List; 159 | list.add([1, 2, 3, 4, 5]); 160 | list.removeElement(1); 161 | list.removeElement(4); 162 | list.removeElement(99); 163 | expect(list).to.have.length(3); 164 | expect(list.first()).to.be(2); 165 | expect(list.last()).to.be(5); 166 | expect(list.contains(1)).to.be(false); 167 | expect(list.contains(2)).to.be(true); 168 | expect(list.contains(3)).to.be(true); 169 | expect(list.contains(4)).to.be(false); 170 | expect(list.contains(5)).to.be(true); 171 | }); 172 | 173 | it('should remove all the elements of another list', () => { 174 | var list = new List; 175 | var list2 = new List; 176 | list.add([1, 2, 3, 4, 5]); 177 | list2.add([1, 4]); 178 | list.removeAll(list2); 179 | expect(list).to.have.length(3); 180 | expect(list.first()).to.be(2); 181 | expect(list.last()).to.be(5); 182 | expect(list.contains(1)).to.be(false); 183 | expect(list.contains(2)).to.be(true); 184 | expect(list.contains(3)).to.be(true); 185 | expect(list.contains(4)).to.be(false); 186 | expect(list.contains(5)).to.be(true); 187 | }); 188 | }); 189 | 190 | describe('toArray()', () => { 191 | it('should return an array with all the elements of the list', () => { 192 | var list = new List; 193 | list.add([1, 2, 3]); 194 | list.add([4, 5, 6]); 195 | expect(list.equals([1, 2, 3, 4, 5, 6])).to.be(true); 196 | }); 197 | }); 198 | 199 | describe('each() and forEach()', () => { 200 | it('should call a function for each element', () => { 201 | var list = new List; 202 | var count = 0; 203 | list.add([1, 2, 3, 4, 5, 6]); 204 | list.each(() => count++); 205 | expect(count).to.be(6); 206 | count = 0; 207 | list.forEach(() => count++); 208 | expect(count).to.be(6); 209 | }); 210 | }); 211 | 212 | describe('clone()', () => { 213 | it('should create a new list equal to the cloned one', () => { 214 | var list = new List; 215 | var list2; 216 | list.add([1, 2, 3, 4, 5, 6]); 217 | list2 = list.clone(); 218 | expect(list.equals(list2)).to.be(true); 219 | list2.removeAll([1, 2, 3]); 220 | expect(list).to.have.length(6); 221 | expect(list2).to.have.length(3); 222 | }); 223 | }); 224 | 225 | describe('rest()', () => { 226 | it('should return the rest of the list', () => { 227 | var list = new List; 228 | list.add([1, 2, 3, 4]); 229 | expect(list.rest()).to.be.a(List); 230 | expect(list.rest().equals([2, 3, 4])).to.be(true); 231 | }); 232 | }); 233 | 234 | describe('compact()', () => { 235 | it('should return a new list compacted', () => { 236 | var list = new List; 237 | list.add([1, 0, 2, false, 3, '']); 238 | expect(list.compact()).to.be.a(List); 239 | expect(list.compact().equals([1, 2, 3])).to.be(true); 240 | }); 241 | }); 242 | 243 | describe('flatten()', () => { 244 | it('should return a new list flatten', () => { 245 | var list = new List; 246 | list.add([[1, [2], [[3, [4, 5, [[6]]]]]]]); 247 | expect(list.flatten()).to.be.a(List); 248 | expect(list.flatten().equals([1, 2, 3, 4, 5, 6])).to.be(true); 249 | }); 250 | }); 251 | 252 | describe('without()', () => { 253 | it('should return a list without the specified values', () => { 254 | var list = new List; 255 | list.add([1, 2, 3, 4, 5, 6]); 256 | expect(list.without(1)).to.be.a(List); 257 | expect(list.without(1, 3, 5).equals([2, 4, 6])).to.be(true); 258 | expect(list.without(1, 2, 3, 4, 5, 6).isEmpty()).to.be(true); 259 | expect(list.without(99).equals(list)).to.be(true); 260 | }); 261 | }); 262 | 263 | describe('find() and filter()', () => { 264 | it('should fine one element that match the query', () => { 265 | var list = new List; 266 | list.add([1, 2, 3, 4, 5, 6]); 267 | var even = list.find(num => num % 2 === 0); 268 | expect(even).to.be(2); 269 | }); 270 | 271 | it('should find all the elements that match the query', () => { 272 | var list = new List; 273 | list.add([1, 2, 3, 4, 5, 6]); 274 | var evens = list.filter(num => num % 2 === 0); 275 | expect(evens).to.be.a(List); 276 | expect(evens.equals([2, 4, 6])).to.be(true); 277 | }); 278 | }); 279 | }); -------------------------------------------------------------------------------- /examples/simple-blog/src/Post/Tests/Model/PostTest.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect.js'); 2 | var sinon = require('sinon'); 3 | var mongoose = require('mongoose'); 4 | import { Post } from '../../Model/Post'; 5 | 6 | describe('Post', () => { 7 | 8 | describe('Instance', () => { 9 | var post; 10 | var fakeData; 11 | 12 | /** 13 | * Creates a new Post instance and fake data 14 | */ 15 | beforeEach(() => { 16 | fakeData = { 17 | title: 'fake title', 18 | body: 'fake body', 19 | tags: 'tag1, tag2, tag3' 20 | }; 21 | post = new Post 22 | }); 23 | 24 | describe('_loadObject()', () => { 25 | 26 | /** 27 | * Test if the fake data was loaded on post 28 | */ 29 | it('should load an object on the model', () => { 30 | post._loadObject(fakeData); 31 | expect(post.title).to.be(fakeData.title); 32 | expect(post.body).to.be(fakeData.body); 33 | expect(post.tags).to.be(fakeData.tags); 34 | }); 35 | 36 | }); 37 | 38 | describe('save()', () => { 39 | var mongooseModel = Post._mongooseModel.prototype; 40 | 41 | /** 42 | * Restores the original functionality of save() 43 | */ 44 | afterEach(() => mongooseModel.save.restore()); 45 | 46 | /** 47 | * Test if mongoose.save is called correctly 48 | */ 49 | it('should call to mongoose\'s save method', done => { 50 | // Create a spy on mongoose save() 51 | sinon.stub(mongooseModel, 'save', fn => { 52 | fn(null); 53 | }); 54 | 55 | post._loadObject(fakeData); 56 | post.save() 57 | .then(() => { 58 | // test if the post was reloaded correctly 59 | expect(post).to.be.a(Post); 60 | expect(post._id).to.be.a(mongoose.Types.ObjectId); 61 | expect(post.title).to.be(fakeData.title); 62 | expect(post.body).to.be(fakeData.body); 63 | expect(post.tags).to.be.an(Array); 64 | expect(post.tags[0]).to.be('tag1'); 65 | expect(post.tags[1]).to.be('tag2'); 66 | expect(post.tags[2]).to.be('tag3'); 67 | expect(post.posted).to.be.a(Date); 68 | }) 69 | .catch(err => expect().fail(err)) 70 | .done(done); 71 | }); 72 | 73 | /** 74 | * Test if a mongoose error is catched by the promise 75 | */ 76 | it('should fail if mongoose fails', done => { 77 | // Create a spy on mongoose save() 78 | sinon.stub(mongooseModel, 'save', fn => { 79 | fn('Fake Error'); 80 | }); 81 | 82 | post._loadObject(fakeData); 83 | post.save() 84 | .then(() => expect().fail('It should never be here!')) 85 | .catch(err => { 86 | expect(err).to.be('Fake Error'); 87 | }) 88 | .done(done); 89 | }); 90 | 91 | }); 92 | 93 | }); 94 | 95 | describe('Statics', () => { 96 | var mongooseModel = Post._mongooseModel; 97 | 98 | describe('findById()', () => { 99 | 100 | afterEach(() => mongooseModel.findById.restore()); 101 | 102 | /** 103 | * Test if mongoose find is called correctly 104 | */ 105 | it('should find a post by id', done => { 106 | var id = 'fake_id'; 107 | var fakeDoc = { 108 | _doc: { 109 | _id: id, 110 | title: 'Fake Doc', 111 | body: 'Fake Body', 112 | tags: [ 'tag1', 'tag2', 'tag3' ], 113 | posted: Date.now() 114 | } 115 | }; 116 | 117 | // Create a spy on mongoose findById() 118 | sinon.stub(mongooseModel, 'findById', _id => { 119 | expect(_id).to.be(id); 120 | return { 121 | exec(fn) { 122 | fn(null, fakeDoc); 123 | } 124 | } 125 | }); 126 | 127 | Post.findById(id) 128 | .then(post => { 129 | expect(post).to.be.a(Post); 130 | expect(post._id).to.be(id); 131 | expect(post.title).to.be(fakeDoc._doc.title); 132 | expect(post.body).to.be(fakeDoc._doc.body); 133 | expect(post.tags).to.be(fakeDoc._doc.tags); 134 | expect(post.posted).to.be(fakeDoc._doc.posted); 135 | }) 136 | .catch(err => expect().fail(err)) 137 | .done(done); 138 | }); 139 | 140 | /** 141 | * Test if a mongoose error is catched by the promise 142 | */ 143 | it('should fail if mongoose fails', done => { 144 | // Create a spy on mongoose findById() 145 | sinon.stub(mongooseModel, 'findById', () => { 146 | return { 147 | exec(fn) { 148 | fn('Fake Error'); 149 | } 150 | }; 151 | }); 152 | 153 | Post.findById('id') 154 | .then(() => expect().fail('It should never be here!')) 155 | .catch(err => { 156 | expect(err).to.be('Fake Error'); 157 | }) 158 | .done(done); 159 | }); 160 | 161 | }); 162 | 163 | describe('findLatestPosted()', () => { 164 | var fakeDocs = [ 165 | { 166 | _doc: { 167 | _id: 'fake_id', 168 | title: 'fake_doc', 169 | posted: Date.now() 170 | } 171 | }, { 172 | _doc: { 173 | _id: 'fake_id2', 174 | title: 'fake_doc2', 175 | posted: Date.now() 176 | } 177 | } 178 | ]; 179 | 180 | /** 181 | * Test if an array of docs is an array of Pages with the fake docs 182 | * 183 | * @param {Array} docs 184 | */ 185 | var checkDocs = function (docs) { 186 | expect(docs).to.be.an(Array); 187 | expect(docs[0]).to.be.a(Post); 188 | expect(docs[0]._id).to.be(fakeDocs[0]._doc._id); 189 | expect(docs[0].title).to.be(fakeDocs[0]._doc.title); 190 | expect(docs[0].posted).to.be(fakeDocs[0]._doc.posted); 191 | expect(docs[1]._id).to.be(fakeDocs[1]._doc._id); 192 | expect(docs[1].title).to.be(fakeDocs[1]._doc.title); 193 | expect(docs[1].posted).to.be(fakeDocs[1]._doc.posted); 194 | }; 195 | 196 | afterEach(() => mongooseModel.find.restore()); 197 | 198 | /** 199 | * Test if mongoose find is called correctly to find the 10 latest posts 200 | */ 201 | it('should find the latest post added using the default parameters', done => { 202 | // Create a spy on mongoose find() 203 | sinon.stub(mongooseModel, 'find', (query, attrs, options) => { 204 | expect(query).to.be.empty(); 205 | expect(attrs).to.be.empty(); 206 | expect(options.skip).to.be(0); 207 | expect(options.limit).to.be(10); 208 | expect(options.sort.posted).to.be(-1); 209 | return { 210 | exec(fn) { 211 | fn(null, fakeDocs); 212 | } 213 | } 214 | }); 215 | 216 | Post.findLatestPosted() 217 | .then(checkDocs) 218 | .catch(err => expect().fail(err)) 219 | .done(done); 220 | }); 221 | 222 | /** 223 | * Test if mongoose find is called correctly to find the 15 latest post, starting from the post 20 224 | */ 225 | it('should find the latest post added using skip = 20 and limit = 15', done => { 226 | // Create a spy on mongoose find() 227 | sinon.stub(mongooseModel, 'find', (query, attrs, options) => { 228 | expect(query).to.be.empty(); 229 | expect(attrs).to.be.empty(); 230 | expect(options.skip).to.be(20); 231 | expect(options.limit).to.be(15); 232 | expect(options.sort.posted).to.be(-1); 233 | return { 234 | exec(fn) { 235 | fn(null, fakeDocs); 236 | } 237 | }; 238 | }); 239 | 240 | Post.findLatestPosted(20, 15) 241 | .then(checkDocs) 242 | .catch(err => expect().fail(err)) 243 | .done(done); 244 | }); 245 | 246 | /** 247 | * Test if a mongoose error is catched by the promise 248 | */ 249 | it('should fail if mongoose fails', done => { 250 | // Create a spy on mongoose find() 251 | sinon.stub(mongooseModel, 'find', () => { 252 | return { 253 | exec(fn) { 254 | fn('Fake Error'); 255 | } 256 | }; 257 | }); 258 | 259 | Post.findLatestPosted() 260 | .then(() => expect().fail('It should never be here!')) 261 | .catch(err => { 262 | expect(err).to.be('Fake Error'); 263 | }) 264 | .done(done); 265 | }); 266 | 267 | }); 268 | 269 | describe('count()', () => { 270 | 271 | afterEach(() => mongooseModel.count.restore()); 272 | 273 | /** 274 | * Test if mongoose count is called correctly 275 | */ 276 | it('should return the number of post', done => { 277 | var fakeCount = 320; 278 | 279 | // Create a spy on mongoose count() 280 | sinon.stub(mongooseModel, 'count', () => { 281 | return { 282 | exec(fn) { 283 | fn(null, fakeCount); 284 | } 285 | }; 286 | }); 287 | 288 | Post.count() 289 | .then(count => { 290 | expect(count).to.be(fakeCount); 291 | }) 292 | .catch(err => expect().fail(err)) 293 | .done(done); 294 | }); 295 | 296 | /** 297 | * Test if a mongoose error is catched by the promise 298 | */ 299 | it('should fail if mongoose fails', done => { 300 | // Create a spy on mongoose count() 301 | sinon.stub(mongooseModel, 'count', () => { 302 | return { 303 | exec(fn) { 304 | fn('Fake Error'); 305 | } 306 | }; 307 | }); 308 | 309 | Post.count() 310 | .then(() => expect().fail('It should never be here!')) 311 | .catch(err => { 312 | expect(err).to.be('Fake Error'); 313 | }) 314 | .done(done); 315 | }); 316 | 317 | }); 318 | 319 | }); 320 | 321 | }); -------------------------------------------------------------------------------- /src/Util/List.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | export class List extends Array { 4 | 5 | /** 6 | * Returns an element by position 7 | * 8 | * @param index 9 | * @returns {*} 10 | */ 11 | get(index) { 12 | return this[index]; 13 | } 14 | 15 | /** 16 | * Set an element by position 17 | * 18 | * @param index 19 | * @param value 20 | * @returns {List} 21 | */ 22 | set(index, value) { 23 | this[index] = value; 24 | return this; 25 | } 26 | 27 | /** 28 | * Check if an element is in the list 29 | * 30 | * @param element 31 | * @returns {boolean} 32 | */ 33 | contains(element) { 34 | for (var e of this) { 35 | if (element === e) { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | 42 | /** 43 | * Add elements to the list 44 | * 45 | * @param elements 46 | * @returns {List} 47 | */ 48 | add(elements) { 49 | if (!Array.isArray(elements) && !(elements instanceof List)) { 50 | elements = [ elements ]; 51 | } 52 | elements.forEach(e => { 53 | this.push(e); 54 | }); 55 | return this; 56 | } 57 | 58 | /** 59 | * Check if the list contains no elements 60 | * 61 | * @returns {boolean} 62 | */ 63 | isEmpty() { 64 | return !this.length; 65 | } 66 | 67 | /** 68 | * Check if two lists are equals 69 | * 70 | * @param list 71 | * @returns {boolean} 72 | */ 73 | equals(list) { 74 | if (this.length !== list.length) { 75 | return false; 76 | } 77 | for (var i = 0; i < this.length; i++) { 78 | if (this[i] !== list[i]) { 79 | return false; 80 | } 81 | } 82 | return true; 83 | } 84 | 85 | /** 86 | * 87 | * @returns {List} 88 | */ 89 | clone () { 90 | var list = new List; 91 | return list.add(this); 92 | } 93 | 94 | /** 95 | * Removes an element by position 96 | * 97 | * @param index 98 | * @returns {boolean} 99 | */ 100 | remove(index) { 101 | if (~index) { 102 | this.splice(index, 1); 103 | return true; 104 | } 105 | return false; 106 | } 107 | 108 | /** 109 | * Removes a specified element 110 | * 111 | * @param element 112 | * @returns {boolean} 113 | */ 114 | removeElement(element) { 115 | return this.remove(this.indexOf(element)); 116 | } 117 | 118 | /** 119 | * Remove all the elements in another collection 120 | * 121 | * @param list 122 | * @returns {boolean} 123 | */ 124 | removeAll(list) { 125 | var result = false; 126 | list.forEach(e => result |= this.removeElement(e)); 127 | return result; 128 | } 129 | 130 | /** 131 | * Replace all the elements of the list 132 | * 133 | * @param list 134 | */ 135 | replaceAll(list) { 136 | this.clear(); 137 | this.add(list); 138 | } 139 | 140 | /** 141 | * Remove all the elements in the list 142 | */ 143 | clear() { 144 | this.length = 0; 145 | } 146 | 147 | /** 148 | * Return a new array with all the elements in the same order 149 | * 150 | * @returns {Array} 151 | */ 152 | toArray() { 153 | var arr = []; 154 | this.forEach(e => arr.push(e)); 155 | return arr; 156 | } 157 | 158 | /** 159 | * Returns the first element of the list 160 | * 161 | * @param [n] 162 | * @returns {*} 163 | */ 164 | first(n) { 165 | return _.first(this, n); 166 | } 167 | 168 | /** 169 | * Returns everything but the last entry of the list 170 | * 171 | * @param [n] 172 | * @returns {List} 173 | */ 174 | initial(n) { 175 | var list = new List; 176 | return list.add(_.initial(this, n)); 177 | } 178 | 179 | /** 180 | * Returns the last element of the list 181 | * 182 | * @param [n] 183 | * @returns {*} 184 | */ 185 | last(n) { 186 | return _.last(this, n); 187 | } 188 | 189 | /** 190 | * Returns the rest of the elements in the list 191 | * 192 | * @param index 193 | * @returns {*} 194 | */ 195 | rest(index) { 196 | var list = new List; 197 | return list.add(_.rest(this, index)); 198 | } 199 | 200 | /** 201 | * Returns a new list with the false values removed 202 | * 203 | * @returns {List} 204 | */ 205 | compact() { 206 | var list = new List; 207 | return list.add(_.compact(this)); 208 | } 209 | 210 | /** 211 | * Returns a new list flattened 212 | * 213 | * @returns {List} 214 | */ 215 | flatten() { 216 | var list = new List; 217 | return list.add(_.flatten(this)); 218 | } 219 | 220 | /** 221 | * Returns a new list without the specified values 222 | * 223 | * @returns {List} 224 | */ 225 | without() { 226 | var list = new List; 227 | return list.add(_.without(this, ...arguments)); 228 | } 229 | 230 | /** 231 | * Split the list into two arrays 232 | * 233 | * @param predicate 234 | * @returns {Array} 235 | */ 236 | partition(predicate) { 237 | var lists = []; 238 | var arrays = _.partition(this, predicate); 239 | arrays.forEach(arr => { 240 | var list = new List; 241 | lists.push(list.add(arr)); 242 | }); 243 | return lists; 244 | } 245 | 246 | /** 247 | * Returns the list joined with the arrays specified, the join is unique 248 | * 249 | * @returns {List} 250 | */ 251 | union() { 252 | var list = new List; 253 | return list.add(_.union(this, ...arguments)); 254 | } 255 | 256 | /** 257 | * Returns the list intercepted with the arrays specified, the intersection is unique 258 | * 259 | * @returns {List} 260 | */ 261 | intersection() { 262 | var list = new List; 263 | return list.add(_.intersection(this, ...arguments)); 264 | } 265 | 266 | /** 267 | * Returns the list minus the arrays specified, the difference is unique 268 | * 269 | * @returns {List} 270 | */ 271 | difference() { 272 | var list = new List; 273 | return list.add(_.difference(this, ...arguments)); 274 | } 275 | 276 | /** 277 | * Returns a list with the duplicated values removed 278 | * 279 | * @param {boolean} isSorted 280 | * @param iterator 281 | * @returns {List} 282 | */ 283 | unique(isSorted, iterator) { 284 | var list = new List; 285 | return list.add(_.uniq(this, isSorted, iterator)); 286 | } 287 | 288 | /** 289 | * Alias for unique 290 | * 291 | * @param {boolean} isSorted 292 | * @param iterator 293 | * @returns {List} 294 | */ 295 | uniq(isSorted, iterator) { 296 | return this.unique(isSorted, iterator); 297 | } 298 | 299 | /** 300 | * 301 | * @returns {List} 302 | */ 303 | zip() { 304 | var list = new List; 305 | return list.add(_.zip(this, ...arguments)); 306 | } 307 | 308 | /** 309 | * 310 | * @param values 311 | * @returns {List} 312 | */ 313 | object(values) { 314 | var list = new List; 315 | return list.add(_.object(this, values)); 316 | } 317 | 318 | /** 319 | * Returns the index at which the value should be inserted into the list 320 | * 321 | * @param value 322 | * @param iterator 323 | * @param context 324 | * @returns {Number} 325 | */ 326 | sortedIndex(value, iterator, context) { 327 | return _.sortedIndex(this, value, iterator, context); 328 | } 329 | 330 | /** 331 | * alias for forEach 332 | * 333 | * @returns {List.forEach} 334 | */ 335 | each() { 336 | return this.forEach.apply(this, arguments); 337 | } 338 | 339 | /** 340 | * Returns a new list with each value mapped through a transformation 341 | * 342 | * @param iterator 343 | * @param context 344 | * @returns {List} 345 | */ 346 | map(iterator, context) { 347 | var list = new List; 348 | return list.add(_.map(this, iterator, context)); 349 | } 350 | 351 | /** 352 | * Returns a new list with the occurrences that passes the test 353 | * 354 | * @param predicate 355 | * @param context 356 | * @returns {List} 357 | */ 358 | filter(predicate, context) { 359 | var list = new List; 360 | return list.add(_.filter(this, predicate, context)); 361 | } 362 | 363 | /** 364 | * 365 | * @param properties 366 | * @returns {*} 367 | */ 368 | where(properties) { 369 | return _.where(this, properties); 370 | } 371 | 372 | /** 373 | * 374 | * @param properties 375 | * @returns {*} 376 | */ 377 | findWhere(properties) { 378 | return _.findWhere(this, properties); 379 | } 380 | 381 | /** 382 | * 383 | * @param predicate 384 | * @param context 385 | * @returns {List} 386 | */ 387 | reject(predicate, context) { 388 | var list = new List; 389 | return list.add(_.reject(this, predicate, context)); 390 | } 391 | 392 | /** 393 | * 394 | * @param methodName 395 | * @returns {List} 396 | */ 397 | invoke(methodName) { 398 | var list = new List; 399 | return list.add(_.invoke(this, ...arguments)); 400 | } 401 | 402 | /** 403 | * 404 | * @param propertyName 405 | * @returns {*} 406 | */ 407 | pluck(propertyName) { 408 | return _.pluck(this, propertyName); 409 | } 410 | 411 | /** 412 | * Returns the maximum value in list 413 | * 414 | * @param iterator 415 | * @param context 416 | * @returns {*} 417 | */ 418 | max(iterator, context) { 419 | return _.max(this, iterator, context); 420 | } 421 | 422 | /** 423 | * Returns the minimum value in list 424 | * 425 | * @param iterator 426 | * @param context 427 | * @returns {*} 428 | */ 429 | min(iterator, context) { 430 | return _.min(this, iterator, context); 431 | } 432 | 433 | /** 434 | * Returns a new list with the values sorted 435 | * 436 | * @param iterator 437 | * @param context 438 | * @returns {List} 439 | */ 440 | sortBy(iterator, context) { 441 | var list = new List; 442 | return list.add(_.sortBy(this, iterator, context)); 443 | } 444 | 445 | /** 446 | * 447 | * @param iterator 448 | * @param context 449 | * @returns {*} 450 | */ 451 | groupBy(iterator, context) { 452 | return _.groupBy(this, iterator, context); 453 | } 454 | 455 | /** 456 | * 457 | * @param iterator 458 | * @param context 459 | * @returns {*} 460 | */ 461 | indexBy(iterator, context) { 462 | return _.indexBy(this, iterator, context); 463 | } 464 | 465 | /** 466 | * 467 | * @param iterator 468 | * @param context 469 | * @returns {*} 470 | */ 471 | countBy(iterator, context) { 472 | return _.countBy(this, iterator, context); 473 | } 474 | 475 | /** 476 | * Returns a shuffled copy of the list, using a version of the Fisher-Yates shuffle 477 | * 478 | * @link http://en.wikipedia.org/wiki/Fisher–Yates_shuffle 479 | * @returns {List} 480 | */ 481 | shuffle() { 482 | var list = new List; 483 | return list.add(_.shuffle(this)); 484 | } 485 | 486 | /** 487 | * Returns a random sample from the list. 488 | * 489 | * @param n 490 | * @returns {*} 491 | */ 492 | sample(n) { 493 | return _.sample(this, n); 494 | } 495 | 496 | /** 497 | * Returns the length of the list 498 | * 499 | * @returns {Number} 500 | */ 501 | size() { 502 | return this.length; 503 | } 504 | } --------------------------------------------------------------------------------