├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── Procfile ├── README.md ├── bower.json ├── dist ├── jan.js └── jan.min.js ├── doctemplate ├── README.md ├── config.json ├── logo.acorn ├── publish.js ├── static │ ├── jan.jpg │ ├── logo.png │ ├── scripts │ │ ├── enhancements.js │ │ └── prettify │ │ │ ├── Apache-License-2.0.txt │ │ │ ├── lang-css.js │ │ │ └── prettify.js │ └── styles │ │ ├── jsdoc-default.css │ │ ├── prettify-jsdoc.css │ │ └── prettify-tomorrow.css └── tmpl │ ├── container.tmpl │ ├── details.tmpl │ ├── example.tmpl │ ├── examples.tmpl │ ├── exceptions.tmpl │ ├── layout.tmpl │ ├── mainpage.tmpl │ ├── members.tmpl │ ├── method.tmpl │ ├── params.tmpl │ ├── properties.tmpl │ ├── returns.tmpl │ ├── source.tmpl │ ├── tutorial.tmpl │ └── type.tmpl ├── logo.png ├── package.json ├── server.js ├── src └── jan.js └── test ├── css └── mocha.css ├── index.html ├── js ├── chai.js ├── mocha.js ├── require.js ├── sinon-chai.js └── sinon.js └── spec └── jan.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | bower_components 5 | docs 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "-W041" : true, 3 | "-W030" : true, 4 | "-W084" : true, 5 | "-W093" : true 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_script: 3 | - npm install -g grunt-cli 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | 6 | concat: { 7 | options: { 8 | separator: '\n' 9 | }, 10 | main : { 11 | src: [ 12 | 'src/**/*.js' 13 | ], 14 | dest: 'dist/<%= pkg.name %>.js' 15 | } 16 | }, 17 | 18 | uglify: { 19 | main : { 20 | options: { 21 | banner: '/*! <%= pkg.name %> <%= pkg.version %> */\n' 22 | }, 23 | files: { 24 | 'dist/<%= pkg.name %>.min.js': [ 25 | '<%= concat.main.dest %>' 26 | ] 27 | } 28 | } 29 | }, 30 | 31 | jshint : { 32 | options : { 33 | 'browser' : true 34 | }, 35 | main : { 36 | options : { 37 | jshintrc : '.jshintrc' 38 | }, 39 | src : ['src/**/*.js'] 40 | }, 41 | test : { 42 | options : { 43 | '-W030' : true 44 | }, 45 | src : [ 46 | 'test/**/*.js', 47 | '!test/js/**/*' 48 | ] 49 | } 50 | }, 51 | 52 | jsdoc : { 53 | main : { 54 | src: [ 55 | 'src/*.js', 56 | 'README.md' 57 | ], 58 | jsdoc : './node_modules/.bin/jsdoc', 59 | dest : 'docs', 60 | options : { 61 | configure : './doctemplate/config.json' 62 | } 63 | } 64 | }, 65 | 66 | mocha : { 67 | test : { 68 | options : { 69 | run : true, 70 | reporter : process.env.MOCHA_REPORTER || (process.env.ENVIRONMENT==='ci' ? 'XUnit' : 'Spec') 71 | }, 72 | src : ['test/**/*.html'], 73 | dest : (process.env.ENVIRONMENT==='ci' || process.env.OUTPUT_TESTS) && './test-reports/default.xml' 74 | } 75 | }, 76 | 77 | watch : { 78 | options : { 79 | interrupt : true 80 | }, 81 | src : { 82 | files : ['src/**/*.js'], 83 | tasks : ['default'] 84 | }, 85 | test : { 86 | files : ['test/**/*'], 87 | tasks : ['test'] 88 | } 89 | } 90 | }); 91 | 92 | require('load-grunt-tasks')(grunt); 93 | 94 | grunt.registerTask('default', [ 95 | 'jshint:main', 96 | 'concat:main', 97 | 'uglify:main', 98 | 'jsdoc:main' 99 | ]); 100 | 101 | grunt.registerTask('test', [ 102 | 'jshint:test', 103 | 'mocha:test' 104 | ]); 105 | 106 | grunt.registerTask('build-watch', [ 107 | 'default', 108 | 'watch' 109 | ]); 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Synacor, Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jan [![npm](http://img.shields.io/npm/v/jan.svg?style=flat)](https://www.npmjs.org/package/jan) [![travis](https://travis-ci.org/synacor/jan.svg?branch=master)](https://travis-ci.org/synacor/jan) 2 | ============= 3 | 4 | ![Jan](https://janjs.herokuapp.com/logo.png) 5 | 6 | **Jan is a simple library for making HTTP requests.** 7 | *Issue network calls without dealing with awkward legacy API signatures.* 8 | 9 | **[Documentation](https://janjs.herokuapp.com)** 10 | 11 | 12 | --- 13 | 14 | 15 | 16 | 17 | Why Jan? 18 | -------- 19 | 20 | **"AJAX" just isn't a thing anymore.** 21 | It's 2014, we're on Web 3.0 or some "living document" version of the web by now. 22 | We don't need to invent names for basic concepts like making HTTP requests. 23 | 24 | > **Ask yourself:** 25 | > 26 | > - Am I using a DOM manipulation library to do networking? 27 | > - Would I consider using a NPM package that both rendered HTML templates and abstracted WebSocket communications? 28 | 29 | Application architecture is easier when it isn't tied to monolithic frameworks. 30 | It's time to drop those aging AJAX APIs and get back to the basics of what makes networking simple. 31 | 32 | 33 | Usage 34 | ----- 35 | 36 | **Basic GET request:** 37 | 38 | ```js 39 | jan.get('/api/foo.json', function(err, res, json) { 40 | console.log(err, res, json); 41 | }); 42 | ``` 43 | 44 | 45 | **POST request:** 46 | 47 | ```js 48 | jan.post({ 49 | url : '/api/todos', 50 | body : 'name=Get%20gas' 51 | }, function(err, res, data) { 52 | if (err) throw err; 53 | alert('ToDo created: ' + data.name); 54 | }); 55 | ``` 56 | 57 | 58 | **Request with all options:** 59 | 60 | ```js 61 | jan({ 62 | method : 'PUT', 63 | url : 'http://foo.com/bar.json', 64 | headers : { 65 | 'Content-Type' : 'application/json' 66 | }, 67 | user : 'bob', 68 | pass : 'firebird', 69 | body : JSON.stringify({ key : 'value' }) 70 | }, function(err, res) { 71 | if (err) throw err; 72 | console.log(res); 73 | }); 74 | ``` 75 | 76 | 77 | Plugins / Events 78 | ---------------- 79 | 80 | **Hook requests with the `req` event:** 81 | 82 | ```js 83 | // A plugin that adds an API key header to all requests: 84 | jan.on('req', function(e) { 85 | e.req.headers['x-api-key'] = 'my-super-secure-api-key'; 86 | }); 87 | ``` 88 | 89 | **Hook responses with the `res` event:** 90 | 91 | ```js 92 | // A plugin that parses CSV responses 93 | jan.on('res', function(e) { 94 | if (e.res.headers['content-type']==='text/csv') { 95 | e.res.data = e.res.csv = e.res.text.split(/\s*\,\s*/g); 96 | } 97 | }); 98 | ``` 99 | 100 | 101 | Instantiation 102 | ------------- 103 | 104 | **Via node / browserify:** 105 | 106 | ```js 107 | var jan = require('jan'); 108 | ``` 109 | 110 | **Via AMD / requirejs:** 111 | 112 | ```js 113 | define(['jan'], function(jan) { 114 | 115 | }); 116 | ``` 117 | 118 | **Via globals / script tag:** 119 | 120 | ```html 121 | 122 | 125 | ``` 126 | 127 | 128 | Installation 129 | ------------ 130 | 131 | **Installation via Bower:** *(Recommended)* 132 | 133 | ```bash 134 | bower install jan 135 | ``` 136 | 137 | **Manual Download:** 138 | 139 | - [jan.js](dist/jan.js) - *full source with comments, for development* 140 | - [jan.min.js](dist/jan.min.js) - *minified, for production* 141 | 142 | 143 | License 144 | ------- 145 | 146 | BSD 147 | 148 | 149 | --- 150 | 151 | 152 | [![Jan Hankl](https://janjs.herokuapp.com/jan.jpg)](http://youtu.be/DY-Zdgo0OXo) 153 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jan", 3 | "version": "1.0.3", 4 | "description": "Jan is a simple library for making HTTP requests.", 5 | "authors": [ 6 | "Jason Miller " 7 | ], 8 | "main": "dist/jan.js", 9 | "moduleType": [ 10 | "amd", 11 | "cjs", 12 | "globals" 13 | ], 14 | "keywords": [ 15 | "jan", 16 | "request", 17 | "http", 18 | "network", 19 | "ajax", 20 | "api" 21 | ], 22 | "license": "BSD", 23 | "homepage": "https://github.com/synacorinc/jan", 24 | "ignore": [ 25 | "**/.*", 26 | "**/*.min.js", 27 | "package.json", 28 | "node_modules", 29 | "bower_components", 30 | "src", 31 | "doctemplate", 32 | "docs", 33 | "Gruntfile.js", 34 | "server.js", 35 | "test" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /dist/jan.js: -------------------------------------------------------------------------------- 1 | /** Jan is a simple library for making HTTP requests. 2 | * 3 | * Jan makes it easy to issue network calls without dealing with awkward legacy API signatures. 4 | * 5 | * If called as a function, `jan()` is an alias of {@link module:jan.request `jan.request()`} 6 | * @module jan 7 | * 8 | * @example 9 | * Basic Usage 10 | * // grab the library: 11 | * require(['jan'], function(jan) { 12 | * 13 | * // Log requests before they go out: 14 | * jan.on('req', function(e) { 15 | * console.log('Request: ', e.req); 16 | * }); 17 | * 18 | * // Log responses when they come in: 19 | * jan.on('res', function(e) { 20 | * console.log('Response: ', e.res); 21 | * }); 22 | * 23 | * // Make a basic GET request: 24 | * jan('/api/todos', function(err, res, body) { 25 | * if (err) throw err; 26 | * var names = data.map(function(todo){ return todo.name; }); 27 | * alert('ToDos: ' + names.join(', ')); 28 | * }); 29 | * }); 30 | */ 31 | (function(root, factory) { 32 | if (typeof define==='function' && define.amd) { 33 | define([], factory); 34 | } 35 | else if (typeof module==='object' && module.exports) { 36 | module.exports = factory(); 37 | } 38 | else { 39 | root.jan = factory(); 40 | } 41 | }(this, function() { 42 | var events = { req:[], res:[] }, 43 | methods = 'GET POST PUT DELETE HEAD OPTIONS'.split(' '), 44 | hop = {}.hasOwnProperty; 45 | 46 | /** Issue an HTTP request. 47 | * @memberOf module:jan 48 | * @name request 49 | * @function 50 | * @param {Object|String} options Options for the request, or a `String` `"url"` to which a GET request should be issued. 51 | * @param {String} [opt.method=GET] HTTP method 52 | * @param {String} [opt.url=/] The URL to request 53 | * @param {String|FormData|Blob|ArrayBufferView} [opt.body=none] Request body, for HTTP methods that allow it. Supported types: [XMLHttpRequest#send](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#send()) 54 | * @param {Object} [opt.headers={ }] A map of request headers 55 | * @param {String} [opt.user=none] Authentication username, if basic auth is to be used 56 | * @param {String} [opt.pass=none] Authentication password for basic auth 57 | * @param {Function} callback A function to call when the request has completed (error or success). Gets passed `(err, httpResponse, responseBody)`. 58 | * 59 | * @example 60 | * "Kitchen Sync" 61 | * jan({ 62 | * method : 'PUT', 63 | * url : 'http://foo.com/bar.json', 64 | * headers : { 65 | * 'Content-Type' : 'application/json' 66 | * }, 67 | * user : 'bob', 68 | * pass : 'firebird', 69 | * body : JSON.stringify({ key : 'value' }) 70 | * }, function(err, res, body) { 71 | * if (err) throw err; 72 | * console.log(res.status===204, body); 73 | * }); 74 | */ 75 | function jan(opt, callback, a) { 76 | if (a) { 77 | opt = extend(callback || {}, { url:opt }); 78 | callback = a; 79 | } 80 | opt = typeof opt==='string' ? {url:opt} : opt || {}; 81 | if (opt.baseUrl) { 82 | // TODO: proper support for URL concatenation 83 | opt.url = opt.baseUrl + opt.url; 84 | } 85 | opt.headers = opt.headers || {}; 86 | var xhr = new XMLHttpRequest(), 87 | e = { xhr:xhr, req:opt }; 88 | emit('req', e); 89 | (xhr = e.xhr).open(opt.method || 'GET', opt.url || '/', true, opt.user, opt.pass); 90 | for (var name in opt.headers) if (hop.call(opt.headers, name)) xhr.setRequestHeader(name, opt.headers[name]); 91 | xhr.onreadystatechange = function() { 92 | if (xhr.readyState!==4) return; 93 | var res = { 94 | status : xhr.status, 95 | error : !xhr.status ? 'Connection Error' : xhr.status>399 ? xhr.statusText : null, 96 | headers : {}, 97 | body : xhr.responseText, 98 | xml : xhr.responseXML, 99 | xhr : xhr 100 | }, 101 | hreg = /^\s*([a-z0-9_-]+)\s*\:\s*(.*?)\s*$/gim, 102 | h = xhr.getAllResponseHeaders(), 103 | m; 104 | try{ res.json = JSON.parse(res.body); }catch(o){} 105 | res.data = res.json || res.xml || res.body; 106 | while (m=hreg.exec(h)) res.headers[m[1].toLowerCase()] = m[2]; 107 | e.res = res; 108 | emit('res', e); 109 | (callback || opt.callback).call(e, res.error, res, res.data); 110 | }; 111 | xhr.send(opt.body); 112 | } 113 | 114 | 115 | /**Get a namespaced copy of {@link module:jan jan} where all methods are relative to a base URL. 116 | * @function 117 | * @name module:jan.ns 118 | * @param {String} baseUrl A URL to which all namespaced methods should be relative 119 | * @returns A `baseUrl`-namespaced jan interface 120 | * @example 121 | * // Create a namespaced API client: 122 | * var api = jan.ns('https://example.com/api'); 123 | * 124 | * // GET /api/images: 125 | * api.get('/images', function(err, res, images) { 126 | * console.log(images); 127 | * }); 128 | * 129 | * // Log response headers for any requests to the base URL: 130 | * api.on('res', function(e) { 131 | * console.log( e.res.headers ); 132 | * }); 133 | */ 134 | jan.ns = function(uri) { 135 | var opt = { baseUrl:uri }, 136 | ns = mapVerbs(alias(opt), opt); 137 | ns.on = function(type, handler, a) { 138 | jan.on(type, uri + (handler.sub ? handler : ''), a || handler); 139 | return ns; 140 | }; 141 | return ns; 142 | }; 143 | 144 | 145 | /**Register a handler function to be called in response to a given type of event. 146 | * 147 | * Valid event types are: `req` and `res`, fired on request and response respectively. 148 | * @function 149 | * @name module:jan.on 150 | * @example 151 | * jan.on('res', function(e) { 152 | * // e.req 153 | * // e.res 154 | * // e.xhr 155 | * }); 156 | * @param {String} type An event type to observe 157 | * @param {String|RegExp} [urlFilter] A String prefix or RegExp to filter against each event's request url 158 | * @param {Function} handler Handler function, gets passed an Event object 159 | */ 160 | jan.on = function(type, handler, a) { 161 | events[type].push(a ? function(e) { 162 | if (handler.exec ? e.req.url.match(handler) : e.req.url.indexOf(handler)===0) { 163 | a.call(this, e); 164 | } 165 | } : handler); 166 | return jan; 167 | }; 168 | 169 | 170 | /**Alias of {@link module:jan.request request()} that presupplies the option `method:'GET'` 171 | * @name module:jan.get 172 | * @function 173 | * 174 | * @example 175 | * Get popular YouTube videos 176 | * var url = 'http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=json'; 177 | * jan.get(url, function(err, data) { 178 | * if (err) throw err; 179 | * // display video links: 180 | * document.body.innerHTML = data.feed.entry.map(function(vid) { 181 | * return vid.title.$t.anchor(vid.content.src); // String#neverforget 182 | * }).join('
'); 183 | * }); 184 | */ 185 | 186 | /**Alias of {@link module:jan.request request()} that presupplies the option `method:'POST'` 187 | * @name module:jan.post 188 | * @function 189 | * 190 | * @example 191 | * Submit a contact form 192 | * jan.post({ 193 | * url : 'http://example.com/contact-form.php', 194 | * headers : { 195 | * 'Content-Type' : 'application/x-www-form-encoded' 196 | * }, 197 | * body : new FormData(document.querySelector('form')) 198 | * }, function(err, data) { 199 | * if (err) throw err; 200 | * alert('Submitted: ' + data.message); 201 | * }); 202 | */ 203 | 204 | /** Alias of {@link module:jan.request request()} that presupplies the option `method:'PUT'` 205 | * @name module:jan.put 206 | * @function 207 | * 208 | * @example 209 | * Update a REST resource 210 | * jan.put({ 211 | * url : 'http://foo.com/bar.json', 212 | * headers : { 'Content-Type':'application/json' }, 213 | * body : '{"key":"val"}' 214 | * }, function(err, data) { 215 | * if (err) throw err; 216 | * console.log(data); 217 | * }); 218 | */ 219 | 220 | /** Alias of {@link module:jan.request request()} that presupplies the option `method:'HEAD'` 221 | * @name module:jan.head 222 | * @function 223 | * 224 | * @example 225 | * Get headers 226 | * jan.head('/massive.json', function(err, data, res) { 227 | * if (err) throw err; 228 | * console.log(res.headers); 229 | * }); 230 | */ 231 | 232 | /** Alias of {@link module:jan.request request()} that presupplies the option `method:'OPTIONS'` 233 | * @name module:jan.options 234 | * @function 235 | * 236 | * @example 237 | * Get WADL XML 238 | * jan.options('/api/v1', function(err, data, res) { 239 | * if (err) throw err; 240 | * console.log(res.headers, res.body); 241 | * }); 242 | */ 243 | 244 | /** Alias of {@link module:jan.request request()} that presupplies the option `method:'DELETE'` 245 | * @name module:jan.del 246 | * @function 247 | * 248 | * @example 249 | * Delete a REST resource 250 | * jan.del({ 251 | * url : '/api/items/1a2b3c' 252 | * }, function(err, data) { 253 | * if (err) throw err; 254 | * alert('Deleted'); 255 | * }); 256 | */ 257 | 258 | /** Alias of {@link module:jan.del del()}. 259 | * This alias is provided for completeness, but [should not be used](http://mothereff.in/js-properties#delete) because [it throws in ES3](http://mathiasbynens.be/notes/javascript-properties). 260 | * @name module:jan.delete 261 | * @function 262 | * @deprecated Don't call delete() if you need to support ES3. jan['delete']() is okay. 263 | */ 264 | 265 | mapVerbs(jan); 266 | 267 | 268 | function emit(type, args) { 269 | args = Array.prototype.slice.call(arguments, 1); 270 | for (var e=events[type], i=e.length; i--; ) e[i].apply(jan, args); 271 | } 272 | 273 | 274 | function alias(overrides) { 275 | return function(opt, callback) { 276 | return jan(extend({}, typeof opt==='string' ? {url:opt} : opt, overrides), callback); 277 | }; 278 | } 279 | 280 | 281 | function mapVerbs(onto, opts) { 282 | for (var i=methods.length; i--; ) { 283 | onto[methods[i].toLowerCase()] = alias(extend({}, opts || {}, { 284 | method : methods[i] 285 | })); 286 | } 287 | onto.del = onto['delete']; 288 | return onto; 289 | } 290 | 291 | 292 | function extend(base, obj) { 293 | for (var i=1, p, o; i399?f.statusText:null:"Connection Error",headers:{},body:f.responseText,xml:f.responseXML,xhr:f},h=/^\s*([a-z0-9_-]+)\s*\:\s*(.*?)\s*$/gim,i=f.getAllResponseHeaders();try{e.json=JSON.parse(e.body)}catch(j){}for(e.data=e.json||e.xml||e.body;d=h.exec(i);)e.headers[d[1].toLowerCase()]=d[2];g.res=e,b("res",g),(c||a.callback).call(g,e.error,e,e.data)}},f.send(a.body)}function b(b,c){c=Array.prototype.slice.call(arguments,1);for(var d=f[b],e=d.length;e--;)d[e].apply(a,c)}function c(b){return function(c,d){return a(e({},"string"==typeof c?{url:c}:c,b),d)}}function d(a,b){for(var d=g.length;d--;)a[g[d].toLowerCase()]=c(e({},b||{},{method:g[d]}));return a.del=a["delete"],a}function e(a){for(var b,c,d=1;d' + hash + ''; 39 | } 40 | 41 | function needsSignature(doclet) { 42 | var needsSig = false; 43 | 44 | // function and class definitions always get a signature 45 | if (doclet.kind === 'function' || doclet.kind === 'class') { 46 | needsSig = true; 47 | } 48 | // typedefs that contain functions get a signature, too 49 | else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names && 50 | doclet.type.names.length) { 51 | for (var i = 0, l = doclet.type.names.length; i < l; i++) { 52 | if (doclet.type.names[i].toLowerCase() === 'function') { 53 | needsSig = true; 54 | break; 55 | } 56 | } 57 | } 58 | 59 | return needsSig; 60 | } 61 | 62 | function addSignatureParams(f) { 63 | var params = helper.getSignatureParams(f, 'optional'); 64 | 65 | f.signature = (f.signature || '') + '('+params.join(', ')+')'; 66 | } 67 | 68 | function addSignatureReturns(f) { 69 | var returnTypes = helper.getSignatureReturns(f); 70 | 71 | f.signature = '' + (f.signature || '') + '' + 72 | '' + 73 | (returnTypes && returnTypes.length ? ' → {' + returnTypes.join('|') + '}' : '') + 74 | ''; 75 | } 76 | 77 | function addSignatureTypes(f) { 78 | var types = helper.getSignatureTypes(f); 79 | 80 | f.signature = (f.signature || '') + ''+(types.length? ' :'+types.join('|') : '')+''; 81 | } 82 | 83 | function addAttribs(f) { 84 | var attribs = helper.getAttribs(f); 85 | 86 | f.attribs = '' + htmlsafe(attribs.length ? 87 | // we want the template output to say 'abstract', not 'virtual' 88 | '<' + attribs.join(', ').replace('virtual', 'abstract') + '> ' : '') + ''; 89 | } 90 | 91 | function shortenPaths(files, commonPrefix) { 92 | Object.keys(files).forEach(function(file) { 93 | files[file].shortened = files[file].resolved.replace(commonPrefix, '') 94 | // always use forward slashes 95 | .replace(/\\/g, '/'); 96 | }); 97 | 98 | return files; 99 | } 100 | 101 | function getPathFromDoclet(doclet) { 102 | if (!doclet.meta) { 103 | return; 104 | } 105 | 106 | return doclet.meta.path && doclet.meta.path !== 'null' ? 107 | path.join(doclet.meta.path, doclet.meta.filename) : 108 | doclet.meta.filename; 109 | } 110 | 111 | function generate(title, docs, filename, resolveLinks) { 112 | resolveLinks = resolveLinks === false ? false : true; 113 | 114 | var docData = { 115 | title: title, 116 | docs: docs 117 | }; 118 | 119 | var outpath = path.join(outdir, filename), 120 | html = view.render('container.tmpl', docData); 121 | 122 | if (resolveLinks) { 123 | html = helper.resolveLinks(html); // turn {@link foo} into foo 124 | } 125 | 126 | fs.writeFileSync(outpath, html, 'utf8'); 127 | } 128 | 129 | function generateSourceFiles(sourceFiles, encoding) { 130 | encoding = encoding || 'utf8'; 131 | Object.keys(sourceFiles).forEach(function(file) { 132 | var source; 133 | // links are keyed to the shortened path in each doclet's `meta.shortpath` property 134 | var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened); 135 | helper.registerLink(sourceFiles[file].shortened, sourceOutfile); 136 | 137 | try { 138 | source = { 139 | kind: 'source', 140 | code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) ) 141 | }; 142 | } 143 | catch(e) { 144 | logger.error('Error while generating source file %s: %s', file, e.message); 145 | } 146 | 147 | generate('Source: ' + sourceFiles[file].shortened, [source], sourceOutfile, 148 | false); 149 | }); 150 | } 151 | 152 | /** 153 | * Look for classes or functions with the same name as modules (which indicates that the module 154 | * exports only that class or function), then attach the classes or functions to the `module` 155 | * property of the appropriate module doclets. The name of each class or function is also updated 156 | * for display purposes. This function mutates the original arrays. 157 | * 158 | * @private 159 | * @param {Array.} doclets - The array of classes and functions to 160 | * check. 161 | * @param {Array.} modules - The array of module doclets to search. 162 | */ 163 | function attachModuleSymbols(doclets, modules) { 164 | var symbols = {}; 165 | 166 | // build a lookup table 167 | doclets.forEach(function(symbol) { 168 | symbols[symbol.longname] = symbol; 169 | }); 170 | 171 | return modules.map(function(module) { 172 | if (symbols[module.longname]) { 173 | module.module = symbols[module.longname]; 174 | module.module.name = module.module.name.replace('module:', 'require("') + '")'; 175 | } 176 | }); 177 | } 178 | 179 | /** 180 | * Create the navigation sidebar. 181 | * @param {object} members The members that will be used to create the sidebar. 182 | * @param {array} members.classes 183 | * @param {array} members.externals 184 | * @param {array} members.globals 185 | * @param {array} members.mixins 186 | * @param {array} members.modules 187 | * @param {array} members.namespaces 188 | * @param {array} members.tutorials 189 | * @param {array} members.events 190 | * @return {string} The HTML for the navigation sidebar. 191 | */ 192 | function buildNav(members, opts) { 193 | var nav = '', 194 | seen = {}, 195 | list = { 196 | Modules : 'modules', 197 | Externals : 'externals', 198 | Classes : 'classes', 199 | Events : 'events', 200 | Namespaces : 'namespaces', 201 | Mixins : 'mixins' 202 | }, 203 | hasClassList = false, 204 | classNav = '', 205 | globalNav = ''; 206 | 207 | nav += '

' + (opts.indexName || 'Index') + '

'; 208 | 209 | for (var name in list) { 210 | if (members[list[name]].length) { 211 | nav += '