diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..2ccbbab --- /dev/null +++ b/.eslintrc @@ -0,0 +1,19 @@ +{ + "rules": { + "indent": [2, 2], + "linebreak-style": [2, "unix"], + "no-console": 0, + "no-unexpected-multiline": 2, + "no-unreachable": 2, + "quotes": [2, "single"], + "semi": [2, "never"] + }, + "env": { + "browser": true, + "mocha": true, + "node": true + }, + "extends": "eslint:recommended", + "ecmaFeatures": {}, + "plugins": [] +} diff --git a/README.md b/README.md index e0931c5..5d3316a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,52 @@ # routing-buddy -Another routing library + +Yet another routing library in JavaScript. +This project was created after looking at and trying several JavaScript routing libraries out there and not finding one that really worked that way I wanted it to work. +I wanted a library that could be used both on the client and server side without feeling like it was designed for one side with the other bolted on like it was an afterthought. +I also wanted a library that gave more flexibility for defining routes. + +## Status +**NOTE**: This is currently a Work in progress. +I'm not yet sure if this will be the routing library I want. + +## Design + +The main class exported in this library is `Router`. +`Router` is a wrapper around `RouteNode`. +A `RouteNode` is a tree structure to define all routes. + +## For development +To run unit tests: + +```bash +$ # run unit tests and lint +$ npm test +$ # run unit tests only +$ mocha +$ # run linter only +$ eslint +``` + +See unit tests for more details. + +### Usage: + +```javascript +var Router = require('routing-buddy') + +var router = new Router() + +// Define a handler to be called when routed to the url +router.add('/some/path/here', function () { + // do something for '/some/path/here' +}) + +// Get the handler for '/some/path/here' and call it. +// Call the callback after finding the route +router.route('/some/path/here', function (err, maybeResults) { + // `err` will be either null or Error. + // null: everything was fine + // Error: missing route or any other error that might happen + // maybeResults: The return value from the hander from add +}) +``` diff --git a/lib/route-node.js b/lib/route-node.js new file mode 100644 index 0000000..56ae7ce --- /dev/null +++ b/lib/route-node.js @@ -0,0 +1,53 @@ +'use strict' +var utils = require('./utils') + +var noop = utils.noop +var assign = utils.assign + +function RouteNode (options) { + options = options || {} + this._debug = !!options.debug + this._exact = Object.create(null) +} + +assign(RouteNode.prototype, { + handler: null, + + add: function add(parts, handler, context) { + parts = parts || [] + handler = handler || noop + context = context || null + + if (parts.length === 0) { + this.handler = handler + this.context = context + return this + } + var part = parts.shift() + var node = this._exact[part] + if (node == null) { + node = new RouteNode({debug: this._debug}) + this._exact[part] = node + } + return node.add(parts, handler, context) + }, + + get: function get(parts, args, done) { + parts = parts || [] + args = args || [] + + if (parts.length === 0) { + return done(null, this.handler, this.context, args) + } + var part = parts.shift() + var childNode = this._exact[part] + if (childNode) { + return childNode.get(parts, args, done) + } else { + return done(new Error('not found')) + } + } + +}) + +module.exports = RouteNode diff --git a/lib/router.js b/lib/router.js new file mode 100644 index 0000000..fac9e9a --- /dev/null +++ b/lib/router.js @@ -0,0 +1,38 @@ +'use strict' +var url = require('url') +var RouteNode = require('./route-node') +var utils = require('./utils') +var assign = utils.assign +var noop = utils.noop + +function Router(options) { + options = options || {} + this.routes = new RouteNode() +} + +assign(Router.prototype, { + add: function add(path, handler) { + return this.routes.add(this._uriToParts(path), handler) + }, + + route: function route(path, done) { + var parts = this._uriToParts(path) + done = done || noop + return this.routes.get(parts, [], function (err, func, context, args) { + if (err) { + return done(err) + } else { + return done(null, func.apply(context, args)) + } + }) + }, + + _uriToParts: function _uriToParts(uri) { + return url.parse(uri) + .pathname + .split('/') + .filter(function (str) { return str !== ''}) + } +}) + +module.exports = Router diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..c3fe4aa --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,12 @@ +'use strict' +module.exports = { + noop: function noop() {}, + + assign: function assign (dest, adding) { + for (var key in adding) { + if (adding.hasOwnProperty(key)) { + dest[key] = adding[key] + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..34045c4 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "routing-buddy", + "version": "0.0.0", + "description": "Another router library for both client and server", + "main": "lib/router.js", + "scripts": { + "test": "mocha --reporter dot && eslint lib/*.js test/*.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/xbudex/routing-buddy.git" + }, + "keywords": [ + "routing", + "router", + "url", + "routing", + "universal", + "browser", + "server", + "client" + ], + "author": "Buddy Sandidge ", + "license": "MIT", + "bugs": { + "url": "https://github.com/xbudex/routing-buddy/issues" + }, + "homepage": "https://github.com/xbudex/routing-buddy#readme", + "devDependencies": { + "eslint": "^1.10.3", + "expect": "^1.14.0", + "mocha": "^2.4.5" + } +} diff --git a/test/route-node.js b/test/route-node.js new file mode 100644 index 0000000..c469827 --- /dev/null +++ b/test/route-node.js @@ -0,0 +1,50 @@ +var expect = require('expect') +var RouteNode = require('../lib/route-node') + +module.exports = describe('RouteNode', function () { + it('→ exists', function () { + expect(new RouteNode()).toExist() + }) + + describe('→ add nodes', function () { + var node = null + var callback = function () {} + var context = {} + beforeEach(function () { + node = new RouteNode() + }) + + it('→ add root node', function (done) { + node.add([], callback, context) + node.get([], [], function (err, func, cbContext, args) { + expect(err).toBe(null) + expect(func).toBe(callback) + expect(cbContext).toBe(context) + expect(args).toEqual([]) + done() + }) + }) + + it('→ get nested callback', function (done) { + node.add(['nested'], callback, context) + node.get(['nested'], [], function (err, func, cbContext, args) { + expect(err).toBe(null) + expect(func).toBe(callback) + expect(cbContext).toBe(context) + expect(args).toEqual([]) + done() + }) + }) + + it('→ get error callback for not found', function (done) { + node.get(['fake', 'route'], [], function (err, func, cbContext, args) { + expect(err).toBeA(Error) + expect(err.message).toMatch(/not found/) + expect(func).toNotExist() + expect(cbContext).toNotExist() + expect(args).toNotExist() + done() + }) + }) + }) +}) diff --git a/test/router.js b/test/router.js new file mode 100644 index 0000000..1f1193b --- /dev/null +++ b/test/router.js @@ -0,0 +1,40 @@ +var expect = require('expect') +var Router = require('../lib/router') + +describe('Router', function () { + it('→ exists', function () { + expect(Router).toExist() + }) + + describe('→ add routes', function () { + it('→ add root node', function (done) { + var router = new Router() + router.add('/', function () { return 'some value' }) + router.route('/', function (err, handlerResult) { + expect(err).toBe(null) + expect(handlerResult).toBe('some value') + done() + }) + }) + + it('→ add/get nested route', function (done) { + var router = new Router() + router.add('/some/path/here', function () { return 'some value' }) + router.route('/some/path/here', function (err, handlerResult) { + expect(err).toBe(null) + expect(handlerResult).toBe('some value') + done() + }) + }) + + it('→ get error in callback for missing handler', function (done) { + var router = new Router() + router.route('/some/fake/path', function (err, handlerResult) { + expect(err).toBeA(Error) + expect(err.message).toMatch(/not found/) + expect(handlerResult).toNotExist() + done() + }) + }) + }) +}) diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..1c21859 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,45 @@ +var expect = require('expect') +var utils = require('../lib/utils') + +var noop = utils.noop +var assign = utils.assign + +describe('utils', function () { + it('→ exists', function () { + expect(utils).toExist() + }) + + describe('→ noop', function () { + it('→ is a function', function () { + expect(noop).toBeA(Function) + }) + }) + + describe('→ assign', function () { + it('→ adds properties to dest', function () { + var dest = {} + var src = {foo: 123, bar: 'asdf'} + assign(dest, src) + expect(dest).toEqual({foo: 123, bar: 'asdf'}) + }) + + it('→ overrides properties to dest', function () { + var dest = {foo: 456, bar: 'qwerty', blah: /foo/} + var src = {foo: 123, bar: 'asdf'} + assign(dest, src) + expect(dest).toEqual({foo: 123, bar: 'asdf', blah: /foo/}) + }) + + it('→ does not overrides properties on prototype', function () { + function SourceClass (foo) { + this.foo = foo + } + SourceClass.prototype.bar = 'asdf' + + var dest = {foo: 456, bar: 'qwerty', blah: /foo/} + assign(dest, new SourceClass(123)) + + expect(dest).toEqual({foo: 123, bar: 'qwerty', blah: /foo/}) + }) + }) +})