├── 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 | }
--------------------------------------------------------------------------------