├── src
├── server.coffee
├── project.coffee
└── main.coffee
├── bin
├── compiler.jar
├── yuicompressor-2.4.2.jar
└── capt
├── templates
├── templates
│ ├── template.hbs
│ ├── template.eco
│ ├── i18n
│ │ ├── en_us.json
│ │ └── en_ca.json
│ └── helpers
│ │ └── all.js
├── models
│ ├── model.coffee
│ ├── model.js
│ ├── spec.coffee
│ └── spec.js
├── app.js
├── collection
│ ├── collection.coffee
│ ├── collection.js
│ ├── spec.coffee
│ └── spec.js
├── routers
│ ├── application.coffee
│ ├── router.coffee
│ ├── ApplicationRouter.js
│ └── spec.coffee
├── views
│ ├── view.coffee
│ ├── ApplicationView.js
│ ├── view.js
│ ├── spec.coffee
│ ├── ApplicationViewSpec.js
│ └── spec.js
├── spec.js
├── html
│ ├── runner.html
│ └── index.html
└── lib
│ ├── Handlebars
│ └── i18nprecompile.js
│ ├── jasmine.css
│ ├── jasmine-html.js
│ ├── hbs.js
│ └── underscore.js
├── package.json
├── readme.md
└── lib
├── request.js
├── yaml.js
├── router.js
├── parseopt.js
└── underscore.js
/src/server.coffee:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/compiler.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burin/capt/HEAD/bin/compiler.jar
--------------------------------------------------------------------------------
/templates/templates/template.hbs:
--------------------------------------------------------------------------------
1 |
<%= view.capitalize() %> Template!
2 |
--------------------------------------------------------------------------------
/templates/templates/template.eco:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/yuicompressor-2.4.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burin/capt/HEAD/bin/yuicompressor-2.4.2.jar
--------------------------------------------------------------------------------
/templates/templates/i18n/en_us.json:
--------------------------------------------------------------------------------
1 | {
2 | "key1" : "This is key 1.",
3 | "key2" : "This is key 2."
4 | }
--------------------------------------------------------------------------------
/templates/templates/i18n/en_ca.json:
--------------------------------------------------------------------------------
1 | {
2 | "key1" : "This is key 1, eh?",
3 | "key2" : "This is key 2, eh?"
4 | }
--------------------------------------------------------------------------------
/templates/models/model.coffee:
--------------------------------------------------------------------------------
1 | class <%= model.capitalize() %> extends Backbone.Model
2 | initialize: ->
3 | # ...
4 |
5 | @<%= model.capitalize() %> = <%= model.capitalize() %>
6 |
--------------------------------------------------------------------------------
/templates/app.js:
--------------------------------------------------------------------------------
1 | require([
2 | 'jquery', 'backbone', 'routers/ApplicationRouter'
3 | ],
4 | function($, Backbone, ApplicationRouter) {
5 | new ApplicationRouter();
6 | Backbone.history.start();
7 | });
8 |
--------------------------------------------------------------------------------
/templates/collection/collection.coffee:
--------------------------------------------------------------------------------
1 | class <%= model.capitalize() %>Collection extends Backbone.Collection
2 | model: <%= model.capitalize() %>
3 |
4 | @<%= model.capitalize() %>Collection = <%= model.capitalize() %>Collection
5 |
--------------------------------------------------------------------------------
/templates/models/model.js:
--------------------------------------------------------------------------------
1 | // register as a module with AMD.
2 | define(['backbone'],function(Backbone) {
3 | var <%= model.capitalize() %> = Backbone.Model.extend({
4 | initialize: function() {
5 | //
6 | }
7 | });
8 | return <%= model.capitalize() %>;
9 | });
10 |
--------------------------------------------------------------------------------
/templates/routers/application.coffee:
--------------------------------------------------------------------------------
1 | class Application
2 | constructor: ->
3 |
4 | start: ->
5 | console.log 'App started'
6 |
7 | # Create your controllers here...
8 |
9 | # Then start backbone
10 | # Backbone.history.start()
11 |
12 |
13 | @Application = Application
14 |
--------------------------------------------------------------------------------
/templates/views/view.coffee:
--------------------------------------------------------------------------------
1 | class <%= controller.capitalize() %><%= view.capitalize() %>View extends Backbone.View
2 | initialize: ->
3 |
4 | render: ->
5 | $(@el).html("blah!")
6 |
7 | @<%= controller.capitalize() %><%= view.capitalize() %>View = <%= controller.capitalize() %><%= view.capitalize() %>View
8 |
--------------------------------------------------------------------------------
/bin/capt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | root = __dirname + "/.."
4 |
5 | require("coffee-script")
6 | require(root + "/src/main.coffee")
7 | //
8 | // var path = require('path');
9 | // var fs = require('fs');
10 | // var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
11 | //
12 | // require(lib + '/cake').run();
13 |
--------------------------------------------------------------------------------
/templates/collection/collection.js:
--------------------------------------------------------------------------------
1 | // register as a module with AMD, bring in Model as dependency
2 | define(['backbone', 'models/<%= model.capitalize() %>'], function (Backbone, <%= model.capitalize() %>) {
3 | var <%= model.capitalize() %>Collection = Backbone.Collection.extend({
4 | model: <%= model.capitalize() %>
5 | });
6 | return <%= model.capitalize() %>Collection;
7 | });
8 |
9 |
--------------------------------------------------------------------------------
/src/project.coffee:
--------------------------------------------------------------------------------
1 | sys = require('sys')
2 |
3 | sys.puts "Capt:"
4 |
5 | String.prototype.capitalize = ->
6 | this.charAt(0).toUpperCase() + this.substring(1).toLowerCase()
7 |
8 | class Project
9 | constructor: (cwd) ->
10 | @cwd = cwd
11 | @root = cwd
12 | name : ->
13 | @cwd.replace(/.+\//,'')
14 |
15 | language : ->
16 | 'js' # or 'coffee'
17 |
18 | exports.Project = Project
19 |
--------------------------------------------------------------------------------
/templates/views/ApplicationView.js:
--------------------------------------------------------------------------------
1 | define(['backbone'], function(Backbone) {
2 | var ApplicationView = Backbone.View.extend({
3 | initialize: function() {
4 | this.el = $(this.el);
5 | },
6 | render: function() {
7 | this.el.append('');
8 | }
9 | });
10 |
11 | return ApplicationView;
12 | });
13 |
--------------------------------------------------------------------------------
/templates/models/spec.coffee:
--------------------------------------------------------------------------------
1 | describe '<%= model %> model', ->
2 |
3 | it 'should handle the truth', ->
4 | expect(true).toBeTruthy()
5 |
6 | it 'should exist', ->
7 | expect(<%= model.capitalize() %>).toBeTruthy()
8 |
9 | it 'should instantiate', ->
10 | x = new <%= model.capitalize() %>
11 | expect(x instanceof <%= model.capitalize() %>).toBeTruthy()
12 | expect(x instanceof Backbone.Model).toBeTruthy()
13 |
14 |
--------------------------------------------------------------------------------
/templates/routers/router.coffee:
--------------------------------------------------------------------------------
1 | class <%= controller.capitalize() %>Controller extends Backbone.Controller
2 | routes :
3 | "<%= controller %>/:id/edit" : "edit"
4 | "<%= controller %>/new" : "new"
5 | "<%= controller %>/:id" : "show"
6 | "<%= controller %>" : "index"
7 |
8 | index: ->
9 | # new <%= controller.capitalize() %>IndexView
10 |
11 | @<%= controller.capitalize() %>Controller = <%= controller.capitalize() %>Controller
12 |
--------------------------------------------------------------------------------
/templates/spec.js:
--------------------------------------------------------------------------------
1 | // baseUrl has been set to ../app so that module paths work properly
2 | // specs should be referenced here as though you were in `app`
3 | // app/
4 | // spec/
5 | require([
6 | //
7 | '../spec/views/ApplicationViewSpec'// last item marker, do not remove :)
8 | ],
9 | function() {
10 | jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
11 | jasmine.getEnv().execute();
12 | });
13 |
--------------------------------------------------------------------------------
/templates/collection/spec.coffee:
--------------------------------------------------------------------------------
1 | describe '<%= model %> collection', ->
2 |
3 | it 'should handle the truth', ->
4 | expect(true).toBeTruthy()
5 |
6 | it 'should exist', ->
7 | expect(<%= model.capitalize() %>Collection).toBeTruthy()
8 |
9 | it 'should instantiate', ->
10 | x = new <%= model.capitalize() %>Collection
11 | expect(x instanceof <%= model.capitalize() %>Collection).toBeTruthy()
12 | expect(x instanceof Backbone.Collection).toBeTruthy()
13 | expect(x.model == <%= model.capitalize() %>).toBeTruthy()
14 |
15 |
--------------------------------------------------------------------------------
/templates/views/view.js:
--------------------------------------------------------------------------------
1 | define(['backbone', 'hbs!templates/<%= router %>/<%= view.capitalize() %>Template'], function(Backbone, template) {
2 | var <%= view.capitalize() %>View = Backbone.View.extend({
3 | initialize: function() {
4 | this.el = $(this.el);
5 | this.model.bind('change', this.render, this);
6 | },
7 | render: function() {
8 | this.el.html(this.template(this.model.toJSON()));
9 | },
10 | template: template
11 | });
12 | return <%= view.capitalize() %>View;
13 | });
14 |
15 |
--------------------------------------------------------------------------------
/templates/routers/ApplicationRouter.js:
--------------------------------------------------------------------------------
1 | define(['backbone', 'views/ApplicationView'], function(Backbone, ApplicationView) {
2 | var ApplicationRouter = Backbone.Router.extend({
3 | initialize: function() {
4 | this.viewport = new ApplicationView({
5 | el: $('#app')
6 | });
7 | },
8 | routes: {
9 | "" : "main",
10 | "other" : "other"
11 | },
12 | main: function() {
13 | this.viewport.render();
14 | },
15 | other: function() {
16 | this.viewport.render();
17 | }
18 | });
19 |
20 | return ApplicationRouter;
21 | });
22 |
--------------------------------------------------------------------------------
/templates/routers/spec.coffee:
--------------------------------------------------------------------------------
1 | describe '<%= controller %> controller', ->
2 |
3 | it 'should handle the truth', ->
4 | expect(true).toBeTruthy()
5 |
6 | it 'should exist', ->
7 | expect(<%= controller.capitalize() %>Controller).toBeTruthy()
8 |
9 | it 'should instantiate', ->
10 | x = new <%= controller.capitalize() %>Controller
11 | expect(x instanceof <%= controller.capitalize() %>Controller).toBeTruthy()
12 | expect(x instanceof Backbone.Controller).toBeTruthy()
13 |
14 | it 'should have index method', ->
15 | x = new <%= controller.capitalize() %>Controller
16 | x.index()
17 |
18 | # Umm..?
19 | expect(true).toBeTruthy()
20 |
--------------------------------------------------------------------------------
/templates/models/spec.js:
--------------------------------------------------------------------------------
1 | define(['backbone', 'models/<%= model.capitalize() %>'], function(Backbone, <%= model.capitalize() %>) {
2 | describe('<%= model.capitalize() %> model', function() {
3 | it('should handle the truth', function() {
4 | expect(true).toBeTruthy();
5 | });
6 |
7 | it('should exist', function() {
8 | expect(<%= model.capitalize() %>).toBeTruthy();
9 | });
10 |
11 | it('should instantiate', function() {
12 | var x = new <%= model.capitalize() %>();
13 | expect(x instanceof <%= model.capitalize() %>).toBeTruthy();
14 | expect(x instanceof Backbone.Model).toBeTruthy();
15 | });
16 | });
17 | });
18 |
19 |
--------------------------------------------------------------------------------
/templates/html/runner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jasmine Test Runner
5 |
6 |
7 |
8 |
9 |
10 |
11 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/templates/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Enterprise Mobile Development
5 |
6 |
7 |
8 |
18 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/templates/collection/spec.js:
--------------------------------------------------------------------------------
1 | define(['backbone', 'collections/<%= model.capitalize() %>Collection', 'models/<%= model.capitalize() %>'], function(Backbone, <%= model.capitalize() %>Collection, <%= model.capitalize() %>) {
2 | describe('<%= model.capitalize() %>Collection', function() {
3 | it('should handle the truth', function() {
4 | expect(true).toBeTruthy();
5 | });
6 |
7 | it('should exist', function() {
8 | expect(<%= model.capitalize() %>Collection).toBeTruthy();
9 | });
10 |
11 | it('should instantiate', function() {
12 | var x = new <%= model.capitalize() %>Collection();
13 | expect(x instanceof <%= model.capitalize() %>Collection).toBeTruthy();
14 | expect(x instanceof Backbone.Collection).toBeTruthy();
15 | expect(x.model === <%= model.capitalize() %>).toBeTruthy()
16 | });
17 | });
18 | });
19 |
20 |
21 |
--------------------------------------------------------------------------------
/templates/views/spec.coffee:
--------------------------------------------------------------------------------
1 | describe '<%= controller %> controller', ->
2 |
3 | describe '<%= view %> view', ->
4 |
5 | it 'should handle the truth', ->
6 | expect(true).toBeTruthy()
7 |
8 | it 'should exist', ->
9 | expect(<%= controller.capitalize() %><%= view.capitalize() %>View).toBeTruthy()
10 |
11 | it 'should instantiate', ->
12 | x = new <%= controller.capitalize() %><%= view.capitalize() %>View
13 | expect(x instanceof <%= controller.capitalize() %><%= view.capitalize() %>View).toBeTruthy()
14 | expect(x instanceof Backbone.View).toBeTruthy()
15 |
16 | it 'should have render method', ->
17 | x = new <%= controller.capitalize() %><%= view.capitalize() %>View
18 | x.render()
19 |
20 | # Implement as you see fit
21 | xit 'should render some text', ->
22 | x = new <%= controller.capitalize() %><%= view.capitalize() %>View { el : $("") }
23 | x.render()
24 | expect(x.$(".myselector").html()).toMatch /some text/
25 |
--------------------------------------------------------------------------------
/templates/views/ApplicationViewSpec.js:
--------------------------------------------------------------------------------
1 | define(['backbone', 'views/ApplicationView'], function(Backbone, ApplicationView) {
2 | describe('ApplicationView', function() {
3 | it('should handle the truth', function() {
4 | expect(true).toBeTruthy();
5 | });
6 |
7 | it('should exist', function() {
8 | expect(ApplicationView).toBeTruthy();
9 | });
10 |
11 | it('should instantiate', function() {
12 | var x = new ApplicationView();
13 | expect(x instanceof ApplicationView).toBeTruthy();
14 | expect(x instanceof Backbone.View).toBeTruthy();
15 | });
16 |
17 | it('should have a render method', function() {
18 | var x = new ApplicationView();
19 | x.render();
20 | });
21 |
22 | it('should render some text', function() {
23 | var x = new ApplicationView();
24 | x.render();
25 | expect(x.$(".appended").html()).toMatch(/well hello there!/);
26 | });
27 | });
28 | });
29 |
30 |
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "capt",
3 | "description": "Command line tool for creating backbone.js applications with coffeescript",
4 | "version": "0.5.5",
5 | "keywords": ["jst", "templates", "rest", "backbone", "jquery", "zepto", "framework", "coffeescript", "less", "underscore"],
6 | "homepage": "http://bnolan.github.com/capt/",
7 | "author": "Ben Nolan , twitter: @bnolan",
8 | "licenses": [{
9 | "type": "MIT",
10 | "url": "http://github.com/brunch/brunch/raw/master/LICENSE"
11 | }],
12 | "directories" : {
13 | "lib" : "./lib"
14 | },
15 | "main" : "./lib/brunch",
16 | "bin": {
17 | "capt": "./bin/capt"
18 | },
19 | "dependencies": {
20 | "coffee-script": "= 1.0.1",
21 | "less": ">=1.0.41",
22 | "glob": ">=2.0.6",
23 | "jasmine-node" : ">=1.0.0rc3",
24 | "eco" : ">=1.0.3"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/bnolan/capt.git"
29 | },
30 | "bugs": {
31 | "url": "http://github.com/bnolan/capt/issues"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/templates/views/spec.js:
--------------------------------------------------------------------------------
1 | define(['backbone', 'views/<%= router %>/<%= view.capitalize() %>View'], function(Backbone, <%= view.capitalize() %>View) {
2 | describe('<%= router.capitalize() %> <%= view.capitalize() %>View', function() {
3 | it('should handle the truth', function() {
4 | expect(true).toBeTruthy();
5 | });
6 |
7 | it('should exist', function() {
8 | expect(<%= view.capitalize() %>View).toBeTruthy();
9 | });
10 |
11 | it('should instantiate', function() {
12 | var x = new <%= view.capitalize() %>View({
13 | model: new Backbone.Model()
14 | });
15 | expect(x instanceof <%= view.capitalize() %>View).toBeTruthy();
16 | expect(x instanceof Backbone.View).toBeTruthy();
17 | });
18 |
19 | it('should have a render method', function() {
20 | var x = new <%= view.capitalize() %>View({
21 | model: new Backbone.Model()
22 | });
23 | x.render();
24 | });
25 |
26 | it('should render some text', function() {
27 | var x = new <%= view.capitalize() %>View({
28 | model: new Backbone.Model()
29 | });
30 | x.render();
31 | expect(x.el.html()).toMatch('<%= view.capitalize() %> Template!');
32 | });
33 | });
34 | });
35 |
36 |
--------------------------------------------------------------------------------
/templates/templates/helpers/all.js:
--------------------------------------------------------------------------------
1 | define(['Handlebars'], function(Handlebars) {
2 | function ISODateString(d) {
3 | function pad(n){
4 | return n < 10 ? '0'+n : n
5 | }
6 | return d.getUTCFullYear()+'-'
7 | + pad(d.getUTCMonth()+1)+'-'
8 | + pad(d.getUTCDate())+'T'
9 | + pad(d.getUTCHours())+':'
10 | + pad(d.getUTCMinutes())+':'
11 | + pad(d.getUTCSeconds())+'Z'
12 | }
13 | Handlebars.registerHelper('timeSimple', function(date) {
14 | return moment(date).format('hh:mma');
15 | });
16 |
17 | Handlebars.registerHelper('prettyDate', function(date) {
18 | var hehe = moment(date).format('ddd MMM D, YYYY');
19 | return hehe;
20 | });
21 |
22 | Handlebars.registerHelper('toLowerCase', function(string) {
23 | return string.toLowerCase();
24 | });
25 |
26 | Handlebars.registerHelper('carrierIconTag', function(carrierCode) {
27 | // escape carrier code
28 | carrierCode = Handlebars.Utils.escapeExpression(carrierCode);
29 | // concat image tag with carrier code
30 | var img = '
';
31 | // output safestring
32 | return new Handlebars.SafeString(img);
33 | });
34 |
35 | Handlebars.registerHelper('formatPrice', function(price) {
36 | return parseFloat((price)*100/100).toFixed(2);
37 | });
38 | });
39 |
40 |
--------------------------------------------------------------------------------
/templates/lib/Handlebars/i18nprecompile.js:
--------------------------------------------------------------------------------
1 | //>>excludeStart('excludeAfterBuild', pragmas.excludeAfterBuild)
2 | define(['Handlebars', "underscore"], function ( Handlebars, _ ) {
3 | // define(function () {
4 | function replaceLocaleStrings ( ast, mapping ) {
5 | mapping = mapping || {};
6 | // Base set of things
7 | if ( ast && ast.type === "program" && ast.statements ) {
8 | _(ast.statements).forEach(function(statement, i){
9 | var newString = "";
10 | // If it's a translation node
11 | if ( statement.type == "mustache" && statement.id && statement.id.original == "$" ) {
12 |
13 | if ( statement.params.length && statement.params[0].string ) {
14 | newString = mapping[ statement.params[0].string ] || newString;
15 | }
16 | ast.statements[i] = new Handlebars.AST.ContentNode(newString);
17 | }
18 | // If we need to recurse
19 | else if ( statement.program ) {
20 | statement.program = replaceLocaleStrings( statement.program, mapping );
21 | }
22 | });
23 | }
24 | return ast;
25 | }
26 |
27 | return function(string, mapping, options) {
28 | options = options || {};
29 | var ast = replaceLocaleStrings(Handlebars.parse(string), mapping),
30 | environment = new Handlebars.Compiler().compile(ast, options);
31 | return new Handlebars.JavaScriptCompiler().compile(environment, options);
32 | };
33 | });
34 | //>>excludeEnd('excludeAfterBuild')
35 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Backbone environment for node.js (capt)
2 |
3 | `capt` is a tool to help you quickly create backbone.js applications and maintain a good directory structure and give you build tools to help development.
4 |
5 | This version of `capt` only includes the `new` and `generate` tasks.
6 |
7 | It assumes the project will use [RequireJS](http://requirejs.org/) for module loading and dependency management. JavaScript is the preferred language in this fork.
8 |
9 |
10 | Directory Structure:
11 |
12 | index.html
13 | app/
14 | app.js
15 | collections/
16 | models/
17 | routers/
18 | templates/
19 | views/
20 | css/
21 | lib/
22 | spec/
23 |
24 | Testing framework:
25 |
26 | * Jasmine
27 |
28 | Libraries built in (as AMD modules):
29 |
30 | * jQuery
31 | * backbone.js
32 | * underscore.js
33 | * require.js
34 | * handlebars.js
35 | * require-handlebars-plugin (https://github.com/SlexAxton/require-handlebars-plugin)
36 |
37 | ## Getting Started
38 | Create a new project by
39 |
40 | capt new projectname
41 |
42 | Open `projectname/index.html` to see your initial app
43 |
44 | Then you can `cd` into the directory `projectname` to start using generators
45 |
46 | cd projectname
47 |
48 | Generate a model
49 |
50 | capt generate model animal
51 |
52 | Generate a collection with the model name
53 |
54 | capt generate collection animal
55 |
56 | Or generate some views with the directory name and view name
57 |
58 | capt generate view animals show
59 |
60 |
61 |
62 | ## License
63 |
64 | [BSD Licensed](http://creativecommons.org/licenses/BSD/). YUI Compressor and Closure are licensed under their respective licences.
65 |
66 | ## Author
67 |
68 | Ben Nolan @bnolan bnolan@gmail.com
69 |
70 | Modified by Burin Asavesna @burin
71 |
72 | # Changelog
73 |
74 | 0.5.4 - Fixed template which said `initailizer` instead of `initialize`
--------------------------------------------------------------------------------
/templates/lib/jasmine.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
3 | }
4 |
5 |
6 | .jasmine_reporter a:visited, .jasmine_reporter a {
7 | color: #303;
8 | }
9 |
10 | .jasmine_reporter a:hover, .jasmine_reporter a:active {
11 | color: blue;
12 | }
13 |
14 | .run_spec {
15 | float:right;
16 | padding-right: 5px;
17 | font-size: .8em;
18 | text-decoration: none;
19 | }
20 |
21 | .jasmine_reporter {
22 | margin: 0 5px;
23 | }
24 |
25 | .banner {
26 | color: #303;
27 | background-color: #fef;
28 | padding: 5px;
29 | }
30 |
31 | .logo {
32 | float: left;
33 | font-size: 1.1em;
34 | padding-left: 5px;
35 | }
36 |
37 | .logo .version {
38 | font-size: .6em;
39 | padding-left: 1em;
40 | }
41 |
42 | .runner.running {
43 | background-color: yellow;
44 | }
45 |
46 |
47 | .options {
48 | text-align: right;
49 | font-size: .8em;
50 | }
51 |
52 |
53 |
54 |
55 | .suite {
56 | border: 1px outset gray;
57 | margin: 5px 0;
58 | padding-left: 1em;
59 | }
60 |
61 | .suite .suite {
62 | margin: 5px;
63 | }
64 |
65 | .suite.passed {
66 | background-color: #dfd;
67 | }
68 |
69 | .suite.failed {
70 | background-color: #fdd;
71 | }
72 |
73 | .spec {
74 | margin: 5px;
75 | padding-left: 1em;
76 | clear: both;
77 | }
78 |
79 | .spec.failed, .spec.passed, .spec.skipped {
80 | padding-bottom: 5px;
81 | border: 1px solid gray;
82 | }
83 |
84 | .spec.failed {
85 | background-color: #fbb;
86 | border-color: red;
87 | }
88 |
89 | .spec.passed {
90 | background-color: #bfb;
91 | border-color: green;
92 | }
93 |
94 | .spec.skipped {
95 | background-color: #bbb;
96 | }
97 |
98 | .messages {
99 | border-left: 1px dashed gray;
100 | padding-left: 1em;
101 | padding-right: 1em;
102 | }
103 |
104 | .passed {
105 | background-color: #cfc;
106 | display: none;
107 | }
108 |
109 | .failed {
110 | background-color: #fbb;
111 | }
112 |
113 | .skipped {
114 | color: #777;
115 | background-color: #eee;
116 | display: none;
117 | }
118 |
119 |
120 | /*.resultMessage {*/
121 | /*white-space: pre;*/
122 | /*}*/
123 |
124 | .resultMessage span.result {
125 | display: block;
126 | line-height: 2em;
127 | color: black;
128 | }
129 |
130 | .resultMessage .mismatch {
131 | color: black;
132 | }
133 |
134 | .stackTrace {
135 | white-space: pre;
136 | font-size: .8em;
137 | margin-left: 10px;
138 | max-height: 5em;
139 | overflow: auto;
140 | border: 1px inset red;
141 | padding: 1em;
142 | background: #eef;
143 | }
144 |
145 | .finished-at {
146 | padding-left: 1em;
147 | font-size: .6em;
148 | }
149 |
150 | .show-passed .passed,
151 | .show-skipped .skipped {
152 | display: block;
153 | }
154 |
155 |
156 | #jasmine_content {
157 | position:fixed;
158 | right: 100%;
159 | }
160 |
161 | .runner {
162 | border: 1px solid gray;
163 | display: block;
164 | margin: 5px 0;
165 | padding: 2px 0 2px 10px;
166 | }
167 |
--------------------------------------------------------------------------------
/lib/request.js:
--------------------------------------------------------------------------------
1 | var http = require('http')
2 | , url = require('url')
3 | , sys = require('sys')
4 | ;
5 |
6 | var toBase64 = function(str) {
7 | return (new Buffer(str || "", "ascii")).toString("base64");
8 | };
9 |
10 | function request (options, callback) {
11 | if (!options.uri) {
12 | throw new Error("options.uri is a required argument")
13 | } else {
14 | if (typeof options.uri == "string") options.uri = url.parse(options.uri);
15 | }
16 | if (options.proxy) {
17 | if (typeof options.proxy == 'string') options.proxy = url.parse(options.proxy);
18 | }
19 |
20 | options._redirectsFollowed = options._redirectsFollowed ? options._redirectsFollowed : 0;
21 | options.maxRedirects = options.maxRedirects ? options.maxRedirects : 10;
22 |
23 | options.followRedirect = (options.followRedirect !== undefined) ? options.followRedirect : true;
24 | options.method = options.method ? options.method : 'GET';
25 |
26 | options.headers = options.headers ? options.headers : {};
27 | if (!options.headers.host) {
28 | options.headers.host = options.uri.hostname;
29 | if (options.uri.port) {
30 | if ( !(options.uri.port === 80 && options.uri.protocol === 'http:') &&
31 | !(options.uri.port === 443 && options.uri.protocol === 'https:') )
32 | options.headers.host += (':'+options.uri.port)
33 | }
34 | var setHost = true;
35 | } else {
36 | var setHost = false;
37 | }
38 |
39 | if (!options.uri.pathname) {options.uri.pathname = '/'}
40 | if (!options.uri.port) {
41 | if (options.uri.protocol == 'http:') {options.uri.port = 80}
42 | else if (options.uri.protocol == 'https:') {options.uri.port = 443}
43 | }
44 |
45 | if (options.bodyStream) {
46 | sys.error('options.bodyStream is deprecated. use options.reponseBodyStream instead.');
47 | options.responseBodyStream = options.bodyStream;
48 | }
49 | if (options.proxy) {
50 | var secure = (options.proxy.protocol == 'https:') ? true : false
51 | options.client = options.client ? options.client : http.createClient(options.proxy.port, options.proxy.hostname, secure);
52 | } else {
53 | var secure = (options.uri.protocol == 'https:') ? true : false
54 | options.client = options.client ? options.client : http.createClient(options.uri.port, options.uri.hostname, secure);
55 | }
56 |
57 | var clientErrorHandler = function (error) {
58 | if (setHost) delete options.headers.host;
59 | if (callback) callback(error);
60 | }
61 | options.client.addListener('error', clientErrorHandler);
62 |
63 | if (options.uri.auth && !options.headers.authorization) {
64 | options.headers.authorization = "Basic " + toBase64(options.uri.auth);
65 | }
66 | if (options.proxy && options.proxy.auth && !options.headers['proxy-authorization']) {
67 | options.headers['proxy-authorization'] = "Basic " + toBase64(options.proxy.auth);
68 | }
69 |
70 | options.fullpath = options.uri.href.replace(options.uri.protocol + '//' + options.uri.host, '');
71 | if (options.fullpath.length === 0) options.fullpath = '/'
72 |
73 | if (options.proxy) options.fullpath = (options.uri.protocol + '//' + options.uri.host + options.fullpath)
74 |
75 | if (options.body) {options.headers['content-length'] = options.body.length}
76 | options.request = options.client.request(options.method, options.fullpath, options.headers);
77 | options.request.addListener("response", function (response) {
78 | var buffer;
79 | if (options.responseBodyStream) {
80 | buffer = options.responseBodyStream;
81 | sys.pump(response, options.responseBodyStream);
82 | }
83 | else {
84 | buffer = '';
85 | response.addListener("data", function (chunk) { buffer += chunk; } )
86 | }
87 |
88 | response.addListener("end", function () {
89 | options.client.removeListener("error", clientErrorHandler);
90 |
91 | if (response.statusCode > 299 && response.statusCode < 400 && options.followRedirect && response.headers.location && (options._redirectsFollowed < options.maxRedirects) ) {
92 | options._redirectsFollowed += 1
93 | options.uri = response.headers.location;
94 | delete options.client;
95 | if (options.headers) {
96 | delete options.headers.host;
97 | }
98 | request(options, callback);
99 | return;
100 | } else {options._redirectsFollowed = 0}
101 |
102 | if (setHost) delete options.headers.host;
103 | if (callback) callback(null, response, buffer);
104 | })
105 | })
106 |
107 | if (options.body) {
108 | options.request.write(options.body, 'binary');
109 | options.request.end();
110 | } else if (options.requestBodyStream) {
111 | sys.pump(options.requestBodyStream, options.request);
112 | } else {
113 | options.request.end();
114 | }
115 | }
116 |
117 | module.exports = request;
118 |
119 | request.get = request;
120 | request.post = function () {arguments[0].method = 'POST', request.apply(request, arguments)};
121 | request.put = function () {arguments[0].method = 'PUT', request.apply(request, arguments)};
122 | request.head = function () {arguments[0].method = 'HEAD', request.apply(request, arguments)};
--------------------------------------------------------------------------------
/templates/lib/jasmine-html.js:
--------------------------------------------------------------------------------
1 | jasmine.TrivialReporter = function(doc) {
2 | this.document = doc || document;
3 | this.suiteDivs = {};
4 | this.logRunningSpecs = false;
5 | };
6 |
7 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
8 | var el = document.createElement(type);
9 |
10 | for (var i = 2; i < arguments.length; i++) {
11 | var child = arguments[i];
12 |
13 | if (typeof child === 'string') {
14 | el.appendChild(document.createTextNode(child));
15 | } else {
16 | if (child) { el.appendChild(child); }
17 | }
18 | }
19 |
20 | for (var attr in attrs) {
21 | if (attr == "className") {
22 | el[attr] = attrs[attr];
23 | } else {
24 | el.setAttribute(attr, attrs[attr]);
25 | }
26 | }
27 |
28 | return el;
29 | };
30 |
31 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
32 | var showPassed, showSkipped;
33 |
34 | this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
35 | this.createDom('div', { className: 'banner' },
36 | this.createDom('div', { className: 'logo' },
37 | this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"),
38 | this.createDom('span', { className: 'version' }, runner.env.versionString())),
39 | this.createDom('div', { className: 'options' },
40 | "Show ",
41 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
42 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
43 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
44 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
45 | )
46 | ),
47 |
48 | this.runnerDiv = this.createDom('div', { className: 'runner running' },
49 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
50 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
51 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
52 | );
53 |
54 | this.document.body.appendChild(this.outerDiv);
55 |
56 | var suites = runner.suites();
57 | for (var i = 0; i < suites.length; i++) {
58 | var suite = suites[i];
59 | var suiteDiv = this.createDom('div', { className: 'suite' },
60 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
61 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
62 | this.suiteDivs[suite.id] = suiteDiv;
63 | var parentDiv = this.outerDiv;
64 | if (suite.parentSuite) {
65 | parentDiv = this.suiteDivs[suite.parentSuite.id];
66 | }
67 | parentDiv.appendChild(suiteDiv);
68 | }
69 |
70 | this.startedAt = new Date();
71 |
72 | var self = this;
73 | showPassed.onclick = function(evt) {
74 | if (showPassed.checked) {
75 | self.outerDiv.className += ' show-passed';
76 | } else {
77 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
78 | }
79 | };
80 |
81 | showSkipped.onclick = function(evt) {
82 | if (showSkipped.checked) {
83 | self.outerDiv.className += ' show-skipped';
84 | } else {
85 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
86 | }
87 | };
88 | };
89 |
90 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
91 | var results = runner.results();
92 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
93 | this.runnerDiv.setAttribute("class", className);
94 | //do it twice for IE
95 | this.runnerDiv.setAttribute("className", className);
96 | var specs = runner.specs();
97 | var specCount = 0;
98 | for (var i = 0; i < specs.length; i++) {
99 | if (this.specFilter(specs[i])) {
100 | specCount++;
101 | }
102 | }
103 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
104 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
105 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
106 |
107 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
108 | };
109 |
110 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
111 | var results = suite.results();
112 | var status = results.passed() ? 'passed' : 'failed';
113 | if (results.totalCount === 0) { // todo: change this to check results.skipped
114 | status = 'skipped';
115 | }
116 | this.suiteDivs[suite.id].className += " " + status;
117 | };
118 |
119 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
120 | if (this.logRunningSpecs) {
121 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
122 | }
123 | };
124 |
125 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
126 | var results = spec.results();
127 | var status = results.passed() ? 'passed' : 'failed';
128 | if (results.skipped) {
129 | status = 'skipped';
130 | }
131 | var specDiv = this.createDom('div', { className: 'spec ' + status },
132 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
133 | this.createDom('a', {
134 | className: 'description',
135 | href: '?spec=' + encodeURIComponent(spec.getFullName()),
136 | title: spec.getFullName()
137 | }, spec.description));
138 |
139 |
140 | var resultItems = results.getItems();
141 | var messagesDiv = this.createDom('div', { className: 'messages' });
142 | for (var i = 0; i < resultItems.length; i++) {
143 | var result = resultItems[i];
144 |
145 | if (result.type == 'log') {
146 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
147 | } else if (result.type == 'expect' && result.passed && !result.passed()) {
148 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
149 |
150 | if (result.trace.stack) {
151 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
152 | }
153 | }
154 | }
155 |
156 | if (messagesDiv.childNodes.length > 0) {
157 | specDiv.appendChild(messagesDiv);
158 | }
159 |
160 | this.suiteDivs[spec.suite.id].appendChild(specDiv);
161 | };
162 |
163 | jasmine.TrivialReporter.prototype.log = function() {
164 | var console = jasmine.getGlobal().console;
165 | if (console && console.log) {
166 | if (console.log.apply) {
167 | console.log.apply(console, arguments);
168 | } else {
169 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
170 | }
171 | }
172 | };
173 |
174 | jasmine.TrivialReporter.prototype.getLocation = function() {
175 | return this.document.location;
176 | };
177 |
178 | jasmine.TrivialReporter.prototype.specFilter = function(spec) {
179 | var paramMap = {};
180 | var params = this.getLocation().search.substring(1).split('&');
181 | for (var i = 0; i < params.length; i++) {
182 | var p = params[i].split('=');
183 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
184 | }
185 |
186 | if (!paramMap.spec) {
187 | return true;
188 | }
189 | return spec.getFullName().indexOf(paramMap.spec) === 0;
190 | };
--------------------------------------------------------------------------------
/lib/yaml.js:
--------------------------------------------------------------------------------
1 |
2 | // YAML - Core - Copyright TJ Holowaychuk (MIT Licensed)
3 |
4 | /**
5 | * Version triplet.
6 | */
7 |
8 | exports.version = '0.1.0'
9 |
10 | // --- Helpers
11 |
12 | /**
13 | * Return 'near "context"' where context
14 | * is replaced by a chunk of _str_.
15 | *
16 | * @param {string} str
17 | * @return {string}
18 | * @api public
19 | */
20 |
21 | function context(str) {
22 | if (typeof str !== 'string') return ''
23 | str = str
24 | .slice(0, 25)
25 | .replace(/\n/g, '\\n')
26 | .replace(/"/g, '\\\"')
27 | return 'near "' + str + '"'
28 | }
29 |
30 | // --- Lexer
31 |
32 | /**
33 | * YAML grammar tokens.
34 | */
35 |
36 | var tokens = [
37 | ['comment', /^#[^\n]*/],
38 | ['indent', /^\n( *)/],
39 | ['space', /^ +/],
40 | ['true', /^(enabled|true|yes|on)/],
41 | ['false', /^(disabled|false|no|off)/],
42 | ['string', /^"(.*?)"/],
43 | ['string', /^'(.*?)'/],
44 | ['float', /^(\d+\.\d+)/],
45 | ['int', /^(\d+)/],
46 | ['id', /^([\w ]+)/],
47 | ['doc', /^---/],
48 | [',', /^,/],
49 | ['{', /^\{/],
50 | ['}', /^\}/],
51 | ['[', /^\[/],
52 | [']', /^\]/],
53 | ['-', /^\-/],
54 | [':', /^[:]/],
55 | ]
56 |
57 | /**
58 | * Tokenize the given _str_.
59 | *
60 | * @param {string} str
61 | * @return {array}
62 | * @api private
63 | */
64 |
65 | exports.tokenize = function (str) {
66 | var token, captures, ignore, input,
67 | indents = lastIndents = 0,
68 | stack = []
69 | while (str.length) {
70 | for (var i = 0, len = tokens.length; i < len; ++i)
71 | if (captures = tokens[i][1].exec(str)) {
72 | token = [tokens[i][0], captures],
73 | str = str.replace(tokens[i][1], '')
74 | switch (token[0]) {
75 | case 'comment':
76 | ignore = true
77 | break
78 | case 'indent':
79 | lastIndents = indents
80 | indents = token[1][1].length / 2
81 | if (indents === lastIndents)
82 | ignore = true
83 | else if (indents > lastIndents + 1)
84 | throw new SyntaxError('invalid indentation, got ' + indents + ' instead of ' + (lastIndents + 1))
85 | else if (indents < lastIndents) {
86 | input = token[1].input
87 | token = ['dedent']
88 | token.input = input
89 | while (--lastIndents > 0)
90 | stack.push(token)
91 | }
92 | }
93 | break
94 | }
95 | if (!ignore)
96 | if (token)
97 | stack.push(token),
98 | token = null
99 | else
100 | throw new SyntaxError(context(str))
101 | ignore = false
102 | }
103 | return stack
104 | }
105 |
106 | // --- Parser
107 |
108 | /**
109 | * Initialize with _tokens_.
110 | */
111 |
112 | function Parser(tokens) {
113 | this.tokens = tokens
114 | }
115 |
116 | /**
117 | * Look-ahead a single token.
118 | *
119 | * @return {array}
120 | * @api public
121 | */
122 |
123 | Parser.prototype.peek = function() {
124 | return this.tokens[0]
125 | }
126 |
127 | /**
128 | * Advance by a single token.
129 | *
130 | * @return {array}
131 | * @api public
132 | */
133 |
134 | Parser.prototype.advance = function() {
135 | return this.tokens.shift()
136 | }
137 |
138 | /**
139 | * Advance and return the token's value.
140 | *
141 | * @return {mixed}
142 | * @api private
143 | */
144 |
145 | Parser.prototype.advanceValue = function() {
146 | return this.advance()[1][1]
147 | }
148 |
149 | /**
150 | * Accept _type_ and advance or do nothing.
151 | *
152 | * @param {string} type
153 | * @return {bool}
154 | * @api private
155 | */
156 |
157 | Parser.prototype.accept = function(type) {
158 | if (this.peekType(type))
159 | return this.advance()
160 | }
161 |
162 | /**
163 | * Expect _type_ or throw an error _msg_.
164 | *
165 | * @param {string} type
166 | * @param {string} msg
167 | * @api private
168 | */
169 |
170 | Parser.prototype.expect = function(type, msg) {
171 | if (this.accept(type)) return
172 | throw new Error(msg + ', ' + context(this.peek()[1].input))
173 | }
174 |
175 | /**
176 | * Return the next token type.
177 | *
178 | * @return {string}
179 | * @api private
180 | */
181 |
182 | Parser.prototype.peekType = function(val) {
183 | return this.tokens[0] &&
184 | this.tokens[0][0] === val
185 | }
186 |
187 | /**
188 | * space*
189 | */
190 |
191 | Parser.prototype.ignoreSpace = function() {
192 | while (this.peekType('space'))
193 | this.advance()
194 | }
195 |
196 | /**
197 | * (space | indent | dedent)*
198 | */
199 |
200 | Parser.prototype.ignoreWhitespace = function() {
201 | while (this.peekType('space') ||
202 | this.peekType('indent') ||
203 | this.peekType('dedent'))
204 | this.advance()
205 | }
206 |
207 | /**
208 | * block
209 | * | doc
210 | * | list
211 | * | inlineList
212 | * | hash
213 | * | inlineHash
214 | * | string
215 | * | float
216 | * | int
217 | * | true
218 | * | false
219 | */
220 |
221 | Parser.prototype.parse = function() {
222 | switch (this.peek()[0]) {
223 | case 'doc':
224 | return this.parseDoc()
225 | case '-':
226 | return this.parseList()
227 | case '{':
228 | return this.parseInlineHash()
229 | case '[':
230 | return this.parseInlineList()
231 | case 'id':
232 | return this.parseHash()
233 | case 'string':
234 | return this.advanceValue()
235 | case 'float':
236 | return parseFloat(this.advanceValue())
237 | case 'int':
238 | return parseInt(this.advanceValue())
239 | case 'true':
240 | return true
241 | case 'false':
242 | return false
243 | }
244 | }
245 |
246 | /**
247 | * '---'? indent expr dedent
248 | */
249 |
250 | Parser.prototype.parseDoc = function() {
251 | this.accept('doc')
252 | this.expect('indent', 'expected indent after document')
253 | var val = this.parse()
254 | this.expect('dedent', 'document not properly dedented')
255 | return val
256 | }
257 |
258 | /**
259 | * ( id ':' - expr -
260 | * | id ':' - indent expr dedent
261 | * )+
262 | */
263 |
264 | Parser.prototype.parseHash = function() {
265 | var id, hash = {}
266 | while (this.peekType('id') && (id = this.advanceValue())) {
267 | this.expect(':', 'expected semi-colon after id')
268 | this.ignoreSpace()
269 | if (this.accept('indent'))
270 | hash[id] = this.parse(),
271 | this.expect('dedent', 'hash not properly dedented')
272 | else
273 | hash[id] = this.parse()
274 | this.ignoreSpace()
275 | }
276 | return hash
277 | }
278 |
279 | /**
280 | * '{' (- ','? ws id ':' - expr ws)* '}'
281 | */
282 |
283 | Parser.prototype.parseInlineHash = function() {
284 | var hash = {}, id, i = 0
285 | this.accept('{')
286 | while (!this.accept('}')) {
287 | this.ignoreSpace()
288 | if (i) this.expect(',', 'expected comma')
289 | this.ignoreWhitespace()
290 | if (this.peekType('id') && (id = this.advanceValue())) {
291 | this.expect(':', 'expected semi-colon after id')
292 | this.ignoreSpace()
293 | hash[id] = this.parse()
294 | this.ignoreWhitespace()
295 | }
296 | ++i
297 | }
298 | return hash
299 | }
300 |
301 | /**
302 | * ( '-' - expr -
303 | * | '-' - indent expr dedent
304 | * )+
305 | */
306 |
307 | Parser.prototype.parseList = function() {
308 | var list = []
309 | while (this.accept('-')) {
310 | this.ignoreSpace()
311 | if (this.accept('indent'))
312 | list.push(this.parse()),
313 | this.expect('dedent', 'list item not properly dedented')
314 | else
315 | list.push(this.parse())
316 | this.ignoreSpace()
317 | }
318 | return list
319 | }
320 |
321 | /**
322 | * '[' (- ','? - expr -)* ']'
323 | */
324 |
325 | Parser.prototype.parseInlineList = function() {
326 | var list = [], i = 0
327 | this.accept('[')
328 | while (!this.accept(']')) {
329 | this.ignoreSpace()
330 | if (i) this.expect(',', 'expected comma')
331 | this.ignoreSpace()
332 | list.push(this.parse())
333 | this.ignoreSpace()
334 | ++i
335 | }
336 | return list
337 | }
338 |
339 | /**
340 | * Evaluate a _str_ of yaml.
341 | *
342 | * @param {string} str
343 | * @return {mixed}
344 | * @api public
345 | */
346 |
347 | exports.eval = function(str) {
348 | return (new Parser(exports.tokenize(str))).parse()
349 | }
350 |
--------------------------------------------------------------------------------
/src/main.coffee:
--------------------------------------------------------------------------------
1 | fs = require('fs')
2 | sys = require('sys')
3 | Path = require("path")
4 |
5 | root = __dirname + "/../"
6 | router = require("#{root}/lib/router")
7 | request = require("#{root}/lib/request")
8 | _ = require("#{root}/lib/underscore")
9 | server = router.getServer()
10 | Project = require("#{root}/src/project").Project
11 |
12 | String.prototype.capitalize = ->
13 | this.charAt(0).toUpperCase() + this.substring(1).toLowerCase()
14 |
15 | OptionParser = require("#{root}/lib/parseopt").OptionParser
16 | parser = new OptionParser {
17 | minargs : 0
18 | maxargs : 10
19 | }
20 |
21 | $usage = '''
22 | Usage:
23 |
24 | capt new projectname
25 | - create a new project
26 |
27 | Code generators:
28 | * capt generate model post
29 | * capt generate collection post
30 | * capt generate router posts
31 | * capt generate view posts show
32 |
33 |
34 | '''
35 |
36 | # Parse command line
37 | data = parser.parse()
38 |
39 | #
40 | # Raise an error
41 | #
42 | raise = (error) ->
43 | sys.puts error
44 | process.exit()
45 |
46 | task = (command, description, func) ->
47 | length = command.split(' ').length
48 |
49 | if data.arguments.slice(0,length).join(" ") == command
50 | func(data.arguments.slice(length))
51 | task.done = true
52 |
53 |
54 | task 'new', 'create a new project', (arguments) ->
55 | project = arguments[0] or raise("Must supply a name for new project.")
56 |
57 | sys.puts " * Creating folders"
58 |
59 | dirs = ["", "spec", "spec/jasmine", "spec/models", "spec/collections", "spec/routers", "spec/views", "spec/fixtures",
60 | "app", "app/views", "app/templates", "app/templates/helpers", "app/templates/i18n","app/collections", "app/models", "app/routers",
61 | "lib", "lib/Handlebars", "css"]
62 |
63 | for dir in dirs
64 | fs.mkdirSync "#{project}/#{dir}", 0755
65 |
66 | sys.puts " * Creating directory structure"
67 |
68 | libs = {
69 | "lib/require-jquery.js" : "lib/require-jquery.js"
70 | "lib/underscore.js" : "lib/underscore.js"
71 | "lib/backbone.js" : "lib/backbone.js"
72 | "lib/handlebars.js" : "lib/handlebars.js"
73 | "lib/hbs.js" : "lib/hbs.js"
74 | "lib/Handlebars/i18nprecompile.js" : "lib/Handlebars/i18nprecompile.js"
75 | "index.html" : "html/index.html"
76 | "css/bootstrap.css" : "css/bootstrap.css"
77 | "app/app.js" : "app.js"
78 | "app/routers/ApplicationRouter.js" : "routers/ApplicationRouter.js"
79 | "app/views/ApplicationView.js" : "views/ApplicationView.js"
80 | "app/templates/helpers/all.js" : "templates/helpers/all.js"
81 | "app/templates/i18n/en_us.json" : "templates/i18n/en_us.json"
82 | "app/templates/i18n/en_ca.json" : "templates/i18n/en_ca.json"
83 | "spec/index.html" : "html/runner.html"
84 | "spec/spec.js" : "spec.js"
85 | "spec/views/ApplicationViewSpec.js" : "views/ApplicationViewSpec.js"
86 | "spec/jasmine/jasmine-html.js" : "lib/jasmine-html.js"
87 | "spec/jasmine/jasmine.css" : "lib/jasmine.css"
88 | "spec/jasmine/jasmine.js" : "lib/jasmine.js"
89 | }
90 |
91 | downloadLibrary = (path, lib) ->
92 | request { uri : lib }, (error, response, body) ->
93 | if (!error && response.statusCode == 200)
94 | sys.puts " * " + Path.basename(path)
95 | fs.writeFileSync("#{project}/#{path}", body)
96 | else
97 | sys.puts " * [ERROR] Could not download " + Path.basename(path)
98 |
99 | copyLibrary = (path, lib) ->
100 | fs.writeFileSync(Path.join(project, path), fs.readFileSync(lib) + "")
101 |
102 | for path, lib of libs
103 | if lib.match(/^http/)
104 | downloadLibrary(path, lib)
105 | else
106 | copyLibrary(path, Path.join(root, "templates/", lib))
107 |
108 | task 'generate model', 'create a new model', (arguments) ->
109 | project = new Project(process.cwd())
110 |
111 | if arguments[0]
112 | model = arguments[0].toLowerCase()
113 | else
114 | raise("Must supply a name for the model")
115 |
116 | appendToSpecRunner = (path) ->
117 | specs = fs.readFileSync(Path.join(project.root, 'spec/spec.js')) + ""
118 | marker = '// '
119 | newSpecs = specs.split(marker)
120 | newSpecs.splice(1,0,",\n '#{path}'#{marker}")
121 | newSpecs = newSpecs.join('')
122 |
123 | fs.writeFileSync(Path.join(project.root, 'spec/spec.js'), newSpecs)
124 |
125 | copyFile = (from, to) ->
126 | ejs = fs.readFileSync(from) + ""
127 | fs.writeFileSync(Path.join(project.root, to), _.template(ejs, { project : project, model : model }))
128 | sys.puts " * Created #{to}"
129 |
130 | copyFile "#{root}/templates/models/model.#{project.language()}", "app/models/#{model.capitalize()}.#{project.language()}"
131 | copyFile "#{root}/templates/models/spec.#{project.language()}", "spec/models/#{model.capitalize()}Spec.#{project.language()}"
132 |
133 | appendToSpecRunner "../spec/models/#{model.capitalize()}Spec"
134 |
135 |
136 | task 'generate collection', 'create a new collection', (arguments) ->
137 | project = new Project(process.cwd())
138 |
139 | if arguments[0]
140 | model = arguments[0].toLowerCase()
141 | else
142 | raise("Must supply a name for the model")
143 |
144 | appendToSpecRunner = (path) ->
145 | specs = fs.readFileSync(Path.join(project.root, 'spec/spec.js')) + ""
146 | marker = '// '
147 | newSpecs = specs.split(marker)
148 | newSpecs.splice(1,0,",\n '#{path}'#{marker}")
149 | newSpecs = newSpecs.join('')
150 |
151 | fs.writeFileSync(Path.join(project.root, 'spec/spec.js'), newSpecs)
152 |
153 | copyFile = (from, to) ->
154 | ejs = fs.readFileSync(from) + ""
155 | fs.writeFileSync(Path.join(project.root, to), _.template(ejs, { project : project, model : model }))
156 | sys.puts " * Created #{to}"
157 |
158 | copyFile "#{root}/templates/collection/collection.#{project.language()}", "app/collections/#{model.capitalize()}Collection.#{project.language()}"
159 | copyFile "#{root}/templates/collection/spec.#{project.language()}", "spec/collections/#{model.capitalize()}CollectionSpec.#{project.language()}"
160 |
161 | appendToSpecRunner "../spec/collections/#{model.capitalize()}CollectionSpec"
162 |
163 | task 'generate router', 'create a new router', (arguments) ->
164 | project = new Project(process.cwd())
165 |
166 | if arguments[0]
167 | router = arguments[0].toLowerCase()
168 | else
169 | raise("Must supply a name for the router")
170 |
171 | appendToSpecRunner = (path) ->
172 | specs = fs.readFileSync(Path.join(project.root, 'spec/spec.js')) + ""
173 | marker = '// '
174 | newSpecs = specs.split(marker)
175 | newSpecs.splice(1,0,",\n '#{path}'#{marker}")
176 | newSpecs = newSpecs.join('')
177 |
178 | fs.writeFileSync(Path.join(project.root, 'spec/spec.js'), newSpecs)
179 |
180 | copyFile = (from, to) ->
181 | ejs = fs.readFileSync(from) + ""
182 | fs.writeFileSync(Path.join(project.root, to), _.template(ejs, { project : project, router : router }))
183 | sys.puts " * Created #{to}"
184 |
185 | try
186 | fs.mkdirSync "#{project.root}/app/views/#{router}", 0755
187 | fs.mkdirSync "#{project.root}/app/templates/#{router}", 0755
188 | catch e
189 | # ...
190 |
191 | copyFile "#{root}/templates/routers/router.#{project.language()}", "app/router/#{router.capitalize()}Router.#{project.language()}"
192 | copyFile "#{root}/templates/routers/spec.#{project.language()}", "spec/routers/#{router.capitalize}RouterSpec.#{project.language()}"
193 |
194 | appendToSpecRunner "../spec/routers/#{router.capitalize()}RouterSpec"
195 |
196 | task 'generate view', 'create a new view', (arguments) ->
197 | project = new Project(process.cwd())
198 |
199 | if arguments[0] and arguments[1]
200 | router = arguments[0].toLowerCase()
201 | view = arguments[1].toLowerCase()
202 | else
203 | raise("Must supply a name for the router and then view")
204 |
205 | appendToSpecRunner = (path) ->
206 | specs = fs.readFileSync(Path.join(project.root, 'spec/spec.js')) + ""
207 | marker = '// '
208 | newSpecs = specs.split(marker)
209 | newSpecs.splice(1,0,",\n '#{path}'#{marker}")
210 | newSpecs = newSpecs.join('')
211 |
212 | fs.writeFileSync(Path.join(project.root, 'spec/spec.js'), newSpecs)
213 |
214 | copyFile = (from, to) ->
215 | ejs = fs.readFileSync(from).toString()
216 | fs.writeFileSync(Path.join(project.root, to), _.template(ejs, { project : project, router: router, view : view }))
217 | sys.puts " * Created #{to}"
218 |
219 | if !Path.existsSync("#{project.root}/app/views/#{router}")
220 | fs.mkdirSync "#{project.root}/app/views/#{router}", 0755
221 |
222 | if !Path.existsSync("#{project.root}/spec/views/#{router}")
223 | fs.mkdirSync "#{project.root}/spec/views/#{router}", 0755
224 |
225 |
226 | if !Path.existsSync("#{project.root}/app/templates/#{router}")
227 | fs.mkdirSync "#{project.root}/app/templates/#{router}", 0755
228 |
229 | copyFile "#{root}/templates/views/view.#{project.language()}", "app/views/#{router}/#{view.capitalize()}View.#{project.language()}"
230 | copyFile "#{root}/templates/templates/template.hbs", "app/templates/#{router}/#{view.capitalize()}Template.hbs"
231 | copyFile "#{root}/templates/views/spec.#{project.language()}", "spec/views/#{router}/#{view.capitalize()}ViewSpec.#{project.language()}"
232 |
233 | appendToSpecRunner "../spec/views/#{router}/#{view.capitalize()}ViewSpec"
234 |
235 | if !task.done
236 | sys.puts $usage
237 | process.exit()
238 |
--------------------------------------------------------------------------------
/templates/lib/hbs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license handlebars hbs 0.2.1 - Alex Sexton, but Handlebars has it's own licensing junk
3 | *
4 | * Available via the MIT or new BSD license.
5 | * see: http://github.com/jrburke/require-cs for details on the plugin this was based off of
6 | */
7 |
8 | /* Yes, deliciously evil. */
9 | /*jslint evil: true, strict: false, plusplus: false, regexp: false */
10 | /*global require: false, XMLHttpRequest: false, ActiveXObject: false,
11 | define: false, process: false, window: false */
12 | define([
13 | //>>excludeStart('excludeAfterBuild', pragmas.excludeAfterBuild)
14 | '../lib/Handlebars/i18nprecompile', 'Handlebars', 'underscore'
15 | // '../lib/Handlebars', '../lib/underscore', '../lib/Handlebars/i18nprecompile', '../lib/json2'
16 | //>>excludeEnd('excludeAfterBuild')
17 | ], function (
18 | //>>excludeStart('excludeAfterBuild', pragmas.excludeAfterBuild)
19 | precompile, Handlebars, _
20 | // Handlebars, _, precompile, JSON
21 | //>>excludeEnd('excludeAfterBuild')
22 | ) {
23 | // NOTE :: if you want to load template in production outside of the build, either precompile
24 | // them into modules or take out the conditional build stuff here
25 |
26 | //>>excludeStart('excludeAfterBuild', pragmas.excludeAfterBuild)
27 | var fs, getXhr,
28 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
29 | fetchText = function () {
30 | throw new Error('Environment unsupported.');
31 | },
32 | buildMap = [],
33 | filecode = "w+",
34 | templateExtension = ".hbs",
35 | customNameExtension = "@hbs",
36 | devStyleDirectory = "/demo/styles/",
37 | buildStyleDirectory = "/demo-build/styles/",
38 | buildCSSFileName = "screen.build.css";
39 |
40 | if (typeof window !== "undefined" && window.navigator && window.document) {
41 | // Browser action
42 | getXhr = function () {
43 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
44 | var xhr, i, progId;
45 | if (typeof XMLHttpRequest !== "undefined") {
46 | return new XMLHttpRequest();
47 | } else {
48 | for (i = 0; i < 3; i++) {
49 | progId = progIds[i];
50 | try {
51 | xhr = new ActiveXObject(progId);
52 | } catch (e) {}
53 |
54 | if (xhr) {
55 | progIds = [progId]; // so faster next time
56 | break;
57 | }
58 | }
59 | }
60 |
61 | if (!xhr) {
62 | throw new Error("getXhr(): XMLHttpRequest not available");
63 | }
64 |
65 | return xhr;
66 | };
67 |
68 | fetchText = function (url, callback) {
69 | var xhr = getXhr();
70 | xhr.open('GET', url, true);
71 | xhr.onreadystatechange = function (evt) {
72 | //Do not explicitly handle errors, those should be
73 | //visible via console output in the browser.
74 | if (xhr.readyState === 4) {
75 | callback(xhr.responseText);
76 | }
77 | };
78 | xhr.send(null);
79 | };
80 |
81 | } else if (typeof process !== "undefined" &&
82 | process.versions &&
83 | !!process.versions.node) {
84 | //Using special require.nodeRequire, something added by r.js.
85 | fs = require.nodeRequire('fs');
86 | fetchText = function (path, callback) {
87 | callback(fs.readFileSync(path, 'utf8'));
88 | };
89 | } else if (typeof java !== "undefined" && typeof java.io !== "undefined") {
90 | fetchText = function(path, callback) {
91 | var f = new java.io.File(path);
92 | var is = new java.io.FileReader(f);
93 | var reader = new java.io.BufferedReader(is);
94 | var line;
95 | var text = "";
96 | while ((line = reader.readLine()) !== null) {
97 | text += new String(line) + "\n";
98 | }
99 | reader.close();
100 | callback(text);
101 | };
102 | }
103 |
104 | var cache = {};
105 | var fetchOrGetCached = function ( path, callback ){
106 | if ( cache[path] ){
107 | callback(cache[path]);
108 | }
109 | else {
110 | fetchText(path, function(data){
111 | cache[path] = data;
112 | callback.call(this, data);
113 | });
114 | }
115 | };
116 | var styleList = [], styleMap = {};
117 | //>>excludeEnd('excludeAfterBuild')
118 |
119 | return {
120 |
121 | setExtension : function (ext) {
122 | templateExtension = ext;
123 | },
124 |
125 | get: function () {
126 | return Handlebars;
127 | },
128 |
129 | write: function (pluginName, name, write) {
130 |
131 | if ( (name + customNameExtension ) in buildMap) {
132 | var text = buildMap[name + customNameExtension];
133 | write.asModule(pluginName + "!" + name, text);
134 | }
135 | },
136 |
137 | version: '1.0.3beta',
138 |
139 | load: function (name, parentRequire, load, config) {
140 | //>>excludeStart('excludeAfterBuild', pragmas.excludeAfterBuild)
141 |
142 |
143 | var compiledName = name + customNameExtension,
144 | partialDeps = [];
145 |
146 | function recursiveNodeSearch( statements, res ) {
147 | _(statements).forEach(function ( statement ) {
148 | if ( statement && statement.type && statement.type === 'partial' ) {
149 | res.push(statement.id.string);
150 | }
151 | if ( statement && statement.program && statement.program.statements ) {
152 | recursiveNodeSearch( statement.program.statements, res );
153 | }
154 | });
155 | return res;
156 | }
157 |
158 | // TODO :: use the parser to do this!
159 | function findPartialDeps( nodes ) {
160 | var res = [];
161 | if ( nodes && nodes.statements ) {
162 | res = recursiveNodeSearch( nodes.statements, [] );
163 | }
164 | return _(res).unique();
165 | }
166 |
167 | // See if the first item is a comment that's json
168 | function getMetaData( nodes ) {
169 | var statment, res, test;
170 | if ( nodes && nodes.statements ) {
171 | statement = nodes.statements[0];
172 | if ( statement.type === "comment" ) {
173 | try {
174 | res = _.str.trim( statement.comment );
175 | test = JSON.parse(res);
176 | return res;
177 | }
178 | catch (e) {
179 | return "{}";
180 | }
181 | }
182 | }
183 | return "{}";
184 | }
185 | function composeParts ( parts ) {
186 | if ( !parts ) {
187 | return [];
188 | }
189 | var res = [parts[0]],
190 | cur = parts[0],
191 | i;
192 |
193 | for (i = 1; i < parts.length; ++i) {
194 | if ( parts.hasOwnProperty(i) ) {
195 | cur += "." + parts[i];
196 | res.push( cur );
197 | }
198 | }
199 | return res;
200 | }
201 |
202 | function recursiveVarSearch( statements, res, prefix, helpersres ) {
203 | prefix = prefix ? prefix+"." : "";
204 |
205 | var newprefix = "", flag = false;
206 |
207 | // loop through each statement
208 | _(statements).forEach(function ( statement ) {
209 | var parts, part, sideways;
210 |
211 | // if it's a mustache block
212 | if ( statement && statement.type && statement.type === 'mustache' ) {
213 |
214 | // If it has params, the first part is a helper or something
215 | if ( !statement.params || ! statement.params.length ) {
216 | parts = composeParts( statement.id.parts );
217 | for( part in parts ) {
218 | if ( parts[ part ] ) {
219 | newprefix = parts[ part ] || newprefix;
220 | res.push( prefix + parts[ part ] );
221 | }
222 | }
223 | res.push(prefix + statement.id.string);
224 | }
225 |
226 | // grab the params
227 | if ( statement.params ) {
228 | _(statement.params).forEach(function(param){
229 | parts = composeParts( param.parts );
230 |
231 | for(var part in parts ) {
232 | if ( parts[ part ] ) {
233 | newprefix = parts[part] || newprefix;
234 | helpersres.push(statement.id.string);
235 | res.push( prefix + parts[ part ] );
236 | }
237 | }
238 | });
239 | }
240 | }
241 |
242 | // If it's a meta block
243 | if ( statement && statement.mustache ) {
244 | recursiveVarSearch( [statement.mustache], res, prefix + newprefix, helpersres );
245 | }
246 |
247 | // if it's a whole new program
248 | if ( statement && statement.program && statement.program.statements ) {
249 | sideways = recursiveVarSearch([statement.mustache],[], "", helpersres)[0] || "";
250 | recursiveVarSearch( statement.program.statements, res, prefix + newprefix + (sideways ? (prefix+newprefix) ? "."+sideways : sideways : ""), helpersres);
251 | }
252 | });
253 | return res;
254 | }
255 |
256 | // This finds the Helper dependencies since it's soooo similar
257 | function getExternalDeps( nodes ) {
258 | var res = [];
259 | var helpersres = [];
260 |
261 | if ( nodes && nodes.statements ) {
262 | res = recursiveVarSearch( nodes.statements, [], undefined, helpersres );
263 | }
264 |
265 | var defaultHelpers = ["helperMissing", "blockHelperMissing", "each", "if", "unless", "with"];
266 |
267 | return {
268 | vars : _(res).unique().map(function(e){
269 | if ( e === "" ) {
270 | return '.';
271 | }
272 | if ( e.length && e[e.length-1] === '.' ) {
273 | return e.substr(0,e.length-1) + '[]';
274 | }
275 | return e;
276 | }),
277 | helpers : _(_(helpersres).unique().map(function(e){
278 | if ( _(defaultHelpers).contains(e) ) {
279 | return undefined;
280 | }
281 | return e;
282 | })).compact()
283 | };
284 | }
285 |
286 | var path = parentRequire.toUrl(name + templateExtension);
287 | fetchOrGetCached( parentRequire.toUrl('templates/i18n/'+(config.locale || "en_us")+'.json'), function (langMap) {
288 | // if local stuff isn't set, 404 isn't caught, and JSON.parse gets passed whatever generic 404 server text is sent back
289 | try {
290 | langMap = JSON.parse(langMap);
291 | }
292 | catch(e) {
293 | langMap = {};
294 | }
295 |
296 | fetchText(path, function (text) {
297 | // for some reason it doesn't include hbs _first_ when i don't add it here...
298 | var nodes = Handlebars.parse(text),
299 | deps = findPartialDeps( nodes ),
300 | meta = getMetaData( nodes ),
301 | extDeps = getExternalDeps( nodes ),
302 | vars = extDeps.vars,
303 | helps = extDeps.helpers || [],
304 | depStr = deps.join("', 'hbs!").replace(/_/g, '/'),
305 | helpDepStr = helps.join("', 'templates/helpers/"),
306 | debugOutputStart = "",
307 | debugOutputEnd = "",
308 | debugProperties = "",
309 | metaObj, head, linkElem;
310 |
311 | if ( depStr ) {
312 | depStr = ",'hbs!" + depStr + "'";
313 | }
314 | if ( helpDepStr ) {
315 | helpDepStr = ",'templates/helpers/" + helpDepStr + "'";
316 | }
317 | // use a single helper file instead of trying to bring in individual ones.
318 | // TODO: make this configurable
319 | helpDepStr = ",'templates/helpers/all'";
320 | if ( meta !== "{}" ) {
321 | try {
322 | metaObj = JSON.parse(meta);
323 | if ( metaObj && metaObj.styles ) {
324 | styleList = _.union(styleList, metaObj.styles);
325 |
326 | // In dev mode in the browser
327 | if ( require.isBrowser && ! config.isBuild ) {
328 | head = document.head || document.getElementsByTagName('head')[0];
329 | _(metaObj.styles).forEach(function (style) {
330 | if ( !styleMap[style] ) {
331 | linkElem = document.createElement('link');
332 | linkElem.href = config.baseUrl + 'styles/' + style + '.css';
333 | linkElem.media = 'all';
334 | linkElem.rel = 'stylesheet';
335 | linkElem.type = 'text/css';
336 | head.appendChild(linkElem);
337 | styleMap[style] = linkElem;
338 | }
339 | });
340 | }
341 | else if ( config.isBuild ) {
342 | (function(){
343 | var fs = require.nodeRequire('fs'),
344 | str = _(metaObj.styles).map(function (style) {
345 | if (!styleMap[style]) {
346 | styleMap[style] = true;
347 | return "@import url("+buildStyleDirectory+style+".css);\n";
348 | }
349 | return "";
350 | }).join("\n");
351 |
352 | // I write out my import statements to a file in order to help me build stuff.
353 | // Then I use a tool to inline my import statements afterwards. (you can run r.js on it too)
354 | fs.open(__dirname + buildStyleDirectory + buildCSSFileName, filecode, '0666', function( e, id ) {
355 | fs.writeSync(id, str, null, encoding='utf8');
356 | fs.close(id);
357 | });
358 | filecode = "a";
359 | })();
360 | }
361 | }
362 | }
363 | catch(e){
364 | console.log('error injecting styles');
365 | }
366 | }
367 |
368 | if ( ! config.isBuild && ! config.serverRender ) {
369 | debugOutputStart = "";
370 | debugOutputEnd = "";
371 | debugProperties = "t.meta = " + meta + ";\n" +
372 | "t.helpers = " + JSON.stringify(helps) + ";\n" +
373 | "t.deps = " + JSON.stringify(deps) + ";\n" +
374 | "t.vars = " + JSON.stringify(vars) + ";\n";
375 | }
376 |
377 | var prec = precompile( text, _.extend( langMap, config.localeMapping ) );
378 |
379 | text = "/* START_TEMPLATE */\n" +
380 | "define(['hbs','Handlebars'"+depStr+helpDepStr+"], function( hbs, Handlebars ){ \n" +
381 | "var t = Handlebars.template(" + prec + ");\n" +
382 | "Handlebars.registerPartial('" + name.replace( /\//g , '_') + "', t);\n" +
383 | debugProperties +
384 | "return t;\n" +
385 | "});\n" +
386 | "/* END_TEMPLATE */\n";
387 |
388 | //Hold on to the transformed text if a build.
389 | if (config.isBuild) {
390 | buildMap[compiledName] = text;
391 | }
392 |
393 | //IE with conditional comments on cannot handle the
394 | //sourceURL trick, so skip it if enabled.
395 | /*@if (@_jscript) @else @*/
396 | if (!config.isBuild) {
397 | text += "\r\n//@ sourceURL=" + path;
398 | }
399 | /*@end@*/
400 |
401 | for ( var i in deps ) {
402 | if ( deps.hasOwnProperty(i) ) {
403 | deps[ i ] = 'hbs!' + deps[ i ].replace(/_/g, '/');
404 | }
405 | }
406 |
407 | if ( !config.isBuild ) {
408 | require( deps, function (){
409 | load.fromText(compiledName, text);
410 |
411 | //Give result to load. Need to wait until the module
412 | //is fully parse, which will happen after this
413 | //execution.
414 | parentRequire([compiledName], function (value) {
415 | load(value);
416 | });
417 | });
418 | }
419 | else {
420 | load.fromText(compiledName, text);
421 |
422 | //Give result to load. Need to wait until the module
423 | //is fully parse, which will happen after this
424 | //execution.
425 | parentRequire([compiledName], function (value) {
426 | load(value);
427 | });
428 | }
429 | });
430 | });
431 | //>>excludeEnd('excludeAfterBuild')
432 | }
433 | };
434 | });
435 | /* END_hbs_PLUGIN */
436 |
--------------------------------------------------------------------------------
/lib/router.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2010 Tim Caswell
3 |
4 | Permission is hereby granted, free of charge, to any person
5 | obtaining a copy of this software and associated documentation
6 | files (the "Software"), to deal in the Software without
7 | restriction, including without limitation the rights to use,
8 | copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the
10 | Software is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | OTHER DEALINGS IN THE SOFTWARE.
24 | */
25 |
26 | var sys = require('sys');
27 | var fs = require('fs');
28 | var path = require('path');
29 | var http = require('http');
30 | var url_parse = require("url").parse;
31 |
32 | // Used as a simple, convient 404 handler.
33 | function notFound(req, res, message) {
34 | message = (message || "Not Found\n") + "";
35 | res.writeHead(404, {
36 | "Content-Type": "text/plain",
37 | "Content-Length": message.length
38 | });
39 | if (req.method !== "HEAD")
40 | res.write(message);
41 | res.end();
42 | }
43 |
44 | // Modifies req and res to call logger with a log line on each res.end
45 | // Think of it as "middleware"
46 | function logify(req, res, logger) {
47 | var end = res.end;
48 | res.end = function () {
49 | // Common Log Format (mostly)
50 | logger((req.socket && req.socket.remoteAddress) + " - - [" + (new Date()).toUTCString() + "]"
51 | + " \"" + req.method + " " + req.url
52 | + " HTTP/" + req.httpVersionMajor + "." + req.httpVersionMinor + "\" "
53 | + res.statusCode + " - \""
54 | + (req.headers['referer'] || "") + "\" \"" + (req.headers["user-agent"] ? req.headers["user-agent"].split(' ')[0] : '') + "\"");
55 | return end.apply(this, arguments);
56 | }
57 | var writeHead = res.writeHead;
58 | res.writeHead = function (code) {
59 | res.statusCode = code;
60 | return writeHead.apply(this, arguments);
61 | }
62 | }
63 |
64 | exports.getServer = function getServer(logger) {
65 |
66 | logger = logger || sys.puts;
67 |
68 | var routes = [];
69 |
70 | // Adds a route the the current server
71 | function addRoute(method, pattern, handler, format) {
72 | if (typeof pattern === 'string') {
73 | pattern = new RegExp("^" + pattern + "$");
74 | }
75 | var route = {
76 | method: method,
77 | pattern: pattern,
78 | handler: handler
79 | };
80 | if (format !== undefined) {
81 | route.format = format;
82 | }
83 | routes.push(route);
84 | }
85 |
86 | // The four verbs are wrappers around addRoute
87 | function get(pattern, handler) {
88 | return addRoute("GET", pattern, handler);
89 | }
90 | function post(pattern, handler, format) {
91 | return addRoute("POST", pattern, handler, format);
92 | }
93 | function put(pattern, handler, format) {
94 | return addRoute("PUT", pattern, handler, format);
95 | }
96 | function del(pattern, handler) {
97 | return addRoute("DELETE", pattern, handler);
98 | }
99 | function head(pattern, handler) {
100 | return addRoute("HEAD", pattern, handler);
101 | }
102 |
103 | // This is a meta pattern that expands to a common RESTful mapping
104 | function resource(name, controller, format) {
105 | get(new RegExp('^/' + name + '$'), controller.index);
106 | get(new RegExp('^/' + name + '/([^/]+)$'), controller.show);
107 | post(new RegExp('^/' + name + '$'), controller.create, format);
108 | put(new RegExp('^/' + name + '/([^/]+)$'), controller.update, format);
109 | del(new RegExp('^/' + name + '/([^/]+)$'), controller.destroy);
110 | };
111 |
112 | function resourceController(name, data, on_change) {
113 | data = data || [];
114 | on_change = on_change || function () {};
115 | return {
116 | index: function (req, res) {
117 | res.simpleJson(200, {content: data, self: '/' + name});
118 | },
119 | show: function (req, res, id) {
120 | var item = data[id];
121 | if (item) {
122 | res.simpleJson(200, {content: item, self: '/' + name + '/' + id});
123 | } else {
124 | res.notFound();
125 | }
126 | },
127 | create: function (req, res) {
128 | req.jsonBody(function (json) {
129 | var item, id, url;
130 | item = json && json.content;
131 | if (!item) {
132 | res.notFound();
133 | } else {
134 | data.push(item);
135 | id = data.length - 1;
136 | on_change(id);
137 | url = "/" + name + "/" + id;
138 | res.simpleJson(201, {content: item, self: url}, [["Location", url]]);
139 | }
140 | });
141 | },
142 | update: function (req, res, id) {
143 | req.jsonBody(function (json) {
144 | var item = json && json.content;
145 | if (!item) {
146 | res.notFound();
147 | } else {
148 | data[id] = item;
149 | on_change(id);
150 | res.simpleJson(200, {content: item, self: "/" + name + "/" + id});
151 | }
152 | });
153 | },
154 | destroy: function (req, res, id) {
155 | delete data[id];
156 | on_change(id);
157 | res.simpleJson(200, "200 Destroyed");
158 | }
159 | };
160 | };
161 |
162 | // Create the http server object
163 | var server = http.createServer(function (req, res) {
164 |
165 | // Enable logging on all requests using common-logger style
166 | logify(req, res, logger);
167 |
168 | var uri, path;
169 |
170 | // Performs an HTTP 302 redirect
171 | res.redirect = function redirect(location) {
172 | res.writeHead(302, {"Location": location});
173 | res.end();
174 | }
175 |
176 | // Performs an internal redirect
177 | res.innerRedirect = function innerRedirect(location) {
178 | logger("Internal Redirect: " + req.url + " -> " + location);
179 | req.url = location;
180 | doRoute();
181 | }
182 |
183 | function simpleResponse(code, body, content_type, extra_headers) {
184 | res.writeHead(code, (extra_headers || []).concat(
185 | [ ["Content-Type", content_type],
186 | ["Content-Length", Buffer.byteLength(body, 'utf8')]
187 | ]));
188 | if (req.method !== "HEAD")
189 | res.write(body, 'utf8');
190 | res.end();
191 | }
192 |
193 | res.simpleText = function (code, body, extra_headers) {
194 | simpleResponse(code, body, "text/plain", extra_headers);
195 | };
196 |
197 | res.simpleHtml = function (code, body, extra_headers) {
198 | simpleResponse(code, body, "text/html", extra_headers);
199 | };
200 |
201 | res.simpleJson = function (code, json, extra_headers) {
202 | simpleResponse(code, JSON.stringify(json), "application/json", extra_headers);
203 | };
204 |
205 | res.notFound = function (message) {
206 | notFound(req, res, message);
207 | };
208 |
209 | res.onlyHead = function (code, extra_headers) {
210 | res.writeHead(code, (extra_headers || []).concat(
211 | [["Content-Type", content_type]]));
212 | res.end();
213 | }
214 |
215 | function doRoute() {
216 | uri = url_parse(req.url);
217 | path = uri.pathname;
218 |
219 | for (var i = 0, l = routes.length; i < l; i += 1) {
220 | var route = routes[i];
221 | if (req.method === route.method) {
222 | var match = path.match(route.pattern);
223 | if (match && match[0].length > 0) {
224 | match.shift();
225 | match = match.map(function (part) {
226 | return part ? unescape(part) : part;
227 | });
228 | match.unshift(res);
229 | match.unshift(req);
230 | if (route.format !== undefined) {
231 | var body = "";
232 | req.setEncoding('utf8');
233 | req.addListener('data', function (chunk) {
234 | body += chunk;
235 | });
236 | req.addListener('end', function () {
237 | if (route.format === 'json') {
238 | try {
239 | body = JSON.parse(unescape(body));
240 | } catch(e) {
241 | body = null;
242 | }
243 | }
244 | match.push(body);
245 | route.handler.apply(null, match);
246 | });
247 | return;
248 | }
249 | var result = route.handler.apply(null, match);
250 | switch (typeof result) {
251 | case "string":
252 | res.simpleHtml(200, result);
253 | break;
254 | case "object":
255 | res.simpleJson(200, result);
256 | break;
257 | }
258 |
259 | return;
260 | }
261 | }
262 | }
263 |
264 | notFound(req, res);
265 | }
266 | doRoute();
267 |
268 | });
269 |
270 |
271 | function listen(port, host, callback) {
272 | port = port || 8080;
273 |
274 | if (typeof host === 'undefined' || host == '*')
275 | host = null;
276 |
277 | server.listen(port, host, callback);
278 |
279 | if (typeof port === 'number') {
280 | logger("node-router server instance at http://" + (host || '*') + ":" + port + "/");
281 | } else {
282 | logger("node-router server instance at unix:" + port);
283 | }
284 | }
285 |
286 | function end() {
287 | return server.end();
288 | }
289 |
290 | // Return a handle to the public facing functions from this closure as the
291 | // server object.
292 | return {
293 | get: get,
294 | post: post,
295 | put: put,
296 | del: del,
297 | resource: resource,
298 | resourceController: resourceController,
299 | listen: listen,
300 | end: end
301 | };
302 | }
303 |
304 |
305 |
306 |
307 | exports.staticHandler = function (filename) {
308 | var body, headers;
309 | var content_type = mime.getMime(filename)
310 | var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
311 |
312 | function loadResponseData(req, res, callback) {
313 | if (body && headers) {
314 | callback();
315 | return;
316 | }
317 |
318 | fs.readFile(filename, encoding, function (err, data) {
319 | if (err) {
320 | notFound(req, res, "Cannot find file: " + filename);
321 | return;
322 | }
323 | body = data;
324 | headers = [ [ "Content-Type" , content_type ],
325 | [ "Content-Length" , body.length ]
326 | ];
327 | headers.push(["Cache-Control", "public"]);
328 |
329 | callback();
330 | });
331 | }
332 |
333 | return function (req, res) {
334 | loadResponseData(req, res, function () {
335 | res.writeHead(200, headers);
336 | if (req.method !== "HEAD")
337 | res.write(body, encoding);
338 | res.end();
339 | });
340 | };
341 | };
342 |
343 | exports.staticDirHandler = function(root, prefix) {
344 | function loadResponseData(req, res, filename, callback) {
345 | var content_type = mime.getMime(filename);
346 | var encoding = (content_type.slice(0,4) === "text" ? "utf8" : "binary");
347 |
348 | fs.readFile(filename, encoding, function(err, data) {
349 | if(err) {
350 | notFound(req, res, "Cannot find file: " + filename);
351 | return;
352 | }
353 | var headers = [ [ "Content-Type" , content_type ],
354 | [ "Content-Length" , data.length ],
355 | [ "Cache-Control" , "public" ]
356 | ];
357 | callback(headers, data, encoding);
358 | });
359 | }
360 |
361 | return function (req, res) {
362 | // trim off any query/anchor stuff
363 | var filename = req.url.replace(/[\?|#].*$/, '');
364 | if (prefix) filename = filename.replace(new RegExp('^'+prefix), '');
365 | // make sure nobody can explore our local filesystem
366 | filename = path.join(root, filename.replace(/\.\.+/g, '.'));
367 | if (filename == root) filename = path.join(root, 'index.html');
368 | loadResponseData(req, res, filename, function(headers, body, encoding) {
369 | res.writeHead(200, headers);
370 | if (req.method !== "HEAD")
371 | res.write(body, encoding);
372 | res.end();
373 | });
374 | };
375 | };
376 |
377 |
378 | // Mini mime module for static file serving
379 | var DEFAULT_MIME = 'application/octet-stream';
380 | var mime = exports.mime = {
381 |
382 | getMime: function getMime(path) {
383 | var index = path.lastIndexOf(".");
384 | if (index < 0) {
385 | return DEFAULT_MIME;
386 | }
387 | return mime.TYPES[path.substring(index).toLowerCase()] || DEFAULT_MIME;
388 | },
389 |
390 | TYPES : { ".3gp" : "video/3gpp",
391 | ".a" : "application/octet-stream",
392 | ".ai" : "application/postscript",
393 | ".aif" : "audio/x-aiff",
394 | ".aiff" : "audio/x-aiff",
395 | ".asc" : "application/pgp-signature",
396 | ".asf" : "video/x-ms-asf",
397 | ".asm" : "text/x-asm",
398 | ".asx" : "video/x-ms-asf",
399 | ".atom" : "application/atom+xml",
400 | ".au" : "audio/basic",
401 | ".avi" : "video/x-msvideo",
402 | ".bat" : "application/x-msdownload",
403 | ".bin" : "application/octet-stream",
404 | ".bmp" : "image/bmp",
405 | ".bz2" : "application/x-bzip2",
406 | ".c" : "text/x-c",
407 | ".cab" : "application/vnd.ms-cab-compressed",
408 | ".cc" : "text/x-c",
409 | ".chm" : "application/vnd.ms-htmlhelp",
410 | ".class" : "application/octet-stream",
411 | ".com" : "application/x-msdownload",
412 | ".conf" : "text/plain",
413 | ".cpp" : "text/x-c",
414 | ".crt" : "application/x-x509-ca-cert",
415 | ".css" : "text/css",
416 | ".csv" : "text/csv",
417 | ".cxx" : "text/x-c",
418 | ".deb" : "application/x-debian-package",
419 | ".der" : "application/x-x509-ca-cert",
420 | ".diff" : "text/x-diff",
421 | ".djv" : "image/vnd.djvu",
422 | ".djvu" : "image/vnd.djvu",
423 | ".dll" : "application/x-msdownload",
424 | ".dmg" : "application/octet-stream",
425 | ".doc" : "application/msword",
426 | ".dot" : "application/msword",
427 | ".dtd" : "application/xml-dtd",
428 | ".dvi" : "application/x-dvi",
429 | ".ear" : "application/java-archive",
430 | ".eml" : "message/rfc822",
431 | ".eps" : "application/postscript",
432 | ".exe" : "application/x-msdownload",
433 | ".f" : "text/x-fortran",
434 | ".f77" : "text/x-fortran",
435 | ".f90" : "text/x-fortran",
436 | ".flv" : "video/x-flv",
437 | ".for" : "text/x-fortran",
438 | ".gem" : "application/octet-stream",
439 | ".gemspec" : "text/x-script.ruby",
440 | ".gif" : "image/gif",
441 | ".gz" : "application/x-gzip",
442 | ".h" : "text/x-c",
443 | ".hh" : "text/x-c",
444 | ".htm" : "text/html",
445 | ".html" : "text/html",
446 | ".ico" : "image/vnd.microsoft.icon",
447 | ".ics" : "text/calendar",
448 | ".ifb" : "text/calendar",
449 | ".iso" : "application/octet-stream",
450 | ".jar" : "application/java-archive",
451 | ".java" : "text/x-java-source",
452 | ".jnlp" : "application/x-java-jnlp-file",
453 | ".jpeg" : "image/jpeg",
454 | ".jpg" : "image/jpeg",
455 | ".js" : "application/javascript",
456 | ".json" : "application/json",
457 | ".log" : "text/plain",
458 | ".m3u" : "audio/x-mpegurl",
459 | ".m4v" : "video/mp4",
460 | ".man" : "text/troff",
461 | ".mathml" : "application/mathml+xml",
462 | ".mbox" : "application/mbox",
463 | ".mdoc" : "text/troff",
464 | ".me" : "text/troff",
465 | ".mid" : "audio/midi",
466 | ".midi" : "audio/midi",
467 | ".mime" : "message/rfc822",
468 | ".mml" : "application/mathml+xml",
469 | ".mng" : "video/x-mng",
470 | ".mov" : "video/quicktime",
471 | ".mp3" : "audio/mpeg",
472 | ".mp4" : "video/mp4",
473 | ".mp4v" : "video/mp4",
474 | ".mpeg" : "video/mpeg",
475 | ".mpg" : "video/mpeg",
476 | ".ms" : "text/troff",
477 | ".msi" : "application/x-msdownload",
478 | ".odp" : "application/vnd.oasis.opendocument.presentation",
479 | ".ods" : "application/vnd.oasis.opendocument.spreadsheet",
480 | ".odt" : "application/vnd.oasis.opendocument.text",
481 | ".ogg" : "application/ogg",
482 | ".p" : "text/x-pascal",
483 | ".pas" : "text/x-pascal",
484 | ".pbm" : "image/x-portable-bitmap",
485 | ".pdf" : "application/pdf",
486 | ".pem" : "application/x-x509-ca-cert",
487 | ".pgm" : "image/x-portable-graymap",
488 | ".pgp" : "application/pgp-encrypted",
489 | ".pkg" : "application/octet-stream",
490 | ".pl" : "text/x-script.perl",
491 | ".pm" : "text/x-script.perl-module",
492 | ".png" : "image/png",
493 | ".pnm" : "image/x-portable-anymap",
494 | ".ppm" : "image/x-portable-pixmap",
495 | ".pps" : "application/vnd.ms-powerpoint",
496 | ".ppt" : "application/vnd.ms-powerpoint",
497 | ".ps" : "application/postscript",
498 | ".psd" : "image/vnd.adobe.photoshop",
499 | ".py" : "text/x-script.python",
500 | ".qt" : "video/quicktime",
501 | ".ra" : "audio/x-pn-realaudio",
502 | ".rake" : "text/x-script.ruby",
503 | ".ram" : "audio/x-pn-realaudio",
504 | ".rar" : "application/x-rar-compressed",
505 | ".rb" : "text/x-script.ruby",
506 | ".rdf" : "application/rdf+xml",
507 | ".roff" : "text/troff",
508 | ".rpm" : "application/x-redhat-package-manager",
509 | ".rss" : "application/rss+xml",
510 | ".rtf" : "application/rtf",
511 | ".ru" : "text/x-script.ruby",
512 | ".s" : "text/x-asm",
513 | ".sgm" : "text/sgml",
514 | ".sgml" : "text/sgml",
515 | ".sh" : "application/x-sh",
516 | ".sig" : "application/pgp-signature",
517 | ".snd" : "audio/basic",
518 | ".so" : "application/octet-stream",
519 | ".svg" : "image/svg+xml",
520 | ".svgz" : "image/svg+xml",
521 | ".swf" : "application/x-shockwave-flash",
522 | ".t" : "text/troff",
523 | ".tar" : "application/x-tar",
524 | ".tbz" : "application/x-bzip-compressed-tar",
525 | ".tci" : "application/x-topcloud",
526 | ".tcl" : "application/x-tcl",
527 | ".tex" : "application/x-tex",
528 | ".texi" : "application/x-texinfo",
529 | ".texinfo" : "application/x-texinfo",
530 | ".text" : "text/plain",
531 | ".tif" : "image/tiff",
532 | ".tiff" : "image/tiff",
533 | ".torrent" : "application/x-bittorrent",
534 | ".tr" : "text/troff",
535 | ".ttf" : "application/x-font-ttf",
536 | ".txt" : "text/plain",
537 | ".vcf" : "text/x-vcard",
538 | ".vcs" : "text/x-vcalendar",
539 | ".vrml" : "model/vrml",
540 | ".war" : "application/java-archive",
541 | ".wav" : "audio/x-wav",
542 | ".wma" : "audio/x-ms-wma",
543 | ".wmv" : "video/x-ms-wmv",
544 | ".wmx" : "video/x-ms-wmx",
545 | ".wrl" : "model/vrml",
546 | ".wsdl" : "application/wsdl+xml",
547 | ".xbm" : "image/x-xbitmap",
548 | ".xhtml" : "application/xhtml+xml",
549 | ".xls" : "application/vnd.ms-excel",
550 | ".xml" : "application/xml",
551 | ".xpm" : "image/x-xpixmap",
552 | ".xsl" : "application/xml",
553 | ".xslt" : "application/xslt+xml",
554 | ".yaml" : "text/yaml",
555 | ".yml" : "text/yaml",
556 | ".zip" : "application/zip"
557 | }
558 | };
559 |
--------------------------------------------------------------------------------
/lib/parseopt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JavaScript Option Parser (parseopt)
3 | * Copyright (C) 2010 Mathias Panzenböck
4 | *
5 | * This library is free software; you can redistribute it and/or
6 | * modify it under the terms of the GNU Lesser General Public
7 | * License as published by the Free Software Foundation; either
8 | * version 2.1 of the License, or (at your option) any later version.
9 | *
10 | * This library is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | * Lesser General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU Lesser General Public
16 | * License along with this library; if not, write to the Free Software
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 | */
19 |
20 | /**
21 | * Construct a new OptionParser.
22 | * See the demo folder the end of this file for example usage.
23 | *
24 | * @param object params optional Parameter-Object
25 | *
26 | * ===== Parameter-Object =====
27 | * {
28 | * minargs: integer, optional
29 | * maxargs: integer, optional
30 | * program: string, per default inferred from process.argv
31 | * strings: object, optional
32 | * Table of strings used in the output. See below.
33 | * options: array, optional
34 | * Array of option definitions. See below.
35 | * }
36 | *
37 | * ===== String-Table =====
38 | * {
39 | * help: string, default: 'No help available for this option.'
40 | * usage: string, default: 'Usage'
41 | * options: string, default: 'OPTIONS'
42 | * arguments: string, default: 'ARGUMENTS'
43 | * required: string, default: 'required'
44 | * default: string, default: 'default'
45 | * base: string, default: 'base'
46 | * metavars: object, optional
47 | * Table of default metavar names per type.
48 | * Per default the type name in capital letters or derived
49 | * from the possible values.
50 | * }
51 | *
52 | * ===== Option Definition =====
53 | * {
54 | * // Only when passed to the OptionParser constructor:
55 | * name: string or array
56 | * names: string or array, alias of name
57 | * Only one of both may be used at the same time.
58 | *
59 | * Names can be long options (e.g. '--foo') and short options
60 | * (e.g. '-f'). The first name is used to indentify the option.
61 | * Names musst be unique and may not contain '='.
62 | *
63 | * Short options may be combined when passed to a programm. E.g.
64 | * the options '-f' and '-b' can be combined to '-fb'. Only one
65 | * of these combined options may require an argument.
66 | *
67 | * Short options are separated from ther arguments by space,
68 | * long options per '='. If a long option requires an argument
69 | * and none is passed using '=' it also uses the next commandline
70 | * argument as it's argument (like short options).
71 | *
72 | * If '--' is encountered all remaining arguments are treated as
73 | * arguments and not as options.
74 | *
75 | * // General fields:
76 | * target: string, per deflault inferred from first name
77 | * This defines the name used in the returned options object.
78 | * Multiple options may have the same target.
79 | * default: any, default: undefined
80 | * The default value associated with a certain target and is
81 | * overwritten by each new option with the same target.
82 | * type: string, default: 'string', see below
83 | * required: boolean, default: false
84 | * redefinable: boolean, default: true
85 | * help: string, optional
86 | * details: array, optional
87 | * short list of details shown in braces after the option name
88 | * e.g. integer type options add 'base: '+base if base !== undefined
89 | * metavar: string or array, per deflault inferred from type
90 | * onOption: function (value) -> boolean, optional
91 | * Returning true canceles any further option parsing
92 | * and the parse() method returns null.
93 | *
94 | * // Type: string (alias: str)
95 | * // Type: boolean (alias: bool)
96 | * // Type: object (alias: obj)
97 | *
98 | * // Type: integer (alias: int)
99 | * min: integer, optional
100 | * max: integer, optional
101 | * NaN: boolean, default: false
102 | * base: integer, optional
103 | *
104 | * // Type: float (alias: number)
105 | * min: float, optional
106 | * max: float, optional
107 | * NaN: boolean, default: false
108 | *
109 | * // Type: flag
110 | * value: boolean, default: true
111 | * default: boolean, default: false
112 | *
113 | * // Type: option
114 | * value: any, per default inferred from first name
115 | *
116 | * // Type: enum
117 | * ignoreCase: boolean, default: true
118 | * values: array or object where the user enteres the field name of
119 | * the object and you get the value of the field
120 | *
121 | * // Type: record
122 | * create: function () -> object, default: Array
123 | * args: array of type definitions (type part of option definitions)
124 | *
125 | * // Type: custom
126 | * argc: integer, default: -1
127 | * Number of required arguments.
128 | * -1 means one optional argument.
129 | * parse: function (string, ...) -> value
130 | * stringify: function (value) -> string, optional
131 | * }
132 | *
133 | * ===== Option-Arguments =====
134 | * For the following types exactly one argument is required:
135 | * integer, float, string, boolean, object, enum
136 | *
137 | * The following types have optional arguments:
138 | * flag
139 | *
140 | * The following types have no arguments:
141 | * option
142 | *
143 | * Custom types may set this through the argc field.
144 | */
145 | function OptionParser (params) {
146 | this.optionsPerName = {};
147 | this.defaultValues = {};
148 | this.options = [];
149 |
150 | if (params !== undefined) {
151 | this.minargs = params.minargs == 0 ? undefined : params.minargs;
152 | this.maxargs = params.maxargs;
153 | this.program = params.program;
154 | this.strings = params.strings;
155 |
156 | if (this.minargs > this.maxargs) {
157 | throw new Error('minargs > maxargs');
158 | }
159 | }
160 |
161 | if (this.strings === undefined) {
162 | this.strings = {};
163 | }
164 |
165 | defaults(this.strings, {
166 | help: 'No help available for this option.',
167 | usage: 'Usage',
168 | options: 'OPTIONS',
169 | arguments: 'ARGUMENTS',
170 | required: 'required',
171 | default: 'default',
172 | base: 'base',
173 | metavars: {}
174 | });
175 |
176 | defaults(this.strings.metavars, METAVARS);
177 |
178 | if (this.program === undefined) {
179 | this.program = process.argv[0] + ' ' + process.argv[1];
180 | }
181 |
182 | if (params !== undefined && params.options !== undefined) {
183 | for (var i in params.options) {
184 | var opt = params.options[i];
185 | var names;
186 |
187 | if (opt instanceof Array || typeof(opt) == 'string') {
188 | opt = undefined;
189 | names = opt;
190 | }
191 | else {
192 | names = opt.names;
193 | if (names === undefined) {
194 | names = opt.name;
195 | delete opt.name;
196 | }
197 | else {
198 | delete opt.names;
199 | }
200 | }
201 | this.add(names, opt);
202 | }
203 | }
204 | }
205 |
206 | OptionParser.prototype = {
207 | /**
208 | * Parse command line options.
209 | *
210 | * @param array args Commandline arguments.
211 | * If undefined process.argv.slice(2) is used.
212 | *
213 | * @return object
214 | * {
215 | * arguments: array
216 | * options: object, { target -> value }
217 | * }
218 | */
219 | parse: function (args) {
220 | if (args === undefined) {
221 | args = process.argv.slice(2);
222 | }
223 |
224 | var data = {
225 | options: {},
226 | arguments: []
227 | };
228 |
229 | for (var name in this.defaultValues) {
230 | var value = this.defaultValues[name];
231 |
232 | if (value !== undefined) {
233 | data.options[this.optionsPerName[name].target] = value;
234 | }
235 | }
236 |
237 | var got = {};
238 | var i = 0;
239 | for (; i < args.length; ++ i) {
240 | var arg = args[i];
241 |
242 | if (arg == '--') {
243 | ++ i;
244 | break;
245 | }
246 | else if (/^--.+$/.test(arg)) {
247 | var j = arg.indexOf('=');
248 | var name, value = undefined;
249 |
250 | if (j == -1) {
251 | name = arg;
252 | }
253 | else {
254 | name = arg.substring(0,j);
255 | value = arg.substring(j+1);
256 | }
257 |
258 | var optdef = this.optionsPerName[name];
259 |
260 | if (optdef === undefined) {
261 | throw new Error('unknown option: '+name);
262 | }
263 |
264 | if (value === undefined) {
265 | if (optdef.argc < 1) {
266 | value = optdef.value;
267 | }
268 | else if ((i + optdef.argc) >= args.length) {
269 | throw new Error('option '+name+' needs '+optdef.argc+' arguments');
270 | }
271 | else {
272 | value = optdef.parse.apply(optdef, args.slice(i+1, i+1+optdef.argc));
273 | i += optdef.argc;
274 | }
275 | }
276 | else if (optdef.argc == 0) {
277 | throw new Error('option '+name+' does not need an argument');
278 | }
279 | else if (optdef.argc > 1) {
280 | throw new Error('option '+name+' needs '+optdef.argc+' arguments');
281 | }
282 | else {
283 | value = optdef.parse(value);
284 | }
285 |
286 | if (!optdef.redefinable && optdef.target in got) {
287 | throw new Error('cannot redefine option '+name);
288 | }
289 |
290 | got[optdef.target] = true;
291 | data.options[optdef.target] = value;
292 |
293 | if (optdef.onOption && optdef.onOption(value) === true) {
294 | return null;
295 | }
296 | }
297 | else if (/^-.+$/.test(arg)) {
298 | if (arg.indexOf('=') != -1) {
299 | throw new Error('illegal option syntax: '+arg);
300 | }
301 |
302 | var tookarg = false;
303 | arg = arg.substring(1);
304 |
305 | for (var j = 0; j < arg.length; ++ j) {
306 | var name = '-'+arg[j];
307 | var optdef = this.optionsPerName[name];
308 | var value;
309 |
310 | if (optdef === undefined) {
311 | throw new Error('unknown option: '+name);
312 | }
313 |
314 | if (optdef.argc < 1) {
315 | value = optdef.value;
316 | }
317 | else {
318 | if (tookarg || (i+optdef.argc) >= args.length) {
319 | throw new Error('option '+name+' needs '+optdef.argc+' arguments');
320 | }
321 |
322 | value = optdef.parse.apply(optdef, args.slice(i+1, i+1+optdef.argc));
323 | i += optdef.argc;
324 | tookarg = true;
325 | }
326 |
327 | if (!optdef.redefinable && optdef.target in got) {
328 | throw new Error('redefined option: '+name);
329 | }
330 |
331 | got[optdef.target] = true;
332 | data.options[optdef.target] = value;
333 |
334 | if (optdef.onOption && optdef.onOption(value) === true) {
335 | return null;
336 | }
337 | }
338 | }
339 | else {
340 | data.arguments.push(arg);
341 | }
342 | }
343 |
344 | for (; i < args.length; ++ i) {
345 | data.arguments.push(args[i]);
346 | }
347 |
348 | var argc = data.arguments.length;
349 | if ((this.maxargs !== undefined && argc > this.maxargs) ||
350 | (this.minargs !== undefined && argc < this.minargs)) {
351 | var msg = 'illegal number of arguments: ' + argc;
352 |
353 | if (this.minargs !== undefined) {
354 | msg += ', minumum is ' + this.minargs;
355 | if (this.maxargs !== undefined) {
356 | msg += ' and maximum is ' + this.maxargs;
357 | }
358 | }
359 | else {
360 | msg += ', maximum is ' + this.maxargs;
361 | }
362 |
363 | throw new Error(msg);
364 | }
365 |
366 | for (var i in this.options) {
367 | var optdef = this.options[i];
368 | if (optdef.required && !(optdef.target in got)) {
369 | throw new Error('missing required option: ' + optdef.names[0]);
370 | }
371 | }
372 |
373 | return data;
374 | },
375 | /**
376 | * Add an option definition.
377 | *
378 | * @param string or array names Option names
379 | * @param object optdef Option definition
380 | */
381 | add: function (names, optdef) {
382 | if (typeof(names) == 'string') {
383 | names = [names];
384 | }
385 | else if (names === undefined || names.length == 0) {
386 | throw new Error('no option name given');
387 | }
388 |
389 | if (optdef === undefined) {
390 | optdef = {};
391 | }
392 |
393 | optdef.names = names;
394 |
395 | for (var i in names) {
396 | var name = names[i];
397 | var match = /(-*)(.*)/.exec(name);
398 |
399 | if (name.length == 0 || match[1].length < 1 ||
400 | match[1].length > 2 || match[2].length == 0 ||
401 | (match[1].length == 1 && match[2].length > 1) ||
402 | match[2].indexOf('=') != -1) {
403 | throw new Error('illegal option name: ' + name);
404 | }
405 |
406 | if (name in this.optionsPerName) {
407 | throw new Error('option already exists: '+name);
408 | }
409 | }
410 |
411 | if (optdef.target === undefined) {
412 | var target = names[0].replace(/^--?/,'');
413 |
414 | if (target.toUpperCase() == target) {
415 | // FOO-BAR -> FOO_BAR
416 | target = target.replace(/[^a-zA-Z0-9]+/,'_');
417 | }
418 | else {
419 | // foo-bar -> fooBar
420 | target = target.split(/[^a-zA-Z0-9]+/);
421 | for (var i = 1; i < target.length; ++ i) {
422 | var part = target[i];
423 |
424 | if (part) {
425 | target[i] = part[0].toUpperCase() + part.substring(1);
426 | }
427 | }
428 | target = target.join('');
429 | }
430 |
431 | optdef.target = target;
432 | }
433 |
434 | this._initType(optdef, optdef.names[0]);
435 |
436 | if (optdef.redefinable === undefined) {
437 | optdef.redefinable = true;
438 | }
439 |
440 | if (optdef.required === undefined) {
441 | optdef.required = false;
442 | }
443 |
444 | if (optdef.help === undefined) {
445 | optdef.help = this.strings.help;
446 | }
447 | else {
448 | optdef.help = optdef.help.trim();
449 | }
450 |
451 | for (var i in names) {
452 | this.optionsPerName[names[i]] = optdef;
453 | }
454 |
455 | if (optdef.default !== undefined) {
456 | this.defaultValues[names[0]] = optdef.default;
457 | }
458 |
459 | this.options.push(optdef);
460 | },
461 | /**
462 | * Show an error message, usage and exit program with exit code 1.
463 | *
464 | * @param string msg The error message
465 | * @param WriteStream out Where to write the message.
466 | * If undefined process.stdout is used.
467 | */
468 | error: function (msg, out) {
469 | if (!out) {
470 | out = process.stdout;
471 | }
472 | out.write('*** '+msg+'\n\n');
473 | this.usage(undefined, out);
474 | process.exit(1);
475 | },
476 | /**
477 | * Print usage message.
478 | *
479 | * @param string help Optional additional help message.
480 | * @param WriteStream out Where to write the message.
481 | * If undefined process.stdout is used.
482 | */
483 | usage: function (help, out) {
484 | if (!out) {
485 | out = process.stdout;
486 | }
487 |
488 | out.write(this.strings.usage+': '+this.program+' ['+
489 | this.strings.options+']'+(this.maxargs != 0 ?
490 | ' ['+this.strings.arguments+']\n' : '\n'));
491 | out.write('\n');
492 | out.write(this.strings.options+':\n');
493 |
494 | for (var i in this.options) {
495 | var optdef = this.options[i];
496 | var optnames = [];
497 | var metavar = optdef.metavar;
498 |
499 | if (metavar instanceof Array) {
500 | metavar = metavar.join(' ');
501 | }
502 |
503 | for (var j in optdef.names) {
504 | var optname = optdef.names[j];
505 |
506 | if (metavar !== undefined) {
507 | if (optdef.argc < 2 && optname.substring(0,2) == '--') {
508 | if (optdef.argc < 0) {
509 | optname = optname+'[='+metavar+']';
510 | }
511 | else {
512 | optname = optname+'='+metavar;
513 | }
514 | }
515 | else {
516 | optname = optname+' '+metavar;
517 | }
518 | }
519 | optnames.push(optname);
520 | }
521 |
522 | var details = optdef.details !== undefined ? optdef.details.slice() : [];
523 | if (optdef.required) {
524 | details.push(this.strings.required);
525 | }
526 | else if (optdef.argc > 0 && optdef.default !== undefined) {
527 | details.push(this.strings.default+': '+optdef.stringify(optdef.default));
528 | }
529 |
530 | if (details.length > 0) {
531 | details = ' (' + details.join(', ') + ')';
532 | }
533 |
534 | if (metavar !== undefined) {
535 | optnames[0] += details;
536 | out.write(' '+optnames.join('\n '));
537 | }
538 | else {
539 | out.write(' '+optnames.join(', ')+details);
540 | }
541 | if (optdef.help) {
542 | var lines = optdef.help.split('\n');
543 | for (var j in lines) {
544 | out.write('\n '+lines[j]);
545 | }
546 | }
547 | out.write('\n\n');
548 | }
549 |
550 | if (help !== undefined) {
551 | out.write(help);
552 | if (help[help.length-1] != '\n') {
553 | out.write('\n');
554 | }
555 | }
556 | },
557 | _initType: function (optdef, name) {
558 | optdef.name = name;
559 |
560 | if (optdef.type === undefined) {
561 | optdef.type = 'string';
562 | }
563 | else if (optdef.type in TYPE_ALIAS) {
564 | optdef.type = TYPE_ALIAS[optdef.type];
565 | }
566 |
567 | switch (optdef.type) {
568 | case 'flag':
569 | if (optdef.value === undefined) {
570 | optdef.value = true;
571 | }
572 | optdef.parse = parseBool;
573 | optdef.argc = -1;
574 |
575 | if (optdef.default === undefined) {
576 | optdef.default = this.defaultValues[name];
577 |
578 | if (optdef.default === undefined) {
579 | optdef.default = false;
580 | }
581 | }
582 | break;
583 |
584 | case 'option':
585 | optdef.argc = 0;
586 |
587 | if (optdef.value === undefined) {
588 | optdef.value = name.replace(/^--?/,'');
589 | }
590 | break;
591 |
592 | case 'enum':
593 | this._initEnum(optdef, name);
594 | break;
595 |
596 | case 'integer':
597 | case 'float':
598 | this._initNumber(optdef, name);
599 | break;
600 |
601 | case 'record':
602 | if (optdef.args === undefined || optdef.args.length == 0) {
603 | throw new Error('record '+name+' needs at least one argument');
604 | }
605 | optdef.argc = 0;
606 | var metavar = [];
607 | for (var i in optdef.args) {
608 | var arg = optdef.args[i];
609 | if (arg.target === undefined) {
610 | arg.target = i;
611 | }
612 | this._initType(arg, name+'['+i+']');
613 |
614 | if (arg.argc < 1) {
615 | throw new Error('argument '+i+' of option '+name+
616 | ' has illegal number of arguments');
617 | }
618 | if (arg.metavar instanceof Array) {
619 | for (var j in arg.metavar) {
620 | metavar.push(arg.metavar[j]);
621 | }
622 | }
623 | else {
624 | metavar.push(arg.metavar);
625 | }
626 | delete arg.metavar;
627 | optdef.argc += arg.argc;
628 | }
629 | if (optdef.metavar === undefined) {
630 | optdef.metavar = metavar;
631 | }
632 | var onOption = optdef.onOption;
633 | if (onOption !== undefined) {
634 | optdef.onOption = function (values) {
635 | return onOption.apply(this, values);
636 | };
637 | }
638 | if (optdef.create === undefined) {
639 | optdef.create = Array;
640 | }
641 | optdef.parse = function () {
642 | var values = this.create();
643 | var parserIndex = 0;
644 | for (var i = 0; i < arguments.length;) {
645 | var arg = optdef.args[parserIndex ++];
646 | var raw = [];
647 | for (var j = 0; j < arg.argc; ++ j) {
648 | raw.push(arguments[i+j]);
649 | }
650 | values[arg.target] = arg.parse.apply(arg, raw);
651 | i += arg.argc;
652 | }
653 | return values;
654 | };
655 | break;
656 |
657 | case 'custom':
658 | if (optdef.argc === undefined || optdef.argc < -1) {
659 | optdef.argc = -1;
660 | }
661 |
662 | if (optdef.parse === undefined) {
663 | throw new Error(
664 | 'no parse function defined for custom type option '+name);
665 | }
666 | break;
667 |
668 | default:
669 | optdef.argc = 1;
670 | optdef.parse = PARSERS[optdef.type];
671 |
672 | if (optdef.parse === undefined) {
673 | throw new Error('type of option '+name+' is unknown: '+optdef.type);
674 | }
675 | }
676 |
677 | initStringify(optdef);
678 |
679 | var count = 1;
680 | if (optdef.metavar === undefined) {
681 | optdef.metavar = this.strings.metavars[optdef.type];
682 | }
683 |
684 | if (optdef.metavar === undefined) {
685 | count = 0;
686 | }
687 | else if (optdef.metavar instanceof Array) {
688 | count = optdef.metavar.length;
689 | }
690 |
691 | if (optdef.argc == -1) {
692 | if (count > 1) {
693 | throw new Error('illegal number of metavars for option '+name+
694 | ': '+JSON.stringify(optdef.metavar));
695 | }
696 | }
697 | else if (optdef.argc != count) {
698 | throw new Error('illegal number of metavars for option '+name+
699 | ': '+JSON.stringify(optdef.metavar));
700 | }
701 | },
702 | _initEnum: function (optdef, name) {
703 | optdef.argc = 1;
704 |
705 | if (optdef.ignoreCase === undefined) {
706 | optdef.ignoreCase = true;
707 | }
708 |
709 | if (optdef.values === undefined || optdef.values.length == 0) {
710 | throw new Error('no values for enum '+name+' defined');
711 | }
712 |
713 | initStringify(optdef);
714 |
715 | var labels = [];
716 | var values = {};
717 | if (optdef.values instanceof Array) {
718 | for (var i in optdef.values) {
719 | var value = optdef.values[i];
720 | var label = String(value);
721 | values[optdef.ignoreCase ? label.toLowerCase() : label] = value;
722 | labels.push(optdef.stringify(value));
723 | }
724 | }
725 | else {
726 | for (var label in optdef.values) {
727 | var value = optdef.values[label];
728 | values[optdef.ignoreCase ? label.toLowerCase() : label] = value;
729 | labels.push(optdef.stringify(label));
730 | }
731 | labels.sort();
732 | }
733 | optdef.values = values;
734 |
735 |
736 | if (optdef.metavar === undefined) {
737 | optdef.metavar = '<'+labels.join(', ')+'>';
738 | }
739 |
740 | optdef.parse = function (s) {
741 | var value = values[optdef.ignoreCase ? s.toLowerCase() : s];
742 | if (value !== undefined) {
743 | return value;
744 | }
745 | throw new Error('illegal value for option '+name+': '+s);
746 | };
747 | },
748 | _initNumber: function (optdef, name) {
749 | optdef.argc = 1;
750 |
751 | if (optdef.NaN === undefined) {
752 | optdef.NaN = false;
753 | }
754 |
755 | if (optdef.min > optdef.max) {
756 | throw new Error('min > max for option '+name);
757 | }
758 |
759 | var parse, toStr;
760 | if (optdef.type == 'integer') {
761 | parse = function (s) {
762 | var i = NaN;
763 | if (s.indexOf('.') == -1) {
764 | i = parseInt(s, optdef.base)
765 | }
766 | return i;
767 | };
768 | if (optdef.base === undefined) {
769 | toStr = dec;
770 | }
771 | else {
772 | switch (optdef.base) {
773 | case 8: toStr = oct; break;
774 | case 10: toStr = dec; break;
775 | case 16: toStr = hex; break;
776 | default: toStr = function (val) {
777 | return val.toString(optdef.base);
778 | };
779 | var detail = this.strings.base+': '+optdef.base;
780 | if (optdef.details) {
781 | optdef.details.push(detail);
782 | }
783 | else {
784 | optdef.details = [detail];
785 | }
786 | }
787 | }
788 | }
789 | else {
790 | parse = parseFloat;
791 | toStr = dec;
792 | }
793 |
794 | if (optdef.metavar === undefined) {
795 | if (optdef.min === undefined && optdef.max === undefined) {
796 | optdef.metavar = this.strings.metavars[optdef.type];
797 | }
798 | else if (optdef.min === undefined) {
799 | optdef.metavar = '...'+toStr(optdef.max);
800 | }
801 | else if (optdef.max === undefined) {
802 | optdef.metavar = toStr(optdef.min)+'...';
803 | }
804 | else {
805 | optdef.metavar = toStr(optdef.min)+'...'+toStr(optdef.max);
806 | }
807 | }
808 | optdef.parse = function (s) {
809 | var n = parse(s);
810 |
811 | if ((!this.NaN && isNaN(n))
812 | || (optdef.min !== undefined && n < optdef.min)
813 | || (optdef.max !== undefined && n > optdef.max)) {
814 | throw new Error('illegal value for option '+name+': '+s);
815 | }
816 |
817 | return n;
818 | };
819 | }
820 | };
821 |
822 | function initStringify (optdef) {
823 | if (optdef.stringify === undefined) {
824 | optdef.stringify = STRINGIFIERS[optdef.type];
825 | }
826 |
827 | if (optdef.stringify === undefined) {
828 | optdef.stringify = stringifyAny;
829 | }
830 | }
831 |
832 | function defaults (target, defaults) {
833 | for (var name in defaults) {
834 | if (target[name] === undefined) {
835 | target[name] = defaults[name];
836 | }
837 | }
838 | }
839 |
840 | function dec (val) {
841 | return val.toString();
842 | }
843 |
844 | function oct (val) {
845 | return '0'+val.toString(8);
846 | }
847 |
848 | function hex (val) {
849 | return '0x'+val.toString(16);
850 | }
851 |
852 | const TRUE_VALUES = {true: true, on: true, 1: true, yes: true};
853 | const FALSE_VALUES = {false: true, off: true, 0: true, no: true};
854 |
855 | function parseBool (s) {
856 | s = s.trim().toLowerCase();
857 | if (s in TRUE_VALUES) {
858 | return true;
859 | }
860 | else if (s in FALSE_VALUES) {
861 | return false;
862 | }
863 | else {
864 | throw new Error('illegal boolean value: '+s);
865 | }
866 | }
867 |
868 | function id (x) {
869 | return x;
870 | }
871 |
872 | const PARSERS = {
873 | boolean: parseBool,
874 | string: id,
875 | object: JSON.parse
876 | };
877 |
878 | const TYPE_ALIAS = {
879 | int: 'integer',
880 | number: 'float',
881 | bool: 'boolean',
882 | str: 'string',
883 | obj: 'object'
884 | };
885 |
886 | const METAVARS = {
887 | string: 'STRING',
888 | integer: 'INTEGER',
889 | float: 'FLOAT',
890 | boolean: 'BOOLEAN',
891 | object: 'OBJECT',
892 | enum: 'VALUE',
893 | custom: 'VALUE'
894 | };
895 |
896 | function stringifyString(s) {
897 | if (/[\s'"\\<>,]/.test(s)) {
898 | // s = "'"+s.replace(/\\/g,'\\\\').replace(/'/g, "'\\''")+"'";
899 | s = JSON.stringify(s);
900 | }
901 | return s;
902 | }
903 |
904 | function stringifyPrimitive(value) {
905 | return ''+value;
906 | }
907 |
908 | function stringifyAny (value) {
909 | if (value instanceof Array) {
910 | var buf = [];
911 | for (var i in value) {
912 | buf.push(stringifyAny(value[i]));
913 | }
914 | return buf.join(' ');
915 | }
916 | else if (typeof(value) == 'string') {
917 | return stringifyString(value);
918 | }
919 | else {
920 | return String(value);
921 | }
922 | }
923 |
924 | function stringifyInteger (value) {
925 | if (this.base === undefined) {
926 | return value.toString();
927 | }
928 |
929 | switch (this.base) {
930 | case 8: return oct(value);
931 | case 16: return hex(value);
932 | default: return value.toString(this.base);
933 | }
934 | }
935 |
936 | function stringifyRecord (record) {
937 | var buf = [];
938 | for (var i = 0; i < this.args.length; ++ i) {
939 | var arg = this.args[i];
940 | buf.push(arg.stringify(record[arg.target]));
941 | }
942 | return buf.join(' ');
943 | }
944 |
945 | const STRINGIFIERS = {
946 | string: stringifyString,
947 | integer: stringifyInteger,
948 | boolean: stringifyPrimitive,
949 | float: stringifyPrimitive,
950 | object: JSON.stringify,
951 | enum: stringifyAny,
952 | custom: stringifyAny,
953 | record: stringifyRecord
954 | };
955 |
956 | exports.OptionParser = OptionParser;
957 |
--------------------------------------------------------------------------------
/lib/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.1.3
2 | // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 |
9 | (function() {
10 |
11 | // Baseline setup
12 | // --------------
13 |
14 | // Establish the root object, `window` in the browser, or `global` on the server.
15 | var root = this;
16 |
17 | // Save the previous value of the `_` variable.
18 | var previousUnderscore = root._;
19 |
20 | // Establish the object that gets returned to break out of a loop iteration.
21 | var breaker = {};
22 |
23 | // Save bytes in the minified (but not gzipped) version:
24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype;
25 |
26 | // Create quick reference variables for speed access to core prototypes.
27 | var slice = ArrayProto.slice,
28 | unshift = ArrayProto.unshift,
29 | toString = ObjProto.toString,
30 | hasOwnProperty = ObjProto.hasOwnProperty;
31 |
32 | // All **ECMAScript 5** native function implementations that we hope to use
33 | // are declared here.
34 | var
35 | nativeForEach = ArrayProto.forEach,
36 | nativeMap = ArrayProto.map,
37 | nativeReduce = ArrayProto.reduce,
38 | nativeReduceRight = ArrayProto.reduceRight,
39 | nativeFilter = ArrayProto.filter,
40 | nativeEvery = ArrayProto.every,
41 | nativeSome = ArrayProto.some,
42 | nativeIndexOf = ArrayProto.indexOf,
43 | nativeLastIndexOf = ArrayProto.lastIndexOf,
44 | nativeIsArray = Array.isArray,
45 | nativeKeys = Object.keys;
46 |
47 | // Create a safe reference to the Underscore object for use below.
48 | var _ = function(obj) { return new wrapper(obj); };
49 |
50 | // Export the Underscore object for **CommonJS**, with backwards-compatibility
51 | // for the old `require()` API. If we're not in CommonJS, add `_` to the
52 | // global object.
53 | if (typeof module !== 'undefined' && module.exports) {
54 | module.exports = _;
55 | _._ = _;
56 | } else {
57 | root._ = _;
58 | }
59 |
60 | // Current version.
61 | _.VERSION = '1.1.3';
62 |
63 | // Collection Functions
64 | // --------------------
65 |
66 | // The cornerstone, an `each` implementation, aka `forEach`.
67 | // Handles objects implementing `forEach`, arrays, and raw objects.
68 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
69 | var each = _.each = _.forEach = function(obj, iterator, context) {
70 | var value;
71 | if (nativeForEach && obj.forEach === nativeForEach) {
72 | obj.forEach(iterator, context);
73 | } else if (_.isNumber(obj.length)) {
74 | for (var i = 0, l = obj.length; i < l; i++) {
75 | if (iterator.call(context, obj[i], i, obj) === breaker) return;
76 | }
77 | } else {
78 | for (var key in obj) {
79 | if (hasOwnProperty.call(obj, key)) {
80 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
81 | }
82 | }
83 | }
84 | };
85 |
86 | // Return the results of applying the iterator to each element.
87 | // Delegates to **ECMAScript 5**'s native `map` if available.
88 | _.map = function(obj, iterator, context) {
89 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
90 | var results = [];
91 | each(obj, function(value, index, list) {
92 | results[results.length] = iterator.call(context, value, index, list);
93 | });
94 | return results;
95 | };
96 |
97 | // **Reduce** builds up a single result from a list of values, aka `inject`,
98 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
99 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
100 | var initial = memo !== void 0;
101 | if (nativeReduce && obj.reduce === nativeReduce) {
102 | if (context) iterator = _.bind(iterator, context);
103 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
104 | }
105 | each(obj, function(value, index, list) {
106 | if (!initial && index === 0) {
107 | memo = value;
108 | } else {
109 | memo = iterator.call(context, memo, value, index, list);
110 | }
111 | });
112 | return memo;
113 | };
114 |
115 | // The right-associative version of reduce, also known as `foldr`.
116 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
117 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
118 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
119 | if (context) iterator = _.bind(iterator, context);
120 | return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
121 | }
122 | var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
123 | return _.reduce(reversed, iterator, memo, context);
124 | };
125 |
126 | // Return the first value which passes a truth test. Aliased as `detect`.
127 | _.find = _.detect = function(obj, iterator, context) {
128 | var result;
129 | any(obj, function(value, index, list) {
130 | if (iterator.call(context, value, index, list)) {
131 | result = value;
132 | return true;
133 | }
134 | });
135 | return result;
136 | };
137 |
138 | // Return all the elements that pass a truth test.
139 | // Delegates to **ECMAScript 5**'s native `filter` if available.
140 | // Aliased as `select`.
141 | _.filter = _.select = function(obj, iterator, context) {
142 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
143 | var results = [];
144 | each(obj, function(value, index, list) {
145 | if (iterator.call(context, value, index, list)) results[results.length] = value;
146 | });
147 | return results;
148 | };
149 |
150 | // Return all the elements for which a truth test fails.
151 | _.reject = function(obj, iterator, context) {
152 | var results = [];
153 | each(obj, function(value, index, list) {
154 | if (!iterator.call(context, value, index, list)) results[results.length] = value;
155 | });
156 | return results;
157 | };
158 |
159 | // Determine whether all of the elements match a truth test.
160 | // Delegates to **ECMAScript 5**'s native `every` if available.
161 | // Aliased as `all`.
162 | _.every = _.all = function(obj, iterator, context) {
163 | iterator = iterator || _.identity;
164 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
165 | var result = true;
166 | each(obj, function(value, index, list) {
167 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
168 | });
169 | return result;
170 | };
171 |
172 | // Determine if at least one element in the object matches a truth test.
173 | // Delegates to **ECMAScript 5**'s native `some` if available.
174 | // Aliased as `any`.
175 | var any = _.some = _.any = function(obj, iterator, context) {
176 | iterator = iterator || _.identity;
177 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
178 | var result = false;
179 | each(obj, function(value, index, list) {
180 | if (result = iterator.call(context, value, index, list)) return breaker;
181 | });
182 | return result;
183 | };
184 |
185 | // Determine if a given value is included in the array or object using `===`.
186 | // Aliased as `contains`.
187 | _.include = _.contains = function(obj, target) {
188 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
189 | var found = false;
190 | any(obj, function(value) {
191 | if (found = value === target) return true;
192 | });
193 | return found;
194 | };
195 |
196 | // Invoke a method (with arguments) on every item in a collection.
197 | _.invoke = function(obj, method) {
198 | var args = slice.call(arguments, 2);
199 | return _.map(obj, function(value) {
200 | return (method ? value[method] : value).apply(value, args);
201 | });
202 | };
203 |
204 | // Convenience version of a common use case of `map`: fetching a property.
205 | _.pluck = function(obj, key) {
206 | return _.map(obj, function(value){ return value[key]; });
207 | };
208 |
209 | // Return the maximum element or (element-based computation).
210 | _.max = function(obj, iterator, context) {
211 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
212 | var result = {computed : -Infinity};
213 | each(obj, function(value, index, list) {
214 | var computed = iterator ? iterator.call(context, value, index, list) : value;
215 | computed >= result.computed && (result = {value : value, computed : computed});
216 | });
217 | return result.value;
218 | };
219 |
220 | // Return the minimum element (or element-based computation).
221 | _.min = function(obj, iterator, context) {
222 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
223 | var result = {computed : Infinity};
224 | each(obj, function(value, index, list) {
225 | var computed = iterator ? iterator.call(context, value, index, list) : value;
226 | computed < result.computed && (result = {value : value, computed : computed});
227 | });
228 | return result.value;
229 | };
230 |
231 | // Sort the object's values by a criterion produced by an iterator.
232 | _.sortBy = function(obj, iterator, context) {
233 | return _.pluck(_.map(obj, function(value, index, list) {
234 | return {
235 | value : value,
236 | criteria : iterator.call(context, value, index, list)
237 | };
238 | }).sort(function(left, right) {
239 | var a = left.criteria, b = right.criteria;
240 | return a < b ? -1 : a > b ? 1 : 0;
241 | }), 'value');
242 | };
243 |
244 | // Use a comparator function to figure out at what index an object should
245 | // be inserted so as to maintain order. Uses binary search.
246 | _.sortedIndex = function(array, obj, iterator) {
247 | iterator = iterator || _.identity;
248 | var low = 0, high = array.length;
249 | while (low < high) {
250 | var mid = (low + high) >> 1;
251 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
252 | }
253 | return low;
254 | };
255 |
256 | // Safely convert anything iterable into a real, live array.
257 | _.toArray = function(iterable) {
258 | if (!iterable) return [];
259 | if (iterable.toArray) return iterable.toArray();
260 | if (_.isArray(iterable)) return iterable;
261 | if (_.isArguments(iterable)) return slice.call(iterable);
262 | return _.values(iterable);
263 | };
264 |
265 | // Return the number of elements in an object.
266 | _.size = function(obj) {
267 | return _.toArray(obj).length;
268 | };
269 |
270 | // Array Functions
271 | // ---------------
272 |
273 | // Get the first element of an array. Passing **n** will return the first N
274 | // values in the array. Aliased as `head`. The **guard** check allows it to work
275 | // with `_.map`.
276 | _.first = _.head = function(array, n, guard) {
277 | return n && !guard ? slice.call(array, 0, n) : array[0];
278 | };
279 |
280 | // Returns everything but the first entry of the array. Aliased as `tail`.
281 | // Especially useful on the arguments object. Passing an **index** will return
282 | // the rest of the values in the array from that index onward. The **guard**
283 | // check allows it to work with `_.map`.
284 | _.rest = _.tail = function(array, index, guard) {
285 | return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
286 | };
287 |
288 | // Get the last element of an array.
289 | _.last = function(array) {
290 | return array[array.length - 1];
291 | };
292 |
293 | // Trim out all falsy values from an array.
294 | _.compact = function(array) {
295 | return _.filter(array, function(value){ return !!value; });
296 | };
297 |
298 | // Return a completely flattened version of an array.
299 | _.flatten = function(array) {
300 | return _.reduce(array, function(memo, value) {
301 | if (_.isArray(value)) return memo.concat(_.flatten(value));
302 | memo[memo.length] = value;
303 | return memo;
304 | }, []);
305 | };
306 |
307 | // Return a version of the array that does not contain the specified value(s).
308 | _.without = function(array) {
309 | var values = slice.call(arguments, 1);
310 | return _.filter(array, function(value){ return !_.include(values, value); });
311 | };
312 |
313 | // Produce a duplicate-free version of the array. If the array has already
314 | // been sorted, you have the option of using a faster algorithm.
315 | // Aliased as `unique`.
316 | _.uniq = _.unique = function(array, isSorted) {
317 | return _.reduce(array, function(memo, el, i) {
318 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
319 | return memo;
320 | }, []);
321 | };
322 |
323 | // Produce an array that contains every item shared between all the
324 | // passed-in arrays.
325 | _.intersect = function(array) {
326 | var rest = slice.call(arguments, 1);
327 | return _.filter(_.uniq(array), function(item) {
328 | return _.every(rest, function(other) {
329 | return _.indexOf(other, item) >= 0;
330 | });
331 | });
332 | };
333 |
334 | // Zip together multiple lists into a single array -- elements that share
335 | // an index go together.
336 | _.zip = function() {
337 | var args = slice.call(arguments);
338 | var length = _.max(_.pluck(args, 'length'));
339 | var results = new Array(length);
340 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
341 | return results;
342 | };
343 |
344 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
345 | // we need this function. Return the position of the first occurrence of an
346 | // item in an array, or -1 if the item is not included in the array.
347 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
348 | _.indexOf = function(array, item) {
349 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
350 | for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
351 | return -1;
352 | };
353 |
354 |
355 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
356 | _.lastIndexOf = function(array, item) {
357 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
358 | var i = array.length;
359 | while (i--) if (array[i] === item) return i;
360 | return -1;
361 | };
362 |
363 | // Generate an integer Array containing an arithmetic progression. A port of
364 | // the native Python `range()` function. See
365 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
366 | _.range = function(start, stop, step) {
367 | var args = slice.call(arguments),
368 | solo = args.length <= 1,
369 | start = solo ? 0 : args[0],
370 | stop = solo ? args[0] : args[1],
371 | step = args[2] || 1,
372 | len = Math.max(Math.ceil((stop - start) / step), 0),
373 | idx = 0,
374 | range = new Array(len);
375 | while (idx < len) {
376 | range[idx++] = start;
377 | start += step;
378 | }
379 | return range;
380 | };
381 |
382 | // Function (ahem) Functions
383 | // ------------------
384 |
385 | // Create a function bound to a given object (assigning `this`, and arguments,
386 | // optionally). Binding with arguments is also known as `curry`.
387 | _.bind = function(func, obj) {
388 | var args = slice.call(arguments, 2);
389 | return function() {
390 | return func.apply(obj || {}, args.concat(slice.call(arguments)));
391 | };
392 | };
393 |
394 | // Bind all of an object's methods to that object. Useful for ensuring that
395 | // all callbacks defined on an object belong to it.
396 | _.bindAll = function(obj) {
397 | var funcs = slice.call(arguments, 1);
398 | if (funcs.length == 0) funcs = _.functions(obj);
399 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
400 | return obj;
401 | };
402 |
403 | // Memoize an expensive function by storing its results.
404 | _.memoize = function(func, hasher) {
405 | var memo = {};
406 | hasher = hasher || _.identity;
407 | return function() {
408 | var key = hasher.apply(this, arguments);
409 | return key in memo ? memo[key] : (memo[key] = func.apply(this, arguments));
410 | };
411 | };
412 |
413 | // Delays a function for the given number of milliseconds, and then calls
414 | // it with the arguments supplied.
415 | _.delay = function(func, wait) {
416 | var args = slice.call(arguments, 2);
417 | return setTimeout(function(){ return func.apply(func, args); }, wait);
418 | };
419 |
420 | // Defers a function, scheduling it to run after the current call stack has
421 | // cleared.
422 | _.defer = function(func) {
423 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
424 | };
425 |
426 | // Internal function used to implement `_.throttle` and `_.debounce`.
427 | var limit = function(func, wait, debounce) {
428 | var timeout;
429 | return function() {
430 | var context = this, args = arguments;
431 | var throttler = function() {
432 | timeout = null;
433 | func.apply(context, args);
434 | };
435 | if (debounce) clearTimeout(timeout);
436 | if (debounce || !timeout) timeout = setTimeout(throttler, wait);
437 | };
438 | };
439 |
440 | // Returns a function, that, when invoked, will only be triggered at most once
441 | // during a given window of time.
442 | _.throttle = function(func, wait) {
443 | return limit(func, wait, false);
444 | };
445 |
446 | // Returns a function, that, as long as it continues to be invoked, will not
447 | // be triggered. The function will be called after it stops being called for
448 | // N milliseconds.
449 | _.debounce = function(func, wait) {
450 | return limit(func, wait, true);
451 | };
452 |
453 | // Returns the first function passed as an argument to the second,
454 | // allowing you to adjust arguments, run code before and after, and
455 | // conditionally execute the original function.
456 | _.wrap = function(func, wrapper) {
457 | return function() {
458 | var args = [func].concat(slice.call(arguments));
459 | return wrapper.apply(wrapper, args);
460 | };
461 | };
462 |
463 | // Returns a function that is the composition of a list of functions, each
464 | // consuming the return value of the function that follows.
465 | _.compose = function() {
466 | var funcs = slice.call(arguments);
467 | return function() {
468 | var args = slice.call(arguments);
469 | for (var i=funcs.length-1; i >= 0; i--) {
470 | args = [funcs[i].apply(this, args)];
471 | }
472 | return args[0];
473 | };
474 | };
475 |
476 | // Object Functions
477 | // ----------------
478 |
479 | // Retrieve the names of an object's properties.
480 | // Delegates to **ECMAScript 5**'s native `Object.keys`
481 | _.keys = nativeKeys || function(obj) {
482 | if (_.isArray(obj)) return _.range(0, obj.length);
483 | var keys = [];
484 | for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
485 | return keys;
486 | };
487 |
488 | // Retrieve the values of an object's properties.
489 | _.values = function(obj) {
490 | return _.map(obj, _.identity);
491 | };
492 |
493 | // Return a sorted list of the function names available on the object.
494 | // Aliased as `methods`
495 | _.functions = _.methods = function(obj) {
496 | return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
497 | };
498 |
499 | // Extend a given object with all the properties in passed-in object(s).
500 | _.extend = function(obj) {
501 | each(slice.call(arguments, 1), function(source) {
502 | for (var prop in source) obj[prop] = source[prop];
503 | });
504 | return obj;
505 | };
506 |
507 | // Create a (shallow-cloned) duplicate of an object.
508 | _.clone = function(obj) {
509 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
510 | };
511 |
512 | // Invokes interceptor with the obj, and then returns obj.
513 | // The primary purpose of this method is to "tap into" a method chain, in
514 | // order to perform operations on intermediate results within the chain.
515 | _.tap = function(obj, interceptor) {
516 | interceptor(obj);
517 | return obj;
518 | };
519 |
520 | // Perform a deep comparison to check if two objects are equal.
521 | _.isEqual = function(a, b) {
522 | // Check object identity.
523 | if (a === b) return true;
524 | // Different types?
525 | var atype = typeof(a), btype = typeof(b);
526 | if (atype != btype) return false;
527 | // Basic equality test (watch out for coercions).
528 | if (a == b) return true;
529 | // One is falsy and the other truthy.
530 | if ((!a && b) || (a && !b)) return false;
531 | // One of them implements an isEqual()?
532 | if (a.isEqual) return a.isEqual(b);
533 | // Check dates' integer values.
534 | if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
535 | // Both are NaN?
536 | if (_.isNaN(a) && _.isNaN(b)) return false;
537 | // Compare regular expressions.
538 | if (_.isRegExp(a) && _.isRegExp(b))
539 | return a.source === b.source &&
540 | a.global === b.global &&
541 | a.ignoreCase === b.ignoreCase &&
542 | a.multiline === b.multiline;
543 | // If a is not an object by this point, we can't handle it.
544 | if (atype !== 'object') return false;
545 | // Check for different array lengths before comparing contents.
546 | if (a.length && (a.length !== b.length)) return false;
547 | // Nothing else worked, deep compare the contents.
548 | var aKeys = _.keys(a), bKeys = _.keys(b);
549 | // Different object sizes?
550 | if (aKeys.length != bKeys.length) return false;
551 | // Recursive comparison of contents.
552 | for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
553 | return true;
554 | };
555 |
556 | // Is a given array or object empty?
557 | _.isEmpty = function(obj) {
558 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
559 | for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
560 | return true;
561 | };
562 |
563 | // Is a given value a DOM element?
564 | _.isElement = function(obj) {
565 | return !!(obj && obj.nodeType == 1);
566 | };
567 |
568 | // Is a given value an array?
569 | // Delegates to ECMA5's native Array.isArray
570 | _.isArray = nativeIsArray || function(obj) {
571 | return !!(obj && obj.concat && obj.unshift && !obj.callee);
572 | };
573 |
574 | // Is a given variable an arguments object?
575 | _.isArguments = function(obj) {
576 | return !!(obj && obj.callee);
577 | };
578 |
579 | // Is a given value a function?
580 | _.isFunction = function(obj) {
581 | return !!(obj && obj.constructor && obj.call && obj.apply);
582 | };
583 |
584 | // Is a given value a string?
585 | _.isString = function(obj) {
586 | return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
587 | };
588 |
589 | // Is a given value a number?
590 | _.isNumber = function(obj) {
591 | return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
592 | };
593 |
594 | // Is the given value NaN -- this one is interesting. NaN != NaN, and
595 | // isNaN(undefined) == true, so we make sure it's a number first.
596 | _.isNaN = function(obj) {
597 | return toString.call(obj) === '[object Number]' && isNaN(obj);
598 | };
599 |
600 | // Is a given value a boolean?
601 | _.isBoolean = function(obj) {
602 | return obj === true || obj === false;
603 | };
604 |
605 | // Is a given value a date?
606 | _.isDate = function(obj) {
607 | return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
608 | };
609 |
610 | // Is the given value a regular expression?
611 | _.isRegExp = function(obj) {
612 | return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
613 | };
614 |
615 | // Is a given value equal to null?
616 | _.isNull = function(obj) {
617 | return obj === null;
618 | };
619 |
620 | // Is a given variable undefined?
621 | _.isUndefined = function(obj) {
622 | return obj === void 0;
623 | };
624 |
625 | // Utility Functions
626 | // -----------------
627 |
628 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
629 | // previous owner. Returns a reference to the Underscore object.
630 | _.noConflict = function() {
631 | root._ = previousUnderscore;
632 | return this;
633 | };
634 |
635 | // Keep the identity function around for default iterators.
636 | _.identity = function(value) {
637 | return value;
638 | };
639 |
640 | // Run a function **n** times.
641 | _.times = function (n, iterator, context) {
642 | for (var i = 0; i < n; i++) iterator.call(context, i);
643 | };
644 |
645 | // Add your own custom functions to the Underscore object, ensuring that
646 | // they're correctly added to the OOP wrapper as well.
647 | _.mixin = function(obj) {
648 | each(_.functions(obj), function(name){
649 | addToWrapper(name, _[name] = obj[name]);
650 | });
651 | };
652 |
653 | // Generate a unique integer id (unique within the entire client session).
654 | // Useful for temporary DOM ids.
655 | var idCounter = 0;
656 | _.uniqueId = function(prefix) {
657 | var id = idCounter++;
658 | return prefix ? prefix + id : id;
659 | };
660 |
661 | // By default, Underscore uses ERB-style template delimiters, change the
662 | // following template settings to use alternative delimiters.
663 | _.templateSettings = {
664 | evaluate : /<%([\s\S]+?)%>/g,
665 | interpolate : /<%=([\s\S]+?)%>/g
666 | };
667 |
668 | // JavaScript micro-templating, similar to John Resig's implementation.
669 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
670 | // and correctly escapes quotes within interpolated code.
671 | _.template = function(str, data) {
672 | var c = _.templateSettings;
673 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
674 | 'with(obj||{}){__p.push(\'' +
675 | str.replace(/\\/g, '\\\\')
676 | .replace(/'/g, "\\'")
677 | .replace(c.interpolate, function(match, code) {
678 | return "'," + code.replace(/\\'/g, "'") + ",'";
679 | })
680 | .replace(c.evaluate || null, function(match, code) {
681 | return "');" + code.replace(/\\'/g, "'")
682 | .replace(/[\r\n\t]/g, ' ') + "__p.push('";
683 | })
684 | .replace(/\r/g, '\\r')
685 | .replace(/\n/g, '\\n')
686 | .replace(/\t/g, '\\t')
687 | + "');}return __p.join('');";
688 | var func = new Function('obj', tmpl);
689 | return data ? func(data) : func;
690 | };
691 |
692 | // The OOP Wrapper
693 | // ---------------
694 |
695 | // If Underscore is called as a function, it returns a wrapped object that
696 | // can be used OO-style. This wrapper holds altered versions of all the
697 | // underscore functions. Wrapped objects may be chained.
698 | var wrapper = function(obj) { this._wrapped = obj; };
699 |
700 | // Expose `wrapper.prototype` as `_.prototype`
701 | _.prototype = wrapper.prototype;
702 |
703 | // Helper function to continue chaining intermediate results.
704 | var result = function(obj, chain) {
705 | return chain ? _(obj).chain() : obj;
706 | };
707 |
708 | // A method to easily add functions to the OOP wrapper.
709 | var addToWrapper = function(name, func) {
710 | wrapper.prototype[name] = function() {
711 | var args = slice.call(arguments);
712 | unshift.call(args, this._wrapped);
713 | return result(func.apply(_, args), this._chain);
714 | };
715 | };
716 |
717 | // Add all of the Underscore functions to the wrapper object.
718 | _.mixin(_);
719 |
720 | // Add all mutator Array functions to the wrapper.
721 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
722 | var method = ArrayProto[name];
723 | wrapper.prototype[name] = function() {
724 | method.apply(this._wrapped, arguments);
725 | return result(this._wrapped, this._chain);
726 | };
727 | });
728 |
729 | // Add all accessor Array functions to the wrapper.
730 | each(['concat', 'join', 'slice'], function(name) {
731 | var method = ArrayProto[name];
732 | wrapper.prototype[name] = function() {
733 | return result(method.apply(this._wrapped, arguments), this._chain);
734 | };
735 | });
736 |
737 | // Start chaining a wrapped Underscore object.
738 | wrapper.prototype.chain = function() {
739 | this._chain = true;
740 | return this;
741 | };
742 |
743 | // Extracts the result from a wrapped and chained object.
744 | wrapper.prototype.value = function() {
745 | return this._wrapped;
746 | };
747 |
748 | })();
--------------------------------------------------------------------------------
/templates/lib/underscore.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.3.1
2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
3 | // Underscore is freely distributable under the MIT license.
4 | // Portions of Underscore are inspired or borrowed from Prototype,
5 | // Oliver Steele's Functional, and John Resig's Micro-Templating.
6 | // For all details and documentation:
7 | // http://documentcloud.github.com/underscore
8 | (function() {
9 |
10 | // Baseline setup
11 | // --------------
12 | // Establish the root object, `window` in the browser, or `global` on the server.
13 | var root = this;
14 |
15 | // Save the previous value of the `_` variable.
16 | var previousUnderscore = root._;
17 |
18 | // Establish the object that gets returned to break out of a loop iteration.
19 | var breaker = {};
20 |
21 | // Save bytes in the minified (but not gzipped) version:
22 | var ArrayProto = Array.prototype,
23 | ObjProto = Object.prototype,
24 | FuncProto = Function.prototype;
25 |
26 | // Create quick reference variables for speed access to core prototypes.
27 | var slice = ArrayProto.slice,
28 | unshift = ArrayProto.unshift,
29 | toString = ObjProto.toString,
30 | hasOwnProperty = ObjProto.hasOwnProperty;
31 |
32 | // All **ECMAScript 5** native function implementations that we hope to use
33 | // are declared here.
34 | var
35 | nativeForEach = ArrayProto.forEach,
36 | nativeMap = ArrayProto.map,
37 | nativeReduce = ArrayProto.reduce,
38 | nativeReduceRight = ArrayProto.reduceRight,
39 | nativeFilter = ArrayProto.filter,
40 | nativeEvery = ArrayProto.every,
41 | nativeSome = ArrayProto.some,
42 | nativeIndexOf = ArrayProto.indexOf,
43 | nativeLastIndexOf = ArrayProto.lastIndexOf,
44 | nativeIsArray = Array.isArray,
45 | nativeKeys = Object.keys,
46 | nativeBind = FuncProto.bind;
47 |
48 | // Create a safe reference to the Underscore object for use below.
49 | var _ = function(obj) {
50 | return new wrapper(obj);
51 | };
52 |
53 | // Export the Underscore object for **Node.js** and **"CommonJS"**, with
54 | // backwards-compatibility for the old `require()` API. If we're not in
55 | // CommonJS, add `_` to the global object via a string identifier for
56 | // the Closure Compiler "advanced" mode, and optionally register as an
57 | // AMD module via define().
58 | if (typeof exports !== 'undefined') {
59 | if (typeof module !== 'undefined' && module.exports) {
60 | exports = module.exports = _;
61 | }
62 | exports._ = _;
63 | } else {
64 | if (typeof define === 'function' && define.amd) {
65 | define('underscore', function() {
66 | return _;
67 | });
68 | }
69 | root['_'] = _;
70 | }
71 |
72 | // Current version.
73 | _.VERSION = '1.3.1';
74 |
75 | // Collection Functions
76 | // --------------------
77 | // The cornerstone, an `each` implementation, aka `forEach`.
78 | // Handles objects with the built-in `forEach`, arrays, and raw objects.
79 | // Delegates to **ECMAScript 5**'s native `forEach` if available.
80 | var each = _.each = _.forEach = function(obj, iterator, context) {
81 | if (obj == null) return;
82 | if (nativeForEach && obj.forEach === nativeForEach) {
83 | obj.forEach(iterator, context);
84 | } else if (obj.length === +obj.length) {
85 | for (var i = 0, l = obj.length; i < l; i++) {
86 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
87 | }
88 | } else {
89 | for (var key in obj) {
90 | if (_.has(obj, key)) {
91 | if (iterator.call(context, obj[key], key, obj) === breaker) return;
92 | }
93 | }
94 | }
95 | };
96 |
97 | // Return the results of applying the iterator to each element.
98 | // Delegates to **ECMAScript 5**'s native `map` if available.
99 | _.map = _.collect = function(obj, iterator, context) {
100 | var results = [];
101 | if (obj == null) return results;
102 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
103 | each(obj, function(value, index, list) {
104 | results[results.length] = iterator.call(context, value, index, list);
105 | });
106 | if (obj.length === +obj.length) results.length = obj.length;
107 | return results;
108 | };
109 |
110 | // **Reduce** builds up a single result from a list of values, aka `inject`,
111 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
112 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
113 | var initial = arguments.length > 2;
114 | if (obj == null) obj = [];
115 | if (nativeReduce && obj.reduce === nativeReduce) {
116 | if (context) iterator = _.bind(iterator, context);
117 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
118 | }
119 | each(obj, function(value, index, list) {
120 | if (!initial) {
121 | memo = value;
122 | initial = true;
123 | } else {
124 | memo = iterator.call(context, memo, value, index, list);
125 | }
126 | });
127 | if (!initial) throw new TypeError('Reduce of empty array with no initial value');
128 | return memo;
129 | };
130 |
131 | // The right-associative version of reduce, also known as `foldr`.
132 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available.
133 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) {
134 | var initial = arguments.length > 2;
135 | if (obj == null) obj = [];
136 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
137 | if (context) iterator = _.bind(iterator, context);
138 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
139 | }
140 | var reversed = _.toArray(obj).reverse();
141 | if (context && !initial) iterator = _.bind(iterator, context);
142 | return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
143 | };
144 |
145 | // Return the first value which passes a truth test. Aliased as `detect`.
146 | _.find = _.detect = function(obj, iterator, context) {
147 | var result;
148 | any(obj, function(value, index, list) {
149 | if (iterator.call(context, value, index, list)) {
150 | result = value;
151 | return true;
152 | }
153 | });
154 | return result;
155 | };
156 |
157 | // Return all the elements that pass a truth test.
158 | // Delegates to **ECMAScript 5**'s native `filter` if available.
159 | // Aliased as `select`.
160 | _.filter = _.select = function(obj, iterator, context) {
161 | var results = [];
162 | if (obj == null) return results;
163 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
164 | each(obj, function(value, index, list) {
165 | if (iterator.call(context, value, index, list)) results[results.length] = value;
166 | });
167 | return results;
168 | };
169 |
170 | // Return all the elements for which a truth test fails.
171 | _.reject = function(obj, iterator, context) {
172 | var results = [];
173 | if (obj == null) return results;
174 | each(obj, function(value, index, list) {
175 | if (!iterator.call(context, value, index, list)) results[results.length] = value;
176 | });
177 | return results;
178 | };
179 |
180 | // Determine whether all of the elements match a truth test.
181 | // Delegates to **ECMAScript 5**'s native `every` if available.
182 | // Aliased as `all`.
183 | _.every = _.all = function(obj, iterator, context) {
184 | var result = true;
185 | if (obj == null) return result;
186 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
187 | each(obj, function(value, index, list) {
188 | if (!(result = result && iterator.call(context, value, index, list))) return breaker;
189 | });
190 | return result;
191 | };
192 |
193 | // Determine if at least one element in the object matches a truth test.
194 | // Delegates to **ECMAScript 5**'s native `some` if available.
195 | // Aliased as `any`.
196 | var any = _.some = _.any = function(obj, iterator, context) {
197 | iterator || (iterator = _.identity);
198 | var result = false;
199 | if (obj == null) return result;
200 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
201 | each(obj, function(value, index, list) {
202 | if (result || (result = iterator.call(context, value, index, list))) return breaker;
203 | });
204 | return !!result;
205 | };
206 |
207 | // Determine if a given value is included in the array or object using `===`.
208 | // Aliased as `contains`.
209 | _.include = _.contains = function(obj, target) {
210 | var found = false;
211 | if (obj == null) return found;
212 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
213 | found = any(obj, function(value) {
214 | return value === target;
215 | });
216 | return found;
217 | };
218 |
219 | // Invoke a method (with arguments) on every item in a collection.
220 | _.invoke = function(obj, method) {
221 | var args = slice.call(arguments, 2);
222 | return _.map(obj, function(value) {
223 | return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
224 | });
225 | };
226 |
227 | // Convenience version of a common use case of `map`: fetching a property.
228 | _.pluck = function(obj, key) {
229 | return _.map(obj, function(value) {
230 | return value[key];
231 | });
232 | };
233 |
234 | // Return the maximum element or (element-based computation).
235 | _.max = function(obj, iterator, context) {
236 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
237 | if (!iterator && _.isEmpty(obj)) return -Infinity;
238 | var result = {
239 | computed: -Infinity
240 | };
241 | each(obj, function(value, index, list) {
242 | var computed = iterator ? iterator.call(context, value, index, list) : value;
243 | computed >= result.computed && (result = {
244 | value: value,
245 | computed: computed
246 | });
247 | });
248 | return result.value;
249 | };
250 |
251 | // Return the minimum element (or element-based computation).
252 | _.min = function(obj, iterator, context) {
253 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
254 | if (!iterator && _.isEmpty(obj)) return Infinity;
255 | var result = {
256 | computed: Infinity
257 | };
258 | each(obj, function(value, index, list) {
259 | var computed = iterator ? iterator.call(context, value, index, list) : value;
260 | computed < result.computed && (result = {
261 | value: value,
262 | computed: computed
263 | });
264 | });
265 | return result.value;
266 | };
267 |
268 | // Shuffle an array.
269 | _.shuffle = function(obj) {
270 | var shuffled = [],
271 | rand;
272 | each(obj, function(value, index, list) {
273 | if (index == 0) {
274 | shuffled[0] = value;
275 | } else {
276 | rand = Math.floor(Math.random() * (index + 1));
277 | shuffled[index] = shuffled[rand];
278 | shuffled[rand] = value;
279 | }
280 | });
281 | return shuffled;
282 | };
283 |
284 | // Sort the object's values by a criterion produced by an iterator.
285 | _.sortBy = function(obj, iterator, context) {
286 | return _.pluck(_.map(obj, function(value, index, list) {
287 | return {
288 | value: value,
289 | criteria: iterator.call(context, value, index, list)
290 | };
291 | }).sort(function(left, right) {
292 | var a = left.criteria,
293 | b = right.criteria;
294 | return a < b ? -1 : a > b ? 1 : 0;
295 | }), 'value');
296 | };
297 |
298 | // Groups the object's values by a criterion. Pass either a string attribute
299 | // to group by, or a function that returns the criterion.
300 | _.groupBy = function(obj, val) {
301 | var result = {};
302 | var iterator = _.isFunction(val) ? val : function(obj) {
303 | return obj[val];
304 | };
305 | each(obj, function(value, index) {
306 | var key = iterator(value, index);
307 | (result[key] || (result[key] = [])).push(value);
308 | });
309 | return result;
310 | };
311 |
312 | // Use a comparator function to figure out at what index an object should
313 | // be inserted so as to maintain order. Uses binary search.
314 | _.sortedIndex = function(array, obj, iterator) {
315 | iterator || (iterator = _.identity);
316 | var low = 0,
317 | high = array.length;
318 | while (low < high) {
319 | var mid = (low + high) >> 1;
320 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
321 | }
322 | return low;
323 | };
324 |
325 | // Safely convert anything iterable into a real, live array.
326 | _.toArray = function(iterable) {
327 | if (!iterable) return [];
328 | if (iterable.toArray) return iterable.toArray();
329 | if (_.isArray(iterable)) return slice.call(iterable);
330 | if (_.isArguments(iterable)) return slice.call(iterable);
331 | return _.values(iterable);
332 | };
333 |
334 | // Return the number of elements in an object.
335 | _.size = function(obj) {
336 | return _.toArray(obj).length;
337 | };
338 |
339 | // Array Functions
340 | // ---------------
341 | // Get the first element of an array. Passing **n** will return the first N
342 | // values in the array. Aliased as `head`. The **guard** check allows it to work
343 | // with `_.map`.
344 | _.first = _.head = function(array, n, guard) {
345 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
346 | };
347 |
348 | // Returns everything but the last entry of the array. Especcialy useful on
349 | // the arguments object. Passing **n** will return all the values in
350 | // the array, excluding the last N. The **guard** check allows it to work with
351 | // `_.map`.
352 | _.initial = function(array, n, guard) {
353 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
354 | };
355 |
356 | // Get the last element of an array. Passing **n** will return the last N
357 | // values in the array. The **guard** check allows it to work with `_.map`.
358 | _.last = function(array, n, guard) {
359 | if ((n != null) && !guard) {
360 | return slice.call(array, Math.max(array.length - n, 0));
361 | } else {
362 | return array[array.length - 1];
363 | }
364 | };
365 |
366 | // Returns everything but the first entry of the array. Aliased as `tail`.
367 | // Especially useful on the arguments object. Passing an **index** will return
368 | // the rest of the values in the array from that index onward. The **guard**
369 | // check allows it to work with `_.map`.
370 | _.rest = _.tail = function(array, index, guard) {
371 | return slice.call(array, (index == null) || guard ? 1 : index);
372 | };
373 |
374 | // Trim out all falsy values from an array.
375 | _.compact = function(array) {
376 | return _.filter(array, function(value) {
377 | return !!value;
378 | });
379 | };
380 |
381 | // Return a completely flattened version of an array.
382 | _.flatten = function(array, shallow) {
383 | return _.reduce(array, function(memo, value) {
384 | if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
385 | memo[memo.length] = value;
386 | return memo;
387 | }, []);
388 | };
389 |
390 | // Return a version of the array that does not contain the specified value(s).
391 | _.without = function(array) {
392 | return _.difference(array, slice.call(arguments, 1));
393 | };
394 |
395 | // Produce a duplicate-free version of the array. If the array has already
396 | // been sorted, you have the option of using a faster algorithm.
397 | // Aliased as `unique`.
398 | _.uniq = _.unique = function(array, isSorted, iterator) {
399 | var initial = iterator ? _.map(array, iterator) : array;
400 | var result = [];
401 | _.reduce(initial, function(memo, el, i) {
402 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
403 | memo[memo.length] = el;
404 | result[result.length] = array[i];
405 | }
406 | return memo;
407 | }, []);
408 | return result;
409 | };
410 |
411 | // Produce an array that contains the union: each distinct element from all of
412 | // the passed-in arrays.
413 | _.union = function() {
414 | return _.uniq(_.flatten(arguments, true));
415 | };
416 |
417 | // Produce an array that contains every item shared between all the
418 | // passed-in arrays. (Aliased as "intersect" for back-compat.)
419 | _.intersection = _.intersect = function(array) {
420 | var rest = slice.call(arguments, 1);
421 | return _.filter(_.uniq(array), function(item) {
422 | return _.every(rest, function(other) {
423 | return _.indexOf(other, item) >= 0;
424 | });
425 | });
426 | };
427 |
428 | // Take the difference between one array and a number of other arrays.
429 | // Only the elements present in just the first array will remain.
430 | _.difference = function(array) {
431 | var rest = _.flatten(slice.call(arguments, 1));
432 | return _.filter(array, function(value) {
433 | return !_.include(rest, value);
434 | });
435 | };
436 |
437 | // Zip together multiple lists into a single array -- elements that share
438 | // an index go together.
439 | _.zip = function() {
440 | var args = slice.call(arguments);
441 | var length = _.max(_.pluck(args, 'length'));
442 | var results = new Array(length);
443 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
444 | return results;
445 | };
446 |
447 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
448 | // we need this function. Return the position of the first occurrence of an
449 | // item in an array, or -1 if the item is not included in the array.
450 | // Delegates to **ECMAScript 5**'s native `indexOf` if available.
451 | // If the array is large and already in sort order, pass `true`
452 | // for **isSorted** to use binary search.
453 | _.indexOf = function(array, item, isSorted) {
454 | if (array == null) return -1;
455 | var i, l;
456 | if (isSorted) {
457 | i = _.sortedIndex(array, item);
458 | return array[i] === item ? i : -1;
459 | }
460 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
461 | for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
462 | return -1;
463 | };
464 |
465 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
466 | _.lastIndexOf = function(array, item) {
467 | if (array == null) return -1;
468 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
469 | var i = array.length;
470 | while (i--) if (i in array && array[i] === item) return i;
471 | return -1;
472 | };
473 |
474 | // Generate an integer Array containing an arithmetic progression. A port of
475 | // the native Python `range()` function. See
476 | // [the Python documentation](http://docs.python.org/library/functions.html#range).
477 | _.range = function(start, stop, step) {
478 | if (arguments.length <= 1) {
479 | stop = start || 0;
480 | start = 0;
481 | }
482 | step = arguments[2] || 1;
483 |
484 | var len = Math.max(Math.ceil((stop - start) / step), 0);
485 | var idx = 0;
486 | var range = new Array(len);
487 |
488 | while (idx < len) {
489 | range[idx++] = start;
490 | start += step;
491 | }
492 |
493 | return range;
494 | };
495 |
496 | // Function (ahem) Functions
497 | // ------------------
498 | // Reusable constructor function for prototype setting.
499 | var ctor = function() {};
500 |
501 | // Create a function bound to a given object (assigning `this`, and arguments,
502 | // optionally). Binding with arguments is also known as `curry`.
503 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available.
504 | // We check for `func.bind` first, to fail fast when `func` is undefined.
505 | _.bind = function bind(func, context) {
506 | var bound, args;
507 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
508 | if (!_.isFunction(func)) throw new TypeError;
509 | args = slice.call(arguments, 2);
510 | return bound = function() {
511 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
512 | ctor.prototype = func.prototype;
513 | var self = new ctor;
514 | var result = func.apply(self, args.concat(slice.call(arguments)));
515 | if (Object(result) === result) return result;
516 | return self;
517 | };
518 | };
519 |
520 | // Bind all of an object's methods to that object. Useful for ensuring that
521 | // all callbacks defined on an object belong to it.
522 | _.bindAll = function(obj) {
523 | var funcs = slice.call(arguments, 1);
524 | if (funcs.length == 0) funcs = _.functions(obj);
525 | each(funcs, function(f) {
526 | obj[f] = _.bind(obj[f], obj);
527 | });
528 | return obj;
529 | };
530 |
531 | // Memoize an expensive function by storing its results.
532 | _.memoize = function(func, hasher) {
533 | var memo = {};
534 | hasher || (hasher = _.identity);
535 | return function() {
536 | var key = hasher.apply(this, arguments);
537 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
538 | };
539 | };
540 |
541 | // Delays a function for the given number of milliseconds, and then calls
542 | // it with the arguments supplied.
543 | _.delay = function(func, wait) {
544 | var args = slice.call(arguments, 2);
545 | return setTimeout(function() {
546 | return func.apply(func, args);
547 | }, wait);
548 | };
549 |
550 | // Defers a function, scheduling it to run after the current call stack has
551 | // cleared.
552 | _.defer = function(func) {
553 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
554 | };
555 |
556 | // Returns a function, that, when invoked, will only be triggered at most once
557 | // during a given window of time.
558 | _.throttle = function(func, wait) {
559 | var context, args, timeout, throttling, more;
560 | var whenDone = _.debounce(function() {
561 | more = throttling = false;
562 | }, wait);
563 | return function() {
564 | context = this;
565 | args = arguments;
566 | var later = function() {
567 | timeout = null;
568 | if (more) func.apply(context, args);
569 | whenDone();
570 | };
571 | if (!timeout) timeout = setTimeout(later, wait);
572 | if (throttling) {
573 | more = true;
574 | } else {
575 | func.apply(context, args);
576 | }
577 | whenDone();
578 | throttling = true;
579 | };
580 | };
581 |
582 | // Returns a function, that, as long as it continues to be invoked, will not
583 | // be triggered. The function will be called after it stops being called for
584 | // N milliseconds.
585 | _.debounce = function(func, wait) {
586 | var timeout;
587 | return function() {
588 | var context = this,
589 | args = arguments;
590 | var later = function() {
591 | timeout = null;
592 | func.apply(context, args);
593 | };
594 | clearTimeout(timeout);
595 | timeout = setTimeout(later, wait);
596 | };
597 | };
598 |
599 | // Returns a function that will be executed at most one time, no matter how
600 | // often you call it. Useful for lazy initialization.
601 | _.once = function(func) {
602 | var ran = false,
603 | memo;
604 | return function() {
605 | if (ran) return memo;
606 | ran = true;
607 | return memo = func.apply(this, arguments);
608 | };
609 | };
610 |
611 | // Returns the first function passed as an argument to the second,
612 | // allowing you to adjust arguments, run code before and after, and
613 | // conditionally execute the original function.
614 | _.wrap = function(func, wrapper) {
615 | return function() {
616 | var args = [func].concat(slice.call(arguments, 0));
617 | return wrapper.apply(this, args);
618 | };
619 | };
620 |
621 | // Returns a function that is the composition of a list of functions, each
622 | // consuming the return value of the function that follows.
623 | _.compose = function() {
624 | var funcs = arguments;
625 | return function() {
626 | var args = arguments;
627 | for (var i = funcs.length - 1; i >= 0; i--) {
628 | args = [funcs[i].apply(this, args)];
629 | }
630 | return args[0];
631 | };
632 | };
633 |
634 | // Returns a function that will only be executed after being called N times.
635 | _.after = function(times, func) {
636 | if (times <= 0) return func();
637 | return function() {
638 | if (--times < 1) {
639 | return func.apply(this, arguments);
640 | }
641 | };
642 | };
643 |
644 | // Object Functions
645 | // ----------------
646 | // Retrieve the names of an object's properties.
647 | // Delegates to **ECMAScript 5**'s native `Object.keys`
648 | _.keys = nativeKeys ||
649 | function(obj) {
650 | if (obj !== Object(obj)) throw new TypeError('Invalid object');
651 | var keys = [];
652 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
653 | return keys;
654 | };
655 |
656 | // Retrieve the values of an object's properties.
657 | _.values = function(obj) {
658 | return _.map(obj, _.identity);
659 | };
660 |
661 | // Return a sorted list of the function names available on the object.
662 | // Aliased as `methods`
663 | _.functions = _.methods = function(obj) {
664 | var names = [];
665 | for (var key in obj) {
666 | if (_.isFunction(obj[key])) names.push(key);
667 | }
668 | return names.sort();
669 | };
670 |
671 | // Extend a given object with all the properties in passed-in object(s).
672 | _.extend = function(obj) {
673 | each(slice.call(arguments, 1), function(source) {
674 | for (var prop in source) {
675 | obj[prop] = source[prop];
676 | }
677 | });
678 | return obj;
679 | };
680 |
681 | // Fill in a given object with default properties.
682 | _.defaults = function(obj) {
683 | each(slice.call(arguments, 1), function(source) {
684 | for (var prop in source) {
685 | if (obj[prop] == null) obj[prop] = source[prop];
686 | }
687 | });
688 | return obj;
689 | };
690 |
691 | // Create a (shallow-cloned) duplicate of an object.
692 | _.clone = function(obj) {
693 | if (!_.isObject(obj)) return obj;
694 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
695 | };
696 |
697 | // Invokes interceptor with the obj, and then returns obj.
698 | // The primary purpose of this method is to "tap into" a method chain, in
699 | // order to perform operations on intermediate results within the chain.
700 | _.tap = function(obj, interceptor) {
701 | interceptor(obj);
702 | return obj;
703 | };
704 |
705 | // Internal recursive comparison function.
706 |
707 |
708 | function eq(a, b, stack) {
709 | // Identical objects are equal. `0 === -0`, but they aren't identical.
710 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
711 | if (a === b) return a !== 0 || 1 / a == 1 / b;
712 | // A strict comparison is necessary because `null == undefined`.
713 | if (a == null || b == null) return a === b;
714 | // Unwrap any wrapped objects.
715 | if (a._chain) a = a._wrapped;
716 | if (b._chain) b = b._wrapped;
717 | // Invoke a custom `isEqual` method if one is provided.
718 | if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
719 | if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
720 | // Compare `[[Class]]` names.
721 | var className = toString.call(a);
722 | if (className != toString.call(b)) return false;
723 | switch (className) {
724 | // Strings, numbers, dates, and booleans are compared by value.
725 | case '[object String]':
726 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
727 | // equivalent to `new String("5")`.
728 | return a == String(b);
729 | case '[object Number]':
730 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
731 | // other numeric values.
732 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
733 | case '[object Date]':
734 | case '[object Boolean]':
735 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their
736 | // millisecond representations. Note that invalid dates with millisecond representations
737 | // of `NaN` are not equivalent.
738 | return +a == +b;
739 | // RegExps are compared by their source patterns and flags.
740 | case '[object RegExp]':
741 | return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase;
742 | }
743 | if (typeof a != 'object' || typeof b != 'object') return false;
744 | // Assume equality for cyclic structures. The algorithm for detecting cyclic
745 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
746 | var length = stack.length;
747 | while (length--) {
748 | // Linear search. Performance is inversely proportional to the number of
749 | // unique nested structures.
750 | if (stack[length] == a) return true;
751 | }
752 | // Add the first object to the stack of traversed objects.
753 | stack.push(a);
754 | var size = 0,
755 | result = true;
756 | // Recursively compare objects and arrays.
757 | if (className == '[object Array]') {
758 | // Compare array lengths to determine if a deep comparison is necessary.
759 | size = a.length;
760 | result = size == b.length;
761 | if (result) {
762 | // Deep compare the contents, ignoring non-numeric properties.
763 | while (size--) {
764 | // Ensure commutative equality for sparse arrays.
765 | if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
766 | }
767 | }
768 | } else {
769 | // Objects with different constructors are not equivalent.
770 | if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
771 | // Deep compare objects.
772 | for (var key in a) {
773 | if (_.has(a, key)) {
774 | // Count the expected number of properties.
775 | size++;
776 | // Deep compare each member.
777 | if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
778 | }
779 | }
780 | // Ensure that both objects contain the same number of properties.
781 | if (result) {
782 | for (key in b) {
783 | if (_.has(b, key) && !(size--)) break;
784 | }
785 | result = !size;
786 | }
787 | }
788 | // Remove the first object from the stack of traversed objects.
789 | stack.pop();
790 | return result;
791 | }
792 |
793 | // Perform a deep comparison to check if two objects are equal.
794 | _.isEqual = function(a, b) {
795 | return eq(a, b, []);
796 | };
797 |
798 | // Is a given array, string, or object empty?
799 | // An "empty" object has no enumerable own-properties.
800 | _.isEmpty = function(obj) {
801 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
802 | for (var key in obj) if (_.has(obj, key)) return false;
803 | return true;
804 | };
805 |
806 | // Is a given value a DOM element?
807 | _.isElement = function(obj) {
808 | return !!(obj && obj.nodeType == 1);
809 | };
810 |
811 | // Is a given value an array?
812 | // Delegates to ECMA5's native Array.isArray
813 | _.isArray = nativeIsArray ||
814 | function(obj) {
815 | return toString.call(obj) == '[object Array]';
816 | };
817 |
818 | // Is a given variable an object?
819 | _.isObject = function(obj) {
820 | return obj === Object(obj);
821 | };
822 |
823 | // Is a given variable an arguments object?
824 | _.isArguments = function(obj) {
825 | return toString.call(obj) == '[object Arguments]';
826 | };
827 | if (!_.isArguments(arguments)) {
828 | _.isArguments = function(obj) {
829 | return !!(obj && _.has(obj, 'callee'));
830 | };
831 | }
832 |
833 | // Is a given value a function?
834 | _.isFunction = function(obj) {
835 | return toString.call(obj) == '[object Function]';
836 | };
837 |
838 | // Is a given value a string?
839 | _.isString = function(obj) {
840 | return toString.call(obj) == '[object String]';
841 | };
842 |
843 | // Is a given value a number?
844 | _.isNumber = function(obj) {
845 | return toString.call(obj) == '[object Number]';
846 | };
847 |
848 | // Is the given value `NaN`?
849 | _.isNaN = function(obj) {
850 | // `NaN` is the only value for which `===` is not reflexive.
851 | return obj !== obj;
852 | };
853 |
854 | // Is a given value a boolean?
855 | _.isBoolean = function(obj) {
856 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
857 | };
858 |
859 | // Is a given value a date?
860 | _.isDate = function(obj) {
861 | return toString.call(obj) == '[object Date]';
862 | };
863 |
864 | // Is the given value a regular expression?
865 | _.isRegExp = function(obj) {
866 | return toString.call(obj) == '[object RegExp]';
867 | };
868 |
869 | // Is a given value equal to null?
870 | _.isNull = function(obj) {
871 | return obj === null;
872 | };
873 |
874 | // Is a given variable undefined?
875 | _.isUndefined = function(obj) {
876 | return obj === void 0;
877 | };
878 |
879 | // Has own property?
880 | _.has = function(obj, key) {
881 | return hasOwnProperty.call(obj, key);
882 | };
883 |
884 | // Utility Functions
885 | // -----------------
886 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
887 | // previous owner. Returns a reference to the Underscore object.
888 | _.noConflict = function() {
889 | root._ = previousUnderscore;
890 | return this;
891 | };
892 |
893 | // Keep the identity function around for default iterators.
894 | _.identity = function(value) {
895 | return value;
896 | };
897 |
898 | // Run a function **n** times.
899 | _.times = function(n, iterator, context) {
900 | for (var i = 0; i < n; i++) iterator.call(context, i);
901 | };
902 |
903 | // Escape a string for HTML interpolation.
904 | _.escape = function(string) {
905 | return ('' + string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');
906 | };
907 |
908 | // Add your own custom functions to the Underscore object, ensuring that
909 | // they're correctly added to the OOP wrapper as well.
910 | _.mixin = function(obj) {
911 | each(_.functions(obj), function(name) {
912 | addToWrapper(name, _[name] = obj[name]);
913 | });
914 | };
915 |
916 | // Generate a unique integer id (unique within the entire client session).
917 | // Useful for temporary DOM ids.
918 | var idCounter = 0;
919 | _.uniqueId = function(prefix) {
920 | var id = idCounter++;
921 | return prefix ? prefix + id : id;
922 | };
923 |
924 | // By default, Underscore uses ERB-style template delimiters, change the
925 | // following template settings to use alternative delimiters.
926 | _.templateSettings = {
927 | evaluate: /<%([\s\S]+?)%>/g,
928 | interpolate: /<%=([\s\S]+?)%>/g,
929 | escape: /<%-([\s\S]+?)%>/g
930 | };
931 |
932 | // When customizing `templateSettings`, if you don't want to define an
933 | // interpolation, evaluation or escaping regex, we need one that is
934 | // guaranteed not to match.
935 | var noMatch = /.^/;
936 |
937 | // Within an interpolation, evaluation, or escaping, remove HTML escaping
938 | // that had been previously added.
939 | var unescape = function(code) {
940 | return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
941 | };
942 |
943 | // JavaScript micro-templating, similar to John Resig's implementation.
944 | // Underscore templating handles arbitrary delimiters, preserves whitespace,
945 | // and correctly escapes quotes within interpolated code.
946 | _.template = function(str, data) {
947 | var c = _.templateSettings;
948 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 'with(obj||{}){__p.push(\'' + str.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(c.escape || noMatch, function(match, code) {
949 | return "',_.escape(" + unescape(code) + "),'";
950 | }).replace(c.interpolate || noMatch, function(match, code) {
951 | return "'," + unescape(code) + ",'";
952 | }).replace(c.evaluate || noMatch, function(match, code) {
953 | return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
954 | }).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/\t/g, '\\t') + "');}return __p.join('');";
955 | var func = new Function('obj', '_', tmpl);
956 | if (data) return func(data, _);
957 | return function(data) {
958 | return func.call(this, data, _);
959 | };
960 | };
961 |
962 | // Add a "chain" function, which will delegate to the wrapper.
963 | _.chain = function(obj) {
964 | return _(obj).chain();
965 | };
966 |
967 | // The OOP Wrapper
968 | // ---------------
969 | // If Underscore is called as a function, it returns a wrapped object that
970 | // can be used OO-style. This wrapper holds altered versions of all the
971 | // underscore functions. Wrapped objects may be chained.
972 | var wrapper = function(obj) {
973 | this._wrapped = obj;
974 | };
975 |
976 | // Expose `wrapper.prototype` as `_.prototype`
977 | _.prototype = wrapper.prototype;
978 |
979 | // Helper function to continue chaining intermediate results.
980 | var result = function(obj, chain) {
981 | return chain ? _(obj).chain() : obj;
982 | };
983 |
984 | // A method to easily add functions to the OOP wrapper.
985 | var addToWrapper = function(name, func) {
986 | wrapper.prototype[name] = function() {
987 | var args = slice.call(arguments);
988 | unshift.call(args, this._wrapped);
989 | return result(func.apply(_, args), this._chain);
990 | };
991 | };
992 |
993 | // Add all of the Underscore functions to the wrapper object.
994 | _.mixin(_);
995 |
996 | // Add all mutator Array functions to the wrapper.
997 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
998 | var method = ArrayProto[name];
999 | wrapper.prototype[name] = function() {
1000 | var wrapped = this._wrapped;
1001 | method.apply(wrapped, arguments);
1002 | var length = wrapped.length;
1003 | if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
1004 | return result(wrapped, this._chain);
1005 | };
1006 | });
1007 |
1008 | // Add all accessor Array functions to the wrapper.
1009 | each(['concat', 'join', 'slice'], function(name) {
1010 | var method = ArrayProto[name];
1011 | wrapper.prototype[name] = function() {
1012 | return result(method.apply(this._wrapped, arguments), this._chain);
1013 | };
1014 | });
1015 |
1016 | // Start chaining a wrapped Underscore object.
1017 | wrapper.prototype.chain = function() {
1018 | this._chain = true;
1019 | return this;
1020 | };
1021 |
1022 | // Extracts the result from a wrapped and chained object.
1023 | wrapper.prototype.value = function() {
1024 | return this._wrapped;
1025 | };
1026 |
1027 | }).call(this);
1028 |
--------------------------------------------------------------------------------