Brandon Cornejo
11 years ago
535 changed files with 0 additions and 99780 deletions
-
1node_modules/body-parser/.npmignore
-
3node_modules/body-parser/.travis.yml
-
10node_modules/body-parser/HISTORY.md
-
9node_modules/body-parser/Makefile
-
60node_modules/body-parser/README.md
-
100node_modules/body-parser/index.js
-
6node_modules/body-parser/node_modules/qs/.gitmodules
-
7node_modules/body-parser/node_modules/qs/.npmignore
-
58node_modules/body-parser/node_modules/qs/Readme.md
-
366node_modules/body-parser/node_modules/qs/index.js
-
38node_modules/body-parser/node_modules/qs/package.json
-
1node_modules/body-parser/node_modules/raw-body/.npmignore
-
5node_modules/body-parser/node_modules/raw-body/.travis.yml
-
14node_modules/body-parser/node_modules/raw-body/Makefile
-
96node_modules/body-parser/node_modules/raw-body/README.md
-
164node_modules/body-parser/node_modules/raw-body/index.js
-
1node_modules/body-parser/node_modules/raw-body/node_modules/bytes/.npmignore
-
20node_modules/body-parser/node_modules/raw-body/node_modules/bytes/History.md
-
7node_modules/body-parser/node_modules/raw-body/node_modules/bytes/Makefile
-
54node_modules/body-parser/node_modules/raw-body/node_modules/bytes/Readme.md
-
7node_modules/body-parser/node_modules/raw-body/node_modules/bytes/component.json
-
41node_modules/body-parser/node_modules/raw-body/node_modules/bytes/index.js
-
37node_modules/body-parser/node_modules/raw-body/node_modules/bytes/package.json
-
45node_modules/body-parser/node_modules/raw-body/package.json
-
1node_modules/body-parser/node_modules/type-is/.npmignore
-
4node_modules/body-parser/node_modules/type-is/.travis.yml
-
16node_modules/body-parser/node_modules/type-is/HISTORY.md
-
87node_modules/body-parser/node_modules/type-is/README.md
-
148node_modules/body-parser/node_modules/type-is/index.js
-
19node_modules/body-parser/node_modules/type-is/node_modules/mime/LICENSE
-
66node_modules/body-parser/node_modules/type-is/node_modules/mime/README.md
-
114node_modules/body-parser/node_modules/type-is/node_modules/mime/mime.js
-
36node_modules/body-parser/node_modules/type-is/node_modules/mime/package.json
-
84node_modules/body-parser/node_modules/type-is/node_modules/mime/test.js
-
1588node_modules/body-parser/node_modules/type-is/node_modules/mime/types/mime.types
-
77node_modules/body-parser/node_modules/type-is/node_modules/mime/types/node.types
-
37node_modules/body-parser/node_modules/type-is/package.json
-
41node_modules/body-parser/package.json
-
14node_modules/jade/.npmignore
-
22node_modules/jade/LICENSE
-
161node_modules/jade/README.md
-
1285node_modules/jade/Readme_zh-cn.md
-
16node_modules/jade/component.json
-
4node_modules/jade/index.js
-
1023node_modules/jade/jade-language.md
-
22894node_modules/jade/jade.js
-
510node_modules/jade/jade.md
-
672node_modules/jade/lib/compiler.js
-
12node_modules/jade/lib/doctypes.js
-
14node_modules/jade/lib/filters-client.js
-
18node_modules/jade/lib/filters.js
-
23node_modules/jade/lib/inline-tags.js
-
323node_modules/jade/lib/jade.js
-
865node_modules/jade/lib/lexer.js
-
79node_modules/jade/lib/nodes/attrs.js
-
24node_modules/jade/lib/nodes/block-comment.js
-
112node_modules/jade/lib/nodes/block.js
-
33node_modules/jade/lib/nodes/case.js
-
26node_modules/jade/lib/nodes/code.js
-
23node_modules/jade/lib/nodes/comment.js
-
20node_modules/jade/lib/nodes/doctype.js
-
26node_modules/jade/lib/nodes/each.js
-
25node_modules/jade/lib/nodes/filter.js
-
16node_modules/jade/lib/nodes/index.js
-
20node_modules/jade/lib/nodes/literal.js
-
18node_modules/jade/lib/nodes/mixin-block.js
-
27node_modules/jade/lib/nodes/mixin.js
-
16node_modules/jade/lib/nodes/node.js
-
87node_modules/jade/lib/nodes/tag.js
-
27node_modules/jade/lib/nodes/text.js
-
790node_modules/jade/lib/parser.js
-
203node_modules/jade/lib/runtime.js
-
22node_modules/jade/lib/self-closing.js
-
16node_modules/jade/lib/utils.js
-
2node_modules/jade/node_modules/character-parser/.npmignore
-
19node_modules/jade/node_modules/character-parser/LICENSE
-
142node_modules/jade/node_modules/character-parser/README.md
-
217node_modules/jade/node_modules/character-parser/index.js
-
43node_modules/jade/node_modules/character-parser/package.json
-
195node_modules/jade/node_modules/commander/Readme.md
-
851node_modules/jade/node_modules/commander/index.js
-
45node_modules/jade/node_modules/commander/package.json
-
22node_modules/jade/node_modules/constantinople/.gitattributes
-
13node_modules/jade/node_modules/constantinople/.npmignore
-
4node_modules/jade/node_modules/constantinople/.travis.yml
-
19node_modules/jade/node_modules/constantinople/LICENSE
-
35node_modules/jade/node_modules/constantinople/README.md
-
35node_modules/jade/node_modules/constantinople/index.js
-
1node_modules/jade/node_modules/constantinople/node_modules/.bin/uglifyjs
-
2node_modules/jade/node_modules/constantinople/node_modules/uglify-js/.npmignore
-
6node_modules/jade/node_modules/constantinople/node_modules/uglify-js/.travis.yml
-
29node_modules/jade/node_modules/constantinople/node_modules/uglify-js/LICENSE
-
636node_modules/jade/node_modules/constantinople/node_modules/uglify-js/README.md
-
984node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/ast.js
-
2358node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/compress.js
-
267node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/mozilla-ast.js
-
1300node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/output.js
-
1457node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/parse.js
-
563node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/scope.js
-
84node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/sourcemap.js
@ -1 +0,0 @@ |
|||||
test/ |
|
@ -1,3 +0,0 @@ |
|||||
node_js: |
|
||||
- "0.10" |
|
||||
language: node_js |
|
@ -1,10 +0,0 @@ |
|||||
|
|
||||
1.0.1 / 2014-04-14 |
|
||||
================== |
|
||||
|
|
||||
* use `type-is` module |
|
||||
|
|
||||
1.0.1 / 2014-03-20 |
|
||||
================== |
|
||||
|
|
||||
* lower default limits to 100kb |
|
@ -1,9 +0,0 @@ |
|||||
BIN = ./node_modules/.bin/ |
|
||||
|
|
||||
test: |
|
||||
@$(BIN)mocha \
|
|
||||
--require should \
|
|
||||
--reporter spec \
|
|
||||
--bail |
|
||||
|
|
||||
.PHONY: test |
|
@ -1,60 +0,0 @@ |
|||||
# Body Parser [![Build Status](https://travis-ci.org/expressjs/body-parser.png)](https://travis-ci.org/expressjs/body-parser) |
|
||||
|
|
||||
Connect's body parsing middleware. |
|
||||
|
|
||||
## API |
|
||||
|
|
||||
```js |
|
||||
var bodyParser = require('body-parser'); |
|
||||
|
|
||||
var app = connect(); |
|
||||
|
|
||||
app.use(bodyParser()); |
|
||||
|
|
||||
app.use(function (req, res, next) { |
|
||||
console.log(req.body) // populated! |
|
||||
next(); |
|
||||
}) |
|
||||
``` |
|
||||
|
|
||||
### bodyParser([options]) |
|
||||
|
|
||||
Returns middleware that parses both `json` and `urlencoded`. The `options` are passed to both middleware. |
|
||||
|
|
||||
### bodyParser.json([options]) |
|
||||
|
|
||||
Returns middleware that only parses `json`. The options are: |
|
||||
|
|
||||
- `strict` <true> - only parse objects and arrays |
|
||||
- `limit` <1mb> - maximum request body size |
|
||||
- `reviver` - passed to `JSON.parse()` |
|
||||
|
|
||||
### bodyParser.urlencoded([options]) |
|
||||
|
|
||||
Returns middleware that only parses `urlencoded` with the [qs](https://github.com/visionmedia/node-querystring) module. The options are: |
|
||||
|
|
||||
- `limit` <1mb> - maximum request body size |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
The MIT License (MIT) |
|
||||
|
|
||||
Copyright (c) 2014 Jonathan Ong me@jongleberry.com |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,100 +0,0 @@ |
|||||
|
|
||||
var getBody = require('raw-body'); |
|
||||
var typeis = require('type-is'); |
|
||||
var http = require('http'); |
|
||||
var qs = require('qs'); |
|
||||
|
|
||||
exports = module.exports = bodyParser; |
|
||||
exports.json = json; |
|
||||
exports.urlencoded = urlencoded; |
|
||||
|
|
||||
function bodyParser(options){ |
|
||||
var _urlencoded = urlencoded(options); |
|
||||
var _json = json(options); |
|
||||
|
|
||||
return function bodyParser(req, res, next) { |
|
||||
_json(req, res, function(err){ |
|
||||
if (err) return next(err); |
|
||||
_urlencoded(req, res, next); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function json(options){ |
|
||||
options = options || {}; |
|
||||
var strict = options.strict !== false; |
|
||||
|
|
||||
return function jsonParser(req, res, next) { |
|
||||
if (req._body) return next(); |
|
||||
req.body = req.body || {}; |
|
||||
|
|
||||
if (!typeis(req, 'json')) return next(); |
|
||||
|
|
||||
// flag as parsed
|
|
||||
req._body = true; |
|
||||
|
|
||||
// parse
|
|
||||
getBody(req, { |
|
||||
limit: options.limit || '100kb', |
|
||||
length: req.headers['content-length'], |
|
||||
encoding: 'utf8' |
|
||||
}, function (err, buf) { |
|
||||
if (err) return next(err); |
|
||||
|
|
||||
var first = buf.trim()[0]; |
|
||||
|
|
||||
if (0 == buf.length) { |
|
||||
return next(error(400, 'invalid json, empty body')); |
|
||||
} |
|
||||
|
|
||||
if (strict && '{' != first && '[' != first) return next(error(400, 'invalid json')); |
|
||||
try { |
|
||||
req.body = JSON.parse(buf, options.reviver); |
|
||||
} catch (err){ |
|
||||
err.body = buf; |
|
||||
err.status = 400; |
|
||||
return next(err); |
|
||||
} |
|
||||
next(); |
|
||||
}) |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
function urlencoded(options){ |
|
||||
options = options || {}; |
|
||||
|
|
||||
return function urlencodedParser(req, res, next) { |
|
||||
if (req._body) return next(); |
|
||||
req.body = req.body || {}; |
|
||||
|
|
||||
if (!typeis(req, 'urlencoded')) return next(); |
|
||||
|
|
||||
// flag as parsed
|
|
||||
req._body = true; |
|
||||
|
|
||||
// parse
|
|
||||
getBody(req, { |
|
||||
limit: options.limit || '100kb', |
|
||||
length: req.headers['content-length'], |
|
||||
encoding: 'utf8' |
|
||||
}, function (err, buf) { |
|
||||
if (err) return next(err); |
|
||||
|
|
||||
try { |
|
||||
req.body = buf.length |
|
||||
? qs.parse(buf) |
|
||||
: {}; |
|
||||
} catch (err){ |
|
||||
err.body = buf; |
|
||||
return next(err); |
|
||||
} |
|
||||
next(); |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function error(code, msg) { |
|
||||
var err = new Error(msg || http.STATUS_CODES[code]); |
|
||||
err.status = code; |
|
||||
return err; |
|
||||
} |
|
@ -1,6 +0,0 @@ |
|||||
[submodule "support/expresso"] |
|
||||
path = support/expresso |
|
||||
url = git://github.com/visionmedia/expresso.git |
|
||||
[submodule "support/should"] |
|
||||
path = support/should |
|
||||
url = git://github.com/visionmedia/should.js.git |
|
@ -1,7 +0,0 @@ |
|||||
test |
|
||||
.travis.yml |
|
||||
benchmark.js |
|
||||
component.json |
|
||||
examples.js |
|
||||
History.md |
|
||||
Makefile |
|
@ -1,58 +0,0 @@ |
|||||
# node-querystring |
|
||||
|
|
||||
query string parser for node and the browser supporting nesting, as it was removed from `0.3.x`, so this library provides the previous and commonly desired behaviour (and twice as fast). Used by [express](http://expressjs.com), [connect](http://senchalabs.github.com/connect) and others. |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
$ npm install qs |
|
||||
|
|
||||
## Examples |
|
||||
|
|
||||
```js |
|
||||
var qs = require('qs'); |
|
||||
|
|
||||
qs.parse('user[name][first]=Tobi&user[email]=tobi@learnboost.com'); |
|
||||
// => { user: { name: { first: 'Tobi' }, email: 'tobi@learnboost.com' } } |
|
||||
|
|
||||
qs.stringify({ user: { name: 'Tobi', email: 'tobi@learnboost.com' }}) |
|
||||
// => user[name]=Tobi&user[email]=tobi%40learnboost.com |
|
||||
``` |
|
||||
|
|
||||
## Testing |
|
||||
|
|
||||
Install dev dependencies: |
|
||||
|
|
||||
$ npm install -d |
|
||||
|
|
||||
and execute: |
|
||||
|
|
||||
$ make test |
|
||||
|
|
||||
browser: |
|
||||
|
|
||||
$ open test/browser/index.html |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
(The MIT License) |
|
||||
|
|
||||
Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca> |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
|
||||
a copy of this software and associated documentation files (the |
|
||||
'Software'), to deal in the Software without restriction, including |
|
||||
without limitation the rights to use, copy, modify, merge, publish, |
|
||||
distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
permit persons to whom the Software is furnished to do so, subject to |
|
||||
the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be |
|
||||
included in all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
@ -1,366 +0,0 @@ |
|||||
/** |
|
||||
* Object#toString() ref for stringify(). |
|
||||
*/ |
|
||||
|
|
||||
var toString = Object.prototype.toString; |
|
||||
|
|
||||
/** |
|
||||
* Object#hasOwnProperty ref |
|
||||
*/ |
|
||||
|
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty; |
|
||||
|
|
||||
/** |
|
||||
* Array#indexOf shim. |
|
||||
*/ |
|
||||
|
|
||||
var indexOf = typeof Array.prototype.indexOf === 'function' |
|
||||
? function(arr, el) { return arr.indexOf(el); } |
|
||||
: function(arr, el) { |
|
||||
for (var i = 0; i < arr.length; i++) { |
|
||||
if (arr[i] === el) return i; |
|
||||
} |
|
||||
return -1; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Array.isArray shim. |
|
||||
*/ |
|
||||
|
|
||||
var isArray = Array.isArray || function(arr) { |
|
||||
return toString.call(arr) == '[object Array]'; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Object.keys shim. |
|
||||
*/ |
|
||||
|
|
||||
var objectKeys = Object.keys || function(obj) { |
|
||||
var ret = []; |
|
||||
for (var key in obj) { |
|
||||
if (obj.hasOwnProperty(key)) { |
|
||||
ret.push(key); |
|
||||
} |
|
||||
} |
|
||||
return ret; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Array#forEach shim. |
|
||||
*/ |
|
||||
|
|
||||
var forEach = typeof Array.prototype.forEach === 'function' |
|
||||
? function(arr, fn) { return arr.forEach(fn); } |
|
||||
: function(arr, fn) { |
|
||||
for (var i = 0; i < arr.length; i++) fn(arr[i]); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Array#reduce shim. |
|
||||
*/ |
|
||||
|
|
||||
var reduce = function(arr, fn, initial) { |
|
||||
if (typeof arr.reduce === 'function') return arr.reduce(fn, initial); |
|
||||
var res = initial; |
|
||||
for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]); |
|
||||
return res; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Cache non-integer test regexp. |
|
||||
*/ |
|
||||
|
|
||||
var isint = /^[0-9]+$/; |
|
||||
|
|
||||
function promote(parent, key) { |
|
||||
if (parent[key].length == 0) return parent[key] = {} |
|
||||
var t = {}; |
|
||||
for (var i in parent[key]) { |
|
||||
if (hasOwnProperty.call(parent[key], i)) { |
|
||||
t[i] = parent[key][i]; |
|
||||
} |
|
||||
} |
|
||||
parent[key] = t; |
|
||||
return t; |
|
||||
} |
|
||||
|
|
||||
function parse(parts, parent, key, val) { |
|
||||
var part = parts.shift(); |
|
||||
|
|
||||
// illegal
|
|
||||
if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; |
|
||||
|
|
||||
// end
|
|
||||
if (!part) { |
|
||||
if (isArray(parent[key])) { |
|
||||
parent[key].push(val); |
|
||||
} else if ('object' == typeof parent[key]) { |
|
||||
parent[key] = val; |
|
||||
} else if ('undefined' == typeof parent[key]) { |
|
||||
parent[key] = val; |
|
||||
} else { |
|
||||
parent[key] = [parent[key], val]; |
|
||||
} |
|
||||
// array
|
|
||||
} else { |
|
||||
var obj = parent[key] = parent[key] || []; |
|
||||
if (']' == part) { |
|
||||
if (isArray(obj)) { |
|
||||
if ('' != val) obj.push(val); |
|
||||
} else if ('object' == typeof obj) { |
|
||||
obj[objectKeys(obj).length] = val; |
|
||||
} else { |
|
||||
obj = parent[key] = [parent[key], val]; |
|
||||
} |
|
||||
// prop
|
|
||||
} else if (~indexOf(part, ']')) { |
|
||||
part = part.substr(0, part.length - 1); |
|
||||
if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); |
|
||||
parse(parts, obj, part, val); |
|
||||
// key
|
|
||||
} else { |
|
||||
if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); |
|
||||
parse(parts, obj, part, val); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Merge parent key/val pair. |
|
||||
*/ |
|
||||
|
|
||||
function merge(parent, key, val){ |
|
||||
if (~indexOf(key, ']')) { |
|
||||
var parts = key.split('[') |
|
||||
, len = parts.length |
|
||||
, last = len - 1; |
|
||||
parse(parts, parent, 'base', val); |
|
||||
// optimize
|
|
||||
} else { |
|
||||
if (!isint.test(key) && isArray(parent.base)) { |
|
||||
var t = {}; |
|
||||
for (var k in parent.base) t[k] = parent.base[k]; |
|
||||
parent.base = t; |
|
||||
} |
|
||||
set(parent.base, key, val); |
|
||||
} |
|
||||
|
|
||||
return parent; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Compact sparse arrays. |
|
||||
*/ |
|
||||
|
|
||||
function compact(obj) { |
|
||||
if ('object' != typeof obj) return obj; |
|
||||
|
|
||||
if (isArray(obj)) { |
|
||||
var ret = []; |
|
||||
|
|
||||
for (var i in obj) { |
|
||||
if (hasOwnProperty.call(obj, i)) { |
|
||||
ret.push(obj[i]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ret; |
|
||||
} |
|
||||
|
|
||||
for (var key in obj) { |
|
||||
obj[key] = compact(obj[key]); |
|
||||
} |
|
||||
|
|
||||
return obj; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Parse the given obj. |
|
||||
*/ |
|
||||
|
|
||||
function parseObject(obj){ |
|
||||
var ret = { base: {} }; |
|
||||
|
|
||||
forEach(objectKeys(obj), function(name){ |
|
||||
merge(ret, name, obj[name]); |
|
||||
}); |
|
||||
|
|
||||
return compact(ret.base); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Parse the given str. |
|
||||
*/ |
|
||||
|
|
||||
function parseString(str){ |
|
||||
var ret = reduce(String(str).split('&'), function(ret, pair){ |
|
||||
var eql = indexOf(pair, '=') |
|
||||
, brace = lastBraceInKey(pair) |
|
||||
, key = pair.substr(0, brace || eql) |
|
||||
, val = pair.substr(brace || eql, pair.length) |
|
||||
, val = val.substr(indexOf(val, '=') + 1, val.length); |
|
||||
|
|
||||
// ?foo
|
|
||||
if ('' == key) key = pair, val = ''; |
|
||||
if ('' == key) return ret; |
|
||||
|
|
||||
return merge(ret, decode(key), decode(val)); |
|
||||
}, { base: {} }).base; |
|
||||
|
|
||||
return compact(ret); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Parse the given query `str` or `obj`, returning an object. |
|
||||
* |
|
||||
* @param {String} str | {Object} obj |
|
||||
* @return {Object} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.parse = function(str){ |
|
||||
if (null == str || '' == str) return {}; |
|
||||
return 'object' == typeof str |
|
||||
? parseObject(str) |
|
||||
: parseString(str); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Turn the given `obj` into a query string |
|
||||
* |
|
||||
* @param {Object} obj |
|
||||
* @return {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var stringify = exports.stringify = function(obj, prefix) { |
|
||||
if (isArray(obj)) { |
|
||||
return stringifyArray(obj, prefix); |
|
||||
} else if ('[object Object]' == toString.call(obj)) { |
|
||||
return stringifyObject(obj, prefix); |
|
||||
} else if ('string' == typeof obj) { |
|
||||
return stringifyString(obj, prefix); |
|
||||
} else { |
|
||||
return prefix + '=' + encodeURIComponent(String(obj)); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Stringify the given `str`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {String} prefix |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function stringifyString(str, prefix) { |
|
||||
if (!prefix) throw new TypeError('stringify expects an object'); |
|
||||
return prefix + '=' + encodeURIComponent(str); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Stringify the given `arr`. |
|
||||
* |
|
||||
* @param {Array} arr |
|
||||
* @param {String} prefix |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function stringifyArray(arr, prefix) { |
|
||||
var ret = []; |
|
||||
if (!prefix) throw new TypeError('stringify expects an object'); |
|
||||
for (var i = 0; i < arr.length; i++) { |
|
||||
ret.push(stringify(arr[i], prefix + '[' + i + ']')); |
|
||||
} |
|
||||
return ret.join('&'); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Stringify the given `obj`. |
|
||||
* |
|
||||
* @param {Object} obj |
|
||||
* @param {String} prefix |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function stringifyObject(obj, prefix) { |
|
||||
var ret = [] |
|
||||
, keys = objectKeys(obj) |
|
||||
, key; |
|
||||
|
|
||||
for (var i = 0, len = keys.length; i < len; ++i) { |
|
||||
key = keys[i]; |
|
||||
if ('' == key) continue; |
|
||||
if (null == obj[key]) { |
|
||||
ret.push(encodeURIComponent(key) + '='); |
|
||||
} else { |
|
||||
ret.push(stringify(obj[key], prefix |
|
||||
? prefix + '[' + encodeURIComponent(key) + ']' |
|
||||
: encodeURIComponent(key))); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ret.join('&'); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Set `obj`'s `key` to `val` respecting |
|
||||
* the weird and wonderful syntax of a qs, |
|
||||
* where "foo=bar&foo=baz" becomes an array. |
|
||||
* |
|
||||
* @param {Object} obj |
|
||||
* @param {String} key |
|
||||
* @param {String} val |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function set(obj, key, val) { |
|
||||
var v = obj[key]; |
|
||||
if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; |
|
||||
if (undefined === v) { |
|
||||
obj[key] = val; |
|
||||
} else if (isArray(v)) { |
|
||||
v.push(val); |
|
||||
} else { |
|
||||
obj[key] = [v, val]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Locate last brace in `str` within the key. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @return {Number} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function lastBraceInKey(str) { |
|
||||
var len = str.length |
|
||||
, brace |
|
||||
, c; |
|
||||
for (var i = 0; i < len; ++i) { |
|
||||
c = str[i]; |
|
||||
if (']' == c) brace = false; |
|
||||
if ('[' == c) brace = true; |
|
||||
if ('=' == c && !brace) return i; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Decode `str`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function decode(str) { |
|
||||
try { |
|
||||
return decodeURIComponent(str.replace(/\+/g, ' ')); |
|
||||
} catch (err) { |
|
||||
return str; |
|
||||
} |
|
||||
} |
|
@ -1,38 +0,0 @@ |
|||||
{ |
|
||||
"name": "qs", |
|
||||
"description": "querystring parser", |
|
||||
"version": "0.6.6", |
|
||||
"keywords": [ |
|
||||
"query string", |
|
||||
"parser", |
|
||||
"component" |
|
||||
], |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "git://github.com/visionmedia/node-querystring.git" |
|
||||
}, |
|
||||
"devDependencies": { |
|
||||
"mocha": "*", |
|
||||
"expect.js": "*" |
|
||||
}, |
|
||||
"scripts": { |
|
||||
"test": "make test" |
|
||||
}, |
|
||||
"author": { |
|
||||
"name": "TJ Holowaychuk", |
|
||||
"email": "tj@vision-media.ca", |
|
||||
"url": "http://tjholowaychuk.com" |
|
||||
}, |
|
||||
"main": "index", |
|
||||
"engines": { |
|
||||
"node": "*" |
|
||||
}, |
|
||||
"readme": "# node-querystring\n\n query string parser for node and the browser supporting nesting, as it was removed from `0.3.x`, so this library provides the previous and commonly desired behaviour (and twice as fast). Used by [express](http://expressjs.com), [connect](http://senchalabs.github.com/connect) and others.\n\n## Installation\n\n $ npm install qs\n\n## Examples\n\n```js\nvar qs = require('qs');\n\nqs.parse('user[name][first]=Tobi&user[email]=tobi@learnboost.com');\n// => { user: { name: { first: 'Tobi' }, email: 'tobi@learnboost.com' } }\n\nqs.stringify({ user: { name: 'Tobi', email: 'tobi@learnboost.com' }})\n// => user[name]=Tobi&user[email]=tobi%40learnboost.com\n```\n\n## Testing\n\nInstall dev dependencies:\n\n $ npm install -d\n\nand execute:\n\n $ make test\n\nbrowser:\n\n $ open test/browser/index.html\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.", |
|
||||
"readmeFilename": "Readme.md", |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/visionmedia/node-querystring/issues" |
|
||||
}, |
|
||||
"homepage": "https://github.com/visionmedia/node-querystring", |
|
||||
"_id": "qs@0.6.6", |
|
||||
"_from": "qs@~0.6.6" |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
test/ |
|
@ -1,5 +0,0 @@ |
|||||
node_js: |
|
||||
- "0.8" |
|
||||
- "0.10" |
|
||||
- "0.11" |
|
||||
language: node_js |
|
@ -1,14 +0,0 @@ |
|||||
NODE ?= node |
|
||||
BIN = ./node_modules/.bin/ |
|
||||
|
|
||||
test: |
|
||||
@${NODE} ${BIN}mocha \
|
|
||||
--harmony-generators \
|
|
||||
--reporter spec \
|
|
||||
--bail \
|
|
||||
./test/index.js |
|
||||
|
|
||||
clean: |
|
||||
@rm -rf node_modules |
|
||||
|
|
||||
.PHONY: test clean |
|
@ -1,96 +0,0 @@ |
|||||
# Raw Body [![Build Status](https://travis-ci.org/stream-utils/raw-body.svg?branch=master)](https://travis-ci.org/stream-utils/raw-body) |
|
||||
|
|
||||
Gets the entire buffer of a stream either as a `Buffer` or a string. |
|
||||
Validates the stream's length against an expected length and maximum limit. |
|
||||
Ideal for parsing request bodies. |
|
||||
|
|
||||
## API |
|
||||
|
|
||||
```js |
|
||||
var getRawBody = require('raw-body') |
|
||||
|
|
||||
app.use(function (req, res, next) { |
|
||||
getRawBody(req, { |
|
||||
length: req.headers['content-length'], |
|
||||
limit: '1mb', |
|
||||
encoding: 'utf8' |
|
||||
}, function (err, string) { |
|
||||
if (err) |
|
||||
return next(err) |
|
||||
|
|
||||
req.text = string |
|
||||
next() |
|
||||
}) |
|
||||
}) |
|
||||
``` |
|
||||
|
|
||||
or in a Koa generator: |
|
||||
|
|
||||
```js |
|
||||
app.use(function* (next) { |
|
||||
var string = yield getRawBody(this.req, { |
|
||||
length: this.length, |
|
||||
limit: '1mb', |
|
||||
encoding: 'utf8' |
|
||||
}) |
|
||||
}) |
|
||||
``` |
|
||||
|
|
||||
### getRawBody(stream, [options], [callback]) |
|
||||
|
|
||||
Returns a thunk for yielding with generators. |
|
||||
|
|
||||
Options: |
|
||||
|
|
||||
- `length` - The length length of the stream. |
|
||||
If the contents of the stream do not add up to this length, |
|
||||
an `400` error code is returned. |
|
||||
- `limit` - The byte limit of the body. |
|
||||
If the body ends up being larger than this limit, |
|
||||
a `413` error code is returned. |
|
||||
- `encoding` - The requested encoding. |
|
||||
By default, a `Buffer` instance will be returned. |
|
||||
Most likely, you want `utf8`. |
|
||||
You can use any type of encoding supported by [StringDecoder](http://nodejs.org/api/string_decoder.html). |
|
||||
You can also pass `true` which sets it to the default `utf8` |
|
||||
|
|
||||
`callback(err, res)`: |
|
||||
|
|
||||
- `err` - the following attributes will be defined if applicable: |
|
||||
|
|
||||
- `limit` - the limit in bytes |
|
||||
- `length` and `expected` - the expected length of the stream |
|
||||
- `received` - the received bytes |
|
||||
- `status` and `statusCode` - the corresponding status code for the error |
|
||||
- `type` - either `entity.too.large`, `request.size.invalid`, or `stream.encoding.set` |
|
||||
|
|
||||
- `res` - the result, either as a `String` if an encoding was set or a `Buffer` otherwise. |
|
||||
|
|
||||
If an error occurs, the stream will be paused, |
|
||||
and you are responsible for correctly disposing the stream. |
|
||||
For HTTP requests, no handling is required if you send a response. |
|
||||
For streams that use file descriptors, you should `stream.destroy()` or `stream.close()` to prevent leaks. |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
The MIT License (MIT) |
|
||||
|
|
||||
Copyright (c) 2013 Jonathan Ong me@jongleberry.com |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,164 +0,0 @@ |
|||||
var StringDecoder = require('string_decoder').StringDecoder |
|
||||
var bytes = require('bytes') |
|
||||
|
|
||||
module.exports = function (stream, options, done) { |
|
||||
if (typeof options === 'function') { |
|
||||
done = options |
|
||||
options = {} |
|
||||
} else if (!options) { |
|
||||
options = {} |
|
||||
} else if (options === true) { |
|
||||
options = { |
|
||||
encoding: 'utf8' |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// convert the limit to an integer
|
|
||||
var limit = null |
|
||||
if (typeof options.limit === 'number') |
|
||||
limit = options.limit |
|
||||
if (typeof options.limit === 'string') |
|
||||
limit = bytes(options.limit) |
|
||||
|
|
||||
// convert the expected length to an integer
|
|
||||
var length = null |
|
||||
if (options.length != null && !isNaN(options.length)) |
|
||||
length = parseInt(options.length, 10) |
|
||||
|
|
||||
// check the length and limit options.
|
|
||||
// note: we intentionally leave the stream paused,
|
|
||||
// so users should handle the stream themselves.
|
|
||||
if (limit !== null && length !== null && length > limit) { |
|
||||
if (typeof stream.pause === 'function') |
|
||||
stream.pause() |
|
||||
|
|
||||
process.nextTick(function () { |
|
||||
var err = makeError('request entity too large', 'entity.too.large') |
|
||||
err.status = err.statusCode = 413 |
|
||||
err.length = err.expected = length |
|
||||
err.limit = limit |
|
||||
done(err) |
|
||||
}) |
|
||||
return defer |
|
||||
} |
|
||||
|
|
||||
var state = stream._readableState |
|
||||
// streams2+: assert the stream encoding is buffer.
|
|
||||
if (state && state.encoding != null) { |
|
||||
if (typeof stream.pause === 'function') |
|
||||
stream.pause() |
|
||||
|
|
||||
process.nextTick(function () { |
|
||||
var err = makeError('stream encoding should not be set', |
|
||||
'stream.encoding.set') |
|
||||
// developer error
|
|
||||
err.status = err.statusCode = 500 |
|
||||
done(err) |
|
||||
}) |
|
||||
return defer |
|
||||
} |
|
||||
|
|
||||
var received = 0 |
|
||||
// note: we delegate any invalid encodings to the constructor
|
|
||||
var decoder = options.encoding |
|
||||
? new StringDecoder(options.encoding === true ? 'utf8' : options.encoding) |
|
||||
: null |
|
||||
var buffer = decoder |
|
||||
? '' |
|
||||
: [] |
|
||||
|
|
||||
stream.on('data', onData) |
|
||||
stream.once('end', onEnd) |
|
||||
stream.once('error', onEnd) |
|
||||
stream.once('close', cleanup) |
|
||||
|
|
||||
return defer |
|
||||
|
|
||||
// yieldable support
|
|
||||
function defer(fn) { |
|
||||
done = fn |
|
||||
} |
|
||||
|
|
||||
function onData(chunk) { |
|
||||
received += chunk.length |
|
||||
decoder |
|
||||
? buffer += decoder.write(chunk) |
|
||||
: buffer.push(chunk) |
|
||||
|
|
||||
if (limit !== null && received > limit) { |
|
||||
if (typeof stream.pause === 'function') |
|
||||
stream.pause() |
|
||||
var err = makeError('request entity too large', 'entity.too.large') |
|
||||
err.status = err.statusCode = 413 |
|
||||
err.received = received |
|
||||
err.limit = limit |
|
||||
done(err) |
|
||||
cleanup() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function onEnd(err) { |
|
||||
if (err) { |
|
||||
if (typeof stream.pause === 'function') |
|
||||
stream.pause() |
|
||||
done(err) |
|
||||
} else if (length !== null && received !== length) { |
|
||||
err = makeError('request size did not match content length', |
|
||||
'request.size.invalid') |
|
||||
err.status = err.statusCode = 400 |
|
||||
err.received = received |
|
||||
err.length = err.expected = length |
|
||||
done(err) |
|
||||
} else { |
|
||||
done(null, decoder |
|
||||
? buffer + endStringDecoder(decoder) |
|
||||
: Buffer.concat(buffer) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
cleanup() |
|
||||
} |
|
||||
|
|
||||
function cleanup() { |
|
||||
received = buffer = null |
|
||||
|
|
||||
stream.removeListener('data', onData) |
|
||||
stream.removeListener('end', onEnd) |
|
||||
stream.removeListener('error', onEnd) |
|
||||
stream.removeListener('close', cleanup) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// to create serializable errors you must re-set message so
|
|
||||
// that it is enumerable and you must re configure the type
|
|
||||
// property so that is writable and enumerable
|
|
||||
function makeError(message, type) { |
|
||||
var error = new Error() |
|
||||
error.message = message |
|
||||
Object.defineProperty(error, 'type', { |
|
||||
value: type, |
|
||||
enumerable: true, |
|
||||
writable: true, |
|
||||
configurable: true |
|
||||
}) |
|
||||
return error |
|
||||
} |
|
||||
|
|
||||
// https://github.com/Raynos/body/blob/2512ced39e31776e5a2f7492b907330badac3a40/index.js#L72
|
|
||||
// bug fix for missing `StringDecoder.end` in v0.8.x
|
|
||||
function endStringDecoder(decoder) { |
|
||||
if (decoder.end) { |
|
||||
return decoder.end() |
|
||||
} |
|
||||
|
|
||||
var res = "" |
|
||||
|
|
||||
if (decoder.charReceived) { |
|
||||
var cr = decoder.charReceived |
|
||||
var buf = decoder.charBuffer |
|
||||
var enc = decoder.encoding |
|
||||
res += buf.slice(0, cr).toString(enc) |
|
||||
} |
|
||||
|
|
||||
return res |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
test |
|
@ -1,20 +0,0 @@ |
|||||
|
|
||||
0.3.0 / 2014-03-19 |
|
||||
================== |
|
||||
|
|
||||
* added terabyte support |
|
||||
|
|
||||
0.2.1 / 2013-04-01 |
|
||||
================== |
|
||||
|
|
||||
* add .component |
|
||||
|
|
||||
0.2.0 / 2012-10-28 |
|
||||
================== |
|
||||
|
|
||||
* bytes(200).should.eql('200b') |
|
||||
|
|
||||
0.1.0 / 2012-07-04 |
|
||||
================== |
|
||||
|
|
||||
* add bytes to string conversion [yields] |
|
@ -1,7 +0,0 @@ |
|||||
|
|
||||
test: |
|
||||
@./node_modules/.bin/mocha \
|
|
||||
--reporter spec \
|
|
||||
--require should |
|
||||
|
|
||||
.PHONY: test |
|
@ -1,54 +0,0 @@ |
|||||
# node-bytes |
|
||||
|
|
||||
Byte string parser / formatter. |
|
||||
|
|
||||
## Example: |
|
||||
|
|
||||
```js |
|
||||
bytes('1kb') |
|
||||
// => 1024 |
|
||||
|
|
||||
bytes('2mb') |
|
||||
// => 2097152 |
|
||||
|
|
||||
bytes('1gb') |
|
||||
// => 1073741824 |
|
||||
|
|
||||
bytes(1073741824) |
|
||||
// => 1gb |
|
||||
|
|
||||
bytes(1099511627776) |
|
||||
// => 1tb |
|
||||
``` |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
``` |
|
||||
$ npm install bytes |
|
||||
$ component install visionmedia/bytes.js |
|
||||
``` |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
(The MIT License) |
|
||||
|
|
||||
Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca> |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
|
||||
a copy of this software and associated documentation files (the |
|
||||
'Software'), to deal in the Software without restriction, including |
|
||||
without limitation the rights to use, copy, modify, merge, publish, |
|
||||
distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
permit persons to whom the Software is furnished to do so, subject to |
|
||||
the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be |
|
||||
included in all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
@ -1,7 +0,0 @@ |
|||||
{ |
|
||||
"name": "bytes", |
|
||||
"description": "byte size string parser / serializer", |
|
||||
"keywords": ["bytes", "utility"], |
|
||||
"version": "0.2.1", |
|
||||
"scripts": ["index.js"] |
|
||||
} |
|
@ -1,41 +0,0 @@ |
|||||
|
|
||||
/** |
|
||||
* Parse byte `size` string. |
|
||||
* |
|
||||
* @param {String} size |
|
||||
* @return {Number} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
module.exports = function(size) { |
|
||||
if ('number' == typeof size) return convert(size); |
|
||||
var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb|tb)$/) |
|
||||
, n = parseFloat(parts[1]) |
|
||||
, type = parts[2]; |
|
||||
|
|
||||
var map = { |
|
||||
kb: 1 << 10 |
|
||||
, mb: 1 << 20 |
|
||||
, gb: 1 << 30 |
|
||||
, tb: ((1 << 30) * 1024) |
|
||||
}; |
|
||||
|
|
||||
return map[type] * n; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* convert bytes into string. |
|
||||
* |
|
||||
* @param {Number} b - bytes to convert |
|
||||
* @return {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
function convert (b) { |
|
||||
var tb = ((1 << 30) * 1024), gb = 1 << 30, mb = 1 << 20, kb = 1 << 10; |
|
||||
if (b >= tb) return (Math.round(b / tb * 100) / 100) + 'tb'; |
|
||||
if (b >= gb) return (Math.round(b / gb * 100) / 100) + 'gb'; |
|
||||
if (b >= mb) return (Math.round(b / mb * 100) / 100) + 'mb'; |
|
||||
if (b >= kb) return (Math.round(b / kb * 100) / 100) + 'kb'; |
|
||||
return b + 'b'; |
|
||||
} |
|
@ -1,37 +0,0 @@ |
|||||
{ |
|
||||
"name": "bytes", |
|
||||
"author": { |
|
||||
"name": "TJ Holowaychuk", |
|
||||
"email": "tj@vision-media.ca", |
|
||||
"url": "http://tjholowaychuk.com" |
|
||||
}, |
|
||||
"description": "byte size string parser / serializer", |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "https://github.com/visionmedia/bytes.js.git" |
|
||||
}, |
|
||||
"version": "0.3.0", |
|
||||
"main": "index.js", |
|
||||
"dependencies": {}, |
|
||||
"devDependencies": { |
|
||||
"mocha": "*", |
|
||||
"should": "*" |
|
||||
}, |
|
||||
"component": { |
|
||||
"scripts": { |
|
||||
"bytes/index.js": "index.js" |
|
||||
} |
|
||||
}, |
|
||||
"readme": "# node-bytes\n\n Byte string parser / formatter.\n\n## Example:\n\n```js\nbytes('1kb')\n// => 1024\n\nbytes('2mb')\n// => 2097152\n\nbytes('1gb')\n// => 1073741824\n\nbytes(1073741824)\n// => 1gb\n\nbytes(1099511627776)\n// => 1tb\n```\n\n## Installation\n\n```\n$ npm install bytes\n$ component install visionmedia/bytes.js\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n", |
|
||||
"readmeFilename": "Readme.md", |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/visionmedia/bytes.js/issues" |
|
||||
}, |
|
||||
"homepage": "https://github.com/visionmedia/bytes.js", |
|
||||
"_id": "bytes@0.3.0", |
|
||||
"dist": { |
|
||||
"shasum": "741ac2cc6fdac3cd7ee104196aaad35b6d4c8ee5" |
|
||||
}, |
|
||||
"_from": "bytes@~0.3.0", |
|
||||
"_resolved": "https://registry.npmjs.org/bytes/-/bytes-0.3.0.tgz" |
|
||||
} |
|
@ -1,45 +0,0 @@ |
|||||
{ |
|
||||
"name": "raw-body", |
|
||||
"description": "Get and validate the raw body of a readable stream.", |
|
||||
"version": "1.1.4", |
|
||||
"author": { |
|
||||
"name": "Jonathan Ong", |
|
||||
"email": "me@jongleberry.com", |
|
||||
"url": "http://jongleberry.com" |
|
||||
}, |
|
||||
"license": "MIT", |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "https://github.com/stream-utils/raw-body.git" |
|
||||
}, |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/stream-utils/raw-body/issues" |
|
||||
}, |
|
||||
"dependencies": { |
|
||||
"bytes": "~0.3.0" |
|
||||
}, |
|
||||
"devDependencies": { |
|
||||
"readable-stream": "~1.0.17", |
|
||||
"co": "3", |
|
||||
"gnode": "~0.0.4", |
|
||||
"mocha": "^1.14.0", |
|
||||
"through2": "~0.4.1", |
|
||||
"request": "^2.27.0", |
|
||||
"assert-tap": "~0.1.4" |
|
||||
}, |
|
||||
"scripts": { |
|
||||
"test": "NODE=gnode make test && node ./test/acceptance.js" |
|
||||
}, |
|
||||
"engines": { |
|
||||
"node": ">= 0.8.0" |
|
||||
}, |
|
||||
"readme": "# Raw Body [![Build Status](https://travis-ci.org/stream-utils/raw-body.svg?branch=master)](https://travis-ci.org/stream-utils/raw-body)\n\nGets the entire buffer of a stream either as a `Buffer` or a string.\nValidates the stream's length against an expected length and maximum limit.\nIdeal for parsing request bodies.\n\n## API\n\n```js\nvar getRawBody = require('raw-body')\n\napp.use(function (req, res, next) {\n getRawBody(req, {\n length: req.headers['content-length'],\n limit: '1mb',\n encoding: 'utf8'\n }, function (err, string) {\n if (err)\n return next(err)\n\n req.text = string\n next()\n })\n})\n```\n\nor in a Koa generator:\n\n```js\napp.use(function* (next) {\n var string = yield getRawBody(this.req, {\n length: this.length,\n limit: '1mb',\n encoding: 'utf8'\n })\n})\n```\n\n### getRawBody(stream, [options], [callback])\n\nReturns a thunk for yielding with generators.\n\nOptions:\n\n- `length` - The length length of the stream.\n If the contents of the stream do not add up to this length,\n an `400` error code is returned.\n- `limit` - The byte limit of the body.\n If the body ends up being larger than this limit,\n a `413` error code is returned.\n- `encoding` - The requested encoding.\n By default, a `Buffer` instance will be returned.\n Most likely, you want `utf8`.\n You can use any type of encoding supported by [StringDecoder](http://nodejs.org/api/string_decoder.html).\n You can also pass `true` which sets it to the default `utf8`\n\n`callback(err, res)`:\n\n- `err` - the following attributes will be defined if applicable:\n\n - `limit` - the limit in bytes\n - `length` and `expected` - the expected length of the stream\n - `received` - the received bytes\n - `status` and `statusCode` - the corresponding status code for the error\n - `type` - either `entity.too.large`, `request.size.invalid`, or `stream.encoding.set`\n\n- `res` - the result, either as a `String` if an encoding was set or a `Buffer` otherwise.\n\nIf an error occurs, the stream will be paused,\nand you are responsible for correctly disposing the stream.\nFor HTTP requests, no handling is required if you send a response.\nFor streams that use file descriptors, you should `stream.destroy()` or `stream.close()` to prevent leaks.\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 Jonathan Ong me@jongleberry.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n", |
|
||||
"readmeFilename": "README.md", |
|
||||
"homepage": "https://github.com/stream-utils/raw-body", |
|
||||
"_id": "raw-body@1.1.4", |
|
||||
"dist": { |
|
||||
"shasum": "6bc522a645d7c9939fe2ba2be3bd5ddf29f7f89f" |
|
||||
}, |
|
||||
"_from": "raw-body@~1.1.2", |
|
||||
"_resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.4.tgz" |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
test.js |
|
@ -1,4 +0,0 @@ |
|||||
node_js: |
|
||||
- "0.10" |
|
||||
- "0.11" |
|
||||
language: node_js |
|
@ -1,16 +0,0 @@ |
|||||
|
|
||||
1.1.0 / 2014-04-12 |
|
||||
================== |
|
||||
|
|
||||
* add non-array values support |
|
||||
* expose internal utilities: |
|
||||
|
|
||||
- `.is()` |
|
||||
- `.hasBody()` |
|
||||
- `.normalize()` |
|
||||
- `.match()` |
|
||||
|
|
||||
1.0.1 / 2014-03-30 |
|
||||
================== |
|
||||
|
|
||||
* add `multipart` as a shorthand |
|
@ -1,87 +0,0 @@ |
|||||
# Type Is [![Build Status](https://travis-ci.org/expressjs/type-is.png)](https://travis-ci.org/expressjs/type-is) |
|
||||
|
|
||||
Infer the content type of a request. |
|
||||
Extracted from [koa](https://github.com/koajs/koa) for general use. |
|
||||
|
|
||||
Here's an example body parser: |
|
||||
|
|
||||
```js |
|
||||
var is = require('type-is'); |
|
||||
var parse = require('body'); |
|
||||
var busboy = require('busboy'); |
|
||||
|
|
||||
function bodyParser(req, res, next) { |
|
||||
var hasRequestBody = 'content-type' in req.headers |
|
||||
|| 'transfer-encoding' in req.headers; |
|
||||
if (!hasRequestBody) return next(); |
|
||||
|
|
||||
switch (is(req, ['urlencoded', 'json', 'multipart'])) { |
|
||||
case 'urlencoded': |
|
||||
// parse urlencoded body |
|
||||
break |
|
||||
case 'json': |
|
||||
// parse json body |
|
||||
break |
|
||||
case 'multipart': |
|
||||
// parse multipart body |
|
||||
break |
|
||||
default: |
|
||||
// 415 error code |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## API |
|
||||
|
|
||||
### var type = is(request, types) |
|
||||
|
|
||||
```js |
|
||||
var is = require('type-is') |
|
||||
|
|
||||
http.createServer(function (req, res) { |
|
||||
is(req, ['text/*']) |
|
||||
}) |
|
||||
``` |
|
||||
|
|
||||
`request` is the node HTTP request. `types` is an array of types. Each type can be: |
|
||||
|
|
||||
- An extension name such as `json`. This name will be returned if matched. |
|
||||
- A mime type such as `application/json`. |
|
||||
- A mime type with a wildcard such as `*/json` or `application/*`. The full mime type will be returned if matched |
|
||||
|
|
||||
`false` will be returned if no type matches. |
|
||||
|
|
||||
Examples: |
|
||||
|
|
||||
```js |
|
||||
// req.headers.content-type = 'application/json' |
|
||||
is(req, ['json']) // -> 'json' |
|
||||
is(req, ['html', 'json']) // -> 'json' |
|
||||
is(req, ['application/*']) // -> 'application/json' |
|
||||
is(req, ['application/json']) // -> 'application/json' |
|
||||
is(req, ['html']) // -> false |
|
||||
``` |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
The MIT License (MIT) |
|
||||
|
|
||||
Copyright (c) 2013 Jonathan Ong me@jongleberry.com |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,148 +0,0 @@ |
|||||
|
|
||||
var mime = require('mime'); |
|
||||
|
|
||||
var slice = [].slice; |
|
||||
|
|
||||
module.exports = typeofrequest; |
|
||||
typeofrequest.is = typeis; |
|
||||
typeofrequest.hasBody = hasbody; |
|
||||
typeofrequest.normalize = normalize; |
|
||||
typeofrequest.match = mimeMatch; |
|
||||
|
|
||||
/** |
|
||||
* Compare a `value` content-type with `types`. |
|
||||
* Each `type` can be an extension like `html`, |
|
||||
* a special shortcut like `multipart` or `urlencoded`, |
|
||||
* or a mime type. |
|
||||
* |
|
||||
* If no types match, `false` is returned. |
|
||||
* Otherwise, the first `type` that matches is returned. |
|
||||
* |
|
||||
* @param {String} value |
|
||||
* @param {Array} types |
|
||||
* @return String |
|
||||
*/ |
|
||||
|
|
||||
function typeis(value, types) { |
|
||||
if (!value) return false; |
|
||||
if (types && !Array.isArray(types)) types = slice.call(arguments, 1); |
|
||||
|
|
||||
// remove stuff like charsets
|
|
||||
var index = value.indexOf(';') |
|
||||
value = ~index ? value.slice(0, index) : value |
|
||||
|
|
||||
// no types, return the content type
|
|
||||
if (!types || !types.length) return value; |
|
||||
|
|
||||
var type; |
|
||||
for (var i = 0; i < types.length; i++) |
|
||||
if (mimeMatch(normalize(type = types[i]), value)) |
|
||||
return ~type.indexOf('*') ? value : type; |
|
||||
|
|
||||
// no matches
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Check if a request has a request body. |
|
||||
* A request with a body __must__ either have `transfer-encoding` |
|
||||
* or `content-length` headers set. |
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
|
|
||||
* |
|
||||
* @param {Object} request |
|
||||
* @return {Boolean} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
function hasbody(req) { |
|
||||
var headers = req.headers; |
|
||||
if ('transfer-encoding' in headers) return true; |
|
||||
var length = headers['content-length']; |
|
||||
if (!length) return false; |
|
||||
// no idea when this would happen, but `isNaN(null) === false`
|
|
||||
if (isNaN(length)) return false; |
|
||||
return !!parseInt(length, 10); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Check if the incoming request contains the "Content-Type" |
|
||||
* header field, and it contains any of the give mime `type`s. |
|
||||
* If there is no request body, `null` is returned. |
|
||||
* If there is no content type, `false` is returned. |
|
||||
* Otherwise, it returns the first `type` that matches. |
|
||||
* |
|
||||
* Examples: |
|
||||
* |
|
||||
* // With Content-Type: text/html; charset=utf-8
|
|
||||
* this.is('html'); // => 'html'
|
|
||||
* this.is('text/html'); // => 'text/html'
|
|
||||
* this.is('text/*', 'application/json'); // => 'text/html'
|
|
||||
* |
|
||||
* // When Content-Type is application/json
|
|
||||
* this.is('json', 'urlencoded'); // => 'json'
|
|
||||
* this.is('application/json'); // => 'application/json'
|
|
||||
* this.is('html', 'application/*'); // => 'application/json'
|
|
||||
* |
|
||||
* this.is('html'); // => false
|
|
||||
* |
|
||||
* @param {String|Array} types... |
|
||||
* @return {String|false|null} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
function typeofrequest(req, types) { |
|
||||
if (!hasbody(req)) return null; |
|
||||
if (types && !Array.isArray(types)) types = slice.call(arguments, 1); |
|
||||
return typeis(req.headers['content-type'], types); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Normalize a mime type. |
|
||||
* If it's a shorthand, expand it to a valid mime type. |
|
||||
* |
|
||||
* In general, you probably want: |
|
||||
* |
|
||||
* var type = is(req, ['urlencoded', 'json', 'multipart']); |
|
||||
* |
|
||||
* Then use the appropriate body parsers. |
|
||||
* These three are the most common request body types |
|
||||
* and are thus ensured to work. |
|
||||
* |
|
||||
* @param {String} type |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function normalize(type) { |
|
||||
switch (type) { |
|
||||
case 'urlencoded': return 'application/x-www-form-urlencoded'; |
|
||||
case 'multipart': |
|
||||
type = 'multipart/*'; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
return ~type.indexOf('/') ? type : mime.lookup(type); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Check if `exected` mime type |
|
||||
* matches `actual` mime type with |
|
||||
* wildcard support. |
|
||||
* |
|
||||
* @param {String} expected |
|
||||
* @param {String} actual |
|
||||
* @return {Boolean} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function mimeMatch(expected, actual) { |
|
||||
if (expected === actual) return true; |
|
||||
|
|
||||
if (!~expected.indexOf('*')) return false; |
|
||||
|
|
||||
actual = actual.split('/'); |
|
||||
expected = expected.split('/'); |
|
||||
|
|
||||
if ('*' === expected[0] && expected[1] === actual[1]) return true; |
|
||||
if ('*' === expected[1] && expected[0] === actual[0]) return true; |
|
||||
return false; |
|
||||
} |
|
@ -1,19 +0,0 @@ |
|||||
Copyright (c) 2010 Benjamin Thomas, Robert Kieffer |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,66 +0,0 @@ |
|||||
# mime |
|
||||
|
|
||||
Comprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community. |
|
||||
|
|
||||
## Install |
|
||||
|
|
||||
Install with [npm](http://github.com/isaacs/npm): |
|
||||
|
|
||||
npm install mime |
|
||||
|
|
||||
## API - Queries |
|
||||
|
|
||||
### mime.lookup(path) |
|
||||
Get the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.'). E.g. |
|
||||
|
|
||||
var mime = require('mime'); |
|
||||
|
|
||||
mime.lookup('/path/to/file.txt'); // => 'text/plain' |
|
||||
mime.lookup('file.txt'); // => 'text/plain' |
|
||||
mime.lookup('.TXT'); // => 'text/plain' |
|
||||
mime.lookup('htm'); // => 'text/html' |
|
||||
|
|
||||
### mime.default_type |
|
||||
Sets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.) |
|
||||
|
|
||||
### mime.extension(type) |
|
||||
Get the default extension for `type` |
|
||||
|
|
||||
mime.extension('text/html'); // => 'html' |
|
||||
mime.extension('application/octet-stream'); // => 'bin' |
|
||||
|
|
||||
### mime.charsets.lookup() |
|
||||
|
|
||||
Map mime-type to charset |
|
||||
|
|
||||
mime.charsets.lookup('text/plain'); // => 'UTF-8' |
|
||||
|
|
||||
(The logic for charset lookups is pretty rudimentary. Feel free to suggest improvements.) |
|
||||
|
|
||||
## API - Defining Custom Types |
|
||||
|
|
||||
The following APIs allow you to add your own type mappings within your project. If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types). |
|
||||
|
|
||||
### mime.define() |
|
||||
|
|
||||
Add custom mime/extension mappings |
|
||||
|
|
||||
mime.define({ |
|
||||
'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'], |
|
||||
'application/x-my-type': ['x-mt', 'x-mtt'], |
|
||||
// etc ... |
|
||||
}); |
|
||||
|
|
||||
mime.lookup('x-sft'); // => 'text/x-some-format' |
|
||||
|
|
||||
The first entry in the extensions array is returned by `mime.extension()`. E.g. |
|
||||
|
|
||||
mime.extension('text/x-some-format'); // => 'x-sf' |
|
||||
|
|
||||
### mime.load(filepath) |
|
||||
|
|
||||
Load mappings from an Apache ".types" format file |
|
||||
|
|
||||
mime.load('./my_project.types'); |
|
||||
|
|
||||
The .types file format is simple - See the `types` dir for examples. |
|
@ -1,114 +0,0 @@ |
|||||
var path = require('path'); |
|
||||
var fs = require('fs'); |
|
||||
|
|
||||
function Mime() { |
|
||||
// Map of extension -> mime type
|
|
||||
this.types = Object.create(null); |
|
||||
|
|
||||
// Map of mime type -> extension
|
|
||||
this.extensions = Object.create(null); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Define mimetype -> extension mappings. Each key is a mime-type that maps |
|
||||
* to an array of extensions associated with the type. The first extension is |
|
||||
* used as the default extension for the type. |
|
||||
* |
|
||||
* e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']}); |
|
||||
* |
|
||||
* @param map (Object) type definitions |
|
||||
*/ |
|
||||
Mime.prototype.define = function (map) { |
|
||||
for (var type in map) { |
|
||||
var exts = map[type]; |
|
||||
|
|
||||
for (var i = 0; i < exts.length; i++) { |
|
||||
if (process.env.DEBUG_MIME && this.types[exts]) { |
|
||||
console.warn(this._loading.replace(/.*\//, ''), 'changes "' + exts[i] + '" extension type from ' + |
|
||||
this.types[exts] + ' to ' + type); |
|
||||
} |
|
||||
|
|
||||
this.types[exts[i]] = type; |
|
||||
} |
|
||||
|
|
||||
// Default extension is the first one we encounter
|
|
||||
if (!this.extensions[type]) { |
|
||||
this.extensions[type] = exts[0]; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Load an Apache2-style ".types" file |
|
||||
* |
|
||||
* This may be called multiple times (it's expected). Where files declare |
|
||||
* overlapping types/extensions, the last file wins. |
|
||||
* |
|
||||
* @param file (String) path of file to load. |
|
||||
*/ |
|
||||
Mime.prototype.load = function(file) { |
|
||||
|
|
||||
this._loading = file; |
|
||||
// Read file and split into lines
|
|
||||
var map = {}, |
|
||||
content = fs.readFileSync(file, 'ascii'), |
|
||||
lines = content.split(/[\r\n]+/); |
|
||||
|
|
||||
lines.forEach(function(line) { |
|
||||
// Clean up whitespace/comments, and split into fields
|
|
||||
var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/); |
|
||||
map[fields.shift()] = fields; |
|
||||
}); |
|
||||
|
|
||||
this.define(map); |
|
||||
|
|
||||
this._loading = null; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Lookup a mime type based on extension |
|
||||
*/ |
|
||||
Mime.prototype.lookup = function(path, fallback) { |
|
||||
var ext = path.replace(/.*[\.\/\\]/, '').toLowerCase(); |
|
||||
|
|
||||
return this.types[ext] || fallback || this.default_type; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return file extension associated with a mime type |
|
||||
*/ |
|
||||
Mime.prototype.extension = function(mimeType) { |
|
||||
var type = mimeType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase(); |
|
||||
return this.extensions[type]; |
|
||||
}; |
|
||||
|
|
||||
// Default instance
|
|
||||
var mime = new Mime(); |
|
||||
|
|
||||
// Load local copy of
|
|
||||
// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
|
||||
mime.load(path.join(__dirname, 'types/mime.types')); |
|
||||
|
|
||||
// Load additional types from node.js community
|
|
||||
mime.load(path.join(__dirname, 'types/node.types')); |
|
||||
|
|
||||
// Default type
|
|
||||
mime.default_type = mime.lookup('bin'); |
|
||||
|
|
||||
//
|
|
||||
// Additional API specific to the default instance
|
|
||||
//
|
|
||||
|
|
||||
mime.Mime = Mime; |
|
||||
|
|
||||
/** |
|
||||
* Lookup a charset based on mime type. |
|
||||
*/ |
|
||||
mime.charsets = { |
|
||||
lookup: function(mimeType, fallback) { |
|
||||
// Assume text types are utf8
|
|
||||
return (/^text\//).test(mimeType) ? 'UTF-8' : fallback; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
module.exports = mime; |
|
@ -1,36 +0,0 @@ |
|||||
{ |
|
||||
"author": { |
|
||||
"name": "Robert Kieffer", |
|
||||
"email": "robert@broofa.com", |
|
||||
"url": "http://github.com/broofa" |
|
||||
}, |
|
||||
"contributors": [ |
|
||||
{ |
|
||||
"name": "Benjamin Thomas", |
|
||||
"email": "benjamin@benjaminthomas.org", |
|
||||
"url": "http://github.com/bentomas" |
|
||||
} |
|
||||
], |
|
||||
"dependencies": {}, |
|
||||
"description": "A comprehensive library for mime-type mapping", |
|
||||
"devDependencies": {}, |
|
||||
"keywords": [ |
|
||||
"util", |
|
||||
"mime" |
|
||||
], |
|
||||
"main": "mime.js", |
|
||||
"name": "mime", |
|
||||
"repository": { |
|
||||
"url": "https://github.com/broofa/node-mime", |
|
||||
"type": "git" |
|
||||
}, |
|
||||
"version": "1.2.11", |
|
||||
"readme": "# mime\n\nComprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.\n\n## Install\n\nInstall with [npm](http://github.com/isaacs/npm):\n\n npm install mime\n\n## API - Queries\n\n### mime.lookup(path)\nGet the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.'). E.g.\n\n var mime = require('mime');\n\n mime.lookup('/path/to/file.txt'); // => 'text/plain'\n mime.lookup('file.txt'); // => 'text/plain'\n mime.lookup('.TXT'); // => 'text/plain'\n mime.lookup('htm'); // => 'text/html'\n\n### mime.default_type\nSets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.)\n\n### mime.extension(type)\nGet the default extension for `type`\n\n mime.extension('text/html'); // => 'html'\n mime.extension('application/octet-stream'); // => 'bin'\n\n### mime.charsets.lookup()\n\nMap mime-type to charset\n\n mime.charsets.lookup('text/plain'); // => 'UTF-8'\n\n(The logic for charset lookups is pretty rudimentary. Feel free to suggest improvements.)\n\n## API - Defining Custom Types\n\nThe following APIs allow you to add your own type mappings within your project. If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types).\n\n### mime.define()\n\nAdd custom mime/extension mappings\n\n mime.define({\n 'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],\n 'application/x-my-type': ['x-mt', 'x-mtt'],\n // etc ...\n });\n\n mime.lookup('x-sft'); // => 'text/x-some-format'\n\nThe first entry in the extensions array is returned by `mime.extension()`. E.g.\n\n mime.extension('text/x-some-format'); // => 'x-sf'\n\n### mime.load(filepath)\n\nLoad mappings from an Apache \".types\" format file\n\n mime.load('./my_project.types');\n\nThe .types file format is simple - See the `types` dir for examples.\n", |
|
||||
"readmeFilename": "README.md", |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/broofa/node-mime/issues" |
|
||||
}, |
|
||||
"homepage": "https://github.com/broofa/node-mime", |
|
||||
"_id": "mime@1.2.11", |
|
||||
"_from": "mime@~1.2.11" |
|
||||
} |
|
@ -1,84 +0,0 @@ |
|||||
/** |
|
||||
* Usage: node test.js |
|
||||
*/ |
|
||||
|
|
||||
var mime = require('./mime'); |
|
||||
var assert = require('assert'); |
|
||||
var path = require('path'); |
|
||||
|
|
||||
function eq(a, b) { |
|
||||
console.log('Test: ' + a + ' === ' + b); |
|
||||
assert.strictEqual.apply(null, arguments); |
|
||||
} |
|
||||
|
|
||||
console.log(Object.keys(mime.extensions).length + ' types'); |
|
||||
console.log(Object.keys(mime.types).length + ' extensions\n'); |
|
||||
|
|
||||
//
|
|
||||
// Test mime lookups
|
|
||||
//
|
|
||||
|
|
||||
eq('text/plain', mime.lookup('text.txt')); // normal file
|
|
||||
eq('text/plain', mime.lookup('TEXT.TXT')); // uppercase
|
|
||||
eq('text/plain', mime.lookup('dir/text.txt')); // dir + file
|
|
||||
eq('text/plain', mime.lookup('.text.txt')); // hidden file
|
|
||||
eq('text/plain', mime.lookup('.txt')); // nameless
|
|
||||
eq('text/plain', mime.lookup('txt')); // extension-only
|
|
||||
eq('text/plain', mime.lookup('/txt')); // extension-less ()
|
|
||||
eq('text/plain', mime.lookup('\\txt')); // Windows, extension-less
|
|
||||
eq('application/octet-stream', mime.lookup('text.nope')); // unrecognized
|
|
||||
eq('fallback', mime.lookup('text.fallback', 'fallback')); // alternate default
|
|
||||
|
|
||||
//
|
|
||||
// Test extensions
|
|
||||
//
|
|
||||
|
|
||||
eq('txt', mime.extension(mime.types.text)); |
|
||||
eq('html', mime.extension(mime.types.htm)); |
|
||||
eq('bin', mime.extension('application/octet-stream')); |
|
||||
eq('bin', mime.extension('application/octet-stream ')); |
|
||||
eq('html', mime.extension(' text/html; charset=UTF-8')); |
|
||||
eq('html', mime.extension('text/html; charset=UTF-8 ')); |
|
||||
eq('html', mime.extension('text/html; charset=UTF-8')); |
|
||||
eq('html', mime.extension('text/html ; charset=UTF-8')); |
|
||||
eq('html', mime.extension('text/html;charset=UTF-8')); |
|
||||
eq('html', mime.extension('text/Html;charset=UTF-8')); |
|
||||
eq(undefined, mime.extension('unrecognized')); |
|
||||
|
|
||||
//
|
|
||||
// Test node.types lookups
|
|
||||
//
|
|
||||
|
|
||||
eq('application/font-woff', mime.lookup('file.woff')); |
|
||||
eq('application/octet-stream', mime.lookup('file.buffer')); |
|
||||
eq('audio/mp4', mime.lookup('file.m4a')); |
|
||||
eq('font/opentype', mime.lookup('file.otf')); |
|
||||
|
|
||||
//
|
|
||||
// Test charsets
|
|
||||
//
|
|
||||
|
|
||||
eq('UTF-8', mime.charsets.lookup('text/plain')); |
|
||||
eq(undefined, mime.charsets.lookup(mime.types.js)); |
|
||||
eq('fallback', mime.charsets.lookup('application/octet-stream', 'fallback')); |
|
||||
|
|
||||
//
|
|
||||
// Test for overlaps between mime.types and node.types
|
|
||||
//
|
|
||||
|
|
||||
var apacheTypes = new mime.Mime(), nodeTypes = new mime.Mime(); |
|
||||
apacheTypes.load(path.join(__dirname, 'types/mime.types')); |
|
||||
nodeTypes.load(path.join(__dirname, 'types/node.types')); |
|
||||
|
|
||||
var keys = [].concat(Object.keys(apacheTypes.types)) |
|
||||
.concat(Object.keys(nodeTypes.types)); |
|
||||
keys.sort(); |
|
||||
for (var i = 1; i < keys.length; i++) { |
|
||||
if (keys[i] == keys[i-1]) { |
|
||||
console.warn('Warning: ' + |
|
||||
'node.types defines ' + keys[i] + '->' + nodeTypes.types[keys[i]] + |
|
||||
', mime.types defines ' + keys[i] + '->' + apacheTypes.types[keys[i]]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
console.log('\nOK'); |
|
1588
node_modules/body-parser/node_modules/type-is/node_modules/mime/types/mime.types
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,77 +0,0 @@ |
|||||
# What: WebVTT |
|
||||
# Why: To allow formats intended for marking up external text track resources. |
|
||||
# http://dev.w3.org/html5/webvtt/ |
|
||||
# Added by: niftylettuce |
|
||||
text/vtt vtt |
|
||||
|
|
||||
# What: Google Chrome Extension |
|
||||
# Why: To allow apps to (work) be served with the right content type header. |
|
||||
# http://codereview.chromium.org/2830017 |
|
||||
# Added by: niftylettuce |
|
||||
application/x-chrome-extension crx |
|
||||
|
|
||||
# What: HTC support |
|
||||
# Why: To properly render .htc files such as CSS3PIE |
|
||||
# Added by: niftylettuce |
|
||||
text/x-component htc |
|
||||
|
|
||||
# What: HTML5 application cache manifes ('.manifest' extension) |
|
||||
# Why: De-facto standard. Required by Mozilla browser when serving HTML5 apps |
|
||||
# per https://developer.mozilla.org/en/offline_resources_in_firefox |
|
||||
# Added by: louisremi |
|
||||
text/cache-manifest manifest |
|
||||
|
|
||||
# What: node binary buffer format |
|
||||
# Why: semi-standard extension w/in the node community |
|
||||
# Added by: tootallnate |
|
||||
application/octet-stream buffer |
|
||||
|
|
||||
# What: The "protected" MP-4 formats used by iTunes. |
|
||||
# Why: Required for streaming music to browsers (?) |
|
||||
# Added by: broofa |
|
||||
application/mp4 m4p |
|
||||
audio/mp4 m4a |
|
||||
|
|
||||
# What: Video format, Part of RFC1890 |
|
||||
# Why: See https://github.com/bentomas/node-mime/pull/6 |
|
||||
# Added by: mjrusso |
|
||||
video/MP2T ts |
|
||||
|
|
||||
# What: EventSource mime type |
|
||||
# Why: mime type of Server-Sent Events stream |
|
||||
# http://www.w3.org/TR/eventsource/#text-event-stream |
|
||||
# Added by: francois2metz |
|
||||
text/event-stream event-stream |
|
||||
|
|
||||
# What: Mozilla App manifest mime type |
|
||||
# Why: https://developer.mozilla.org/en/Apps/Manifest#Serving_manifests |
|
||||
# Added by: ednapiranha |
|
||||
application/x-web-app-manifest+json webapp |
|
||||
|
|
||||
# What: Lua file types |
|
||||
# Why: Googling around shows de-facto consensus on these |
|
||||
# Added by: creationix (Issue #45) |
|
||||
text/x-lua lua |
|
||||
application/x-lua-bytecode luac |
|
||||
|
|
||||
# What: Markdown files, as per http://daringfireball.net/projects/markdown/syntax |
|
||||
# Why: http://stackoverflow.com/questions/10701983/what-is-the-mime-type-for-markdown |
|
||||
# Added by: avoidwork |
|
||||
text/x-markdown markdown md mkd |
|
||||
|
|
||||
# What: ini files |
|
||||
# Why: because they're just text files |
|
||||
# Added by: Matthew Kastor |
|
||||
text/plain ini |
|
||||
|
|
||||
# What: DASH Adaptive Streaming manifest |
|
||||
# Why: https://developer.mozilla.org/en-US/docs/DASH_Adaptive_Streaming_for_HTML_5_Video |
|
||||
# Added by: eelcocramer |
|
||||
application/dash+xml mdp |
|
||||
|
|
||||
# What: OpenType font files - http://www.microsoft.com/typography/otspec/ |
|
||||
# Why: Browsers usually ignore the font MIME types and sniff the content, |
|
||||
# but Chrome, shows a warning if OpenType fonts aren't served with |
|
||||
# the `font/opentype` MIME type: http://i.imgur.com/8c5RN8M.png. |
|
||||
# Added by: alrra |
|
||||
font/opentype otf |
|
@ -1,37 +0,0 @@ |
|||||
{ |
|
||||
"name": "type-is", |
|
||||
"description": "Infer the content type if a request", |
|
||||
"version": "1.1.0", |
|
||||
"author": { |
|
||||
"name": "Jonathan Ong", |
|
||||
"email": "me@jongleberry.com", |
|
||||
"url": "http://jongleberry.com" |
|
||||
}, |
|
||||
"license": "MIT", |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "git://github.com/expressjs/type-is" |
|
||||
}, |
|
||||
"dependencies": { |
|
||||
"mime": "~1.2.11" |
|
||||
}, |
|
||||
"devDependencies": { |
|
||||
"mocha": "*", |
|
||||
"should": "*" |
|
||||
}, |
|
||||
"scripts": { |
|
||||
"test": "mocha --require should --reporter spec --bail" |
|
||||
}, |
|
||||
"readme": "# Type Is [![Build Status](https://travis-ci.org/expressjs/type-is.png)](https://travis-ci.org/expressjs/type-is)\n\nInfer the content type of a request. \nExtracted from [koa](https://github.com/koajs/koa) for general use.\n\nHere's an example body parser:\n\n```js\nvar is = require('type-is');\nvar parse = require('body');\nvar busboy = require('busboy');\n\nfunction bodyParser(req, res, next) {\n var hasRequestBody = 'content-type' in req.headers\n || 'transfer-encoding' in req.headers;\n if (!hasRequestBody) return next();\n \n switch (is(req, ['urlencoded', 'json', 'multipart'])) {\n case 'urlencoded':\n // parse urlencoded body\n break\n case 'json':\n // parse json body\n break\n case 'multipart':\n // parse multipart body\n break\n default:\n // 415 error code\n }\n}\n```\n\n## API\n\n### var type = is(request, types)\n\n```js\nvar is = require('type-is')\n\nhttp.createServer(function (req, res) {\n is(req, ['text/*'])\n})\n```\n\n`request` is the node HTTP request. `types` is an array of types. Each type can be:\n\n- An extension name such as `json`. This name will be returned if matched.\n- A mime type such as `application/json`.\n- A mime type with a wildcard such as `*/json` or `application/*`. The full mime type will be returned if matched\n\n`false` will be returned if no type matches.\n\nExamples:\n\n```js\n// req.headers.content-type = 'application/json'\nis(req, ['json']) // -> 'json'\nis(req, ['html', 'json']) // -> 'json'\nis(req, ['application/*']) // -> 'application/json'\nis(req, ['application/json']) // -> 'application/json'\nis(req, ['html']) // -> false\n```\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2013 Jonathan Ong me@jongleberry.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n", |
|
||||
"readmeFilename": "README.md", |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/expressjs/type-is/issues" |
|
||||
}, |
|
||||
"homepage": "https://github.com/expressjs/type-is", |
|
||||
"_id": "type-is@1.1.0", |
|
||||
"dist": { |
|
||||
"shasum": "013fbfeb3955944bae8bf3b632df4ed807aefbbd" |
|
||||
}, |
|
||||
"_from": "type-is@~1.1.0", |
|
||||
"_resolved": "https://registry.npmjs.org/type-is/-/type-is-1.1.0.tgz" |
|
||||
} |
|
@ -1,41 +0,0 @@ |
|||||
{ |
|
||||
"name": "body-parser", |
|
||||
"description": "Connect's body parsing middleware", |
|
||||
"version": "1.0.2", |
|
||||
"author": { |
|
||||
"name": "Jonathan Ong", |
|
||||
"email": "me@jongleberry.com", |
|
||||
"url": "http://jongleberry.com" |
|
||||
}, |
|
||||
"license": "MIT", |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "git://github.com/expressjs/body-parser" |
|
||||
}, |
|
||||
"dependencies": { |
|
||||
"type-is": "~1.1.0", |
|
||||
"raw-body": "~1.1.2", |
|
||||
"qs": "~0.6.6" |
|
||||
}, |
|
||||
"devDependencies": { |
|
||||
"connect": "*", |
|
||||
"mocha": "*", |
|
||||
"should": "*", |
|
||||
"supertest": "*" |
|
||||
}, |
|
||||
"scripts": { |
|
||||
"test": "make test" |
|
||||
}, |
|
||||
"readme": "# Body Parser [![Build Status](https://travis-ci.org/expressjs/body-parser.png)](https://travis-ci.org/expressjs/body-parser)\n\nConnect's body parsing middleware.\n\n## API\n\n```js\nvar bodyParser = require('body-parser');\n\nvar app = connect();\n\napp.use(bodyParser());\n\napp.use(function (req, res, next) {\n console.log(req.body) // populated!\n next();\n})\n```\n\n### bodyParser([options])\n\nReturns middleware that parses both `json` and `urlencoded`. The `options` are passed to both middleware.\n\n### bodyParser.json([options])\n\nReturns middleware that only parses `json`. The options are:\n\n- `strict` <true> - only parse objects and arrays\n- `limit` <1mb> - maximum request body size\n- `reviver` - passed to `JSON.parse()`\n\n### bodyParser.urlencoded([options])\n\nReturns middleware that only parses `urlencoded` with the [qs](https://github.com/visionmedia/node-querystring) module. The options are:\n\n- `limit` <1mb> - maximum request body size\n\n## License\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Jonathan Ong me@jongleberry.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n", |
|
||||
"readmeFilename": "README.md", |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/expressjs/body-parser/issues" |
|
||||
}, |
|
||||
"homepage": "https://github.com/expressjs/body-parser", |
|
||||
"_id": "body-parser@1.0.2", |
|
||||
"dist": { |
|
||||
"shasum": "a5ef66df81af3d2a204ac2c03f1b44c630a70643" |
|
||||
}, |
|
||||
"_from": "body-parser@", |
|
||||
"_resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.0.2.tgz" |
|
||||
} |
|
@ -1,14 +0,0 @@ |
|||||
test |
|
||||
support |
|
||||
benchmarks |
|
||||
examples |
|
||||
lib-cov |
|
||||
coverage.html |
|
||||
.gitmodules |
|
||||
.travis.yml |
|
||||
History.md |
|
||||
Makefile |
|
||||
test/ |
|
||||
support/ |
|
||||
benchmarks/ |
|
||||
examples/ |
|
@ -1,22 +0,0 @@ |
|||||
(The MIT License) |
|
||||
|
|
||||
Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca> |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
|
||||
a copy of this software and associated documentation files (the |
|
||||
'Software'), to deal in the Software without restriction, including |
|
||||
without limitation the rights to use, copy, modify, merge, publish, |
|
||||
distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
permit persons to whom the Software is furnished to do so, subject to |
|
||||
the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be |
|
||||
included in all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
@ -1,161 +0,0 @@ |
|||||
# [![Jade - template engine ](http://i.imgur.com/5zf2aVt.png)](http://jade-lang.com/) |
|
||||
|
|
||||
Full documentation is at [jade-lang.com](http://jade-lang.com/) |
|
||||
|
|
||||
Jade is a high performance template engine heavily influenced by [Haml](http://haml-lang.com) |
|
||||
and implemented with JavaScript for [node](http://nodejs.org). For discussion join the [Google Group](http://groups.google.com/group/jadejs). |
|
||||
|
|
||||
You can test drive Jade online [here](http://naltatis.github.com/jade-syntax-docs). |
|
||||
|
|
||||
[![Build Status](https://travis-ci.org/visionmedia/jade.png?branch=master)](https://travis-ci.org/visionmedia/jade) |
|
||||
[![Dependency Status](https://gemnasium.com/visionmedia/jade.png)](https://gemnasium.com/visionmedia/jade) |
|
||||
[![NPM version](https://badge.fury.io/js/jade.png)](http://badge.fury.io/js/jade) |
|
||||
|
|
||||
## Announcements |
|
||||
|
|
||||
**Deprecation of implicit script/style text-only:** |
|
||||
|
|
||||
Jade version 0.31.0 deprecated implicit text only support for scripts and styles. To fix this all you need to do is add a `.` character after the script or style tag. |
|
||||
|
|
||||
It is hoped that this change will make Jade easier for newcomers to learn without affecting the power of the language or leading to excessive verboseness. |
|
||||
|
|
||||
If you have a lot of Jade files that need fixing you can use [fix-jade](https://github.com/ForbesLindesay/fix-jade) to attempt to automate the process. |
|
||||
|
|
||||
**Command line option change:** |
|
||||
|
|
||||
since `v0.31.0`, `-o` is preferred for `--out` where we used `-O` before. |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
via npm: |
|
||||
|
|
||||
```bash |
|
||||
$ npm install jade |
|
||||
``` |
|
||||
|
|
||||
## Syntax |
|
||||
|
|
||||
Jade is a clean, whitespace sensitive syntax for writing html. Here is a simple example: |
|
||||
|
|
||||
```jade |
|
||||
doctype html |
|
||||
html(lang="en") |
|
||||
head |
|
||||
title= pageTitle |
|
||||
script(type='text/javascript'). |
|
||||
if (foo) bar(1 + 5) |
|
||||
body |
|
||||
h1 Jade - node template engine |
|
||||
#container.col |
|
||||
if youAreUsingJade |
|
||||
p You are amazing |
|
||||
else |
|
||||
p Get on it! |
|
||||
p. |
|
||||
Jade is a terse and simple templating language with a |
|
||||
strong focus on performance and powerful features. |
|
||||
``` |
|
||||
|
|
||||
becomes |
|
||||
|
|
||||
|
|
||||
```html |
|
||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<title>Jade</title> |
|
||||
<script type="text/javascript"> |
|
||||
if (foo) bar(1 + 5) |
|
||||
</script> |
|
||||
</head> |
|
||||
<body> |
|
||||
<h1>Jade - node template engine</h1> |
|
||||
<div id="container" class="col"> |
|
||||
<p>You are amazing</p> |
|
||||
<p>Jade is a terse and simple templating language with a strong focus on performance and powerful features.</p> |
|
||||
</div> |
|
||||
</body> |
|
||||
</html> |
|
||||
``` |
|
||||
|
|
||||
The official [jade tutorial](http://jade-lang.com/tutorial/) is a great place to start. While that (and the syntax documentation) is being finished, you can view some of the old documentation [here](https://github.com/visionmedia/jade/blob/master/jade.md) and [here](https://github.com/visionmedia/jade/blob/master/jade-language.md) |
|
||||
|
|
||||
## API |
|
||||
|
|
||||
For full API, see [jade-lang.com/api](http://jade-lang.com/api/) |
|
||||
|
|
||||
```js |
|
||||
var jade = require('jade'); |
|
||||
|
|
||||
// compile |
|
||||
var fn = jade.compile('string of jade', options); |
|
||||
var html = fn(locals); |
|
||||
|
|
||||
// render |
|
||||
var html = jade.render('string of jade', merge(options, locals)); |
|
||||
|
|
||||
// renderFile |
|
||||
var html = jade.renderFile('filename.jade', merge(options, locals)); |
|
||||
``` |
|
||||
|
|
||||
### Options |
|
||||
|
|
||||
- `filename` Used in exceptions, and required when using includes |
|
||||
- `compileDebug` When `false` no debug instrumentation is compiled |
|
||||
- `pretty` Add pretty-indentation whitespace to output _(false by default)_ |
|
||||
|
|
||||
## Browser Support |
|
||||
|
|
||||
The latest version of jade can be download for the browser in standalone form from [here](https://github.com/visionmedia/jade/raw/master/jade.js). It only supports the very latest browsers though, and is a large file. It is recommended that you pre-compile your jade templates to JavaScript and then just use the [runtime.js](https://github.com/visionmedia/jade/raw/master/runtime.js) library on the client. |
|
||||
|
|
||||
To compile a template for use on the client using the command line, do: |
|
||||
|
|
||||
```console |
|
||||
$ jade --client --no-debug filename.jade |
|
||||
``` |
|
||||
|
|
||||
which will produce `filename.js` containing the compiled template. |
|
||||
|
|
||||
## Command Line |
|
||||
|
|
||||
After installing the latest version of [node](http://nodejs.org/), install with: |
|
||||
|
|
||||
```console |
|
||||
$ npm install jade -g |
|
||||
``` |
|
||||
|
|
||||
and run with |
|
||||
|
|
||||
```console |
|
||||
$ jade --help |
|
||||
``` |
|
||||
|
|
||||
## Additional Resources |
|
||||
|
|
||||
Tutorials: |
|
||||
|
|
||||
- cssdeck interactive [Jade syntax tutorial](http://cssdeck.com/labs/learning-the-jade-templating-engine-syntax) |
|
||||
- cssdeck interactive [Jade logic tutorial](http://cssdeck.com/labs/jade-templating-tutorial-codecast-part-2) |
|
||||
- in [Japanese](http://blog.craftgear.net/4f501e97c1347ec934000001/title/10%E5%88%86%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8Bjade%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3) |
|
||||
|
|
||||
|
|
||||
Implementations in other languages: |
|
||||
|
|
||||
- [php](http://github.com/everzet/jade.php) |
|
||||
- [scala](http://scalate.fusesource.org/versions/snapshot/documentation/scaml-reference.html) |
|
||||
- [ruby](https://github.com/slim-template/slim) |
|
||||
- [python](https://github.com/SyrusAkbary/pyjade) |
|
||||
- [java](https://github.com/neuland/jade4j) |
|
||||
|
|
||||
Other: |
|
||||
|
|
||||
- [Emacs Mode](https://github.com/brianc/jade-mode) |
|
||||
- [Vim Syntax](https://github.com/digitaltoad/vim-jade) |
|
||||
- [TextMate Bundle](http://github.com/miksago/jade-tmbundle) |
|
||||
- [Coda/SubEtha syntax Mode](https://github.com/aaronmccall/jade.mode) |
|
||||
- [Screencasts](http://tjholowaychuk.com/post/1004255394/jade-screencast-template-engine-for-nodejs) |
|
||||
- [html2jade](https://github.com/donpark/html2jade) converter |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
MIT |
|
1285
node_modules/jade/Readme_zh-cn.md
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,16 +0,0 @@ |
|||||
{ |
|
||||
"name": "jade", |
|
||||
"repo": "visionmedia/jade", |
|
||||
"description": "Jade template runtime", |
|
||||
"version": "1.1.5", |
|
||||
"keywords": [ |
|
||||
"template" |
|
||||
], |
|
||||
"dependencies": {}, |
|
||||
"development": {}, |
|
||||
"license": "MIT", |
|
||||
"scripts": [ |
|
||||
"lib/runtime.js" |
|
||||
], |
|
||||
"main": "lib/runtime.js" |
|
||||
} |
|
@ -1,4 +0,0 @@ |
|||||
|
|
||||
module.exports = process.env.JADE_COV |
|
||||
? require('./lib-cov/jade') |
|
||||
: require('./lib/jade'); |
|
1023
node_modules/jade/jade-language.md
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
22894
node_modules/jade/jade.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,510 +0,0 @@ |
|||||
|
|
||||
# Jade |
|
||||
|
|
||||
The jade template engine for node.js |
|
||||
|
|
||||
## Synopsis |
|
||||
|
|
||||
jade [-h|--help] [-v|--version] [-o|--obj STR] |
|
||||
[-O|--out DIR] [-p|--path PATH] [-P|--pretty] |
|
||||
[-c|--client] [-D|--no-debug] |
|
||||
|
|
||||
## Examples |
|
||||
|
|
||||
translate jade the templates dir |
|
||||
|
|
||||
$ jade templates |
|
||||
|
|
||||
create {foo,bar}.html |
|
||||
|
|
||||
$ jade {foo,bar}.jade |
|
||||
|
|
||||
jade over stdio |
|
||||
|
|
||||
$ jade < my.jade > my.html |
|
||||
|
|
||||
jade over s |
|
||||
|
|
||||
$ echo "h1 Jade!" | jade |
|
||||
|
|
||||
foo, bar dirs rendering to /tmp |
|
||||
|
|
||||
$ jade foo bar --out /tmp |
|
||||
|
|
||||
compile client-side templates without debugging |
|
||||
instrumentation, making the output javascript |
|
||||
very light-weight. This requires runtime.js |
|
||||
in your projects. |
|
||||
|
|
||||
$ jade --client --no-debug < my.jade |
|
||||
|
|
||||
## Tags |
|
||||
|
|
||||
Tags are simply nested via whitespace, closing |
|
||||
tags defined for you. These indents are called "blocks". |
|
||||
|
|
||||
ul |
|
||||
li |
|
||||
a Foo |
|
||||
li |
|
||||
a Bar |
|
||||
|
|
||||
You may have several tags in one "block": |
|
||||
|
|
||||
ul |
|
||||
li |
|
||||
a Foo |
|
||||
a Bar |
|
||||
a Baz |
|
||||
|
|
||||
## Self-closing Tags |
|
||||
|
|
||||
Some tags are flagged as self-closing by default, such |
|
||||
as `meta`, `link`, and so on. To explicitly self-close |
|
||||
a tag simply append the `/` character: |
|
||||
|
|
||||
foo/ |
|
||||
foo(bar='baz')/ |
|
||||
|
|
||||
Would yield: |
|
||||
|
|
||||
<foo/> |
|
||||
<foo bar="baz"/> |
|
||||
|
|
||||
## Attributes |
|
||||
|
|
||||
Tag attributes look similar to HTML, however |
|
||||
the values are regular JavaScript, here are |
|
||||
some examples: |
|
||||
|
|
||||
a(href='google.com') Google |
|
||||
a(class='button', href='google.com') Google |
|
||||
|
|
||||
As mentioned the attribute values are just JavaScript, |
|
||||
this means ternary operations and other JavaScript expressions |
|
||||
work just fine: |
|
||||
|
|
||||
body(class=user.authenticated ? 'authenticated' : 'anonymous') |
|
||||
a(href=user.website || 'http://google.com') |
|
||||
|
|
||||
Multiple lines work too: |
|
||||
|
|
||||
input(type='checkbox', |
|
||||
name='agreement', |
|
||||
checked) |
|
||||
|
|
||||
Multiple lines without the comma work fine: |
|
||||
|
|
||||
input(type='checkbox' |
|
||||
name='agreement' |
|
||||
checked) |
|
||||
|
|
||||
Funky whitespace? fine: |
|
||||
|
|
||||
input( |
|
||||
type='checkbox' |
|
||||
name='agreement' |
|
||||
checked) |
|
||||
|
|
||||
## Boolean attributes |
|
||||
|
|
||||
Boolean attributes are mirrored by Jade, and accept |
|
||||
bools, aka _true_ or _false_. When no value is specified |
|
||||
_true_ is assumed. For example: |
|
||||
|
|
||||
input(type="checkbox", checked) |
|
||||
// => "<input type="checkbox" checked="checked" />" |
|
||||
|
|
||||
For example if the checkbox was for an agreement, perhaps `user.agreed` |
|
||||
was _true_ the following would also output 'checked="checked"': |
|
||||
|
|
||||
input(type="checkbox", checked=user.agreed) |
|
||||
|
|
||||
## Class attributes |
|
||||
|
|
||||
The _class_ attribute accepts an array of classes, |
|
||||
this can be handy when generated from a javascript |
|
||||
function etc: |
|
||||
|
|
||||
classes = ['foo', 'bar', 'baz'] |
|
||||
a(class=classes) |
|
||||
// => "<a class="foo bar baz"></a>" |
|
||||
|
|
||||
## Class literal |
|
||||
|
|
||||
Classes may be defined using a ".CLASSNAME" syntax: |
|
||||
|
|
||||
.button |
|
||||
// => "<div class="button"></div>" |
|
||||
|
|
||||
Or chained: |
|
||||
|
|
||||
.large.button |
|
||||
// => "<div class="large button"></div>" |
|
||||
|
|
||||
The previous defaulted to divs, however you |
|
||||
may also specify the tag type: |
|
||||
|
|
||||
h1.title My Title |
|
||||
// => "<h1 class="title">My Title</h1>" |
|
||||
|
|
||||
## Id literal |
|
||||
|
|
||||
Much like the class literal there's an id literal: |
|
||||
|
|
||||
#user-1 |
|
||||
// => "<div id="user-1"></div>" |
|
||||
|
|
||||
Again we may specify the tag as well: |
|
||||
|
|
||||
ul#menu |
|
||||
li: a(href='/home') Home |
|
||||
li: a(href='/store') Store |
|
||||
li: a(href='/contact') Contact |
|
||||
|
|
||||
Finally all of these may be used in any combination, |
|
||||
the following are all valid tags: |
|
||||
|
|
||||
a.button#contact(style: 'color: red') Contact |
|
||||
a.button(style: 'color: red')#contact Contact |
|
||||
a(style: 'color: red').button#contact Contact |
|
||||
|
|
||||
## Block expansion |
|
||||
|
|
||||
Jade supports the concept of "block expansion", in which |
|
||||
using a trailing ":" after a tag will inject a block: |
|
||||
|
|
||||
ul |
|
||||
li: a Foo |
|
||||
li: a Bar |
|
||||
li: a Baz |
|
||||
|
|
||||
## Text |
|
||||
|
|
||||
Arbitrary text may follow tags: |
|
||||
|
|
||||
p Welcome to my site |
|
||||
|
|
||||
yields: |
|
||||
|
|
||||
<p>Welcome to my site</p> |
|
||||
|
|
||||
## Pipe text |
|
||||
|
|
||||
Another form of text is "pipe" text. Pipes act |
|
||||
as the text margin for large bodies of text. |
|
||||
|
|
||||
p |
|
||||
| This is a large |
|
||||
| body of text for |
|
||||
| this tag. |
|
||||
| |
|
||||
| Nothing too |
|
||||
| exciting. |
|
||||
|
|
||||
yields: |
|
||||
|
|
||||
<p>This is a large |
|
||||
body of text for |
|
||||
this tag. |
|
||||
|
|
||||
Nothing too |
|
||||
exciting. |
|
||||
</p> |
|
||||
|
|
||||
Using pipes we can also specify regular Jade tags |
|
||||
within the text: |
|
||||
|
|
||||
p |
|
||||
| Click to visit |
|
||||
a(href='http://google.com') Google |
|
||||
| if you want. |
|
||||
|
|
||||
## Text only tags |
|
||||
|
|
||||
As an alternative to pipe text you may add |
|
||||
a trailing "." to indicate that the block |
|
||||
contains nothing but plain-text, no tags: |
|
||||
|
|
||||
p. |
|
||||
This is a large |
|
||||
body of text for |
|
||||
this tag. |
|
||||
|
|
||||
Nothing too |
|
||||
exciting. |
|
||||
|
|
||||
Some tags are text-only by default, for example |
|
||||
_script_, _textarea_, and _style_ tags do not |
|
||||
contain nested HTML so Jade implies the trailing ".": |
|
||||
|
|
||||
script |
|
||||
if (foo) { |
|
||||
bar(); |
|
||||
} |
|
||||
|
|
||||
style |
|
||||
body { |
|
||||
padding: 50px; |
|
||||
font: 14px Helvetica; |
|
||||
} |
|
||||
|
|
||||
## Template script tags |
|
||||
|
|
||||
Sometimes it's useful to define HTML in script |
|
||||
tags using Jade, typically for client-side templates. |
|
||||
|
|
||||
To do this simply give the _script_ tag an arbitrary |
|
||||
_type_ attribute such as _text/x-template_: |
|
||||
|
|
||||
script(type='text/template') |
|
||||
h1 Look! |
|
||||
p Jade still works in here! |
|
||||
|
|
||||
## Interpolation |
|
||||
|
|
||||
Both plain-text and piped-text support interpolation, |
|
||||
which comes in two forms, escapes and non-escaped. The |
|
||||
following will output the _user.name_ in the paragraph |
|
||||
but HTML within it will be escaped to prevent XSS attacks: |
|
||||
|
|
||||
p Welcome #{user.name} |
|
||||
|
|
||||
The following syntax is identical however it will _not_ escape |
|
||||
HTML, and should only be used with strings that you trust: |
|
||||
|
|
||||
p Welcome !{user.name} |
|
||||
|
|
||||
## Inline HTML |
|
||||
|
|
||||
Sometimes constructing small inline snippets of HTML |
|
||||
in Jade can be annoying, luckily we can add plain |
|
||||
HTML as well: |
|
||||
|
|
||||
p Welcome <em>#{user.name}</em> |
|
||||
|
|
||||
## Code |
|
||||
|
|
||||
To buffer output with Jade simply use _=_ at the beginning |
|
||||
of a line or after a tag. This method escapes any HTML |
|
||||
present in the string. |
|
||||
|
|
||||
p= user.description |
|
||||
|
|
||||
To buffer output unescaped use the _!=_ variant, but again |
|
||||
be careful of XSS. |
|
||||
|
|
||||
p!= user.description |
|
||||
|
|
||||
The final way to mess with JavaScript code in Jade is the unbuffered |
|
||||
_-_, which can be used for conditionals, defining variables etc: |
|
||||
|
|
||||
- var user = { description: 'foo bar baz' } |
|
||||
#user |
|
||||
- if (user.description) { |
|
||||
h2 Description |
|
||||
p.description= user.description |
|
||||
- } |
|
||||
|
|
||||
When compiled blocks are wrapped in anonymous functions, so the |
|
||||
following is also valid, without braces: |
|
||||
|
|
||||
- var user = { description: 'foo bar baz' } |
|
||||
#user |
|
||||
- if (user.description) |
|
||||
h2 Description |
|
||||
p.description= user.description |
|
||||
|
|
||||
If you really want you could even use `.forEach()` and others: |
|
||||
|
|
||||
- users.forEach(function(user){ |
|
||||
.user |
|
||||
h2= user.name |
|
||||
p User #{user.name} is #{user.age} years old |
|
||||
- }) |
|
||||
|
|
||||
Taking this further Jade provides some syntax for conditionals, |
|
||||
iteration, switch statements etc. Let's look at those next! |
|
||||
|
|
||||
## Assignment |
|
||||
|
|
||||
Jade's first-class assignment is simple, simply use the _=_ |
|
||||
operator and Jade will _var_ it for you. The following are equivalent: |
|
||||
|
|
||||
- var user = { name: 'tobi' } |
|
||||
user = { name: 'tobi' } |
|
||||
|
|
||||
## Conditionals |
|
||||
|
|
||||
Jade's first-class conditional syntax allows for optional |
|
||||
parenthesis, and you may now omit the leading _-_ otherwise |
|
||||
it's identical, still just regular javascript: |
|
||||
|
|
||||
user = { description: 'foo bar baz' } |
|
||||
#user |
|
||||
if user.description |
|
||||
h2 Description |
|
||||
p.description= user.description |
|
||||
|
|
||||
Jade provides the negated version, _unless_ as well, the following |
|
||||
are equivalent: |
|
||||
|
|
||||
- if (!(user.isAnonymous)) |
|
||||
p You're logged in as #{user.name} |
|
||||
|
|
||||
unless user.isAnonymous |
|
||||
p You're logged in as #{user.name} |
|
||||
|
|
||||
## Iteration |
|
||||
|
|
||||
JavaScript's _for_ loops don't look very declarative, so Jade |
|
||||
also provides its own _for_ loop construct, aliased as _each_: |
|
||||
|
|
||||
for user in users |
|
||||
.user |
|
||||
h2= user.name |
|
||||
p user #{user.name} is #{user.age} year old |
|
||||
|
|
||||
As mentioned _each_ is identical: |
|
||||
|
|
||||
each user in users |
|
||||
.user |
|
||||
h2= user.name |
|
||||
|
|
||||
If necessary the index is available as well: |
|
||||
|
|
||||
for user, i in users |
|
||||
.user(class='user-#{i}') |
|
||||
h2= user.name |
|
||||
|
|
||||
Remember, it's just JavaScript: |
|
||||
|
|
||||
ul#letters |
|
||||
for letter in ['a', 'b', 'c'] |
|
||||
li= letter |
|
||||
|
|
||||
## Mixins |
|
||||
|
|
||||
Mixins provide a way to define jade "functions" which "mix in" |
|
||||
their contents when called. This is useful for abstracting |
|
||||
out large fragments of Jade. |
|
||||
|
|
||||
The simplest possible mixin which accepts no arguments might |
|
||||
look like this: |
|
||||
|
|
||||
mixin hello |
|
||||
p Hello |
|
||||
|
|
||||
You use a mixin by placing `+` before the name: |
|
||||
|
|
||||
+hello |
|
||||
|
|
||||
For something a little more dynamic, mixins can take |
|
||||
arguments, the mixin itself is converted to a javascript |
|
||||
function internally: |
|
||||
|
|
||||
mixin hello(user) |
|
||||
p Hello #{user} |
|
||||
|
|
||||
+hello('Tobi') |
|
||||
|
|
||||
Yields: |
|
||||
|
|
||||
<p>Hello Tobi</p> |
|
||||
|
|
||||
Mixins may optionally take blocks, when a block is passed |
|
||||
its contents becomes the implicit `block` argument. For |
|
||||
example here is a mixin passed a block, and also invoked |
|
||||
without passing a block: |
|
||||
|
|
||||
mixin article(title) |
|
||||
.article |
|
||||
.article-wrapper |
|
||||
h1= title |
|
||||
if block |
|
||||
block |
|
||||
else |
|
||||
p No content provided |
|
||||
|
|
||||
+article('Hello world') |
|
||||
|
|
||||
+article('Hello world') |
|
||||
p This is my |
|
||||
p Amazing article |
|
||||
|
|
||||
yields: |
|
||||
|
|
||||
<div class="article"> |
|
||||
<div class="article-wrapper"> |
|
||||
<h1>Hello world</h1> |
|
||||
<p>No content provided</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="article"> |
|
||||
<div class="article-wrapper"> |
|
||||
<h1>Hello world</h1> |
|
||||
<p>This is my</p> |
|
||||
<p>Amazing article</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
Mixins can even take attributes, just like a tag. When |
|
||||
attributes are passed they become the implicit `attributes` |
|
||||
argument. Individual attributes can be accessed just like |
|
||||
normal object properties: |
|
||||
|
|
||||
mixin centered |
|
||||
.centered(class=attributes.class) |
|
||||
block |
|
||||
|
|
||||
+centered.bold Hello world |
|
||||
|
|
||||
+centered.red |
|
||||
p This is my |
|
||||
p Amazing article |
|
||||
|
|
||||
yields: |
|
||||
|
|
||||
<div class="centered bold">Hello world</div> |
|
||||
<div class="centered red"> |
|
||||
<p>This is my</p> |
|
||||
<p>Amazing article</p> |
|
||||
</div> |
|
||||
|
|
||||
If you use `attributes` directly, *all* passed attributes |
|
||||
get used: |
|
||||
|
|
||||
mixin link |
|
||||
a.menu(attributes) |
|
||||
block |
|
||||
|
|
||||
+link.highlight(href='#top') Top |
|
||||
+link#sec1.plain(href='#section1') Section 1 |
|
||||
+link#sec2.plain(href='#section2') Section 2 |
|
||||
|
|
||||
yields: |
|
||||
|
|
||||
<a href="#top" class="highlight menu">Top</a> |
|
||||
<a id="sec1" href="#section1" class="plain menu">Section 1</a> |
|
||||
<a id="sec2" href="#section2" class="plain menu">Section 2</a> |
|
||||
|
|
||||
If you pass arguments, they must directly follow the mixin: |
|
||||
|
|
||||
mixin list(arr) |
|
||||
if block |
|
||||
.title |
|
||||
block |
|
||||
ul(attributes) |
|
||||
each item in arr |
|
||||
li= item |
|
||||
|
|
||||
+list(['foo', 'bar', 'baz'])(id='myList', class='bold') |
|
||||
|
|
||||
yields: |
|
||||
|
|
||||
<ul id="myList" class="bold"> |
|
||||
<li>foo</li> |
|
||||
<li>bar</li> |
|
||||
<li>baz</li> |
|
||||
</ul> |
|
@ -1,672 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var nodes = require('./nodes'); |
|
||||
var filters = require('./filters'); |
|
||||
var doctypes = require('./doctypes'); |
|
||||
var selfClosing = require('./self-closing'); |
|
||||
var runtime = require('./runtime'); |
|
||||
var utils = require('./utils'); |
|
||||
var parseJSExpression = require('character-parser').parseMax; |
|
||||
var isConstant = require('constantinople'); |
|
||||
var toConstant = require('constantinople').toConstant; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Initialize `Compiler` with the given `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @param {Object} options |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Compiler = module.exports = function Compiler(node, options) { |
|
||||
this.options = options = options || {}; |
|
||||
this.node = node; |
|
||||
this.hasCompiledDoctype = false; |
|
||||
this.hasCompiledTag = false; |
|
||||
this.pp = options.pretty || false; |
|
||||
this.debug = false !== options.compileDebug; |
|
||||
this.indents = 0; |
|
||||
this.parentIndents = 0; |
|
||||
this.terse = false; |
|
||||
if (options.doctype) this.setDoctype(options.doctype); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Compiler prototype. |
|
||||
*/ |
|
||||
|
|
||||
Compiler.prototype = { |
|
||||
|
|
||||
/** |
|
||||
* Compile parse tree to JavaScript. |
|
||||
* |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
compile: function(){ |
|
||||
this.buf = []; |
|
||||
if (this.pp) this.buf.push("jade.indent = [];"); |
|
||||
this.lastBufferedIdx = -1; |
|
||||
this.visit(this.node); |
|
||||
return this.buf.join('\n'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Sets the default doctype `name`. Sets terse mode to `true` when |
|
||||
* html 5 is used, causing self-closing tags to end with ">" vs "/>", |
|
||||
* and boolean attributes are not mirrored. |
|
||||
* |
|
||||
* @param {string} name |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
setDoctype: function(name){ |
|
||||
name = name || 'default'; |
|
||||
this.doctype = doctypes[name.toLowerCase()] || '<!DOCTYPE ' + name + '>'; |
|
||||
this.terse = this.doctype.toLowerCase() == '<!doctype html>'; |
|
||||
this.xml = 0 == this.doctype.indexOf('<?xml'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Buffer the given `str` exactly as is or with interpolation |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {Boolean} interpolate |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
buffer: function (str, interpolate) { |
|
||||
var self = this; |
|
||||
if (interpolate) { |
|
||||
var match = /(\\)?([#!]){((?:.|\n)*)$/.exec(str); |
|
||||
if (match) { |
|
||||
this.buffer(str.substr(0, match.index), false); |
|
||||
if (match[1]) { // escape
|
|
||||
this.buffer(match[2] + '{', false); |
|
||||
this.buffer(match[3], true); |
|
||||
return; |
|
||||
} else { |
|
||||
try { |
|
||||
var rest = match[3]; |
|
||||
var range = parseJSExpression(rest); |
|
||||
var code = ('!' == match[2] ? '' : 'jade.escape') + "((jade.interp = " + range.src + ") == null ? '' : jade.interp)"; |
|
||||
} catch (ex) { |
|
||||
throw ex; |
|
||||
//didn't match, just as if escaped
|
|
||||
this.buffer(match[2] + '{', false); |
|
||||
this.buffer(match[3], true); |
|
||||
return; |
|
||||
} |
|
||||
this.bufferExpression(code); |
|
||||
this.buffer(rest.substr(range.end + 1), true); |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
str = JSON.stringify(str); |
|
||||
str = str.substr(1, str.length - 2); |
|
||||
|
|
||||
if (this.lastBufferedIdx == this.buf.length) { |
|
||||
if (this.lastBufferedType === 'code') this.lastBuffered += ' + "'; |
|
||||
this.lastBufferedType = 'text'; |
|
||||
this.lastBuffered += str; |
|
||||
this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + '");' |
|
||||
} else { |
|
||||
this.buf.push('buf.push("' + str + '");'); |
|
||||
this.lastBufferedType = 'text'; |
|
||||
this.bufferStartChar = '"'; |
|
||||
this.lastBuffered = str; |
|
||||
this.lastBufferedIdx = this.buf.length; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Buffer the given `src` so it is evaluated at run time |
|
||||
* |
|
||||
* @param {String} src |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
bufferExpression: function (src) { |
|
||||
var fn = Function('', 'return (' + src + ');'); |
|
||||
if (isConstant(src)) { |
|
||||
return this.buffer(fn(), false) |
|
||||
} |
|
||||
if (this.lastBufferedIdx == this.buf.length) { |
|
||||
if (this.lastBufferedType === 'text') this.lastBuffered += '"'; |
|
||||
this.lastBufferedType = 'code'; |
|
||||
this.lastBuffered += ' + (' + src + ')'; |
|
||||
this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + ');' |
|
||||
} else { |
|
||||
this.buf.push('buf.push(' + src + ');'); |
|
||||
this.lastBufferedType = 'code'; |
|
||||
this.bufferStartChar = ''; |
|
||||
this.lastBuffered = '(' + src + ')'; |
|
||||
this.lastBufferedIdx = this.buf.length; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Buffer an indent based on the current `indent` |
|
||||
* property and an additional `offset`. |
|
||||
* |
|
||||
* @param {Number} offset |
|
||||
* @param {Boolean} newline |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
prettyIndent: function(offset, newline){ |
|
||||
offset = offset || 0; |
|
||||
newline = newline ? '\n' : ''; |
|
||||
this.buffer(newline + Array(this.indents + offset).join(' ')); |
|
||||
if (this.parentIndents) |
|
||||
this.buf.push("buf.push.apply(buf, jade.indent);"); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visit: function(node){ |
|
||||
var debug = this.debug; |
|
||||
|
|
||||
if (debug) { |
|
||||
this.buf.push('jade_debug.unshift({ lineno: ' + node.line |
|
||||
+ ', filename: ' + (node.filename |
|
||||
? JSON.stringify(node.filename) |
|
||||
: 'jade_debug[0].filename') |
|
||||
+ ' });'); |
|
||||
} |
|
||||
|
|
||||
// Massive hack to fix our context
|
|
||||
// stack for - else[ if] etc
|
|
||||
if (false === node.debug && this.debug) { |
|
||||
this.buf.pop(); |
|
||||
this.buf.pop(); |
|
||||
} |
|
||||
|
|
||||
this.visitNode(node); |
|
||||
|
|
||||
if (debug) this.buf.push('jade_debug.shift();'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitNode: function(node){ |
|
||||
return this['visit' + node.type](node); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit case `node`. |
|
||||
* |
|
||||
* @param {Literal} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitCase: function(node){ |
|
||||
var _ = this.withinCase; |
|
||||
this.withinCase = true; |
|
||||
this.buf.push('switch (' + node.expr + '){'); |
|
||||
this.visit(node.block); |
|
||||
this.buf.push('}'); |
|
||||
this.withinCase = _; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit when `node`. |
|
||||
* |
|
||||
* @param {Literal} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitWhen: function(node){ |
|
||||
if ('default' == node.expr) { |
|
||||
this.buf.push('default:'); |
|
||||
} else { |
|
||||
this.buf.push('case ' + node.expr + ':'); |
|
||||
} |
|
||||
this.visit(node.block); |
|
||||
this.buf.push(' break;'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit literal `node`. |
|
||||
* |
|
||||
* @param {Literal} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitLiteral: function(node){ |
|
||||
this.buffer(node.str); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit all nodes in `block`. |
|
||||
* |
|
||||
* @param {Block} block |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitBlock: function(block){ |
|
||||
var len = block.nodes.length |
|
||||
, escape = this.escape |
|
||||
, pp = this.pp |
|
||||
|
|
||||
// Pretty print multi-line text
|
|
||||
if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText) |
|
||||
this.prettyIndent(1, true); |
|
||||
|
|
||||
for (var i = 0; i < len; ++i) { |
|
||||
// Pretty print text
|
|
||||
if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText) |
|
||||
this.prettyIndent(1, false); |
|
||||
|
|
||||
this.visit(block.nodes[i]); |
|
||||
// Multiple text nodes are separated by newlines
|
|
||||
if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText) |
|
||||
this.buffer('\n'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit a mixin's `block` keyword. |
|
||||
* |
|
||||
* @param {MixinBlock} block |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitMixinBlock: function(block){ |
|
||||
if (this.pp) this.buf.push("jade.indent.push('" + Array(this.indents + 1).join(' ') + "');"); |
|
||||
this.buf.push('block && block();'); |
|
||||
if (this.pp) this.buf.push("jade.indent.pop();"); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `doctype`. Sets terse mode to `true` when html 5 |
|
||||
* is used, causing self-closing tags to end with ">" vs "/>", |
|
||||
* and boolean attributes are not mirrored. |
|
||||
* |
|
||||
* @param {Doctype} doctype |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitDoctype: function(doctype){ |
|
||||
if (doctype && (doctype.val || !this.doctype)) { |
|
||||
this.setDoctype(doctype.val || 'default'); |
|
||||
} |
|
||||
|
|
||||
if (this.doctype) this.buffer(this.doctype); |
|
||||
this.hasCompiledDoctype = true; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `mixin`, generating a function that |
|
||||
* may be called within the template. |
|
||||
* |
|
||||
* @param {Mixin} mixin |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitMixin: function(mixin){ |
|
||||
var name = 'jade_mixins['; |
|
||||
var args = mixin.args || ''; |
|
||||
var block = mixin.block; |
|
||||
var attrs = mixin.attrs; |
|
||||
var attrsBlocks = mixin.attributeBlocks; |
|
||||
var pp = this.pp; |
|
||||
|
|
||||
name += (mixin.name[0]=='#' ? mixin.name.substr(2,mixin.name.length-3):'"'+mixin.name+'"')+']'; |
|
||||
|
|
||||
if (mixin.call) { |
|
||||
if (pp) this.buf.push("jade.indent.push('" + Array(this.indents + 1).join(' ') + "');") |
|
||||
if (block || attrs.length || attrsBlocks.length) { |
|
||||
|
|
||||
this.buf.push(name + '.call({'); |
|
||||
|
|
||||
if (block) { |
|
||||
this.buf.push('block: function(){'); |
|
||||
|
|
||||
// Render block with no indents, dynamically added when rendered
|
|
||||
this.parentIndents++; |
|
||||
var _indents = this.indents; |
|
||||
this.indents = 0; |
|
||||
this.visit(mixin.block); |
|
||||
this.indents = _indents; |
|
||||
this.parentIndents--; |
|
||||
|
|
||||
if (attrs.length || attrsBlocks.length) { |
|
||||
this.buf.push('},'); |
|
||||
} else { |
|
||||
this.buf.push('}'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (attrsBlocks.length) { |
|
||||
if (attrs.length) { |
|
||||
var val = this.attrs(attrs); |
|
||||
attrsBlocks.unshift(val); |
|
||||
} |
|
||||
this.buf.push('attributes: jade.merge([' + attrsBlocks.join(',') + '])'); |
|
||||
} else if (attrs.length) { |
|
||||
var val = this.attrs(attrs); |
|
||||
this.buf.push('attributes: ' + val); |
|
||||
} |
|
||||
|
|
||||
if (args) { |
|
||||
this.buf.push('}, ' + args + ');'); |
|
||||
} else { |
|
||||
this.buf.push('});'); |
|
||||
} |
|
||||
|
|
||||
} else { |
|
||||
this.buf.push(name + '(' + args + ');'); |
|
||||
} |
|
||||
if (pp) this.buf.push("jade.indent.pop();") |
|
||||
} else { |
|
||||
this.buf.push(name + ' = function(' + args + '){'); |
|
||||
this.buf.push('var block = (this && this.block), attributes = (this && this.attributes) || {};'); |
|
||||
this.parentIndents++; |
|
||||
this.visit(block); |
|
||||
this.parentIndents--; |
|
||||
this.buf.push('};'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `tag` buffering tag markup, generating |
|
||||
* attributes, visiting the `tag`'s code and block. |
|
||||
* |
|
||||
* @param {Tag} tag |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitTag: function(tag){ |
|
||||
this.indents++; |
|
||||
var name = tag.name |
|
||||
, pp = this.pp |
|
||||
, self = this; |
|
||||
|
|
||||
function bufferName() { |
|
||||
if (tag.buffer) self.bufferExpression(name); |
|
||||
else self.buffer(name); |
|
||||
} |
|
||||
|
|
||||
if ('pre' == tag.name) this.escape = true; |
|
||||
|
|
||||
if (!this.hasCompiledTag) { |
|
||||
if (!this.hasCompiledDoctype && 'html' == name) { |
|
||||
this.visitDoctype(); |
|
||||
} |
|
||||
this.hasCompiledTag = true; |
|
||||
} |
|
||||
|
|
||||
// pretty print
|
|
||||
if (pp && !tag.isInline()) |
|
||||
this.prettyIndent(0, true); |
|
||||
|
|
||||
if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) { |
|
||||
this.buffer('<'); |
|
||||
bufferName(); |
|
||||
this.visitAttributes(tag.attrs, tag.attributeBlocks); |
|
||||
this.terse |
|
||||
? this.buffer('>') |
|
||||
: this.buffer('/>'); |
|
||||
// if it is non-empty throw an error
|
|
||||
if (tag.block && !(tag.block.type === 'Block' && tag.block.nodes.length === 0) |
|
||||
&& tag.block.nodes.some(function (tag) { return tag.type !== 'Text' || !/^\s*$/.test(tag.val)})) { |
|
||||
throw new Error(name + ' is self closing and should not have content.'); |
|
||||
} |
|
||||
} else { |
|
||||
// Optimize attributes buffering
|
|
||||
this.buffer('<'); |
|
||||
bufferName(); |
|
||||
this.visitAttributes(tag.attrs, tag.attributeBlocks); |
|
||||
this.buffer('>'); |
|
||||
if (tag.code) this.visitCode(tag.code); |
|
||||
this.visit(tag.block); |
|
||||
|
|
||||
// pretty print
|
|
||||
if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline()) |
|
||||
this.prettyIndent(0, true); |
|
||||
|
|
||||
this.buffer('</'); |
|
||||
bufferName(); |
|
||||
this.buffer('>'); |
|
||||
} |
|
||||
|
|
||||
if ('pre' == tag.name) this.escape = false; |
|
||||
|
|
||||
this.indents--; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `filter`, throwing when the filter does not exist. |
|
||||
* |
|
||||
* @param {Filter} filter |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitFilter: function(filter){ |
|
||||
var text = filter.block.nodes.map( |
|
||||
function(node){ return node.val; } |
|
||||
).join('\n'); |
|
||||
filter.attrs = filter.attrs || {}; |
|
||||
filter.attrs.filename = this.options.filename; |
|
||||
this.buffer(filters(filter.name, text, filter.attrs), true); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `text` node. |
|
||||
* |
|
||||
* @param {Text} text |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitText: function(text){ |
|
||||
this.buffer(text.val, true); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit a `comment`, only buffering when the buffer flag is set. |
|
||||
* |
|
||||
* @param {Comment} comment |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitComment: function(comment){ |
|
||||
if (!comment.buffer) return; |
|
||||
if (this.pp) this.prettyIndent(1, true); |
|
||||
this.buffer('<!--' + comment.val + '-->'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit a `BlockComment`. |
|
||||
* |
|
||||
* @param {Comment} comment |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitBlockComment: function(comment){ |
|
||||
if (!comment.buffer) return; |
|
||||
if (this.pp) this.prettyIndent(1, true); |
|
||||
this.buffer('<!--' + comment.val); |
|
||||
this.visit(comment.block); |
|
||||
if (this.pp) this.prettyIndent(1, true); |
|
||||
this.buffer('-->'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `code`, respecting buffer / escape flags. |
|
||||
* If the code is followed by a block, wrap it in |
|
||||
* a self-calling function. |
|
||||
* |
|
||||
* @param {Code} code |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitCode: function(code){ |
|
||||
// Wrap code blocks with {}.
|
|
||||
// we only wrap unbuffered code blocks ATM
|
|
||||
// since they are usually flow control
|
|
||||
|
|
||||
// Buffer code
|
|
||||
if (code.buffer) { |
|
||||
var val = code.val.trimLeft(); |
|
||||
val = 'null == (jade.interp = '+val+') ? "" : jade.interp'; |
|
||||
if (code.escape) val = 'jade.escape(' + val + ')'; |
|
||||
this.bufferExpression(val); |
|
||||
} else { |
|
||||
this.buf.push(code.val); |
|
||||
} |
|
||||
|
|
||||
// Block support
|
|
||||
if (code.block) { |
|
||||
if (!code.buffer) this.buf.push('{'); |
|
||||
this.visit(code.block); |
|
||||
if (!code.buffer) this.buf.push('}'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `each` block. |
|
||||
* |
|
||||
* @param {Each} each |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitEach: function(each){ |
|
||||
this.buf.push('' |
|
||||
+ '// iterate ' + each.obj + '\n' |
|
||||
+ ';(function(){\n' |
|
||||
+ ' var $$obj = ' + each.obj + ';\n' |
|
||||
+ ' if (\'number\' == typeof $$obj.length) {\n'); |
|
||||
|
|
||||
if (each.alternative) { |
|
||||
this.buf.push(' if ($$obj.length) {'); |
|
||||
} |
|
||||
|
|
||||
this.buf.push('' |
|
||||
+ ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' |
|
||||
+ ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); |
|
||||
|
|
||||
this.visit(each.block); |
|
||||
|
|
||||
this.buf.push(' }\n'); |
|
||||
|
|
||||
if (each.alternative) { |
|
||||
this.buf.push(' } else {'); |
|
||||
this.visit(each.alternative); |
|
||||
this.buf.push(' }'); |
|
||||
} |
|
||||
|
|
||||
this.buf.push('' |
|
||||
+ ' } else {\n' |
|
||||
+ ' var $$l = 0;\n' |
|
||||
+ ' for (var ' + each.key + ' in $$obj) {\n' |
|
||||
+ ' $$l++;' |
|
||||
+ ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); |
|
||||
|
|
||||
this.visit(each.block); |
|
||||
|
|
||||
this.buf.push(' }\n'); |
|
||||
if (each.alternative) { |
|
||||
this.buf.push(' if ($$l === 0) {'); |
|
||||
this.visit(each.alternative); |
|
||||
this.buf.push(' }'); |
|
||||
} |
|
||||
this.buf.push(' }\n}).call(this);\n'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Visit `attrs`. |
|
||||
* |
|
||||
* @param {Array} attrs |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
visitAttributes: function(attrs, attributeBlocks){ |
|
||||
if (attributeBlocks.length) { |
|
||||
if (attrs.length) { |
|
||||
var val = this.attrs(attrs); |
|
||||
attributeBlocks.unshift(val); |
|
||||
} |
|
||||
this.bufferExpression('jade.attrs(jade.merge([' + attributeBlocks.join(',') + ']), ' + JSON.stringify(this.terse) + ')'); |
|
||||
} else if (attrs.length) { |
|
||||
this.attrs(attrs, true); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Compile attributes. |
|
||||
*/ |
|
||||
|
|
||||
attrs: function(attrs, buffer){ |
|
||||
var buf = []; |
|
||||
var classes = []; |
|
||||
var classEscaping = []; |
|
||||
|
|
||||
attrs.forEach(function(attr){ |
|
||||
var key = attr.name; |
|
||||
var escaped = attr.escaped; |
|
||||
|
|
||||
if (key === 'class') { |
|
||||
classes.push(attr.val); |
|
||||
classEscaping.push(attr.escaped); |
|
||||
} else if (isConstant(attr.val)) { |
|
||||
if (buffer) { |
|
||||
this.buffer(runtime.attr(key, toConstant(attr.val), escaped, this.terse)); |
|
||||
} else { |
|
||||
var val = toConstant(attr.val); |
|
||||
if (escaped && !(key.indexOf('data') === 0 && typeof val !== 'string')) { |
|
||||
val = runtime.escape(val); |
|
||||
} |
|
||||
buf.push(JSON.stringify(key) + ': ' + JSON.stringify(val)); |
|
||||
} |
|
||||
} else { |
|
||||
if (buffer) { |
|
||||
this.bufferExpression('jade.attr("' + key + '", ' + attr.val + ', ' + JSON.stringify(escaped) + ', ' + JSON.stringify(this.terse) + ')'); |
|
||||
} else { |
|
||||
var val = attr.val; |
|
||||
if (escaped && !(key.indexOf('data') === 0)) { |
|
||||
val = 'jade.escape(' + val + ')'; |
|
||||
} else if (escaped) { |
|
||||
val = '(typeof (jade.interp = ' + val + ') == "string" ? jade.escape(jade.interp) : jade.interp)"'; |
|
||||
} |
|
||||
buf.push(JSON.stringify(key) + ': ' + val); |
|
||||
} |
|
||||
} |
|
||||
}.bind(this)); |
|
||||
if (buffer) { |
|
||||
if (classes.every(isConstant)) { |
|
||||
this.buffer(runtime.cls(classes.map(toConstant), classEscaping)); |
|
||||
} else { |
|
||||
this.bufferExpression('jade.cls([' + classes.join(',') + '], ' + JSON.stringify(classEscaping) + ')'); |
|
||||
} |
|
||||
} else { |
|
||||
if (classes.every(isConstant)) { |
|
||||
classes = JSON.stringify(runtime.joinClasses(classes.map(toConstant).map(runtime.joinClasses).map(function (cls, i) { |
|
||||
return classEscaping[i] ? runtime.escape(cls) : cls; |
|
||||
}))); |
|
||||
} else if (classes.length) { |
|
||||
classes = '(jade.interp = ' + JSON.stringify(classEscaping) + ',' + |
|
||||
' jade.joinClasses([' + classes.join(',') + '].map(jade.joinClasses).map(function (cls, i) {' + |
|
||||
' return jade.interp[i] ? jade.escape(cls) : cls' + |
|
||||
' }))' + |
|
||||
')'; |
|
||||
} |
|
||||
if (classes.length) |
|
||||
buf.push('"class": ' + classes); |
|
||||
} |
|
||||
return '{' + buf.join(',') + '}'; |
|
||||
} |
|
||||
}; |
|
@ -1,12 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
module.exports = { |
|
||||
'default': '<!DOCTYPE html>' |
|
||||
, 'xml': '<?xml version="1.0" encoding="utf-8" ?>' |
|
||||
, 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' |
|
||||
, 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' |
|
||||
, 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">' |
|
||||
, '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' |
|
||||
, 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">' |
|
||||
, 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">' |
|
||||
}; |
|
@ -1,14 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
module.exports = filter; |
|
||||
function filter(name, str, options) { |
|
||||
if (typeof filter[name] === 'function') { |
|
||||
var res = filter[name](str, options); |
|
||||
} else { |
|
||||
throw new Error('unknown filter ":' + name + '"'); |
|
||||
} |
|
||||
return res; |
|
||||
} |
|
||||
filter.exists = function (name, str, options) { |
|
||||
return typeof filter[name] === 'function'; |
|
||||
}; |
|
@ -1,18 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var transformers = require('transformers'); |
|
||||
|
|
||||
module.exports = filter; |
|
||||
function filter(name, str, options) { |
|
||||
if (typeof filter[name] === 'function') { |
|
||||
var res = filter[name](str, options); |
|
||||
} else if (transformers[name]) { |
|
||||
var res = transformers[name].renderSync(str, options); |
|
||||
} else { |
|
||||
throw new Error('unknown filter ":' + name + '"'); |
|
||||
} |
|
||||
return res; |
|
||||
} |
|
||||
filter.exists = function (name, str, options) { |
|
||||
return typeof filter[name] === 'function' || transformers[name]; |
|
||||
}; |
|
@ -1,23 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
module.exports = [ |
|
||||
'a' |
|
||||
, 'abbr' |
|
||||
, 'acronym' |
|
||||
, 'b' |
|
||||
, 'br' |
|
||||
, 'code' |
|
||||
, 'em' |
|
||||
, 'font' |
|
||||
, 'i' |
|
||||
, 'img' |
|
||||
, 'ins' |
|
||||
, 'kbd' |
|
||||
, 'map' |
|
||||
, 'samp' |
|
||||
, 'small' |
|
||||
, 'span' |
|
||||
, 'strong' |
|
||||
, 'sub' |
|
||||
, 'sup' |
|
||||
]; |
|
@ -1,323 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
/*! |
|
||||
* Jade |
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> |
|
||||
* MIT Licensed |
|
||||
*/ |
|
||||
|
|
||||
/** |
|
||||
* Module dependencies. |
|
||||
*/ |
|
||||
|
|
||||
var Parser = require('./parser') |
|
||||
, Lexer = require('./lexer') |
|
||||
, Compiler = require('./compiler') |
|
||||
, runtime = require('./runtime') |
|
||||
, addWith = require('with') |
|
||||
, fs = require('fs'); |
|
||||
|
|
||||
/** |
|
||||
* Expose self closing tags. |
|
||||
*/ |
|
||||
|
|
||||
exports.selfClosing = require('./self-closing'); |
|
||||
|
|
||||
/** |
|
||||
* Default supported doctypes. |
|
||||
*/ |
|
||||
|
|
||||
exports.doctypes = require('./doctypes'); |
|
||||
|
|
||||
/** |
|
||||
* Text filters. |
|
||||
*/ |
|
||||
|
|
||||
exports.filters = require('./filters'); |
|
||||
|
|
||||
/** |
|
||||
* Utilities. |
|
||||
*/ |
|
||||
|
|
||||
exports.utils = require('./utils'); |
|
||||
|
|
||||
/** |
|
||||
* Expose `Compiler`. |
|
||||
*/ |
|
||||
|
|
||||
exports.Compiler = Compiler; |
|
||||
|
|
||||
/** |
|
||||
* Expose `Parser`. |
|
||||
*/ |
|
||||
|
|
||||
exports.Parser = Parser; |
|
||||
|
|
||||
/** |
|
||||
* Expose `Lexer`. |
|
||||
*/ |
|
||||
|
|
||||
exports.Lexer = Lexer; |
|
||||
|
|
||||
/** |
|
||||
* Nodes. |
|
||||
*/ |
|
||||
|
|
||||
exports.nodes = require('./nodes'); |
|
||||
|
|
||||
/** |
|
||||
* Jade runtime helpers. |
|
||||
*/ |
|
||||
|
|
||||
exports.runtime = runtime; |
|
||||
|
|
||||
/** |
|
||||
* Template function cache. |
|
||||
*/ |
|
||||
|
|
||||
exports.cache = {}; |
|
||||
|
|
||||
/** |
|
||||
* Parse the given `str` of jade and return a function body. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {Object} options |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function parse(str, options){ |
|
||||
try { |
|
||||
// Parse
|
|
||||
var parser = new (options.parser || Parser)(str, options.filename, options); |
|
||||
|
|
||||
// Compile
|
|
||||
var compiler = new (options.compiler || Compiler)(parser.parse(), options) |
|
||||
, js = compiler.compile(); |
|
||||
|
|
||||
// Debug compiler
|
|
||||
if (options.debug) { |
|
||||
console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' ')); |
|
||||
} |
|
||||
|
|
||||
var globals = options.globals && Array.isArray(options.globals) ? options.globals : []; |
|
||||
|
|
||||
globals.push('jade'); |
|
||||
globals.push('jade_mixins'); |
|
||||
globals.push('jade_debug'); |
|
||||
globals.push('buf'); |
|
||||
|
|
||||
return '' |
|
||||
+ 'var buf = [];\n' |
|
||||
+ 'var jade_mixins = {};\n' |
|
||||
+ (options.self |
|
||||
? 'var self = locals || {};\n' + js |
|
||||
: addWith('locals || {}', '\n' + js, globals)) + ';' |
|
||||
+ 'return buf.join("");'; |
|
||||
} catch (err) { |
|
||||
parser = parser.context(); |
|
||||
runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Compile a `Function` representation of the given jade `str`. |
|
||||
* |
|
||||
* Options: |
|
||||
* |
|
||||
* - `compileDebug` when `false` debugging code is stripped from the compiled |
|
||||
template, when it is explicitly `true`, the source code is included in |
|
||||
the compiled template for better accuracy. |
|
||||
* - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {Options} options |
|
||||
* @return {Function} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.compile = function(str, options){ |
|
||||
var options = options || {} |
|
||||
, filename = options.filename |
|
||||
? JSON.stringify(options.filename) |
|
||||
: 'undefined' |
|
||||
, fn; |
|
||||
|
|
||||
str = String(str); |
|
||||
|
|
||||
if (options.compileDebug !== false) { |
|
||||
fn = [ |
|
||||
'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];' |
|
||||
, 'try {' |
|
||||
, parse(str, options) |
|
||||
, '} catch (err) {' |
|
||||
, ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + JSON.stringify(str) : '') + ');' |
|
||||
, '}' |
|
||||
].join('\n'); |
|
||||
} else { |
|
||||
fn = parse(str, options); |
|
||||
} |
|
||||
fn = new Function('locals, jade', fn) |
|
||||
var res = function(locals){ return fn(locals, Object.create(runtime)) }; |
|
||||
if (options.client) { |
|
||||
res.toString = function () { |
|
||||
var err = new Error('The `client` option is deprecated, use `jade.compileClient`'); |
|
||||
console.error(err.stack || err.message); |
|
||||
return exports.compileClient(str, options); |
|
||||
}; |
|
||||
} |
|
||||
return res; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Compile a JavaScript source representation of the given jade `str`. |
|
||||
* |
|
||||
* Options: |
|
||||
* |
|
||||
* - `compileDebug` When it is `true`, the source code is included in |
|
||||
the compiled template for better error messages. |
|
||||
* - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {Options} options |
|
||||
* @return {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.compileClient = function(str, options){ |
|
||||
var options = options || {} |
|
||||
, filename = options.filename |
|
||||
? JSON.stringify(options.filename) |
|
||||
: 'undefined' |
|
||||
, fn; |
|
||||
|
|
||||
str = String(str); |
|
||||
|
|
||||
if (options.compileDebug) { |
|
||||
options.compileDebug = true; |
|
||||
fn = [ |
|
||||
'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];' |
|
||||
, 'try {' |
|
||||
, parse(str, options) |
|
||||
, '} catch (err) {' |
|
||||
, ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + JSON.stringify(str) + ');' |
|
||||
, '}' |
|
||||
].join('\n'); |
|
||||
} else { |
|
||||
options.compileDebug = false; |
|
||||
fn = parse(str, options); |
|
||||
} |
|
||||
|
|
||||
return 'function template(locals) {\n' + fn + '\n}'; |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Render the given `str` of jade. |
|
||||
* |
|
||||
* Options: |
|
||||
* |
|
||||
* - `cache` enable template caching |
|
||||
* - `filename` filename required for `include` / `extends` and caching |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {Object|Function} options or fn |
|
||||
* @param {Function|undefined} fn |
|
||||
* @returns {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.render = function(str, options, fn){ |
|
||||
// support callback API
|
|
||||
if ('function' == typeof options) { |
|
||||
fn = options, options = undefined; |
|
||||
} |
|
||||
if (typeof fn === 'function') { |
|
||||
var res |
|
||||
try { |
|
||||
res = exports.render(str, options); |
|
||||
} catch (ex) { |
|
||||
return fn(ex); |
|
||||
} |
|
||||
return fn(null, res); |
|
||||
} |
|
||||
|
|
||||
options = options || {}; |
|
||||
|
|
||||
// cache requires .filename
|
|
||||
if (options.cache && !options.filename) { |
|
||||
throw new Error('the "filename" option is required for caching'); |
|
||||
} |
|
||||
|
|
||||
var path = options.filename; |
|
||||
var tmpl = options.cache |
|
||||
? exports.cache[path] || (exports.cache[path] = exports.compile(str, options)) |
|
||||
: exports.compile(str, options); |
|
||||
return tmpl(options); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Render a Jade file at the given `path`. |
|
||||
* |
|
||||
* @param {String} path |
|
||||
* @param {Object|Function} options or callback |
|
||||
* @param {Function|undefined} fn |
|
||||
* @returns {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.renderFile = function(path, options, fn){ |
|
||||
// support callback API
|
|
||||
if ('function' == typeof options) { |
|
||||
fn = options, options = undefined; |
|
||||
} |
|
||||
if (typeof fn === 'function') { |
|
||||
var res |
|
||||
try { |
|
||||
res = exports.renderFile(path, options); |
|
||||
} catch (ex) { |
|
||||
return fn(ex); |
|
||||
} |
|
||||
return fn(null, res); |
|
||||
} |
|
||||
|
|
||||
options = options || {}; |
|
||||
|
|
||||
var key = path + ':string'; |
|
||||
|
|
||||
options.filename = path; |
|
||||
var str = options.cache |
|
||||
? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) |
|
||||
: fs.readFileSync(path, 'utf8'); |
|
||||
return exports.render(str, options); |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Compile a Jade file at the given `path` for use on the client. |
|
||||
* |
|
||||
* @param {String} path |
|
||||
* @param {Object} options |
|
||||
* @returns {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.compileFileClient = function(path, options){ |
|
||||
options = options || {}; |
|
||||
|
|
||||
var key = path + ':string'; |
|
||||
|
|
||||
options.filename = path; |
|
||||
var str = options.cache |
|
||||
? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) |
|
||||
: fs.readFileSync(path, 'utf8'); |
|
||||
|
|
||||
return exports.compileClient(str, options); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Express support. |
|
||||
*/ |
|
||||
|
|
||||
exports.__express = exports.renderFile; |
|
@ -1,865 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var utils = require('./utils'); |
|
||||
var characterParser = require('character-parser'); |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Initialize `Lexer` with the given `str`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {String} filename |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
var Lexer = module.exports = function Lexer(str, filename) { |
|
||||
this.input = str.replace(/\r\n|\r/g, '\n'); |
|
||||
this.filename = filename; |
|
||||
this.deferredTokens = []; |
|
||||
this.lastIndents = 0; |
|
||||
this.lineno = 1; |
|
||||
this.stash = []; |
|
||||
this.indentStack = []; |
|
||||
this.indentRe = null; |
|
||||
this.pipeless = false; |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
function assertExpression(exp) { |
|
||||
//this verifies that a JavaScript expression is valid
|
|
||||
Function('', 'return (' + exp + ')'); |
|
||||
} |
|
||||
function assertNestingCorrect(exp) { |
|
||||
//this verifies that code is properly nested, but allows
|
|
||||
//invalid JavaScript such as the contents of `attributes`
|
|
||||
var res = characterParser(exp) |
|
||||
if (res.isNesting()) { |
|
||||
throw new Error('Nesting must match on expression `' + exp + '`') |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Lexer prototype. |
|
||||
*/ |
|
||||
|
|
||||
Lexer.prototype = { |
|
||||
|
|
||||
/** |
|
||||
* Construct a token with the given `type` and `val`. |
|
||||
* |
|
||||
* @param {String} type |
|
||||
* @param {String} val |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
tok: function(type, val){ |
|
||||
return { |
|
||||
type: type |
|
||||
, line: this.lineno |
|
||||
, val: val |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Consume the given `len` of input. |
|
||||
* |
|
||||
* @param {Number} len |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
consume: function(len){ |
|
||||
this.input = this.input.substr(len); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Scan for `type` with the given `regexp`. |
|
||||
* |
|
||||
* @param {String} type |
|
||||
* @param {RegExp} regexp |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
scan: function(regexp, type){ |
|
||||
var captures; |
|
||||
if (captures = regexp.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
return this.tok(type, captures[1]); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Defer the given `tok`. |
|
||||
* |
|
||||
* @param {Object} tok |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
defer: function(tok){ |
|
||||
this.deferredTokens.push(tok); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Lookahead `n` tokens. |
|
||||
* |
|
||||
* @param {Number} n |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
lookahead: function(n){ |
|
||||
var fetch = n - this.stash.length; |
|
||||
while (fetch-- > 0) this.stash.push(this.next()); |
|
||||
return this.stash[--n]; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Return the indexOf `(` or `{` or `[` / `)` or `}` or `]` delimiters. |
|
||||
* |
|
||||
* @return {Number} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
bracketExpression: function(skip){ |
|
||||
skip = skip || 0; |
|
||||
var start = this.input[skip]; |
|
||||
if (start != '(' && start != '{' && start != '[') throw new Error('unrecognized start character'); |
|
||||
var end = ({'(': ')', '{': '}', '[': ']'})[start]; |
|
||||
var range = characterParser.parseMax(this.input, {start: skip + 1}); |
|
||||
if (this.input[range.end] !== end) throw new Error('start character ' + start + ' does not match end character ' + this.input[range.end]); |
|
||||
return range; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Stashed token. |
|
||||
*/ |
|
||||
|
|
||||
stashed: function() { |
|
||||
return this.stash.length |
|
||||
&& this.stash.shift(); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Deferred token. |
|
||||
*/ |
|
||||
|
|
||||
deferred: function() { |
|
||||
return this.deferredTokens.length |
|
||||
&& this.deferredTokens.shift(); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* end-of-source. |
|
||||
*/ |
|
||||
|
|
||||
eos: function() { |
|
||||
if (this.input.length) return; |
|
||||
if (this.indentStack.length) { |
|
||||
this.indentStack.shift(); |
|
||||
return this.tok('outdent'); |
|
||||
} else { |
|
||||
return this.tok('eos'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Blank line. |
|
||||
*/ |
|
||||
|
|
||||
blank: function() { |
|
||||
var captures; |
|
||||
if (captures = /^\n *\n/.exec(this.input)) { |
|
||||
this.consume(captures[0].length - 1); |
|
||||
++this.lineno; |
|
||||
if (this.pipeless) return this.tok('text', ''); |
|
||||
return this.next(); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Comment. |
|
||||
*/ |
|
||||
|
|
||||
comment: function() { |
|
||||
var captures; |
|
||||
if (captures = /^\/\/(-)?([^\n]*)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var tok = this.tok('comment', captures[2]); |
|
||||
tok.buffer = '-' != captures[1]; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Interpolated tag. |
|
||||
*/ |
|
||||
|
|
||||
interpolation: function() { |
|
||||
if (/^#\{/.test(this.input)) { |
|
||||
var match; |
|
||||
try { |
|
||||
match = this.bracketExpression(1); |
|
||||
} catch (ex) { |
|
||||
return;//not an interpolation expression, just an unmatched open interpolation
|
|
||||
} |
|
||||
|
|
||||
this.consume(match.end + 1); |
|
||||
return this.tok('interpolation', match.src); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Tag. |
|
||||
*/ |
|
||||
|
|
||||
tag: function() { |
|
||||
var captures; |
|
||||
if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var tok, name = captures[1]; |
|
||||
if (':' == name[name.length - 1]) { |
|
||||
name = name.slice(0, -1); |
|
||||
tok = this.tok('tag', name); |
|
||||
this.defer(this.tok(':')); |
|
||||
while (' ' == this.input[0]) this.input = this.input.substr(1); |
|
||||
} else { |
|
||||
tok = this.tok('tag', name); |
|
||||
} |
|
||||
tok.selfClosing = !! captures[2]; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Filter. |
|
||||
*/ |
|
||||
|
|
||||
filter: function() { |
|
||||
return this.scan(/^:([\w\-]+)/, 'filter'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Doctype. |
|
||||
*/ |
|
||||
|
|
||||
doctype: function() { |
|
||||
if (this.scan(/^!!! *([^\n]+)?/, 'doctype')) { |
|
||||
throw new Error('`!!!` is deprecated, you must now use `doctype`'); |
|
||||
} |
|
||||
var node = this.scan(/^(?:doctype) *([^\n]+)?/, 'doctype'); |
|
||||
if (node && node.val && node.val.trim() === '5') { |
|
||||
throw new Error('`doctype 5` is deprecated, you must now use `doctype html`'); |
|
||||
} |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Id. |
|
||||
*/ |
|
||||
|
|
||||
id: function() { |
|
||||
return this.scan(/^#([\w-]+)/, 'id'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Class. |
|
||||
*/ |
|
||||
|
|
||||
className: function() { |
|
||||
return this.scan(/^\.([\w-]+)/, 'class'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Text. |
|
||||
*/ |
|
||||
|
|
||||
text: function() { |
|
||||
return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') || this.scan(/^(<[^\n]*)/, 'text'); |
|
||||
}, |
|
||||
|
|
||||
textFail: function () { |
|
||||
var tok; |
|
||||
if (tok = this.scan(/^([^\.\n][^\n]+)/, 'text')) { |
|
||||
console.warn('Warning: missing space before text for line ' + this.lineno + |
|
||||
' of jade file "' + this.filename + '"'); |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Dot. |
|
||||
*/ |
|
||||
|
|
||||
dot: function() { |
|
||||
return this.scan(/^\./, 'dot'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Extends. |
|
||||
*/ |
|
||||
|
|
||||
"extends": function() { |
|
||||
return this.scan(/^extends? +([^\n]+)/, 'extends'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Block prepend. |
|
||||
*/ |
|
||||
|
|
||||
prepend: function() { |
|
||||
var captures; |
|
||||
if (captures = /^prepend +([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var mode = 'prepend' |
|
||||
, name = captures[1] |
|
||||
, tok = this.tok('block', name); |
|
||||
tok.mode = mode; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Block append. |
|
||||
*/ |
|
||||
|
|
||||
append: function() { |
|
||||
var captures; |
|
||||
if (captures = /^append +([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var mode = 'append' |
|
||||
, name = captures[1] |
|
||||
, tok = this.tok('block', name); |
|
||||
tok.mode = mode; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Block. |
|
||||
*/ |
|
||||
|
|
||||
block: function() { |
|
||||
var captures; |
|
||||
if (captures = /^block\b *(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var mode = captures[1] || 'replace' |
|
||||
, name = captures[2] |
|
||||
, tok = this.tok('block', name); |
|
||||
|
|
||||
tok.mode = mode; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Mixin Block. |
|
||||
*/ |
|
||||
|
|
||||
mixinBlock: function() { |
|
||||
var captures; |
|
||||
if (captures = /^block\s*(\n|$)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length - 1); |
|
||||
return this.tok('mixin-block'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Yield. |
|
||||
*/ |
|
||||
|
|
||||
yield: function() { |
|
||||
return this.scan(/^yield */, 'yield'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Include. |
|
||||
*/ |
|
||||
|
|
||||
include: function() { |
|
||||
return this.scan(/^include +([^\n]+)/, 'include'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Include with filter |
|
||||
*/ |
|
||||
|
|
||||
includeFiltered: function() { |
|
||||
var captures; |
|
||||
if (captures = /^include:([\w\-]+) +([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var filter = captures[1]; |
|
||||
var path = captures[2]; |
|
||||
var tok = this.tok('include', path); |
|
||||
tok.filter = filter; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Case. |
|
||||
*/ |
|
||||
|
|
||||
"case": function() { |
|
||||
return this.scan(/^case +([^\n]+)/, 'case'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* When. |
|
||||
*/ |
|
||||
|
|
||||
when: function() { |
|
||||
return this.scan(/^when +([^:\n]+)/, 'when'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Default. |
|
||||
*/ |
|
||||
|
|
||||
"default": function() { |
|
||||
return this.scan(/^default */, 'default'); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Call mixin. |
|
||||
*/ |
|
||||
|
|
||||
call: function(){ |
|
||||
|
|
||||
var tok, captures; |
|
||||
if (captures = /^\+(([-\w]+)|(#\{))/.exec(this.input)) { |
|
||||
// try to consume simple or interpolated call
|
|
||||
if (captures[2]) { |
|
||||
// simple call
|
|
||||
this.consume(captures[0].length); |
|
||||
tok = this.tok('call', captures[2]); |
|
||||
} else { |
|
||||
// interpolated call
|
|
||||
var match; |
|
||||
try { |
|
||||
match = this.bracketExpression(2); |
|
||||
} catch (ex) { |
|
||||
return;//not an interpolation expression, just an unmatched open interpolation
|
|
||||
} |
|
||||
this.consume(match.end + 1); |
|
||||
assertExpression(match.src); |
|
||||
tok = this.tok('call', '#{'+match.src+'}'); |
|
||||
} |
|
||||
|
|
||||
// Check for args (not attributes)
|
|
||||
if (captures = /^ *\(/.exec(this.input)) { |
|
||||
try { |
|
||||
var range = this.bracketExpression(captures[0].length - 1); |
|
||||
if (!/^ *[-\w]+ *=/.test(range.src)) { // not attributes
|
|
||||
this.consume(range.end + 1); |
|
||||
tok.args = range.src; |
|
||||
} |
|
||||
} catch (ex) { |
|
||||
//not a bracket expcetion, just unmatched open parens
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Mixin. |
|
||||
*/ |
|
||||
|
|
||||
mixin: function(){ |
|
||||
var captures; |
|
||||
if (captures = /^mixin +([-\w]+)(?: *\((.*)\))? */.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var tok = this.tok('mixin', captures[1]); |
|
||||
tok.args = captures[2]; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Conditional. |
|
||||
*/ |
|
||||
|
|
||||
conditional: function() { |
|
||||
var captures; |
|
||||
if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var type = captures[1] |
|
||||
, js = captures[2]; |
|
||||
|
|
||||
switch (type) { |
|
||||
case 'if': |
|
||||
assertExpression(js) |
|
||||
js = 'if (' + js + ')'; |
|
||||
break; |
|
||||
case 'unless': |
|
||||
assertExpression(js) |
|
||||
js = 'if (!(' + js + '))'; |
|
||||
break; |
|
||||
case 'else if': |
|
||||
assertExpression(js) |
|
||||
js = 'else if (' + js + ')'; |
|
||||
break; |
|
||||
case 'else': |
|
||||
if (js && js.trim()) { |
|
||||
throw new Error('`else` cannot have a condition, perhaps you meant `else if`'); |
|
||||
} |
|
||||
js = 'else'; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
return this.tok('code', js); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* While. |
|
||||
*/ |
|
||||
|
|
||||
"while": function() { |
|
||||
var captures; |
|
||||
if (captures = /^while +([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
assertExpression(captures[1]) |
|
||||
return this.tok('code', 'while (' + captures[1] + ')'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Each. |
|
||||
*/ |
|
||||
|
|
||||
each: function() { |
|
||||
var captures; |
|
||||
if (captures = /^(?:- *)?(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * in *([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var tok = this.tok('each', captures[1]); |
|
||||
tok.key = captures[2] || '$index'; |
|
||||
assertExpression(captures[3]) |
|
||||
tok.code = captures[3]; |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Code. |
|
||||
*/ |
|
||||
|
|
||||
code: function() { |
|
||||
var captures; |
|
||||
if (captures = /^(!?=|-)[ \t]*([^\n]+)/.exec(this.input)) { |
|
||||
this.consume(captures[0].length); |
|
||||
var flags = captures[1]; |
|
||||
captures[1] = captures[2]; |
|
||||
var tok = this.tok('code', captures[1]); |
|
||||
tok.escape = flags.charAt(0) === '='; |
|
||||
tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '='; |
|
||||
if (tok.buffer) assertExpression(captures[1]) |
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Attributes. |
|
||||
*/ |
|
||||
|
|
||||
attrs: function() { |
|
||||
if ('(' == this.input.charAt(0)) { |
|
||||
var index = this.bracketExpression().end |
|
||||
, str = this.input.substr(1, index-1) |
|
||||
, tok = this.tok('attrs'); |
|
||||
|
|
||||
assertNestingCorrect(str); |
|
||||
|
|
||||
var quote = ''; |
|
||||
var interpolate = function (attr) { |
|
||||
return attr.replace(/(\\)?#\{(.+)/g, function(_, escape, expr){ |
|
||||
if (escape) return _; |
|
||||
try { |
|
||||
var range = characterParser.parseMax(expr); |
|
||||
if (expr[range.end] !== '}') return _.substr(0, 2) + interpolate(_.substr(2)); |
|
||||
assertExpression(range.src) |
|
||||
return quote + " + (" + range.src + ") + " + quote + interpolate(expr.substr(range.end + 1)); |
|
||||
} catch (ex) { |
|
||||
return _.substr(0, 2) + interpolate(_.substr(2)); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
this.consume(index + 1); |
|
||||
tok.attrs = []; |
|
||||
|
|
||||
var escapedAttr = true |
|
||||
var key = ''; |
|
||||
var val = ''; |
|
||||
var interpolatable = ''; |
|
||||
var state = characterParser.defaultState(); |
|
||||
var loc = 'key'; |
|
||||
var isEndOfAttribute = function (i) { |
|
||||
if (key.trim() === '') return false; |
|
||||
if (i === str.length) return true; |
|
||||
if (loc === 'key') { |
|
||||
if (str[i] === ' ' || str[i] === '\n') { |
|
||||
for (var x = i; x < str.length; x++) { |
|
||||
if (str[x] != ' ' && str[x] != '\n') { |
|
||||
if (str[x] === '=' || str[x] === '!' || str[x] === ',') return false; |
|
||||
else return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return str[i] === ',' |
|
||||
} else if (loc === 'value' && !state.isNesting()) { |
|
||||
try { |
|
||||
Function('', 'return (' + val + ');'); |
|
||||
if (str[i] === ' ' || str[i] === '\n') { |
|
||||
for (var x = i; x < str.length; x++) { |
|
||||
if (str[x] != ' ' && str[x] != '\n') { |
|
||||
if (characterParser.isPunctuator(str[x]) && str[x] != '"' && str[x] != "'") return false; |
|
||||
else return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return str[i] === ','; |
|
||||
} catch (ex) { |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.lineno += str.split("\n").length - 1; |
|
||||
|
|
||||
for (var i = 0; i <= str.length; i++) { |
|
||||
if (isEndOfAttribute(i)) { |
|
||||
val = val.trim(); |
|
||||
if (val) assertExpression(val) |
|
||||
key = key.trim(); |
|
||||
key = key.replace(/^['"]|['"]$/g, ''); |
|
||||
tok.attrs.push({ |
|
||||
name: key, |
|
||||
val: '' == val ? true : val, |
|
||||
escaped: escapedAttr |
|
||||
}); |
|
||||
key = val = ''; |
|
||||
loc = 'key'; |
|
||||
escapedAttr = false; |
|
||||
} else { |
|
||||
switch (loc) { |
|
||||
case 'key-char': |
|
||||
if (str[i] === quote) { |
|
||||
loc = 'key'; |
|
||||
if (i + 1 < str.length && [' ', ',', '!', '=', '\n'].indexOf(str[i + 1]) === -1) |
|
||||
throw new Error('Unexpected character ' + str[i + 1] + ' expected ` `, `\\n`, `,`, `!` or `=`'); |
|
||||
} else if (loc === 'key-char') { |
|
||||
key += str[i]; |
|
||||
} |
|
||||
break; |
|
||||
case 'key': |
|
||||
if (key === '' && (str[i] === '"' || str[i] === "'")) { |
|
||||
loc = 'key-char'; |
|
||||
quote = str[i]; |
|
||||
} else if (str[i] === '!' || str[i] === '=') { |
|
||||
escapedAttr = str[i] !== '!'; |
|
||||
if (str[i] === '!') i++; |
|
||||
if (str[i] !== '=') throw new Error('Unexpected character ' + str[i] + ' expected `=`'); |
|
||||
loc = 'value'; |
|
||||
state = characterParser.defaultState(); |
|
||||
} else { |
|
||||
key += str[i] |
|
||||
} |
|
||||
break; |
|
||||
case 'value': |
|
||||
state = characterParser.parseChar(str[i], state); |
|
||||
if (state.isString()) { |
|
||||
loc = 'string'; |
|
||||
quote = str[i]; |
|
||||
interpolatable = str[i]; |
|
||||
} else { |
|
||||
val += str[i]; |
|
||||
} |
|
||||
break; |
|
||||
case 'string': |
|
||||
state = characterParser.parseChar(str[i], state); |
|
||||
interpolatable += str[i]; |
|
||||
if (!state.isString()) { |
|
||||
loc = 'value'; |
|
||||
val += interpolate(interpolatable); |
|
||||
} |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if ('/' == this.input.charAt(0)) { |
|
||||
this.consume(1); |
|
||||
tok.selfClosing = true; |
|
||||
} |
|
||||
|
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* &attributes block |
|
||||
*/ |
|
||||
attributesBlock: function () { |
|
||||
var captures; |
|
||||
if (/^&attributes\b/.test(this.input)) { |
|
||||
this.consume(11); |
|
||||
var args = this.bracketExpression(); |
|
||||
this.consume(args.end + 1); |
|
||||
return this.tok('&attributes', args.src); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Indent | Outdent | Newline. |
|
||||
*/ |
|
||||
|
|
||||
indent: function() { |
|
||||
var captures, re; |
|
||||
|
|
||||
// established regexp
|
|
||||
if (this.indentRe) { |
|
||||
captures = this.indentRe.exec(this.input); |
|
||||
// determine regexp
|
|
||||
} else { |
|
||||
// tabs
|
|
||||
re = /^\n(\t*) */; |
|
||||
captures = re.exec(this.input); |
|
||||
|
|
||||
// spaces
|
|
||||
if (captures && !captures[1].length) { |
|
||||
re = /^\n( *)/; |
|
||||
captures = re.exec(this.input); |
|
||||
} |
|
||||
|
|
||||
// established
|
|
||||
if (captures && captures[1].length) this.indentRe = re; |
|
||||
} |
|
||||
|
|
||||
if (captures) { |
|
||||
var tok |
|
||||
, indents = captures[1].length; |
|
||||
|
|
||||
++this.lineno; |
|
||||
this.consume(indents + 1); |
|
||||
|
|
||||
if (' ' == this.input[0] || '\t' == this.input[0]) { |
|
||||
throw new Error('Invalid indentation, you can use tabs or spaces but not both'); |
|
||||
} |
|
||||
|
|
||||
// blank line
|
|
||||
if ('\n' == this.input[0]) return this.tok('newline'); |
|
||||
|
|
||||
// outdent
|
|
||||
if (this.indentStack.length && indents < this.indentStack[0]) { |
|
||||
while (this.indentStack.length && this.indentStack[0] > indents) { |
|
||||
this.stash.push(this.tok('outdent')); |
|
||||
this.indentStack.shift(); |
|
||||
} |
|
||||
tok = this.stash.pop(); |
|
||||
// indent
|
|
||||
} else if (indents && indents != this.indentStack[0]) { |
|
||||
this.indentStack.unshift(indents); |
|
||||
tok = this.tok('indent', indents); |
|
||||
// newline
|
|
||||
} else { |
|
||||
tok = this.tok('newline'); |
|
||||
} |
|
||||
|
|
||||
return tok; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Pipe-less text consumed only when |
|
||||
* pipeless is true; |
|
||||
*/ |
|
||||
|
|
||||
pipelessText: function() { |
|
||||
if (this.pipeless) { |
|
||||
if ('\n' == this.input[0]) return; |
|
||||
var i = this.input.indexOf('\n'); |
|
||||
if (-1 == i) i = this.input.length; |
|
||||
var str = this.input.substr(0, i); |
|
||||
this.consume(str.length); |
|
||||
return this.tok('text', str); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* ':' |
|
||||
*/ |
|
||||
|
|
||||
colon: function() { |
|
||||
return this.scan(/^: */, ':'); |
|
||||
}, |
|
||||
|
|
||||
fail: function () { |
|
||||
if (/^ ($|\n)/.test(this.input)) { |
|
||||
this.consume(1); |
|
||||
return this.next(); |
|
||||
} |
|
||||
throw new Error('unexpected text ' + this.input.substr(0, 5)); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Return the next token object, or those |
|
||||
* previously stashed by lookahead. |
|
||||
* |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
advance: function(){ |
|
||||
return this.stashed() |
|
||||
|| this.next(); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Return the next token object. |
|
||||
* |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
next: function() { |
|
||||
return this.deferred() |
|
||||
|| this.blank() |
|
||||
|| this.eos() |
|
||||
|| this.pipelessText() |
|
||||
|| this.yield() |
|
||||
|| this.doctype() |
|
||||
|| this.interpolation() |
|
||||
|| this["case"]() |
|
||||
|| this.when() |
|
||||
|| this["default"]() |
|
||||
|| this["extends"]() |
|
||||
|| this.append() |
|
||||
|| this.prepend() |
|
||||
|| this.block() |
|
||||
|| this.mixinBlock() |
|
||||
|| this.include() |
|
||||
|| this.includeFiltered() |
|
||||
|| this.mixin() |
|
||||
|| this.call() |
|
||||
|| this.conditional() |
|
||||
|| this.each() |
|
||||
|| this["while"]() |
|
||||
|| this.tag() |
|
||||
|| this.filter() |
|
||||
|| this.code() |
|
||||
|| this.id() |
|
||||
|| this.className() |
|
||||
|| this.attrs() |
|
||||
|| this.attributesBlock() |
|
||||
|| this.indent() |
|
||||
|| this.text() |
|
||||
|| this.comment() |
|
||||
|| this.colon() |
|
||||
|| this.dot() |
|
||||
|| this.textFail() |
|
||||
|| this.fail(); |
|
||||
} |
|
||||
}; |
|
@ -1,79 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
var Block = require('./block'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Attrs` node. |
|
||||
* |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Attrs = module.exports = function Attrs() { |
|
||||
this.attributeNames = []; |
|
||||
this.attrs = []; |
|
||||
this.attributeBlocks = []; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Attrs.prototype = Object.create(Node.prototype); |
|
||||
Attrs.prototype.constructor = Attrs; |
|
||||
|
|
||||
Attrs.prototype.type = 'Attrs'; |
|
||||
|
|
||||
/** |
|
||||
* Set attribute `name` to `val`, keep in mind these become |
|
||||
* part of a raw js object literal, so to quote a value you must |
|
||||
* '"quote me"', otherwise or example 'user.name' is literal JavaScript. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @param {String} val |
|
||||
* @param {Boolean} escaped |
|
||||
* @return {Tag} for chaining |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Attrs.prototype.setAttribute = function(name, val, escaped){ |
|
||||
this.attributeNames = this.attributeNames || []; |
|
||||
if (name !== 'class' && this.attributeNames.indexOf(name) !== -1) { |
|
||||
throw new Error('Duplicate attribute "' + name + '" is not allowed.'); |
|
||||
} |
|
||||
this.attributeNames.push(name); |
|
||||
this.attrs.push({ name: name, val: val, escaped: escaped }); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Remove attribute `name` when present. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Attrs.prototype.removeAttribute = function(name){ |
|
||||
for (var i = 0, len = this.attrs.length; i < len; ++i) { |
|
||||
if (this.attrs[i] && this.attrs[i].name == name) { |
|
||||
delete this.attrs[i]; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Get attribute value by `name`. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @return {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Attrs.prototype.getAttribute = function(name){ |
|
||||
for (var i = 0, len = this.attrs.length; i < len; ++i) { |
|
||||
if (this.attrs[i] && this.attrs[i].name == name) { |
|
||||
return this.attrs[i].val; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
Attrs.prototype.addAttributes = function (src) { |
|
||||
this.attributeBlocks.push(src); |
|
||||
}; |
|
@ -1,24 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `BlockComment` with the given `block`. |
|
||||
* |
|
||||
* @param {String} val |
|
||||
* @param {Block} block |
|
||||
* @param {Boolean} buffer |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var BlockComment = module.exports = function BlockComment(val, block, buffer) { |
|
||||
this.block = block; |
|
||||
this.val = val; |
|
||||
this.buffer = buffer; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
BlockComment.prototype = Object.create(Node.prototype); |
|
||||
BlockComment.prototype.constructor = BlockComment; |
|
||||
|
|
||||
BlockComment.prototype.type = 'BlockComment'; |
|
@ -1,112 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a new `Block` with an optional `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Block = module.exports = function Block(node){ |
|
||||
this.nodes = []; |
|
||||
if (node) this.push(node); |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Block.prototype = Object.create(Node.prototype); |
|
||||
Block.prototype.constructor = Block; |
|
||||
|
|
||||
Block.prototype.type = 'Block'; |
|
||||
|
|
||||
/** |
|
||||
* Block flag. |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.isBlock = true; |
|
||||
|
|
||||
/** |
|
||||
* Replace the nodes in `other` with the nodes |
|
||||
* in `this` block. |
|
||||
* |
|
||||
* @param {Block} other |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.replace = function(other){ |
|
||||
other.nodes = this.nodes; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Pust the given `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @return {Number} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.push = function(node){ |
|
||||
return this.nodes.push(node); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Check if this block is empty. |
|
||||
* |
|
||||
* @return {Boolean} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.isEmpty = function(){ |
|
||||
return 0 == this.nodes.length; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Unshift the given `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @return {Number} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.unshift = function(node){ |
|
||||
return this.nodes.unshift(node); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return the "last" block, or the first `yield` node. |
|
||||
* |
|
||||
* @return {Block} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.includeBlock = function(){ |
|
||||
var ret = this |
|
||||
, node; |
|
||||
|
|
||||
for (var i = 0, len = this.nodes.length; i < len; ++i) { |
|
||||
node = this.nodes[i]; |
|
||||
if (node.yield) return node; |
|
||||
else if (node.textOnly) continue; |
|
||||
else if (node.includeBlock) ret = node.includeBlock(); |
|
||||
else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock(); |
|
||||
if (ret.yield) return ret; |
|
||||
} |
|
||||
|
|
||||
return ret; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return a clone of this block. |
|
||||
* |
|
||||
* @return {Block} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Block.prototype.clone = function(){ |
|
||||
var clone = new Block; |
|
||||
for (var i = 0, len = this.nodes.length; i < len; ++i) { |
|
||||
clone.push(this.nodes[i].clone()); |
|
||||
} |
|
||||
return clone; |
|
||||
}; |
|
@ -1,33 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a new `Case` with `expr`. |
|
||||
* |
|
||||
* @param {String} expr |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Case = exports = module.exports = function Case(expr, block){ |
|
||||
this.expr = expr; |
|
||||
this.block = block; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Case.prototype = Object.create(Node.prototype); |
|
||||
Case.prototype.constructor = Case; |
|
||||
|
|
||||
Case.prototype.type = 'Case'; |
|
||||
|
|
||||
var When = exports.When = function When(expr, block){ |
|
||||
this.expr = expr; |
|
||||
this.block = block; |
|
||||
this.debug = false; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
When.prototype = Object.create(Node.prototype); |
|
||||
When.prototype.constructor = When; |
|
||||
|
|
||||
When.prototype.type = 'When'; |
|
@ -1,26 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Code` node with the given code `val`. |
|
||||
* Code may also be optionally buffered and escaped. |
|
||||
* |
|
||||
* @param {String} val |
|
||||
* @param {Boolean} buffer |
|
||||
* @param {Boolean} escape |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Code = module.exports = function Code(val, buffer, escape) { |
|
||||
this.val = val; |
|
||||
this.buffer = buffer; |
|
||||
this.escape = escape; |
|
||||
if (val.match(/^ *else/)) this.debug = false; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Code.prototype = Object.create(Node.prototype); |
|
||||
Code.prototype.constructor = Code; |
|
||||
|
|
||||
Code.prototype.type = 'Code'; // prevent the minifiers removing this
|
|
@ -1,23 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Comment` with the given `val`, optionally `buffer`, |
|
||||
* otherwise the comment may render in the output. |
|
||||
* |
|
||||
* @param {String} val |
|
||||
* @param {Boolean} buffer |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Comment = module.exports = function Comment(val, buffer) { |
|
||||
this.val = val; |
|
||||
this.buffer = buffer; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Comment.prototype = Object.create(Node.prototype); |
|
||||
Comment.prototype.constructor = Comment; |
|
||||
|
|
||||
Comment.prototype.type = 'Comment'; |
|
@ -1,20 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Doctype` with the given `val`. |
|
||||
* |
|
||||
* @param {String} val |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Doctype = module.exports = function Doctype(val) { |
|
||||
this.val = val; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Doctype.prototype = Object.create(Node.prototype); |
|
||||
Doctype.prototype.constructor = Doctype; |
|
||||
|
|
||||
Doctype.prototype.type = 'Doctype'; |
|
@ -1,26 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize an `Each` node, representing iteration |
|
||||
* |
|
||||
* @param {String} obj |
|
||||
* @param {String} val |
|
||||
* @param {String} key |
|
||||
* @param {Block} block |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Each = module.exports = function Each(obj, val, key, block) { |
|
||||
this.obj = obj; |
|
||||
this.val = val; |
|
||||
this.key = key; |
|
||||
this.block = block; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Each.prototype = Object.create(Node.prototype); |
|
||||
Each.prototype.constructor = Each; |
|
||||
|
|
||||
Each.prototype.type = 'Each'; |
|
@ -1,25 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node') |
|
||||
var Block = require('./block'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Filter` node with the given |
|
||||
* filter `name` and `block`. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @param {Block|Node} block |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Filter = module.exports = function Filter(name, block, attrs) { |
|
||||
this.name = name; |
|
||||
this.block = block; |
|
||||
this.attrs = attrs; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Filter.prototype = Object.create(Node.prototype); |
|
||||
Filter.prototype.constructor = Filter; |
|
||||
|
|
||||
Filter.prototype.type = 'Filter'; |
|
@ -1,16 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
exports.Node = require('./node'); |
|
||||
exports.Tag = require('./tag'); |
|
||||
exports.Code = require('./code'); |
|
||||
exports.Each = require('./each'); |
|
||||
exports.Case = require('./case'); |
|
||||
exports.Text = require('./text'); |
|
||||
exports.Block = require('./block'); |
|
||||
exports.MixinBlock = require('./mixin-block'); |
|
||||
exports.Mixin = require('./mixin'); |
|
||||
exports.Filter = require('./filter'); |
|
||||
exports.Comment = require('./comment'); |
|
||||
exports.Literal = require('./literal'); |
|
||||
exports.BlockComment = require('./block-comment'); |
|
||||
exports.Doctype = require('./doctype'); |
|
@ -1,20 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Literal` node with the given `str.
|
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Literal = module.exports = function Literal(str) { |
|
||||
this.str = str; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Literal.prototype = Object.create(Node.prototype); |
|
||||
Literal.prototype.constructor = Literal; |
|
||||
|
|
||||
Literal.prototype.type = 'Literal'; |
|
@ -1,18 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a new `Block` with an optional `node`. |
|
||||
* |
|
||||
* @param {Node} node |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var MixinBlock = module.exports = function MixinBlock(){}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
MixinBlock.prototype = Object.create(Node.prototype); |
|
||||
MixinBlock.prototype.constructor = MixinBlock; |
|
||||
|
|
||||
MixinBlock.prototype.type = 'MixinBlock'; |
|
@ -1,27 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Attrs = require('./attrs'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a new `Mixin` with `name` and `block`. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @param {String} args |
|
||||
* @param {Block} block |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Mixin = module.exports = function Mixin(name, args, block, call){ |
|
||||
this.name = name; |
|
||||
this.args = args; |
|
||||
this.block = block; |
|
||||
this.attrs = []; |
|
||||
this.attributeBlocks = []; |
|
||||
this.call = call; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Attrs`.
|
|
||||
Mixin.prototype = Object.create(Attrs.prototype); |
|
||||
Mixin.prototype.constructor = Mixin; |
|
||||
|
|
||||
Mixin.prototype.type = 'Mixin'; |
|
@ -1,16 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = module.exports = function Node(){}; |
|
||||
|
|
||||
/** |
|
||||
* Clone this node (return itself) |
|
||||
* |
|
||||
* @return {Node} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Node.prototype.clone = function(){ |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
Node.prototype.type = ''; |
|
@ -1,87 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Attrs = require('./attrs'); |
|
||||
var Block = require('./block'); |
|
||||
var inlineTags = require('../inline-tags'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Tag` node with the given tag `name` and optional `block`. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @param {Block} block |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Tag = module.exports = function Tag(name, block) { |
|
||||
this.name = name; |
|
||||
this.attrs = []; |
|
||||
this.attributeBlocks = []; |
|
||||
this.block = block || new Block; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Attrs`.
|
|
||||
Tag.prototype = Object.create(Attrs.prototype); |
|
||||
Tag.prototype.constructor = Tag; |
|
||||
|
|
||||
Tag.prototype.type = 'Tag'; |
|
||||
|
|
||||
/** |
|
||||
* Clone this tag. |
|
||||
* |
|
||||
* @return {Tag} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Tag.prototype.clone = function(){ |
|
||||
var clone = new Tag(this.name, this.block.clone()); |
|
||||
clone.line = this.line; |
|
||||
clone.attrs = this.attrs; |
|
||||
clone.textOnly = this.textOnly; |
|
||||
return clone; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Check if this tag is an inline tag. |
|
||||
* |
|
||||
* @return {Boolean} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Tag.prototype.isInline = function(){ |
|
||||
return ~inlineTags.indexOf(this.name); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Check if this tag's contents can be inlined. Used for pretty printing. |
|
||||
* |
|
||||
* @return {Boolean} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Tag.prototype.canInline = function(){ |
|
||||
var nodes = this.block.nodes; |
|
||||
|
|
||||
function isInline(node){ |
|
||||
// Recurse if the node is a block
|
|
||||
if (node.isBlock) return node.nodes.every(isInline); |
|
||||
return node.isText || (node.isInline && node.isInline()); |
|
||||
} |
|
||||
|
|
||||
// Empty tag
|
|
||||
if (!nodes.length) return true; |
|
||||
|
|
||||
// Text-only or inline-only tag
|
|
||||
if (1 == nodes.length) return isInline(nodes[0]); |
|
||||
|
|
||||
// Multi-line inline-only tag
|
|
||||
if (this.block.nodes.every(isInline)) { |
|
||||
for (var i = 1, len = nodes.length; i < len; ++i) { |
|
||||
if (nodes[i-1].isText && nodes[i].isText) |
|
||||
return false; |
|
||||
} |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
// Mixed tag
|
|
||||
return false; |
|
||||
}; |
|
@ -1,27 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Node = require('./node'); |
|
||||
|
|
||||
/** |
|
||||
* Initialize a `Text` node with optional `line`. |
|
||||
* |
|
||||
* @param {String} line |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Text = module.exports = function Text(line) { |
|
||||
this.val = ''; |
|
||||
if ('string' == typeof line) this.val = line; |
|
||||
}; |
|
||||
|
|
||||
// Inherit from `Node`.
|
|
||||
Text.prototype = Object.create(Node.prototype); |
|
||||
Text.prototype.constructor = Text; |
|
||||
|
|
||||
Text.prototype.type = 'Text'; |
|
||||
|
|
||||
/** |
|
||||
* Flag as text. |
|
||||
*/ |
|
||||
|
|
||||
Text.prototype.isText = true; |
|
@ -1,790 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
var Lexer = require('./lexer'); |
|
||||
var nodes = require('./nodes'); |
|
||||
var utils = require('./utils'); |
|
||||
var filters = require('./filters'); |
|
||||
var path = require('path'); |
|
||||
var constantinople = require('constantinople'); |
|
||||
var parseJSExpression = require('character-parser').parseMax; |
|
||||
var extname = path.extname; |
|
||||
|
|
||||
/** |
|
||||
* Initialize `Parser` with the given input `str` and `filename`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {String} filename |
|
||||
* @param {Object} options |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
var Parser = exports = module.exports = function Parser(str, filename, options){ |
|
||||
//Strip any UTF-8 BOM off of the start of `str`, if it exists.
|
|
||||
this.input = str.replace(/^\uFEFF/, ''); |
|
||||
this.lexer = new Lexer(this.input, filename); |
|
||||
this.filename = filename; |
|
||||
this.blocks = {}; |
|
||||
this.mixins = {}; |
|
||||
this.options = options; |
|
||||
this.contexts = [this]; |
|
||||
this.inMixin = false; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parser prototype. |
|
||||
*/ |
|
||||
|
|
||||
Parser.prototype = { |
|
||||
|
|
||||
/** |
|
||||
* Save original constructor |
|
||||
*/ |
|
||||
|
|
||||
constructor: Parser, |
|
||||
|
|
||||
/** |
|
||||
* Push `parser` onto the context stack, |
|
||||
* or pop and return a `Parser`. |
|
||||
*/ |
|
||||
|
|
||||
context: function(parser){ |
|
||||
if (parser) { |
|
||||
this.contexts.push(parser); |
|
||||
} else { |
|
||||
return this.contexts.pop(); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Return the next token object. |
|
||||
* |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
advance: function(){ |
|
||||
return this.lexer.advance(); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Skip `n` tokens. |
|
||||
* |
|
||||
* @param {Number} n |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
skip: function(n){ |
|
||||
while (n--) this.advance(); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Single token lookahead. |
|
||||
* |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
peek: function() { |
|
||||
return this.lookahead(1); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Return lexer lineno. |
|
||||
* |
|
||||
* @return {Number} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
line: function() { |
|
||||
return this.lexer.lineno; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* `n` token lookahead. |
|
||||
* |
|
||||
* @param {Number} n |
|
||||
* @return {Object} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
lookahead: function(n){ |
|
||||
return this.lexer.lookahead(n); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Parse input returning a string of js for evaluation. |
|
||||
* |
|
||||
* @return {String} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
parse: function(){ |
|
||||
var block = new nodes.Block, parser; |
|
||||
block.line = 0; |
|
||||
block.filename = this.filename; |
|
||||
|
|
||||
while ('eos' != this.peek().type) { |
|
||||
if ('newline' == this.peek().type) { |
|
||||
this.advance(); |
|
||||
} else { |
|
||||
var next = this.peek(); |
|
||||
var expr = this.parseExpr(); |
|
||||
expr.filename = expr.filename || this.filename; |
|
||||
expr.line = next.line; |
|
||||
block.push(expr); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (parser = this.extending) { |
|
||||
this.context(parser); |
|
||||
var ast = parser.parse(); |
|
||||
this.context(); |
|
||||
|
|
||||
// hoist mixins
|
|
||||
for (var name in this.mixins) |
|
||||
ast.unshift(this.mixins[name]); |
|
||||
return ast; |
|
||||
} |
|
||||
|
|
||||
return block; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Expect the given type, or throw an exception. |
|
||||
* |
|
||||
* @param {String} type |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
expect: function(type){ |
|
||||
if (this.peek().type === type) { |
|
||||
return this.advance(); |
|
||||
} else { |
|
||||
throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Accept the given `type`. |
|
||||
* |
|
||||
* @param {String} type |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
accept: function(type){ |
|
||||
if (this.peek().type === type) { |
|
||||
return this.advance(); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* tag |
|
||||
* | doctype |
|
||||
* | mixin |
|
||||
* | include |
|
||||
* | filter |
|
||||
* | comment |
|
||||
* | text |
|
||||
* | each |
|
||||
* | code |
|
||||
* | yield |
|
||||
* | id |
|
||||
* | class |
|
||||
* | interpolation |
|
||||
*/ |
|
||||
|
|
||||
parseExpr: function(){ |
|
||||
switch (this.peek().type) { |
|
||||
case 'tag': |
|
||||
return this.parseTag(); |
|
||||
case 'mixin': |
|
||||
return this.parseMixin(); |
|
||||
case 'block': |
|
||||
return this.parseBlock(); |
|
||||
case 'mixin-block': |
|
||||
return this.parseMixinBlock(); |
|
||||
case 'case': |
|
||||
return this.parseCase(); |
|
||||
case 'when': |
|
||||
return this.parseWhen(); |
|
||||
case 'default': |
|
||||
return this.parseDefault(); |
|
||||
case 'extends': |
|
||||
return this.parseExtends(); |
|
||||
case 'include': |
|
||||
return this.parseInclude(); |
|
||||
case 'doctype': |
|
||||
return this.parseDoctype(); |
|
||||
case 'filter': |
|
||||
return this.parseFilter(); |
|
||||
case 'comment': |
|
||||
return this.parseComment(); |
|
||||
case 'text': |
|
||||
return this.parseText(); |
|
||||
case 'each': |
|
||||
return this.parseEach(); |
|
||||
case 'code': |
|
||||
return this.parseCode(); |
|
||||
case 'call': |
|
||||
return this.parseCall(); |
|
||||
case 'interpolation': |
|
||||
return this.parseInterpolation(); |
|
||||
case 'yield': |
|
||||
this.advance(); |
|
||||
var block = new nodes.Block; |
|
||||
block.yield = true; |
|
||||
return block; |
|
||||
case 'id': |
|
||||
case 'class': |
|
||||
var tok = this.advance(); |
|
||||
this.lexer.defer(this.lexer.tok('tag', 'div')); |
|
||||
this.lexer.defer(tok); |
|
||||
return this.parseExpr(); |
|
||||
default: |
|
||||
throw new Error('unexpected token "' + this.peek().type + '"'); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Text |
|
||||
*/ |
|
||||
|
|
||||
parseText: function(){ |
|
||||
var tok = this.expect('text'); |
|
||||
var tokens = this.parseTextWithInlineTags(tok.val); |
|
||||
if (tokens.length === 1) return tokens[0]; |
|
||||
var node = new nodes.Block; |
|
||||
for (var i = 0; i < tokens.length; i++) { |
|
||||
node.push(tokens[i]); |
|
||||
}; |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* ':' expr |
|
||||
* | block |
|
||||
*/ |
|
||||
|
|
||||
parseBlockExpansion: function(){ |
|
||||
if (':' == this.peek().type) { |
|
||||
this.advance(); |
|
||||
return new nodes.Block(this.parseExpr()); |
|
||||
} else { |
|
||||
return this.block(); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* case |
|
||||
*/ |
|
||||
|
|
||||
parseCase: function(){ |
|
||||
var val = this.expect('case').val; |
|
||||
var node = new nodes.Case(val); |
|
||||
node.line = this.line(); |
|
||||
node.block = this.block(); |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* when |
|
||||
*/ |
|
||||
|
|
||||
parseWhen: function(){ |
|
||||
var val = this.expect('when').val |
|
||||
return new nodes.Case.When(val, this.parseBlockExpansion()); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* default |
|
||||
*/ |
|
||||
|
|
||||
parseDefault: function(){ |
|
||||
this.expect('default'); |
|
||||
return new nodes.Case.When('default', this.parseBlockExpansion()); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* code |
|
||||
*/ |
|
||||
|
|
||||
parseCode: function(){ |
|
||||
var tok = this.expect('code'); |
|
||||
var node = new nodes.Code(tok.val, tok.buffer, tok.escape); |
|
||||
var block; |
|
||||
var i = 1; |
|
||||
node.line = this.line(); |
|
||||
while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i; |
|
||||
block = 'indent' == this.lookahead(i).type; |
|
||||
if (block) { |
|
||||
this.skip(i-1); |
|
||||
node.block = this.block(); |
|
||||
} |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* comment |
|
||||
*/ |
|
||||
|
|
||||
parseComment: function(){ |
|
||||
var tok = this.expect('comment'); |
|
||||
var node; |
|
||||
|
|
||||
if ('indent' == this.peek().type) { |
|
||||
this.lexer.pipeless = true; |
|
||||
node = new nodes.BlockComment(tok.val, this.parseTextBlock(), tok.buffer); |
|
||||
this.lexer.pipeless = false; |
|
||||
} else { |
|
||||
node = new nodes.Comment(tok.val, tok.buffer); |
|
||||
} |
|
||||
|
|
||||
node.line = this.line(); |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* doctype |
|
||||
*/ |
|
||||
|
|
||||
parseDoctype: function(){ |
|
||||
var tok = this.expect('doctype'); |
|
||||
var node = new nodes.Doctype(tok.val); |
|
||||
node.line = this.line(); |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* filter attrs? text-block |
|
||||
*/ |
|
||||
|
|
||||
parseFilter: function(){ |
|
||||
var tok = this.expect('filter'); |
|
||||
var attrs = this.accept('attrs'); |
|
||||
var block; |
|
||||
|
|
||||
if ('indent' == this.peek().type) { |
|
||||
this.lexer.pipeless = true; |
|
||||
block = this.parseTextBlock(); |
|
||||
this.lexer.pipeless = false; |
|
||||
} else { |
|
||||
block = new nodes.Block; |
|
||||
} |
|
||||
|
|
||||
var options = {}; |
|
||||
if (attrs) { |
|
||||
attrs.attrs.forEach(function (attribute) { |
|
||||
options[attribute.name] = constantinople.toConstant(attribute.val); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
var node = new nodes.Filter(tok.val, block, options); |
|
||||
node.line = this.line(); |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* each block |
|
||||
*/ |
|
||||
|
|
||||
parseEach: function(){ |
|
||||
var tok = this.expect('each'); |
|
||||
var node = new nodes.Each(tok.code, tok.val, tok.key); |
|
||||
node.line = this.line(); |
|
||||
node.block = this.block(); |
|
||||
if (this.peek().type == 'code' && this.peek().val == 'else') { |
|
||||
this.advance(); |
|
||||
node.alternative = this.block(); |
|
||||
} |
|
||||
return node; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Resolves a path relative to the template for use in |
|
||||
* includes and extends |
|
||||
* |
|
||||
* @param {String} path |
|
||||
* @param {String} purpose Used in error messages. |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
resolvePath: function (path, purpose) { |
|
||||
var p = require('path'); |
|
||||
var dirname = p.dirname; |
|
||||
var basename = p.basename; |
|
||||
var join = p.join; |
|
||||
|
|
||||
if (path[0] !== '/' && !this.filename) |
|
||||
throw new Error('the "filename" option is required to use "' + purpose + '" with "relative" paths'); |
|
||||
|
|
||||
if (path[0] === '/' && !this.options.basedir) |
|
||||
throw new Error('the "basedir" option is required to use "' + purpose + '" with "absolute" paths'); |
|
||||
|
|
||||
path = join(path[0] === '/' ? this.options.basedir : dirname(this.filename), path); |
|
||||
|
|
||||
if (basename(path).indexOf('.') === -1) path += '.jade'; |
|
||||
|
|
||||
return path; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 'extends' name |
|
||||
*/ |
|
||||
|
|
||||
parseExtends: function(){ |
|
||||
var fs = require('fs'); |
|
||||
|
|
||||
var path = this.resolvePath(this.expect('extends').val.trim(), 'extends'); |
|
||||
if ('.jade' != path.substr(-5)) path += '.jade'; |
|
||||
|
|
||||
var str = fs.readFileSync(path, 'utf8'); |
|
||||
var parser = new this.constructor(str, path, this.options); |
|
||||
|
|
||||
parser.blocks = this.blocks; |
|
||||
parser.contexts = this.contexts; |
|
||||
this.extending = parser; |
|
||||
|
|
||||
// TODO: null node
|
|
||||
return new nodes.Literal(''); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* 'block' name block |
|
||||
*/ |
|
||||
|
|
||||
parseBlock: function(){ |
|
||||
var block = this.expect('block'); |
|
||||
var mode = block.mode; |
|
||||
var name = block.val.trim(); |
|
||||
|
|
||||
block = 'indent' == this.peek().type |
|
||||
? this.block() |
|
||||
: new nodes.Block(new nodes.Literal('')); |
|
||||
|
|
||||
var prev = this.blocks[name] || {prepended: [], appended: []} |
|
||||
if (prev.mode === 'replace') return this.blocks[name] = prev; |
|
||||
|
|
||||
var allNodes = prev.prepended.concat(block.nodes).concat(prev.appended); |
|
||||
|
|
||||
switch (mode) { |
|
||||
case 'append': |
|
||||
prev.appended = prev.parser === this ? |
|
||||
prev.appended.concat(block.nodes) : |
|
||||
block.nodes.concat(prev.appended); |
|
||||
break; |
|
||||
case 'prepend': |
|
||||
prev.prepended = prev.parser === this ? |
|
||||
block.nodes.concat(prev.prepended) : |
|
||||
prev.prepended.concat(block.nodes); |
|
||||
break; |
|
||||
} |
|
||||
block.nodes = allNodes; |
|
||||
block.appended = prev.appended; |
|
||||
block.prepended = prev.prepended; |
|
||||
block.mode = mode; |
|
||||
block.parser = this; |
|
||||
|
|
||||
return this.blocks[name] = block; |
|
||||
}, |
|
||||
|
|
||||
parseMixinBlock: function () { |
|
||||
var block = this.expect('mixin-block'); |
|
||||
if (!this.inMixin) { |
|
||||
throw new Error('Anonymous blocks are not allowed unless they are part of a mixin.'); |
|
||||
} |
|
||||
return new nodes.MixinBlock(); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* include block? |
|
||||
*/ |
|
||||
|
|
||||
parseInclude: function(){ |
|
||||
var fs = require('fs'); |
|
||||
var tok = this.expect('include'); |
|
||||
|
|
||||
var path = this.resolvePath(tok.val.trim(), 'include'); |
|
||||
|
|
||||
// has-filter
|
|
||||
if (tok.filter) { |
|
||||
var str = fs.readFileSync(path, 'utf8').replace(/\r/g, ''); |
|
||||
str = filters(tok.filter, str, { filename: path }); |
|
||||
return new nodes.Literal(str); |
|
||||
} |
|
||||
|
|
||||
// non-jade
|
|
||||
if ('.jade' != path.substr(-5)) { |
|
||||
var str = fs.readFileSync(path, 'utf8').replace(/\r/g, ''); |
|
||||
return new nodes.Literal(str); |
|
||||
} |
|
||||
|
|
||||
var str = fs.readFileSync(path, 'utf8'); |
|
||||
var parser = new this.constructor(str, path, this.options); |
|
||||
parser.blocks = utils.merge({}, this.blocks); |
|
||||
|
|
||||
parser.mixins = this.mixins; |
|
||||
|
|
||||
this.context(parser); |
|
||||
var ast = parser.parse(); |
|
||||
this.context(); |
|
||||
ast.filename = path; |
|
||||
|
|
||||
if ('indent' == this.peek().type) { |
|
||||
ast.includeBlock().push(this.block()); |
|
||||
} |
|
||||
|
|
||||
return ast; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* call ident block |
|
||||
*/ |
|
||||
|
|
||||
parseCall: function(){ |
|
||||
var tok = this.expect('call'); |
|
||||
var name = tok.val; |
|
||||
var args = tok.args; |
|
||||
var mixin = new nodes.Mixin(name, args, new nodes.Block, true); |
|
||||
|
|
||||
this.tag(mixin); |
|
||||
if (mixin.code) { |
|
||||
mixin.block.push(mixin.code); |
|
||||
mixin.code = null; |
|
||||
} |
|
||||
if (mixin.block.isEmpty()) mixin.block = null; |
|
||||
return mixin; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* mixin block |
|
||||
*/ |
|
||||
|
|
||||
parseMixin: function(){ |
|
||||
var tok = this.expect('mixin'); |
|
||||
var name = tok.val; |
|
||||
var args = tok.args; |
|
||||
var mixin; |
|
||||
|
|
||||
// definition
|
|
||||
if ('indent' == this.peek().type) { |
|
||||
this.inMixin = true; |
|
||||
mixin = new nodes.Mixin(name, args, this.block(), false); |
|
||||
this.mixins[name] = mixin; |
|
||||
this.inMixin = false; |
|
||||
return mixin; |
|
||||
// call
|
|
||||
} else { |
|
||||
return new nodes.Mixin(name, args, null, true); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
parseTextWithInlineTags: function (str) { |
|
||||
var line = this.line(); |
|
||||
|
|
||||
var match = /(\\)?#\[((?:.|\n)*)$/.exec(str); |
|
||||
if (match) { |
|
||||
if (match[1]) { // escape
|
|
||||
var text = new nodes.Text(str.substr(0, match.index) + '#['); |
|
||||
text.line = line; |
|
||||
var rest = this.parseTextWithInlineTags(match[2]); |
|
||||
if (rest[0].type === 'Text') { |
|
||||
text.val += rest[0].val; |
|
||||
rest.shift(); |
|
||||
} |
|
||||
return [text].concat(rest); |
|
||||
} else { |
|
||||
var text = new nodes.Text(str.substr(0, match.index)); |
|
||||
text.line = line; |
|
||||
var buffer = [text]; |
|
||||
var rest = match[2]; |
|
||||
var range = parseJSExpression(rest); |
|
||||
var inner = new Parser(range.src, this.filename, this.options); |
|
||||
buffer.push(inner.parse()); |
|
||||
return buffer.concat(this.parseTextWithInlineTags(rest.substr(range.end + 1))); |
|
||||
} |
|
||||
} else { |
|
||||
var text = new nodes.Text(str); |
|
||||
text.line = line; |
|
||||
return [text]; |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* indent (text | newline)* outdent |
|
||||
*/ |
|
||||
|
|
||||
parseTextBlock: function(){ |
|
||||
var block = new nodes.Block; |
|
||||
block.line = this.line(); |
|
||||
var spaces = this.expect('indent').val; |
|
||||
if (null == this._spaces) this._spaces = spaces; |
|
||||
var indent = Array(spaces - this._spaces + 1).join(' '); |
|
||||
while ('outdent' != this.peek().type) { |
|
||||
switch (this.peek().type) { |
|
||||
case 'newline': |
|
||||
this.advance(); |
|
||||
break; |
|
||||
case 'indent': |
|
||||
this.parseTextBlock(true).nodes.forEach(function(node){ |
|
||||
block.push(node); |
|
||||
}); |
|
||||
break; |
|
||||
default: |
|
||||
var texts = this.parseTextWithInlineTags(indent + this.advance().val); |
|
||||
texts.forEach(function (text) { |
|
||||
block.push(text); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (spaces == this._spaces) this._spaces = null; |
|
||||
this.expect('outdent'); |
|
||||
|
|
||||
return block; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* indent expr* outdent |
|
||||
*/ |
|
||||
|
|
||||
block: function(){ |
|
||||
var block = new nodes.Block; |
|
||||
block.line = this.line(); |
|
||||
block.filename = this.filename; |
|
||||
this.expect('indent'); |
|
||||
while ('outdent' != this.peek().type) { |
|
||||
if ('newline' == this.peek().type) { |
|
||||
this.advance(); |
|
||||
} else { |
|
||||
var expr = this.parseExpr(); |
|
||||
expr.filename = this.filename; |
|
||||
block.push(expr); |
|
||||
} |
|
||||
} |
|
||||
this.expect('outdent'); |
|
||||
return block; |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* interpolation (attrs | class | id)* (text | code | ':')? newline* block? |
|
||||
*/ |
|
||||
|
|
||||
parseInterpolation: function(){ |
|
||||
var tok = this.advance(); |
|
||||
var tag = new nodes.Tag(tok.val); |
|
||||
tag.buffer = true; |
|
||||
return this.tag(tag); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* tag (attrs | class | id)* (text | code | ':')? newline* block? |
|
||||
*/ |
|
||||
|
|
||||
parseTag: function(){ |
|
||||
var tok = this.advance(); |
|
||||
var tag = new nodes.Tag(tok.val); |
|
||||
|
|
||||
tag.selfClosing = tok.selfClosing; |
|
||||
|
|
||||
return this.tag(tag); |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Parse tag. |
|
||||
*/ |
|
||||
|
|
||||
tag: function(tag){ |
|
||||
tag.line = this.line(); |
|
||||
|
|
||||
var seenAttrs = false; |
|
||||
// (attrs | class | id)*
|
|
||||
out: |
|
||||
while (true) { |
|
||||
switch (this.peek().type) { |
|
||||
case 'id': |
|
||||
case 'class': |
|
||||
var tok = this.advance(); |
|
||||
tag.setAttribute(tok.type, "'" + tok.val + "'"); |
|
||||
continue; |
|
||||
case 'attrs': |
|
||||
if (seenAttrs) { |
|
||||
console.warn('You should not have jade tags with multiple attributes.'); |
|
||||
} |
|
||||
seenAttrs = true; |
|
||||
var tok = this.advance(); |
|
||||
var attrs = tok.attrs; |
|
||||
|
|
||||
if (tok.selfClosing) tag.selfClosing = true; |
|
||||
|
|
||||
for (var i = 0; i < attrs.length; i++) { |
|
||||
tag.setAttribute(attrs[i].name, attrs[i].val, attrs[i].escaped); |
|
||||
} |
|
||||
continue; |
|
||||
case '&attributes': |
|
||||
var tok = this.advance(); |
|
||||
tag.addAttributes(tok.val); |
|
||||
break; |
|
||||
default: |
|
||||
break out; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// check immediate '.'
|
|
||||
if ('dot' == this.peek().type) { |
|
||||
tag.textOnly = true; |
|
||||
this.advance(); |
|
||||
} |
|
||||
|
|
||||
if (tag.selfClosing |
|
||||
&& ['newline', 'outdent', 'eos'].indexOf(this.peek().type) === -1 |
|
||||
&& (this.peek().type !== 'text' || /^\s*$/.text(this.peek().val))) { |
|
||||
throw new Error(name + ' is self closing and should not have content.'); |
|
||||
} |
|
||||
|
|
||||
// (text | code | ':')?
|
|
||||
switch (this.peek().type) { |
|
||||
case 'text': |
|
||||
tag.block.push(this.parseText()); |
|
||||
break; |
|
||||
case 'code': |
|
||||
tag.code = this.parseCode(); |
|
||||
break; |
|
||||
case ':': |
|
||||
this.advance(); |
|
||||
tag.block = new nodes.Block; |
|
||||
tag.block.push(this.parseExpr()); |
|
||||
break; |
|
||||
case 'newline': |
|
||||
case 'indent': |
|
||||
case 'outdent': |
|
||||
case 'eos': |
|
||||
break; |
|
||||
default: |
|
||||
throw new Error('Unexpected token `' + this.peek().type + '` expected `text`, `code`, `:`, `newline` or `eos`') |
|
||||
} |
|
||||
|
|
||||
// newline*
|
|
||||
while ('newline' == this.peek().type) this.advance(); |
|
||||
|
|
||||
// block?
|
|
||||
if ('indent' == this.peek().type) { |
|
||||
if (tag.textOnly) { |
|
||||
this.lexer.pipeless = true; |
|
||||
tag.block = this.parseTextBlock(); |
|
||||
this.lexer.pipeless = false; |
|
||||
} else { |
|
||||
var block = this.block(); |
|
||||
if (tag.block) { |
|
||||
for (var i = 0, len = block.nodes.length; i < len; ++i) { |
|
||||
tag.block.push(block.nodes[i]); |
|
||||
} |
|
||||
} else { |
|
||||
tag.block = block; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return tag; |
|
||||
} |
|
||||
}; |
|
@ -1,203 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
/** |
|
||||
* Merge two attribute objects giving precedence |
|
||||
* to values in object `b`. Classes are special-cased |
|
||||
* allowing for arrays and merging/joining appropriately |
|
||||
* resulting in a string. |
|
||||
* |
|
||||
* @param {Object} a |
|
||||
* @param {Object} b |
|
||||
* @return {Object} a |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
exports.merge = function merge(a, b) { |
|
||||
if (arguments.length === 1) { |
|
||||
var attrs = a[0]; |
|
||||
for (var i = 1; i < a.length; i++) { |
|
||||
attrs = merge(attrs, a[i]); |
|
||||
} |
|
||||
return attrs; |
|
||||
} |
|
||||
var ac = a['class']; |
|
||||
var bc = b['class']; |
|
||||
|
|
||||
if (ac || bc) { |
|
||||
ac = ac || []; |
|
||||
bc = bc || []; |
|
||||
if (!Array.isArray(ac)) ac = [ac]; |
|
||||
if (!Array.isArray(bc)) bc = [bc]; |
|
||||
a['class'] = ac.concat(bc).filter(nulls); |
|
||||
} |
|
||||
|
|
||||
for (var key in b) { |
|
||||
if (key != 'class') { |
|
||||
a[key] = b[key]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return a; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Filter null `val`s. |
|
||||
* |
|
||||
* @param {*} val |
|
||||
* @return {Boolean} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function nulls(val) { |
|
||||
return val != null && val !== ''; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* join array as classes. |
|
||||
* |
|
||||
* @param {*} val |
|
||||
* @return {String} |
|
||||
*/ |
|
||||
exports.joinClasses = joinClasses; |
|
||||
function joinClasses(val) { |
|
||||
return Array.isArray(val) ? val.map(joinClasses).filter(nulls).join(' ') : val; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Render the given classes. |
|
||||
* |
|
||||
* @param {Array} classes |
|
||||
* @param {Array.<Boolean>} escaped |
|
||||
* @return {String} |
|
||||
*/ |
|
||||
exports.cls = function cls(classes, escaped) { |
|
||||
var buf = []; |
|
||||
for (var i = 0; i < classes.length; i++) { |
|
||||
if (escaped && escaped[i]) { |
|
||||
buf.push(exports.escape(joinClasses([classes[i]]))); |
|
||||
} else { |
|
||||
buf.push(joinClasses(classes[i])); |
|
||||
} |
|
||||
} |
|
||||
var text = joinClasses(buf); |
|
||||
if (text.length) { |
|
||||
return ' class="' + text + '"'; |
|
||||
} else { |
|
||||
return ''; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Render the given attribute. |
|
||||
* |
|
||||
* @param {String} key |
|
||||
* @param {String} val |
|
||||
* @param {Boolean} escaped |
|
||||
* @param {Boolean} terse |
|
||||
* @return {String} |
|
||||
*/ |
|
||||
exports.attr = function attr(key, val, escaped, terse) { |
|
||||
if ('boolean' == typeof val || null == val) { |
|
||||
if (val) { |
|
||||
return ' ' + (terse ? key : key + '="' + key + '"'); |
|
||||
} else { |
|
||||
return ''; |
|
||||
} |
|
||||
} else if (0 == key.indexOf('data') && 'string' != typeof val) { |
|
||||
return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'"; |
|
||||
} else if (escaped) { |
|
||||
return ' ' + key + '="' + exports.escape(val) + '"'; |
|
||||
} else { |
|
||||
return ' ' + key + '="' + val + '"'; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Render the given attributes object. |
|
||||
* |
|
||||
* @param {Object} obj |
|
||||
* @param {Object} escaped |
|
||||
* @return {String} |
|
||||
*/ |
|
||||
exports.attrs = function attrs(obj, terse){ |
|
||||
var buf = []; |
|
||||
|
|
||||
var keys = Object.keys(obj); |
|
||||
|
|
||||
if (keys.length) { |
|
||||
for (var i = 0; i < keys.length; ++i) { |
|
||||
var key = keys[i] |
|
||||
, val = obj[key]; |
|
||||
|
|
||||
if ('class' == key) { |
|
||||
if (val = joinClasses(val)) { |
|
||||
buf.push(' ' + key + '="' + val + '"'); |
|
||||
} |
|
||||
} else { |
|
||||
buf.push(exports.attr(key, val, false, terse)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return buf.join(''); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Escape the given string of `html`. |
|
||||
* |
|
||||
* @param {String} html |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
exports.escape = function escape(html){ |
|
||||
var result = String(html) |
|
||||
.replace(/&/g, '&') |
|
||||
.replace(/</g, '<') |
|
||||
.replace(/>/g, '>') |
|
||||
.replace(/"/g, '"'); |
|
||||
if (result === '' + html) return html; |
|
||||
else return result; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Re-throw the given `err` in context to the |
|
||||
* the jade in `filename` at the given `lineno`. |
|
||||
* |
|
||||
* @param {Error} err |
|
||||
* @param {String} filename |
|
||||
* @param {String} lineno |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
exports.rethrow = function rethrow(err, filename, lineno, str){ |
|
||||
if (!(err instanceof Error)) throw err; |
|
||||
if ((typeof window != 'undefined' || !filename) && !str) { |
|
||||
err.message += ' on line ' + lineno; |
|
||||
throw err; |
|
||||
} |
|
||||
try { |
|
||||
str = str || require('fs').readFileSync(filename, 'utf8') |
|
||||
} catch (ex) { |
|
||||
rethrow(err, null, lineno) |
|
||||
} |
|
||||
var context = 3 |
|
||||
, lines = str.split('\n') |
|
||||
, start = Math.max(lineno - context, 0) |
|
||||
, end = Math.min(lines.length, lineno + context); |
|
||||
|
|
||||
// Error context
|
|
||||
var context = lines.slice(start, end).map(function(line, i){ |
|
||||
var curr = i + start + 1; |
|
||||
return (curr == lineno ? ' > ' : ' ') |
|
||||
+ curr |
|
||||
+ '| ' |
|
||||
+ line; |
|
||||
}).join('\n'); |
|
||||
|
|
||||
// Alter exception message
|
|
||||
err.path = filename; |
|
||||
err.message = (filename || 'Jade') + ':' + lineno |
|
||||
+ '\n' + context + '\n\n' + err.message; |
|
||||
throw err; |
|
||||
}; |
|
@ -1,22 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
// source: http://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
|
|
||||
|
|
||||
module.exports = [ |
|
||||
'area' |
|
||||
, 'base' |
|
||||
, 'br' |
|
||||
, 'col' |
|
||||
, 'embed' |
|
||||
, 'hr' |
|
||||
, 'img' |
|
||||
, 'input' |
|
||||
, 'keygen' |
|
||||
, 'link' |
|
||||
, 'menuitem' |
|
||||
, 'meta' |
|
||||
, 'param' |
|
||||
, 'source' |
|
||||
, 'track' |
|
||||
, 'wbr' |
|
||||
]; |
|
@ -1,16 +0,0 @@ |
|||||
'use strict'; |
|
||||
|
|
||||
/** |
|
||||
* Merge `b` into `a`. |
|
||||
* |
|
||||
* @param {Object} a |
|
||||
* @param {Object} b |
|
||||
* @return {Object} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
exports.merge = function(a, b) { |
|
||||
for (var key in b) a[key] = b[key]; |
|
||||
return a; |
|
||||
}; |
|
||||
|
|
@ -1,2 +0,0 @@ |
|||||
test/ |
|
||||
.travis.yml |
|
@ -1,19 +0,0 @@ |
|||||
Copyright (c) 2013 Forbes Lindesay |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,142 +0,0 @@ |
|||||
# character-parser |
|
||||
|
|
||||
Parse JavaScript one character at a time to look for snippets in Templates. This is not a validator, it's just designed to allow you to have sections of JavaScript delimited by brackets robustly. |
|
||||
|
|
||||
[![Build Status](https://travis-ci.org/ForbesLindesay/character-parser.png?branch=master)](https://travis-ci.org/ForbesLindesay/character-parser) |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
npm install character-parser |
|
||||
|
|
||||
## Usage |
|
||||
|
|
||||
Work out how much depth changes: |
|
||||
|
|
||||
```js |
|
||||
var state = parse('foo(arg1, arg2, {\n foo: [a, b\n'); |
|
||||
assert(state.roundDepth === 1); |
|
||||
assert(state.curlyDepth === 1); |
|
||||
assert(state.squareDepth === 1); |
|
||||
parse(' c, d]\n })', state); |
|
||||
assert(state.squareDepth === 0); |
|
||||
assert(state.curlyDepth === 0); |
|
||||
assert(state.roundDepth === 0); |
|
||||
``` |
|
||||
|
|
||||
### Bracketed Expressions |
|
||||
|
|
||||
Find all the contents of a bracketed expression: |
|
||||
|
|
||||
```js |
|
||||
var section = parser.parseMax('foo="(", bar="}") bing bong'); |
|
||||
assert(section.start === 0); |
|
||||
assert(section.end === 16);//exclusive end of string |
|
||||
assert(section.src = 'foo="(", bar="}"'); |
|
||||
|
|
||||
|
|
||||
var section = parser.parseMax('{foo="(", bar="}"} bing bong', {start: 1}); |
|
||||
assert(section.start === 1); |
|
||||
assert(section.end === 17);//exclusive end of string |
|
||||
assert(section.src = 'foo="(", bar="}"'); |
|
||||
``` |
|
||||
|
|
||||
The bracketed expression parsing simply parses up to but excluding the first unmatched closed bracket (`)`, `}`, `]`). It is clever enough to ignore brackets in comments or strings. |
|
||||
|
|
||||
|
|
||||
### Custom Delimited Expressions |
|
||||
|
|
||||
Find code up to a custom delimiter: |
|
||||
|
|
||||
```js |
|
||||
var section = parser.parseUntil('foo.bar("%>").baz%> bing bong', '%>'); |
|
||||
assert(section.start === 0); |
|
||||
assert(section.end === 17);//exclusive end of string |
|
||||
assert(section.src = 'foo.bar("%>").baz'); |
|
||||
|
|
||||
var section = parser.parseUntil('<%foo.bar("%>").baz%> bing bong', '%>', {start: 2}); |
|
||||
assert(section.start === 2); |
|
||||
assert(section.end === 19);//exclusive end of string |
|
||||
assert(section.src = 'foo.bar("%>").baz'); |
|
||||
``` |
|
||||
|
|
||||
Delimiters are ignored if they are inside strings or comments. |
|
||||
|
|
||||
## API |
|
||||
|
|
||||
### parse(str, state = defaultState(), options = {start: 0, end: src.length}) |
|
||||
|
|
||||
Parse a string starting at the index start, and return the state after parsing that string. |
|
||||
|
|
||||
If you want to parse one string in multiple sections you should keep passing the resulting state to the next parse operation. |
|
||||
|
|
||||
Returns a `State` object. |
|
||||
|
|
||||
### parseMax(src, options = {start: 0}) |
|
||||
|
|
||||
Parses the source until the first unmatched close bracket (any of `)`, `}`, `]`). It returns an object with the structure: |
|
||||
|
|
||||
```js |
|
||||
{ |
|
||||
start: 0,//index of first character of string |
|
||||
end: 13,//index of first character after the end of string |
|
||||
src: 'source string' |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### parseUntil(src, delimiter, options = {start: 0, includeLineComment: false}) |
|
||||
|
|
||||
Parses the source until the first occurence of `delimiter` which is not in a string or a comment. If `includeLineComment` is `true`, it will still count if the delimiter occurs in a line comment, but not in a block comment. It returns an object with the structure: |
|
||||
|
|
||||
```js |
|
||||
{ |
|
||||
start: 0,//index of first character of string |
|
||||
end: 13,//index of first character after the end of string |
|
||||
src: 'source string' |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### parseChar(character, state = defaultState()) |
|
||||
|
|
||||
Parses the single character and returns the state. See `parse` for the structure of the returned state object. N.B. character must be a single character not a multi character string. |
|
||||
|
|
||||
### defaultState() |
|
||||
|
|
||||
Get a default starting state. |
|
||||
|
|
||||
### isPunctuator(character) |
|
||||
|
|
||||
Returns `true` if `character` represents punctuation in JavaScript. |
|
||||
|
|
||||
### isKeyword(name) |
|
||||
|
|
||||
Returns `true` if `name` is a keyword in JavaScript. |
|
||||
|
|
||||
## State |
|
||||
|
|
||||
A state is an object with the following structure |
|
||||
|
|
||||
```js |
|
||||
{ |
|
||||
lineComment: false, //true if inside a line comment |
|
||||
blockComment: false, //true if inside a block comment |
|
||||
|
|
||||
singleQuote: false, //true if inside a single quoted string |
|
||||
doubleQuote: false, //true if inside a double quoted string |
|
||||
regexp: false, //true if inside a regular expression |
|
||||
escaped: false, //true if in a string and the last character was an escape character |
|
||||
|
|
||||
roundDepth: 0, //number of un-closed open `(` brackets |
|
||||
curlyDepth: 0, //number of un-closed open `{` brackets |
|
||||
squareDepth: 0 //number of un-closed open `[` brackets |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
It also has the following useful methods: |
|
||||
|
|
||||
- `.isString()` returns `true` if the current location is inside a string. |
|
||||
- `.isComment()` returns `true` if the current location is inside a comment. |
|
||||
- `isNesting()` returns `true` if the current location is anything but at the top level, i.e. with no nesting. |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
MIT |
|
@ -1,217 +0,0 @@ |
|||||
exports = (module.exports = parse); |
|
||||
exports.parse = parse; |
|
||||
function parse(src, state, options) { |
|
||||
options = options || {}; |
|
||||
state = state || exports.defaultState(); |
|
||||
var start = options.start || 0; |
|
||||
var end = options.end || src.length; |
|
||||
var index = start; |
|
||||
while (index < end) { |
|
||||
if (state.roundDepth < 0 || state.curlyDepth < 0 || state.squareDepth < 0) { |
|
||||
throw new SyntaxError('Mismatched Bracket: ' + src[index - 1]); |
|
||||
} |
|
||||
exports.parseChar(src[index++], state); |
|
||||
} |
|
||||
return state; |
|
||||
} |
|
||||
|
|
||||
exports.parseMax = parseMax; |
|
||||
function parseMax(src, options) { |
|
||||
options = options || {}; |
|
||||
var start = options.start || 0; |
|
||||
var index = start; |
|
||||
var state = exports.defaultState(); |
|
||||
while (state.roundDepth >= 0 && state.curlyDepth >= 0 && state.squareDepth >= 0) { |
|
||||
if (index >= src.length) { |
|
||||
throw new Error('The end of the string was reached with no closing bracket found.'); |
|
||||
} |
|
||||
exports.parseChar(src[index++], state); |
|
||||
} |
|
||||
var end = index - 1; |
|
||||
return { |
|
||||
start: start, |
|
||||
end: end, |
|
||||
src: src.substring(start, end) |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
exports.parseUntil = parseUntil; |
|
||||
function parseUntil(src, delimiter, options) { |
|
||||
options = options || {}; |
|
||||
var includeLineComment = options.includeLineComment || false; |
|
||||
var start = options.start || 0; |
|
||||
var index = start; |
|
||||
var state = exports.defaultState(); |
|
||||
while (state.isString() || state.regexp || state.blockComment || |
|
||||
(!includeLineComment && state.lineComment) || !startsWith(src, delimiter, index)) { |
|
||||
exports.parseChar(src[index++], state); |
|
||||
} |
|
||||
var end = index; |
|
||||
return { |
|
||||
start: start, |
|
||||
end: end, |
|
||||
src: src.substring(start, end) |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
exports.parseChar = parseChar; |
|
||||
function parseChar(character, state) { |
|
||||
if (character.length !== 1) throw new Error('Character must be a string of length 1'); |
|
||||
state = state || exports.defaultState(); |
|
||||
var wasComment = state.blockComment || state.lineComment; |
|
||||
var lastChar = state.history ? state.history[0] : ''; |
|
||||
if (state.lineComment) { |
|
||||
if (character === '\n') { |
|
||||
state.lineComment = false; |
|
||||
} |
|
||||
} else if (state.blockComment) { |
|
||||
if (state.lastChar === '*' && character === '/') { |
|
||||
state.blockComment = false; |
|
||||
} |
|
||||
} else if (state.singleQuote) { |
|
||||
if (character === '\'' && !state.escaped) { |
|
||||
state.singleQuote = false; |
|
||||
} else if (character === '\\' && !state.escaped) { |
|
||||
state.escaped = true; |
|
||||
} else { |
|
||||
state.escaped = false; |
|
||||
} |
|
||||
} else if (state.doubleQuote) { |
|
||||
if (character === '"' && !state.escaped) { |
|
||||
state.doubleQuote = false; |
|
||||
} else if (character === '\\' && !state.escaped) { |
|
||||
state.escaped = true; |
|
||||
} else { |
|
||||
state.escaped = false; |
|
||||
} |
|
||||
} else if (state.regexp) { |
|
||||
if (character === '/' && !state.escaped) { |
|
||||
state.regexp = false; |
|
||||
} else if (character === '\\' && !state.escaped) { |
|
||||
state.escaped = true; |
|
||||
} else { |
|
||||
state.escaped = false; |
|
||||
} |
|
||||
} else if (lastChar === '/' && character === '/') { |
|
||||
state.history = state.history.substr(1); |
|
||||
state.lineComment = true; |
|
||||
} else if (lastChar === '/' && character === '*') { |
|
||||
state.history = state.history.substr(1); |
|
||||
state.blockComment = true; |
|
||||
} else if (character === '/' && isRegexp(state.history)) { |
|
||||
state.regexp = true; |
|
||||
} else if (character === '\'') { |
|
||||
state.singleQuote = true; |
|
||||
} else if (character === '"') { |
|
||||
state.doubleQuote = true; |
|
||||
} else if (character === '(') { |
|
||||
state.roundDepth++; |
|
||||
} else if (character === ')') { |
|
||||
state.roundDepth--; |
|
||||
} else if (character === '{') { |
|
||||
state.curlyDepth++; |
|
||||
} else if (character === '}') { |
|
||||
state.curlyDepth--; |
|
||||
} else if (character === '[') { |
|
||||
state.squareDepth++; |
|
||||
} else if (character === ']') { |
|
||||
state.squareDepth--; |
|
||||
} |
|
||||
if (!state.blockComment && !state.lineComment && !wasComment) state.history = character + state.history; |
|
||||
return state; |
|
||||
} |
|
||||
|
|
||||
exports.defaultState = function () { return new State() }; |
|
||||
function State() { |
|
||||
this.lineComment = false; |
|
||||
this.blockComment = false; |
|
||||
|
|
||||
this.singleQuote = false; |
|
||||
this.doubleQuote = false; |
|
||||
this.regexp = false; |
|
||||
this.escaped = false; |
|
||||
|
|
||||
this.roundDepth = 0; |
|
||||
this.curlyDepth = 0; |
|
||||
this.squareDepth = 0; |
|
||||
|
|
||||
this.history = '' |
|
||||
} |
|
||||
State.prototype.isString = function () { |
|
||||
return this.singleQuote || this.doubleQuote; |
|
||||
} |
|
||||
State.prototype.isComment = function () { |
|
||||
return this.lineComment || this.blockComment; |
|
||||
} |
|
||||
State.prototype.isNesting = function () { |
|
||||
return this.isString() || this.isComment() || this.regexp || this.roundDepth > 0 || this.curlyDepth > 0 || this.squareDepth > 0 |
|
||||
} |
|
||||
|
|
||||
function startsWith(str, start, i) { |
|
||||
return str.substr(i || 0, start.length) === start; |
|
||||
} |
|
||||
|
|
||||
exports.isPunctuator = isPunctuator |
|
||||
function isPunctuator(c) { |
|
||||
var code = c.charCodeAt(0) |
|
||||
|
|
||||
switch (code) { |
|
||||
case 46: // . dot
|
|
||||
case 40: // ( open bracket
|
|
||||
case 41: // ) close bracket
|
|
||||
case 59: // ; semicolon
|
|
||||
case 44: // , comma
|
|
||||
case 123: // { open curly brace
|
|
||||
case 125: // } close curly brace
|
|
||||
case 91: // [
|
|
||||
case 93: // ]
|
|
||||
case 58: // :
|
|
||||
case 63: // ?
|
|
||||
case 126: // ~
|
|
||||
case 37: // %
|
|
||||
case 38: // &
|
|
||||
case 42: // *:
|
|
||||
case 43: // +
|
|
||||
case 45: // -
|
|
||||
case 47: // /
|
|
||||
case 60: // <
|
|
||||
case 62: // >
|
|
||||
case 94: // ^
|
|
||||
case 124: // |
|
|
||||
case 33: // !
|
|
||||
case 61: // =
|
|
||||
return true; |
|
||||
default: |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
exports.isKeyword = isKeyword |
|
||||
function isKeyword(id) { |
|
||||
return (id === 'if') || (id === 'in') || (id === 'do') || (id === 'var') || (id === 'for') || (id === 'new') || |
|
||||
(id === 'try') || (id === 'let') || (id === 'this') || (id === 'else') || (id === 'case') || |
|
||||
(id === 'void') || (id === 'with') || (id === 'enum') || (id === 'while') || (id === 'break') || (id === 'catch') || |
|
||||
(id === 'throw') || (id === 'const') || (id === 'yield') || (id === 'class') || (id === 'super') || |
|
||||
(id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch') || (id === 'export') || |
|
||||
(id === 'import') || (id === 'default') || (id === 'finally') || (id === 'extends') || (id === 'function') || |
|
||||
(id === 'continue') || (id === 'debugger') || (id === 'package') || (id === 'private') || (id === 'interface') || |
|
||||
(id === 'instanceof') || (id === 'implements') || (id === 'protected') || (id === 'public') || (id === 'static') || |
|
||||
(id === 'yield') || (id === 'let'); |
|
||||
} |
|
||||
|
|
||||
function isRegexp(history) { |
|
||||
//could be start of regexp or divide sign
|
|
||||
|
|
||||
history = history.replace(/^\s*/, ''); |
|
||||
|
|
||||
//unless its an `if`, `while`, `for` or `with` it's a divide, so we assume it's a divide
|
|
||||
if (history[0] === ')') return false; |
|
||||
//unless it's a function expression, it's a regexp, so we assume it's a regexp
|
|
||||
if (history[0] === '}') return true; |
|
||||
//any punctuation means it's a regexp
|
|
||||
if (isPunctuator(history[0])) return true; |
|
||||
//if the last thing was a keyword then it must be a regexp (e.g. `typeof /foo/`)
|
|
||||
if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0].split('').reverse().join(''))) return true; |
|
||||
|
|
||||
return false; |
|
||||
} |
|
@ -1,43 +0,0 @@ |
|||||
{ |
|
||||
"name": "character-parser", |
|
||||
"version": "1.2.0", |
|
||||
"description": "Parse JavaScript one character at a time to look for snippets in Templates. This is not a validator, it's just designed to allow you to have sections of JavaScript delimited by brackets robustly.", |
|
||||
"main": "index.js", |
|
||||
"scripts": { |
|
||||
"test": "mocha -R spec" |
|
||||
}, |
|
||||
"repository": { |
|
||||
"type": "git", |
|
||||
"url": "https://github.com/ForbesLindesay/character-parser.git" |
|
||||
}, |
|
||||
"keywords": [ |
|
||||
"parser", |
|
||||
"JavaScript", |
|
||||
"bracket", |
|
||||
"nesting", |
|
||||
"comment", |
|
||||
"string", |
|
||||
"escape", |
|
||||
"escaping" |
|
||||
], |
|
||||
"author": { |
|
||||
"name": "ForbesLindesay" |
|
||||
}, |
|
||||
"license": "MIT", |
|
||||
"devDependencies": { |
|
||||
"better-assert": "~1.0.0", |
|
||||
"mocha": "~1.9.0" |
|
||||
}, |
|
||||
"readme": "# character-parser\r\n\r\nParse JavaScript one character at a time to look for snippets in Templates. This is not a validator, it's just designed to allow you to have sections of JavaScript delimited by brackets robustly.\r\n\r\n[![Build Status](https://travis-ci.org/ForbesLindesay/character-parser.png?branch=master)](https://travis-ci.org/ForbesLindesay/character-parser)\r\n\r\n## Installation\r\n\r\n npm install character-parser\r\n\r\n## Usage\r\n\r\nWork out how much depth changes:\r\n\r\n```js\r\nvar state = parse('foo(arg1, arg2, {\\n foo: [a, b\\n');\r\nassert(state.roundDepth === 1);\r\nassert(state.curlyDepth === 1);\r\nassert(state.squareDepth === 1);\r\nparse(' c, d]\\n })', state);\r\nassert(state.squareDepth === 0);\r\nassert(state.curlyDepth === 0);\r\nassert(state.roundDepth === 0);\r\n```\r\n\r\n### Bracketed Expressions\r\n\r\nFind all the contents of a bracketed expression:\r\n\r\n```js\r\nvar section = parser.parseMax('foo=\"(\", bar=\"}\") bing bong');\r\nassert(section.start === 0);\r\nassert(section.end === 16);//exclusive end of string\r\nassert(section.src = 'foo=\"(\", bar=\"}\"');\r\n\r\n\r\nvar section = parser.parseMax('{foo=\"(\", bar=\"}\"} bing bong', {start: 1});\r\nassert(section.start === 1);\r\nassert(section.end === 17);//exclusive end of string\r\nassert(section.src = 'foo=\"(\", bar=\"}\"');\r\n```\r\n\r\nThe bracketed expression parsing simply parses up to but excluding the first unmatched closed bracket (`)`, `}`, `]`). It is clever enough to ignore brackets in comments or strings.\r\n\r\n\r\n### Custom Delimited Expressions\r\n\r\nFind code up to a custom delimiter:\r\n\r\n```js\r\nvar section = parser.parseUntil('foo.bar(\"%>\").baz%> bing bong', '%>');\r\nassert(section.start === 0);\r\nassert(section.end === 17);//exclusive end of string\r\nassert(section.src = 'foo.bar(\"%>\").baz');\r\n\r\nvar section = parser.parseUntil('<%foo.bar(\"%>\").baz%> bing bong', '%>', {start: 2});\r\nassert(section.start === 2);\r\nassert(section.end === 19);//exclusive end of string\r\nassert(section.src = 'foo.bar(\"%>\").baz');\r\n```\r\n\r\nDelimiters are ignored if they are inside strings or comments.\r\n\r\n## API\r\n\r\n### parse(str, state = defaultState(), options = {start: 0, end: src.length})\r\n\r\nParse a string starting at the index start, and return the state after parsing that string.\r\n\r\nIf you want to parse one string in multiple sections you should keep passing the resulting state to the next parse operation.\r\n\r\nReturns a `State` object.\r\n\r\n### parseMax(src, options = {start: 0})\r\n\r\nParses the source until the first unmatched close bracket (any of `)`, `}`, `]`). It returns an object with the structure:\r\n\r\n```js\r\n{\r\n start: 0,//index of first character of string\r\n end: 13,//index of first character after the end of string\r\n src: 'source string'\r\n}\r\n```\r\n\r\n### parseUntil(src, delimiter, options = {start: 0, includeLineComment: false})\r\n\r\nParses the source until the first occurence of `delimiter` which is not in a string or a comment. If `includeLineComment` is `true`, it will still count if the delimiter occurs in a line comment, but not in a block comment. It returns an object with the structure:\r\n\r\n```js\r\n{\r\n start: 0,//index of first character of string\r\n end: 13,//index of first character after the end of string\r\n src: 'source string'\r\n}\r\n```\r\n\r\n### parseChar(character, state = defaultState())\r\n\r\nParses the single character and returns the state. See `parse` for the structure of the returned state object. N.B. character must be a single character not a multi character string.\r\n\r\n### defaultState()\r\n\r\nGet a default starting state.\r\n\r\n### isPunctuator(character)\r\n\r\nReturns `true` if `character` represents punctuation in JavaScript.\r\n\r\n### isKeyword(name)\r\n\r\nReturns `true` if `name` is a keyword in JavaScript.\r\n\r\n## State\r\n\r\nA state is an object with the following structure\r\n\r\n```js\r\n{\r\n lineComment: false, //true if inside a line comment\r\n blockComment: false, //true if inside a block comment\r\n\r\n singleQuote: false, //true if inside a single quoted string\r\n doubleQuote: false, //true if inside a double quoted string\r\n regexp: false, //true if inside a regular expression\r\n escaped: false, //true if in a string and the last character was an escape character\r\n\r\n roundDepth: 0, //number of un-closed open `(` brackets\r\n curlyDepth: 0, //number of un-closed open `{` brackets\r\n squareDepth: 0 //number of un-closed open `[` brackets\r\n}\r\n```\r\n\r\nIt also has the following useful methods:\r\n\r\n- `.isString()` returns `true` if the current location is inside a string.\r\n- `.isComment()` returns `true` if the current location is inside a comment.\r\n- `isNesting()` returns `true` if the current location is anything but at the top level, i.e. with no nesting.\r\n\r\n## License\r\n\r\nMIT", |
|
||||
"readmeFilename": "README.md", |
|
||||
"bugs": { |
|
||||
"url": "https://github.com/ForbesLindesay/character-parser/issues" |
|
||||
}, |
|
||||
"homepage": "https://github.com/ForbesLindesay/character-parser", |
|
||||
"_id": "character-parser@1.2.0", |
|
||||
"dist": { |
|
||||
"shasum": "4bb14d55dea5c7d1b1ec42a5c092d2bc23741ea3" |
|
||||
}, |
|
||||
"_from": "character-parser@1.2.0", |
|
||||
"_resolved": "https://registry.npmjs.org/character-parser/-/character-parser-1.2.0.tgz" |
|
||||
} |
|
@ -1,195 +0,0 @@ |
|||||
# Commander.js |
|
||||
|
|
||||
The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander). |
|
||||
|
|
||||
[![Build Status](https://secure.travis-ci.org/visionmedia/commander.js.png)](http://travis-ci.org/visionmedia/commander.js) |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
$ npm install commander |
|
||||
|
|
||||
## Option parsing |
|
||||
|
|
||||
Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options. |
|
||||
|
|
||||
```js |
|
||||
#!/usr/bin/env node |
|
||||
|
|
||||
/** |
|
||||
* Module dependencies. |
|
||||
*/ |
|
||||
|
|
||||
var program = require('commander'); |
|
||||
|
|
||||
program |
|
||||
.version('0.0.1') |
|
||||
.option('-p, --peppers', 'Add peppers') |
|
||||
.option('-P, --pineapple', 'Add pineapple') |
|
||||
.option('-b, --bbq', 'Add bbq sauce') |
|
||||
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble') |
|
||||
.parse(process.argv); |
|
||||
|
|
||||
console.log('you ordered a pizza with:'); |
|
||||
if (program.peppers) console.log(' - peppers'); |
|
||||
if (program.pineapple) console.log(' - pineapple'); |
|
||||
if (program.bbq) console.log(' - bbq'); |
|
||||
console.log(' - %s cheese', program.cheese); |
|
||||
``` |
|
||||
|
|
||||
Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. |
|
||||
|
|
||||
## Automated --help |
|
||||
|
|
||||
The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free: |
|
||||
|
|
||||
``` |
|
||||
$ ./examples/pizza --help |
|
||||
|
|
||||
Usage: pizza [options] |
|
||||
|
|
||||
Options: |
|
||||
|
|
||||
-V, --version output the version number |
|
||||
-p, --peppers Add peppers |
|
||||
-P, --pineapple Add pineapple |
|
||||
-b, --bbq Add bbq sauce |
|
||||
-c, --cheese <type> Add the specified type of cheese [marble] |
|
||||
-h, --help output usage information |
|
||||
|
|
||||
``` |
|
||||
|
|
||||
## Coercion |
|
||||
|
|
||||
```js |
|
||||
function range(val) { |
|
||||
return val.split('..').map(Number); |
|
||||
} |
|
||||
|
|
||||
function list(val) { |
|
||||
return val.split(','); |
|
||||
} |
|
||||
|
|
||||
program |
|
||||
.version('0.0.1') |
|
||||
.usage('[options] <file ...>') |
|
||||
.option('-i, --integer <n>', 'An integer argument', parseInt) |
|
||||
.option('-f, --float <n>', 'A float argument', parseFloat) |
|
||||
.option('-r, --range <a>..<b>', 'A range', range) |
|
||||
.option('-l, --list <items>', 'A list', list) |
|
||||
.option('-o, --optional [value]', 'An optional value') |
|
||||
.parse(process.argv); |
|
||||
|
|
||||
console.log(' int: %j', program.integer); |
|
||||
console.log(' float: %j', program.float); |
|
||||
console.log(' optional: %j', program.optional); |
|
||||
program.range = program.range || []; |
|
||||
console.log(' range: %j..%j', program.range[0], program.range[1]); |
|
||||
console.log(' list: %j', program.list); |
|
||||
console.log(' args: %j', program.args); |
|
||||
``` |
|
||||
|
|
||||
## Custom help |
|
||||
|
|
||||
You can display arbitrary `-h, --help` information |
|
||||
by listening for "--help". Commander will automatically |
|
||||
exit once you are done so that the remainder of your program |
|
||||
does not execute causing undesired behaviours, for example |
|
||||
in the following executable "stuff" will not output when |
|
||||
`--help` is used. |
|
||||
|
|
||||
```js |
|
||||
#!/usr/bin/env node |
|
||||
|
|
||||
/** |
|
||||
* Module dependencies. |
|
||||
*/ |
|
||||
|
|
||||
var program = require('../'); |
|
||||
|
|
||||
function list(val) { |
|
||||
return val.split(',').map(Number); |
|
||||
} |
|
||||
|
|
||||
program |
|
||||
.version('0.0.1') |
|
||||
.option('-f, --foo', 'enable some foo') |
|
||||
.option('-b, --bar', 'enable some bar') |
|
||||
.option('-B, --baz', 'enable some baz'); |
|
||||
|
|
||||
// must be before .parse() since |
|
||||
// node's emit() is immediate |
|
||||
|
|
||||
program.on('--help', function(){ |
|
||||
console.log(' Examples:'); |
|
||||
console.log(''); |
|
||||
console.log(' $ custom-help --help'); |
|
||||
console.log(' $ custom-help -h'); |
|
||||
console.log(''); |
|
||||
}); |
|
||||
|
|
||||
program.parse(process.argv); |
|
||||
|
|
||||
console.log('stuff'); |
|
||||
``` |
|
||||
|
|
||||
yielding the following help output: |
|
||||
|
|
||||
``` |
|
||||
|
|
||||
Usage: custom-help [options] |
|
||||
|
|
||||
Options: |
|
||||
|
|
||||
-h, --help output usage information |
|
||||
-V, --version output the version number |
|
||||
-f, --foo enable some foo |
|
||||
-b, --bar enable some bar |
|
||||
-B, --baz enable some baz |
|
||||
|
|
||||
Examples: |
|
||||
|
|
||||
$ custom-help --help |
|
||||
$ custom-help -h |
|
||||
|
|
||||
``` |
|
||||
|
|
||||
## .outputHelp() |
|
||||
|
|
||||
Output help information without exiting. |
|
||||
|
|
||||
## .help() |
|
||||
|
|
||||
Output help information and exit immediately. |
|
||||
|
|
||||
## Links |
|
||||
|
|
||||
- [API documentation](http://visionmedia.github.com/commander.js/) |
|
||||
- [ascii tables](https://github.com/LearnBoost/cli-table) |
|
||||
- [progress bars](https://github.com/visionmedia/node-progress) |
|
||||
- [more progress bars](https://github.com/substack/node-multimeter) |
|
||||
- [examples](https://github.com/visionmedia/commander.js/tree/master/examples) |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
(The MIT License) |
|
||||
|
|
||||
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca> |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
|
||||
a copy of this software and associated documentation files (the |
|
||||
'Software'), to deal in the Software without restriction, including |
|
||||
without limitation the rights to use, copy, modify, merge, publish, |
|
||||
distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
permit persons to whom the Software is furnished to do so, subject to |
|
||||
the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be |
|
||||
included in all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
@ -1,851 +0,0 @@ |
|||||
|
|
||||
/** |
|
||||
* Module dependencies. |
|
||||
*/ |
|
||||
|
|
||||
var EventEmitter = require('events').EventEmitter; |
|
||||
var spawn = require('child_process').spawn; |
|
||||
var fs = require('fs'); |
|
||||
var exists = fs.existsSync; |
|
||||
var path = require('path'); |
|
||||
var dirname = path.dirname; |
|
||||
var basename = path.basename; |
|
||||
|
|
||||
/** |
|
||||
* Expose the root command. |
|
||||
*/ |
|
||||
|
|
||||
exports = module.exports = new Command; |
|
||||
|
|
||||
/** |
|
||||
* Expose `Command`. |
|
||||
*/ |
|
||||
|
|
||||
exports.Command = Command; |
|
||||
|
|
||||
/** |
|
||||
* Expose `Option`. |
|
||||
*/ |
|
||||
|
|
||||
exports.Option = Option; |
|
||||
|
|
||||
/** |
|
||||
* Initialize a new `Option` with the given `flags` and `description`. |
|
||||
* |
|
||||
* @param {String} flags |
|
||||
* @param {String} description |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
function Option(flags, description) { |
|
||||
this.flags = flags; |
|
||||
this.required = ~flags.indexOf('<'); |
|
||||
this.optional = ~flags.indexOf('['); |
|
||||
this.bool = !~flags.indexOf('-no-'); |
|
||||
flags = flags.split(/[ ,|]+/); |
|
||||
if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift(); |
|
||||
this.long = flags.shift(); |
|
||||
this.description = description || ''; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Return option name. |
|
||||
* |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Option.prototype.name = function(){ |
|
||||
return this.long |
|
||||
.replace('--', '') |
|
||||
.replace('no-', ''); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Check if `arg` matches the short or long flag. |
|
||||
* |
|
||||
* @param {String} arg |
|
||||
* @return {Boolean} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Option.prototype.is = function(arg){ |
|
||||
return arg == this.short |
|
||||
|| arg == this.long; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Initialize a new `Command`. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
function Command(name) { |
|
||||
this.commands = []; |
|
||||
this.options = []; |
|
||||
this._execs = []; |
|
||||
this._args = []; |
|
||||
this._name = name; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Inherit from `EventEmitter.prototype`. |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.__proto__ = EventEmitter.prototype; |
|
||||
|
|
||||
/** |
|
||||
* Add command `name`. |
|
||||
* |
|
||||
* The `.action()` callback is invoked when the |
|
||||
* command `name` is specified via __ARGV__, |
|
||||
* and the remaining arguments are applied to the |
|
||||
* function for access. |
|
||||
* |
|
||||
* When the `name` is "*" an un-matched command |
|
||||
* will be passed as the first arg, followed by |
|
||||
* the rest of __ARGV__ remaining. |
|
||||
* |
|
||||
* Examples: |
|
||||
* |
|
||||
* program |
|
||||
* .version('0.0.1') |
|
||||
* .option('-C, --chdir <path>', 'change the working directory') |
|
||||
* .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf') |
|
||||
* .option('-T, --no-tests', 'ignore test hook') |
|
||||
* |
|
||||
* program |
|
||||
* .command('setup') |
|
||||
* .description('run remote setup commands') |
|
||||
* .action(function(){ |
|
||||
* console.log('setup'); |
|
||||
* }); |
|
||||
* |
|
||||
* program |
|
||||
* .command('exec <cmd>') |
|
||||
* .description('run the given remote command') |
|
||||
* .action(function(cmd){ |
|
||||
* console.log('exec "%s"', cmd); |
|
||||
* }); |
|
||||
* |
|
||||
* program |
|
||||
* .command('*') |
|
||||
* .description('deploy the given env') |
|
||||
* .action(function(env){ |
|
||||
* console.log('deploying "%s"', env); |
|
||||
* }); |
|
||||
* |
|
||||
* program.parse(process.argv); |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @param {String} [desc] |
|
||||
* @return {Command} the new command |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.command = function(name, desc){ |
|
||||
var args = name.split(/ +/); |
|
||||
var cmd = new Command(args.shift()); |
|
||||
if (desc) cmd.description(desc); |
|
||||
if (desc) this.executables = true; |
|
||||
if (desc) this._execs[cmd._name] = true; |
|
||||
this.commands.push(cmd); |
|
||||
cmd.parseExpectedArgs(args); |
|
||||
cmd.parent = this; |
|
||||
if (desc) return this; |
|
||||
return cmd; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Add an implicit `help [cmd]` subcommand |
|
||||
* which invokes `--help` for the given command. |
|
||||
* |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.addImplicitHelpCommand = function() { |
|
||||
this.command('help [cmd]', 'display help for [cmd]'); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parse expected `args`. |
|
||||
* |
|
||||
* For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`. |
|
||||
* |
|
||||
* @param {Array} args |
|
||||
* @return {Command} for chaining |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.parseExpectedArgs = function(args){ |
|
||||
if (!args.length) return; |
|
||||
var self = this; |
|
||||
args.forEach(function(arg){ |
|
||||
switch (arg[0]) { |
|
||||
case '<': |
|
||||
self._args.push({ required: true, name: arg.slice(1, -1) }); |
|
||||
break; |
|
||||
case '[': |
|
||||
self._args.push({ required: false, name: arg.slice(1, -1) }); |
|
||||
break; |
|
||||
} |
|
||||
}); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Register callback `fn` for the command. |
|
||||
* |
|
||||
* Examples: |
|
||||
* |
|
||||
* program |
|
||||
* .command('help') |
|
||||
* .description('display verbose help') |
|
||||
* .action(function(){ |
|
||||
* // output help here
|
|
||||
* }); |
|
||||
* |
|
||||
* @param {Function} fn |
|
||||
* @return {Command} for chaining |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.action = function(fn){ |
|
||||
var self = this; |
|
||||
this.parent.on(this._name, function(args, unknown){ |
|
||||
// Parse any so-far unknown options
|
|
||||
unknown = unknown || []; |
|
||||
var parsed = self.parseOptions(unknown); |
|
||||
|
|
||||
// Output help if necessary
|
|
||||
outputHelpIfNecessary(self, parsed.unknown); |
|
||||
|
|
||||
// If there are still any unknown options, then we simply
|
|
||||
// die, unless someone asked for help, in which case we give it
|
|
||||
// to them, and then we die.
|
|
||||
if (parsed.unknown.length > 0) { |
|
||||
self.unknownOption(parsed.unknown[0]); |
|
||||
} |
|
||||
|
|
||||
// Leftover arguments need to be pushed back. Fixes issue #56
|
|
||||
if (parsed.args.length) args = parsed.args.concat(args); |
|
||||
|
|
||||
self._args.forEach(function(arg, i){ |
|
||||
if (arg.required && null == args[i]) { |
|
||||
self.missingArgument(arg.name); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// Always append ourselves to the end of the arguments,
|
|
||||
// to make sure we match the number of arguments the user
|
|
||||
// expects
|
|
||||
if (self._args.length) { |
|
||||
args[self._args.length] = self; |
|
||||
} else { |
|
||||
args.push(self); |
|
||||
} |
|
||||
|
|
||||
fn.apply(this, args); |
|
||||
}); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Define option with `flags`, `description` and optional |
|
||||
* coercion `fn`. |
|
||||
* |
|
||||
* The `flags` string should contain both the short and long flags, |
|
||||
* separated by comma, a pipe or space. The following are all valid |
|
||||
* all will output this way when `--help` is used. |
|
||||
* |
|
||||
* "-p, --pepper" |
|
||||
* "-p|--pepper" |
|
||||
* "-p --pepper" |
|
||||
* |
|
||||
* Examples: |
|
||||
* |
|
||||
* // simple boolean defaulting to false
|
|
||||
* program.option('-p, --pepper', 'add pepper'); |
|
||||
* |
|
||||
* --pepper |
|
||||
* program.pepper |
|
||||
* // => Boolean
|
|
||||
* |
|
||||
* // simple boolean defaulting to false
|
|
||||
* program.option('-C, --no-cheese', 'remove cheese'); |
|
||||
* |
|
||||
* program.cheese |
|
||||
* // => true
|
|
||||
* |
|
||||
* --no-cheese |
|
||||
* program.cheese |
|
||||
* // => true
|
|
||||
* |
|
||||
* // required argument
|
|
||||
* program.option('-C, --chdir <path>', 'change the working directory'); |
|
||||
* |
|
||||
* --chdir /tmp |
|
||||
* program.chdir |
|
||||
* // => "/tmp"
|
|
||||
* |
|
||||
* // optional argument
|
|
||||
* program.option('-c, --cheese [type]', 'add cheese [marble]'); |
|
||||
* |
|
||||
* @param {String} flags |
|
||||
* @param {String} description |
|
||||
* @param {Function|Mixed} fn or default |
|
||||
* @param {Mixed} defaultValue |
|
||||
* @return {Command} for chaining |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.option = function(flags, description, fn, defaultValue){ |
|
||||
var self = this |
|
||||
, option = new Option(flags, description) |
|
||||
, oname = option.name() |
|
||||
, name = camelcase(oname); |
|
||||
|
|
||||
// default as 3rd arg
|
|
||||
if ('function' != typeof fn) defaultValue = fn, fn = null; |
|
||||
|
|
||||
// preassign default value only for --no-*, [optional], or <required>
|
|
||||
if (false == option.bool || option.optional || option.required) { |
|
||||
// when --no-* we make sure default is true
|
|
||||
if (false == option.bool) defaultValue = true; |
|
||||
// preassign only if we have a default
|
|
||||
if (undefined !== defaultValue) self[name] = defaultValue; |
|
||||
} |
|
||||
|
|
||||
// register the option
|
|
||||
this.options.push(option); |
|
||||
|
|
||||
// when it's passed assign the value
|
|
||||
// and conditionally invoke the callback
|
|
||||
this.on(oname, function(val){ |
|
||||
// coercion
|
|
||||
if (null != val && fn) val = fn(val); |
|
||||
|
|
||||
// unassigned or bool
|
|
||||
if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) { |
|
||||
// if no value, bool true, and we have a default, then use it!
|
|
||||
if (null == val) { |
|
||||
self[name] = option.bool |
|
||||
? defaultValue || true |
|
||||
: false; |
|
||||
} else { |
|
||||
self[name] = val; |
|
||||
} |
|
||||
} else if (null !== val) { |
|
||||
// reassign
|
|
||||
self[name] = val; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parse `argv`, settings options and invoking commands when defined. |
|
||||
* |
|
||||
* @param {Array} argv |
|
||||
* @return {Command} for chaining |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.parse = function(argv){ |
|
||||
// implicit help
|
|
||||
if (this.executables) this.addImplicitHelpCommand(); |
|
||||
|
|
||||
// store raw args
|
|
||||
this.rawArgs = argv; |
|
||||
|
|
||||
// guess name
|
|
||||
this._name = this._name || basename(argv[1]); |
|
||||
|
|
||||
// process argv
|
|
||||
var parsed = this.parseOptions(this.normalize(argv.slice(2))); |
|
||||
var args = this.args = parsed.args; |
|
||||
|
|
||||
var result = this.parseArgs(this.args, parsed.unknown); |
|
||||
|
|
||||
// executable sub-commands
|
|
||||
var name = result.args[0]; |
|
||||
if (this._execs[name]) return this.executeSubCommand(argv, args, parsed.unknown); |
|
||||
|
|
||||
return result; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Execute a sub-command executable. |
|
||||
* |
|
||||
* @param {Array} argv |
|
||||
* @param {Array} args |
|
||||
* @param {Array} unknown |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.executeSubCommand = function(argv, args, unknown) { |
|
||||
args = args.concat(unknown); |
|
||||
|
|
||||
if (!args.length) this.help(); |
|
||||
if ('help' == args[0] && 1 == args.length) this.help(); |
|
||||
|
|
||||
// <cmd> --help
|
|
||||
if ('help' == args[0]) { |
|
||||
args[0] = args[1]; |
|
||||
args[1] = '--help'; |
|
||||
} |
|
||||
|
|
||||
// executable
|
|
||||
var dir = dirname(argv[1]); |
|
||||
var bin = basename(argv[1]) + '-' + args[0]; |
|
||||
|
|
||||
// check for ./<bin> first
|
|
||||
var local = path.join(dir, bin); |
|
||||
|
|
||||
// run it
|
|
||||
args = args.slice(1); |
|
||||
var proc = spawn(local, args, { stdio: 'inherit', customFds: [0, 1, 2] }); |
|
||||
proc.on('error', function(err){ |
|
||||
if (err.code == "ENOENT") { |
|
||||
console.error('\n %s(1) does not exist, try --help\n', bin); |
|
||||
} else if (err.code == "EACCES") { |
|
||||
console.error('\n %s(1) not executable. try chmod or run with root\n', bin); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
this.runningCommand = proc; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Normalize `args`, splitting joined short flags. For example |
|
||||
* the arg "-abc" is equivalent to "-a -b -c". |
|
||||
* This also normalizes equal sign and splits "--abc=def" into "--abc def". |
|
||||
* |
|
||||
* @param {Array} args |
|
||||
* @return {Array} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.normalize = function(args){ |
|
||||
var ret = [] |
|
||||
, arg |
|
||||
, lastOpt |
|
||||
, index; |
|
||||
|
|
||||
for (var i = 0, len = args.length; i < len; ++i) { |
|
||||
arg = args[i]; |
|
||||
i > 0 && (lastOpt = this.optionFor(args[i-1])); |
|
||||
|
|
||||
if (lastOpt && lastOpt.required) { |
|
||||
ret.push(arg); |
|
||||
} else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) { |
|
||||
arg.slice(1).split('').forEach(function(c){ |
|
||||
ret.push('-' + c); |
|
||||
}); |
|
||||
} else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) { |
|
||||
ret.push(arg.slice(0, index), arg.slice(index + 1)); |
|
||||
} else { |
|
||||
ret.push(arg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ret; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parse command `args`. |
|
||||
* |
|
||||
* When listener(s) are available those |
|
||||
* callbacks are invoked, otherwise the "*" |
|
||||
* event is emitted and those actions are invoked. |
|
||||
* |
|
||||
* @param {Array} args |
|
||||
* @return {Command} for chaining |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.parseArgs = function(args, unknown){ |
|
||||
var cmds = this.commands |
|
||||
, len = cmds.length |
|
||||
, name; |
|
||||
|
|
||||
if (args.length) { |
|
||||
name = args[0]; |
|
||||
if (this.listeners(name).length) { |
|
||||
this.emit(args.shift(), args, unknown); |
|
||||
} else { |
|
||||
this.emit('*', args); |
|
||||
} |
|
||||
} else { |
|
||||
outputHelpIfNecessary(this, unknown); |
|
||||
|
|
||||
// If there were no args and we have unknown options,
|
|
||||
// then they are extraneous and we need to error.
|
|
||||
if (unknown.length > 0) { |
|
||||
this.unknownOption(unknown[0]); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return an option matching `arg` if any. |
|
||||
* |
|
||||
* @param {String} arg |
|
||||
* @return {Option} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.optionFor = function(arg){ |
|
||||
for (var i = 0, len = this.options.length; i < len; ++i) { |
|
||||
if (this.options[i].is(arg)) { |
|
||||
return this.options[i]; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Parse options from `argv` returning `argv` |
|
||||
* void of these options. |
|
||||
* |
|
||||
* @param {Array} argv |
|
||||
* @return {Array} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.parseOptions = function(argv){ |
|
||||
var args = [] |
|
||||
, len = argv.length |
|
||||
, literal |
|
||||
, option |
|
||||
, arg; |
|
||||
|
|
||||
var unknownOptions = []; |
|
||||
|
|
||||
// parse options
|
|
||||
for (var i = 0; i < len; ++i) { |
|
||||
arg = argv[i]; |
|
||||
|
|
||||
// literal args after --
|
|
||||
if ('--' == arg) { |
|
||||
literal = true; |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (literal) { |
|
||||
args.push(arg); |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
// find matching Option
|
|
||||
option = this.optionFor(arg); |
|
||||
|
|
||||
// option is defined
|
|
||||
if (option) { |
|
||||
// requires arg
|
|
||||
if (option.required) { |
|
||||
arg = argv[++i]; |
|
||||
if (null == arg) return this.optionMissingArgument(option); |
|
||||
this.emit(option.name(), arg); |
|
||||
// optional arg
|
|
||||
} else if (option.optional) { |
|
||||
arg = argv[i+1]; |
|
||||
if (null == arg || ('-' == arg[0] && '-' != arg)) { |
|
||||
arg = null; |
|
||||
} else { |
|
||||
++i; |
|
||||
} |
|
||||
this.emit(option.name(), arg); |
|
||||
// bool
|
|
||||
} else { |
|
||||
this.emit(option.name()); |
|
||||
} |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
// looks like an option
|
|
||||
if (arg.length > 1 && '-' == arg[0]) { |
|
||||
unknownOptions.push(arg); |
|
||||
|
|
||||
// If the next argument looks like it might be
|
|
||||
// an argument for this option, we pass it on.
|
|
||||
// If it isn't, then it'll simply be ignored
|
|
||||
if (argv[i+1] && '-' != argv[i+1][0]) { |
|
||||
unknownOptions.push(argv[++i]); |
|
||||
} |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
// arg
|
|
||||
args.push(arg); |
|
||||
} |
|
||||
|
|
||||
return { args: args, unknown: unknownOptions }; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Argument `name` is missing. |
|
||||
* |
|
||||
* @param {String} name |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.missingArgument = function(name){ |
|
||||
console.error(); |
|
||||
console.error(" error: missing required argument `%s'", name); |
|
||||
console.error(); |
|
||||
process.exit(1); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* `Option` is missing an argument, but received `flag` or nothing. |
|
||||
* |
|
||||
* @param {String} option |
|
||||
* @param {String} flag |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.optionMissingArgument = function(option, flag){ |
|
||||
console.error(); |
|
||||
if (flag) { |
|
||||
console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag); |
|
||||
} else { |
|
||||
console.error(" error: option `%s' argument missing", option.flags); |
|
||||
} |
|
||||
console.error(); |
|
||||
process.exit(1); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Unknown option `flag`. |
|
||||
* |
|
||||
* @param {String} flag |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.unknownOption = function(flag){ |
|
||||
console.error(); |
|
||||
console.error(" error: unknown option `%s'", flag); |
|
||||
console.error(); |
|
||||
process.exit(1); |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* Set the program version to `str`. |
|
||||
* |
|
||||
* This method auto-registers the "-V, --version" flag |
|
||||
* which will print the version number when passed. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {String} flags |
|
||||
* @return {Command} for chaining |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.version = function(str, flags){ |
|
||||
if (0 == arguments.length) return this._version; |
|
||||
this._version = str; |
|
||||
flags = flags || '-V, --version'; |
|
||||
this.option(flags, 'output the version number'); |
|
||||
this.on('version', function(){ |
|
||||
console.log(str); |
|
||||
process.exit(0); |
|
||||
}); |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Set the description `str`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @return {String|Command} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.description = function(str){ |
|
||||
if (0 == arguments.length) return this._description; |
|
||||
this._description = str; |
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Set / get the command usage `str`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @return {String|Command} |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.usage = function(str){ |
|
||||
var args = this._args.map(function(arg){ |
|
||||
return arg.required |
|
||||
? '<' + arg.name + '>' |
|
||||
: '[' + arg.name + ']'; |
|
||||
}); |
|
||||
|
|
||||
var usage = '[options' |
|
||||
+ (this.commands.length ? '] [command' : '') |
|
||||
+ ']' |
|
||||
+ (this._args.length ? ' ' + args : ''); |
|
||||
|
|
||||
if (0 == arguments.length) return this._usage || usage; |
|
||||
this._usage = str; |
|
||||
|
|
||||
return this; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return the largest option length. |
|
||||
* |
|
||||
* @return {Number} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.largestOptionLength = function(){ |
|
||||
return this.options.reduce(function(max, option){ |
|
||||
return Math.max(max, option.flags.length); |
|
||||
}, 0); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return help for options. |
|
||||
* |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.optionHelp = function(){ |
|
||||
var width = this.largestOptionLength(); |
|
||||
|
|
||||
// Prepend the help information
|
|
||||
return [pad('-h, --help', width) + ' ' + 'output usage information'] |
|
||||
.concat(this.options.map(function(option){ |
|
||||
return pad(option.flags, width) |
|
||||
+ ' ' + option.description; |
|
||||
})) |
|
||||
.join('\n'); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return command help documentation. |
|
||||
* |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.commandHelp = function(){ |
|
||||
if (!this.commands.length) return ''; |
|
||||
return [ |
|
||||
'' |
|
||||
, ' Commands:' |
|
||||
, '' |
|
||||
, this.commands.map(function(cmd){ |
|
||||
var args = cmd._args.map(function(arg){ |
|
||||
return arg.required |
|
||||
? '<' + arg.name + '>' |
|
||||
: '[' + arg.name + ']'; |
|
||||
}).join(' '); |
|
||||
|
|
||||
return pad(cmd._name |
|
||||
+ (cmd.options.length |
|
||||
? ' [options]' |
|
||||
: '') + ' ' + args, 22) |
|
||||
+ (cmd.description() |
|
||||
? ' ' + cmd.description() |
|
||||
: ''); |
|
||||
}).join('\n').replace(/^/gm, ' ') |
|
||||
, '' |
|
||||
].join('\n'); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Return program help documentation. |
|
||||
* |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.helpInformation = function(){ |
|
||||
return [ |
|
||||
'' |
|
||||
, ' Usage: ' + this._name + ' ' + this.usage() |
|
||||
, '' + this.commandHelp() |
|
||||
, ' Options:' |
|
||||
, '' |
|
||||
, '' + this.optionHelp().replace(/^/gm, ' ') |
|
||||
, '' |
|
||||
, '' |
|
||||
].join('\n'); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Output help information for this command |
|
||||
* |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.outputHelp = function(){ |
|
||||
process.stdout.write(this.helpInformation()); |
|
||||
this.emit('--help'); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Output help information and exit. |
|
||||
* |
|
||||
* @api public |
|
||||
*/ |
|
||||
|
|
||||
Command.prototype.help = function(){ |
|
||||
this.outputHelp(); |
|
||||
process.exit(); |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Camel-case the given `flag` |
|
||||
* |
|
||||
* @param {String} flag |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function camelcase(flag) { |
|
||||
return flag.split('-').reduce(function(str, word){ |
|
||||
return str + word[0].toUpperCase() + word.slice(1); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Pad `str` to `width`. |
|
||||
* |
|
||||
* @param {String} str |
|
||||
* @param {Number} width |
|
||||
* @return {String} |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function pad(str, width) { |
|
||||
var len = Math.max(0, width - str.length); |
|
||||
return str + Array(len + 1).join(' '); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Output help information if necessary |
|
||||
* |
|
||||
* @param {Command} command to output help for |
|
||||
* @param {Array} array of options to search for -h or --help |
|
||||
* @api private |
|
||||
*/ |
|
||||
|
|
||||
function outputHelpIfNecessary(cmd, options) { |
|
||||
options = options || []; |
|
||||
for (var i = 0; i < options.length; i++) { |
|
||||
if (options[i] == '--help' || options[i] == '-h') { |
|
||||
cmd.outputHelp(); |
|
||||
process.exit(0); |
|
||||
} |
|
||||
} |
|
||||
} |
|
45
node_modules/jade/node_modules/commander/package.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,22 +0,0 @@ |
|||||
# Auto detect text files and perform LF normalization |
|
||||
* text=auto |
|
||||
|
|
||||
# Custom for Visual Studio |
|
||||
*.cs diff=csharp |
|
||||
*.sln merge=union |
|
||||
*.csproj merge=union |
|
||||
*.vbproj merge=union |
|
||||
*.fsproj merge=union |
|
||||
*.dbproj merge=union |
|
||||
|
|
||||
# Standard to msysgit |
|
||||
*.doc diff=astextplain |
|
||||
*.DOC diff=astextplain |
|
||||
*.docx diff=astextplain |
|
||||
*.DOCX diff=astextplain |
|
||||
*.dot diff=astextplain |
|
||||
*.DOT diff=astextplain |
|
||||
*.pdf diff=astextplain |
|
||||
*.PDF diff=astextplain |
|
||||
*.rtf diff=astextplain |
|
||||
*.RTF diff=astextplain |
|
@ -1,13 +0,0 @@ |
|||||
lib-cov |
|
||||
*.seed |
|
||||
*.log |
|
||||
*.csv |
|
||||
*.dat |
|
||||
*.out |
|
||||
*.pid |
|
||||
*.gz |
|
||||
pids |
|
||||
logs |
|
||||
results |
|
||||
npm-debug.log |
|
||||
node_modules |
|
@ -1,4 +0,0 @@ |
|||||
language: node_js |
|
||||
node_js: |
|
||||
- "0.8" |
|
||||
- "0.10" |
|
@ -1,19 +0,0 @@ |
|||||
Copyright (c) 2013 Forbes Lindesay |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,35 +0,0 @@ |
|||||
# constantinople |
|
||||
|
|
||||
Determine whether a JavaScript expression evaluates to a constant (using UglifyJS). Here it is assumed to be safe to underestimate how constant something is. |
|
||||
|
|
||||
[![Build Status](https://travis-ci.org/ForbesLindesay/constantinople.png?branch=master)](https://travis-ci.org/ForbesLindesay/constantinople) |
|
||||
[![Dependency Status](https://gemnasium.com/ForbesLindesay/constantinople.png)](https://gemnasium.com/ForbesLindesay/constantinople) |
|
||||
[![NPM version](https://badge.fury.io/js/constantinople.png)](http://badge.fury.io/js/constantinople) |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
npm install constantinople |
|
||||
|
|
||||
## Usage |
|
||||
|
|
||||
```js |
|
||||
var isConstant = require('constantinople') |
|
||||
|
|
||||
if (isConstant('"foo" + 5')) { |
|
||||
console.dir(isConstant.toConstant('"foo" + 5')) |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## API |
|
||||
|
|
||||
### isConstant(src) |
|
||||
|
|
||||
Returns `true` if `src` evaluates to a constant, `false` otherwise. It will also return `false` if there is a syntax error, which makes it safe to use on potentially ES6 code. |
|
||||
|
|
||||
### toConstant(src) |
|
||||
|
|
||||
Returns the value resulting from evaluating `src`. This method throws an error if the expression is not constant. e.g. `toConstant("Math.random()")` would throw an error. |
|
||||
|
|
||||
## License |
|
||||
|
|
||||
MIT |
|
@ -1,35 +0,0 @@ |
|||||
'use strict' |
|
||||
|
|
||||
var uglify = require('uglify-js') |
|
||||
|
|
||||
var lastSRC = '(null)' |
|
||||
var lastRes = true |
|
||||
|
|
||||
module.exports = isConstant |
|
||||
function isConstant(src) { |
|
||||
src = '(' + src + ')' |
|
||||
if (lastSRC === src) return lastRes |
|
||||
lastSRC = src |
|
||||
try { |
|
||||
return lastRes = (detect(src).length === 0) |
|
||||
} catch (ex) { |
|
||||
return lastRes = false |
|
||||
} |
|
||||
} |
|
||||
isConstant.isConstant = isConstant |
|
||||
|
|
||||
isConstant.toConstant = toConstant |
|
||||
function toConstant(src) { |
|
||||
if (!isConstant(src)) throw new Error(JSON.stringify(src) + ' is not constant.') |
|
||||
return Function('return (' + src + ')')() |
|
||||
} |
|
||||
|
|
||||
function detect(src) { |
|
||||
var ast = uglify.parse(src.toString()) |
|
||||
ast.figure_out_scope() |
|
||||
var globals = ast.globals |
|
||||
.map(function (node, name) { |
|
||||
return name |
|
||||
}) |
|
||||
return globals |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
../uglify-js/bin/uglifyjs |
|
@ -1,2 +0,0 @@ |
|||||
tmp/ |
|
||||
node_modules/ |
|
@ -1,6 +0,0 @@ |
|||||
language: node_js |
|
||||
node_js: |
|
||||
- "0.4" |
|
||||
- "0.8" |
|
||||
- "0.10" |
|
||||
- "0.11" |
|
@ -1,29 +0,0 @@ |
|||||
UglifyJS is released under the BSD license: |
|
||||
|
|
||||
Copyright 2012-2013 (c) Mihai Bazon <mihai.bazon@gmail.com> |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions |
|
||||
are met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above |
|
||||
copyright notice, this list of conditions and the following |
|
||||
disclaimer. |
|
||||
|
|
||||
* 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. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “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 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. |
|
@ -1,636 +0,0 @@ |
|||||
UglifyJS 2 |
|
||||
========== |
|
||||
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.png)](https://travis-ci.org/mishoo/UglifyJS2) |
|
||||
|
|
||||
UglifyJS is a JavaScript parser, minifier, compressor or beautifier toolkit. |
|
||||
|
|
||||
This page documents the command line utility. For |
|
||||
[API and internals documentation see my website](http://lisperator.net/uglifyjs/). |
|
||||
There's also an |
|
||||
[in-browser online demo](http://lisperator.net/uglifyjs/#demo) (for Firefox, |
|
||||
Chrome and probably Safari). |
|
||||
|
|
||||
Install |
|
||||
------- |
|
||||
|
|
||||
First make sure you have installed the latest version of [node.js](http://nodejs.org/) |
|
||||
(You may need to restart your computer after this step). |
|
||||
|
|
||||
From NPM for use as a command line app: |
|
||||
|
|
||||
npm install uglify-js -g |
|
||||
|
|
||||
From NPM for programmatic use: |
|
||||
|
|
||||
npm install uglify-js |
|
||||
|
|
||||
From Git: |
|
||||
|
|
||||
git clone git://github.com/mishoo/UglifyJS2.git |
|
||||
cd UglifyJS2 |
|
||||
npm link . |
|
||||
|
|
||||
Usage |
|
||||
----- |
|
||||
|
|
||||
uglifyjs [input files] [options] |
|
||||
|
|
||||
UglifyJS2 can take multiple input files. It's recommended that you pass the |
|
||||
input files first, then pass the options. UglifyJS will parse input files |
|
||||
in sequence and apply any compression options. The files are parsed in the |
|
||||
same global scope, that is, a reference from a file to some |
|
||||
variable/function declared in another file will be matched properly. |
|
||||
|
|
||||
If you want to read from STDIN instead, pass a single dash instead of input |
|
||||
files. |
|
||||
|
|
||||
The available options are: |
|
||||
|
|
||||
``` |
|
||||
--source-map Specify an output file where to generate source map. |
|
||||
[string] |
|
||||
--source-map-root The path to the original source to be included in the |
|
||||
source map. [string] |
|
||||
--source-map-url The path to the source map to be added in //# |
|
||||
sourceMappingURL. Defaults to the value passed with |
|
||||
--source-map. [string] |
|
||||
--in-source-map Input source map, useful if you're compressing JS that was |
|
||||
generated from some other original code. |
|
||||
--screw-ie8 Pass this flag if you don't care about full compliance |
|
||||
with Internet Explorer 6-8 quirks (by default UglifyJS |
|
||||
will try to be IE-proof). [boolean] |
|
||||
--expr Parse a single expression, rather than a program (for |
|
||||
parsing JSON) [boolean] |
|
||||
-p, --prefix Skip prefix for original filenames that appear in source |
|
||||
maps. For example -p 3 will drop 3 directories from file |
|
||||
names and ensure they are relative paths. You can also |
|
||||
specify -p relative, which will make UglifyJS figure out |
|
||||
itself the relative paths between original sources, the |
|
||||
source map and the output file. [string] |
|
||||
-o, --output Output file (default STDOUT). |
|
||||
-b, --beautify Beautify output/specify output options. [string] |
|
||||
-m, --mangle Mangle names/pass mangler options. [string] |
|
||||
-r, --reserved Reserved names to exclude from mangling. |
|
||||
-c, --compress Enable compressor/pass compressor options. Pass options |
|
||||
like -c hoist_vars=false,if_return=false. Use -c with no |
|
||||
argument to use the default compression options. [string] |
|
||||
-d, --define Global definitions [string] |
|
||||
-e, --enclose Embed everything in a big function, with a configurable |
|
||||
parameter/argument list. [string] |
|
||||
--comments Preserve copyright comments in the output. By default this |
|
||||
works like Google Closure, keeping JSDoc-style comments |
|
||||
that contain "@license" or "@preserve". You can optionally |
|
||||
pass one of the following arguments to this flag: |
|
||||
- "all" to keep all comments |
|
||||
- a valid JS regexp (needs to start with a slash) to keep |
|
||||
only comments that match. |
|
||||
Note that currently not *all* comments can be kept when |
|
||||
compression is on, because of dead code removal or |
|
||||
cascading statements into sequences. [string] |
|
||||
--preamble Preamble to prepend to the output. You can use this to |
|
||||
insert a comment, for example for licensing information. |
|
||||
This will not be parsed, but the source map will adjust |
|
||||
for its presence. |
|
||||
--stats Display operations run time on STDERR. [boolean] |
|
||||
--acorn Use Acorn for parsing. [boolean] |
|
||||
--spidermonkey Assume input files are SpiderMonkey AST format (as JSON). |
|
||||
[boolean] |
|
||||
--self Build itself (UglifyJS2) as a library (implies |
|
||||
--wrap=UglifyJS --export-all) [boolean] |
|
||||
--wrap Embed everything in a big function, making the “exports” |
|
||||
and “global” variables available. You need to pass an |
|
||||
argument to this option to specify the name that your |
|
||||
module will take when included in, say, a browser. |
|
||||
[string] |
|
||||
--export-all Only used when --wrap, this tells UglifyJS to add code to |
|
||||
automatically export all globals. [boolean] |
|
||||
--lint Display some scope warnings [boolean] |
|
||||
-v, --verbose Verbose [boolean] |
|
||||
-V, --version Print version number and exit. [boolean] |
|
||||
``` |
|
||||
|
|
||||
Specify `--output` (`-o`) to declare the output file. Otherwise the output |
|
||||
goes to STDOUT. |
|
||||
|
|
||||
## Source map options |
|
||||
|
|
||||
UglifyJS2 can generate a source map file, which is highly useful for |
|
||||
debugging your compressed JavaScript. To get a source map, pass |
|
||||
`--source-map output.js.map` (full path to the file where you want the |
|
||||
source map dumped). |
|
||||
|
|
||||
Additionally you might need `--source-map-root` to pass the URL where the |
|
||||
original files can be found. In case you are passing full paths to input |
|
||||
files to UglifyJS, you can use `--prefix` (`-p`) to specify the number of |
|
||||
directories to drop from the path prefix when declaring files in the source |
|
||||
map. |
|
||||
|
|
||||
For example: |
|
||||
|
|
||||
uglifyjs /home/doe/work/foo/src/js/file1.js \ |
|
||||
/home/doe/work/foo/src/js/file2.js \ |
|
||||
-o foo.min.js \ |
|
||||
--source-map foo.min.js.map \ |
|
||||
--source-map-root http://foo.com/src \ |
|
||||
-p 5 -c -m |
|
||||
|
|
||||
The above will compress and mangle `file1.js` and `file2.js`, will drop the |
|
||||
output in `foo.min.js` and the source map in `foo.min.js.map`. The source |
|
||||
mapping will refer to `http://foo.com/src/js/file1.js` and |
|
||||
`http://foo.com/src/js/file2.js` (in fact it will list `http://foo.com/src` |
|
||||
as the source map root, and the original files as `js/file1.js` and |
|
||||
`js/file2.js`). |
|
||||
|
|
||||
### Composed source map |
|
||||
|
|
||||
When you're compressing JS code that was output by a compiler such as |
|
||||
CoffeeScript, mapping to the JS code won't be too helpful. Instead, you'd |
|
||||
like to map back to the original code (i.e. CoffeeScript). UglifyJS has an |
|
||||
option to take an input source map. Assuming you have a mapping from |
|
||||
CoffeeScript → compiled JS, UglifyJS can generate a map from CoffeeScript → |
|
||||
compressed JS by mapping every token in the compiled JS to its original |
|
||||
location. |
|
||||
|
|
||||
To use this feature you need to pass `--in-source-map |
|
||||
/path/to/input/source.map`. Normally the input source map should also point |
|
||||
to the file containing the generated JS, so if that's correct you can omit |
|
||||
input files from the command line. |
|
||||
|
|
||||
## Mangler options |
|
||||
|
|
||||
To enable the mangler you need to pass `--mangle` (`-m`). The following |
|
||||
(comma-separated) options are supported: |
|
||||
|
|
||||
- `sort` — to assign shorter names to most frequently used variables. This |
|
||||
saves a few hundred bytes on jQuery before gzip, but the output is |
|
||||
_bigger_ after gzip (and seems to happen for other libraries I tried it |
|
||||
on) therefore it's not enabled by default. |
|
||||
|
|
||||
- `toplevel` — mangle names declared in the toplevel scope (disabled by |
|
||||
default). |
|
||||
|
|
||||
- `eval` — mangle names visible in scopes where `eval` or `when` are used |
|
||||
(disabled by default). |
|
||||
|
|
||||
When mangling is enabled but you want to prevent certain names from being |
|
||||
mangled, you can declare those names with `--reserved` (`-r`) — pass a |
|
||||
comma-separated list of names. For example: |
|
||||
|
|
||||
uglifyjs ... -m -r '$,require,exports' |
|
||||
|
|
||||
to prevent the `require`, `exports` and `$` names from being changed. |
|
||||
|
|
||||
## Compressor options |
|
||||
|
|
||||
You need to pass `--compress` (`-c`) to enable the compressor. Optionally |
|
||||
you can pass a comma-separated list of options. Options are in the form |
|
||||
`foo=bar`, or just `foo` (the latter implies a boolean option that you want |
|
||||
to set `true`; it's effectively a shortcut for `foo=true`). |
|
||||
|
|
||||
- `sequences` -- join consecutive simple statements using the comma operator |
|
||||
|
|
||||
- `properties` -- rewrite property access using the dot notation, for |
|
||||
example `foo["bar"] → foo.bar` |
|
||||
|
|
||||
- `dead_code` -- remove unreachable code |
|
||||
|
|
||||
- `drop_debugger` -- remove `debugger;` statements |
|
||||
|
|
||||
- `unsafe` (default: false) -- apply "unsafe" transformations (discussion below) |
|
||||
|
|
||||
- `conditionals` -- apply optimizations for `if`-s and conditional |
|
||||
expressions |
|
||||
|
|
||||
- `comparisons` -- apply certain optimizations to binary nodes, for example: |
|
||||
`!(a <= b) → a > b` (only when `unsafe`), attempts to negate binary nodes, |
|
||||
e.g. `a = !b && !c && !d && !e → a=!(b||c||d||e)` etc. |
|
||||
|
|
||||
- `evaluate` -- attempt to evaluate constant expressions |
|
||||
|
|
||||
- `booleans` -- various optimizations for boolean context, for example `!!a |
|
||||
? b : c → a ? b : c` |
|
||||
|
|
||||
- `loops` -- optimizations for `do`, `while` and `for` loops when we can |
|
||||
statically determine the condition |
|
||||
|
|
||||
- `unused` -- drop unreferenced functions and variables |
|
||||
|
|
||||
- `hoist_funs` -- hoist function declarations |
|
||||
|
|
||||
- `hoist_vars` (default: false) -- hoist `var` declarations (this is `false` |
|
||||
by default because it seems to increase the size of the output in general) |
|
||||
|
|
||||
- `if_return` -- optimizations for if/return and if/continue |
|
||||
|
|
||||
- `join_vars` -- join consecutive `var` statements |
|
||||
|
|
||||
- `cascade` -- small optimization for sequences, transform `x, x` into `x` |
|
||||
and `x = something(), x` into `x = something()` |
|
||||
|
|
||||
- `warnings` -- display warnings when dropping unreachable code or unused |
|
||||
declarations etc. |
|
||||
|
|
||||
- `negate_iife` -- negate "Immediately-Called Function Expressions" |
|
||||
where the return value is discarded, to avoid the parens that the |
|
||||
code generator would insert. |
|
||||
|
|
||||
- `pure_getters` -- the default is `false`. If you pass `true` for |
|
||||
this, UglifyJS will assume that object property access |
|
||||
(e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects. |
|
||||
|
|
||||
- `pure_funcs` -- default `null`. You can pass an array of names and |
|
||||
UglifyJS will assume that those functions do not produce side |
|
||||
effects. DANGER: will not check if the name is redefined in scope. |
|
||||
An example case here, for instance `var q = Math.floor(a/b)`. If |
|
||||
variable `q` is not used elsewhere, UglifyJS will drop it, but will |
|
||||
still keep the `Math.floor(a/b)`, not knowing what it does. You can |
|
||||
pass `pure_funcs: [ 'Math.floor' ]` to let it know that this |
|
||||
function won't produce any side effect, in which case the whole |
|
||||
statement would get discarded. The current implementation adds some |
|
||||
overhead (compression will be slower). |
|
||||
|
|
||||
- `drop_console` -- default `false`. Pass `true` to discard calls to |
|
||||
`console.*` functions. |
|
||||
|
|
||||
### The `unsafe` option |
|
||||
|
|
||||
It enables some transformations that *might* break code logic in certain |
|
||||
contrived cases, but should be fine for most code. You might want to try it |
|
||||
on your own code, it should reduce the minified size. Here's what happens |
|
||||
when this flag is on: |
|
||||
|
|
||||
- `new Array(1, 2, 3)` or `Array(1, 2, 3)` → `[1, 2, 3 ]` |
|
||||
- `new Object()` → `{}` |
|
||||
- `String(exp)` or `exp.toString()` → `"" + exp` |
|
||||
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new` |
|
||||
- `typeof foo == "undefined"` → `foo === void 0` |
|
||||
- `void 0` → `undefined` (if there is a variable named "undefined" in |
|
||||
scope; we do it because the variable name will be mangled, typically |
|
||||
reduced to a single character). |
|
||||
|
|
||||
### Conditional compilation |
|
||||
|
|
||||
You can use the `--define` (`-d`) switch in order to declare global |
|
||||
variables that UglifyJS will assume to be constants (unless defined in |
|
||||
scope). For example if you pass `--define DEBUG=false` then, coupled with |
|
||||
dead code removal UglifyJS will discard the following from the output: |
|
||||
```javascript |
|
||||
if (DEBUG) { |
|
||||
console.log("debug stuff"); |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
UglifyJS will warn about the condition being always false and about dropping |
|
||||
unreachable code; for now there is no option to turn off only this specific |
|
||||
warning, you can pass `warnings=false` to turn off *all* warnings. |
|
||||
|
|
||||
Another way of doing that is to declare your globals as constants in a |
|
||||
separate file and include it into the build. For example you can have a |
|
||||
`build/defines.js` file with the following: |
|
||||
```javascript |
|
||||
const DEBUG = false; |
|
||||
const PRODUCTION = true; |
|
||||
// etc. |
|
||||
``` |
|
||||
|
|
||||
and build your code like this: |
|
||||
|
|
||||
uglifyjs build/defines.js js/foo.js js/bar.js... -c |
|
||||
|
|
||||
UglifyJS will notice the constants and, since they cannot be altered, it |
|
||||
will evaluate references to them to the value itself and drop unreachable |
|
||||
code as usual. The possible downside of this approach is that the build |
|
||||
will contain the `const` declarations. |
|
||||
|
|
||||
<a name="codegen-options"></a> |
|
||||
## Beautifier options |
|
||||
|
|
||||
The code generator tries to output shortest code possible by default. In |
|
||||
case you want beautified output, pass `--beautify` (`-b`). Optionally you |
|
||||
can pass additional arguments that control the code output: |
|
||||
|
|
||||
- `beautify` (default `true`) -- whether to actually beautify the output. |
|
||||
Passing `-b` will set this to true, but you might need to pass `-b` even |
|
||||
when you want to generate minified code, in order to specify additional |
|
||||
arguments, so you can use `-b beautify=false` to override it. |
|
||||
- `indent-level` (default 4) |
|
||||
- `indent-start` (default 0) -- prefix all lines by that many spaces |
|
||||
- `quote-keys` (default `false`) -- pass `true` to quote all keys in literal |
|
||||
objects |
|
||||
- `space-colon` (default `true`) -- insert a space after the colon signs |
|
||||
- `ascii-only` (default `false`) -- escape Unicode characters in strings and |
|
||||
regexps |
|
||||
- `inline-script` (default `false`) -- escape the slash in occurrences of |
|
||||
`</script` in strings |
|
||||
- `width` (default 80) -- only takes effect when beautification is on, this |
|
||||
specifies an (orientative) line width that the beautifier will try to |
|
||||
obey. It refers to the width of the line text (excluding indentation). |
|
||||
It doesn't work very well currently, but it does make the code generated |
|
||||
by UglifyJS more readable. |
|
||||
- `max-line-len` (default 32000) -- maximum line length (for uglified code) |
|
||||
- `bracketize` (default `false`) -- always insert brackets in `if`, `for`, |
|
||||
`do`, `while` or `with` statements, even if their body is a single |
|
||||
statement. |
|
||||
- `semicolons` (default `true`) -- separate statements with semicolons. If |
|
||||
you pass `false` then whenever possible we will use a newline instead of a |
|
||||
semicolon, leading to more readable output of uglified code (size before |
|
||||
gzip could be smaller; size after gzip insignificantly larger). |
|
||||
- `preamble` (default `null`) -- when passed it must be a string and |
|
||||
it will be prepended to the output literally. The source map will |
|
||||
adjust for this text. Can be used to insert a comment containing |
|
||||
licensing information, for example. |
|
||||
|
|
||||
### Keeping copyright notices or other comments |
|
||||
|
|
||||
You can pass `--comments` to retain certain comments in the output. By |
|
||||
default it will keep JSDoc-style comments that contain "@preserve", |
|
||||
"@license" or "@cc_on" (conditional compilation for IE). You can pass |
|
||||
`--comments all` to keep all the comments, or a valid JavaScript regexp to |
|
||||
keep only comments that match this regexp. For example `--comments |
|
||||
'/foo|bar/'` will keep only comments that contain "foo" or "bar". |
|
||||
|
|
||||
Note, however, that there might be situations where comments are lost. For |
|
||||
example: |
|
||||
```javascript |
|
||||
function f() { |
|
||||
/** @preserve Foo Bar */ |
|
||||
function g() { |
|
||||
// this function is never called |
|
||||
} |
|
||||
return something(); |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
Even though it has "@preserve", the comment will be lost because the inner |
|
||||
function `g` (which is the AST node to which the comment is attached to) is |
|
||||
discarded by the compressor as not referenced. |
|
||||
|
|
||||
The safest comments where to place copyright information (or other info that |
|
||||
needs to be kept in the output) are comments attached to toplevel nodes. |
|
||||
|
|
||||
## Support for the SpiderMonkey AST |
|
||||
|
|
||||
UglifyJS2 has its own abstract syntax tree format; for |
|
||||
[practical reasons](http://lisperator.net/blog/uglifyjs-why-not-switching-to-spidermonkey-ast/) |
|
||||
we can't easily change to using the SpiderMonkey AST internally. However, |
|
||||
UglifyJS now has a converter which can import a SpiderMonkey AST. |
|
||||
|
|
||||
For example [Acorn][acorn] is a super-fast parser that produces a |
|
||||
SpiderMonkey AST. It has a small CLI utility that parses one file and dumps |
|
||||
the AST in JSON on the standard output. To use UglifyJS to mangle and |
|
||||
compress that: |
|
||||
|
|
||||
acorn file.js | uglifyjs --spidermonkey -m -c |
|
||||
|
|
||||
The `--spidermonkey` option tells UglifyJS that all input files are not |
|
||||
JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we |
|
||||
don't use our own parser in this case, but just transform that AST into our |
|
||||
internal AST. |
|
||||
|
|
||||
### Use Acorn for parsing |
|
||||
|
|
||||
More for fun, I added the `--acorn` option which will use Acorn to do all |
|
||||
the parsing. If you pass this option, UglifyJS will `require("acorn")`. |
|
||||
|
|
||||
Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but |
|
||||
converting the SpiderMonkey tree that Acorn produces takes another 150ms so |
|
||||
in total it's a bit more than just using UglifyJS's own parser. |
|
||||
|
|
||||
API Reference |
|
||||
------------- |
|
||||
|
|
||||
Assuming installation via NPM, you can load UglifyJS in your application |
|
||||
like this: |
|
||||
```javascript |
|
||||
var UglifyJS = require("uglify-js"); |
|
||||
``` |
|
||||
|
|
||||
It exports a lot of names, but I'll discuss here the basics that are needed |
|
||||
for parsing, mangling and compressing a piece of code. The sequence is (1) |
|
||||
parse, (2) compress, (3) mangle, (4) generate output code. |
|
||||
|
|
||||
### The simple way |
|
||||
|
|
||||
There's a single toplevel function which combines all the steps. If you |
|
||||
don't need additional customization, you might want to go with `minify`. |
|
||||
Example: |
|
||||
```javascript |
|
||||
var result = UglifyJS.minify("/path/to/file.js"); |
|
||||
console.log(result.code); // minified output |
|
||||
// if you need to pass code instead of file name |
|
||||
var result = UglifyJS.minify("var b = function () {};", {fromString: true}); |
|
||||
``` |
|
||||
|
|
||||
You can also compress multiple files: |
|
||||
```javascript |
|
||||
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ]); |
|
||||
console.log(result.code); |
|
||||
``` |
|
||||
|
|
||||
To generate a source map: |
|
||||
```javascript |
|
||||
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { |
|
||||
outSourceMap: "out.js.map" |
|
||||
}); |
|
||||
console.log(result.code); // minified output |
|
||||
console.log(result.map); |
|
||||
``` |
|
||||
|
|
||||
Note that the source map is not saved in a file, it's just returned in |
|
||||
`result.map`. The value passed for `outSourceMap` is only used to set the |
|
||||
`file` attribute in the source map (see [the spec][sm-spec]). |
|
||||
|
|
||||
You can also specify sourceRoot property to be included in source map: |
|
||||
```javascript |
|
||||
var result = UglifyJS.minify([ "file1.js", "file2.js", "file3.js" ], { |
|
||||
outSourceMap: "out.js.map", |
|
||||
sourceRoot: "http://example.com/src" |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
If you're compressing compiled JavaScript and have a source map for it, you |
|
||||
can use the `inSourceMap` argument: |
|
||||
```javascript |
|
||||
var result = UglifyJS.minify("compiled.js", { |
|
||||
inSourceMap: "compiled.js.map", |
|
||||
outSourceMap: "minified.js.map" |
|
||||
}); |
|
||||
// same as before, it returns `code` and `map` |
|
||||
``` |
|
||||
|
|
||||
The `inSourceMap` is only used if you also request `outSourceMap` (it makes |
|
||||
no sense otherwise). |
|
||||
|
|
||||
Other options: |
|
||||
|
|
||||
- `warnings` (default `false`) — pass `true` to display compressor warnings. |
|
||||
|
|
||||
- `fromString` (default `false`) — if you pass `true` then you can pass |
|
||||
JavaScript source code, rather than file names. |
|
||||
|
|
||||
- `mangle` — pass `false` to skip mangling names. |
|
||||
|
|
||||
- `output` (default `null`) — pass an object if you wish to specify |
|
||||
additional [output options][codegen]. The defaults are optimized |
|
||||
for best compression. |
|
||||
|
|
||||
- `compress` (default `{}`) — pass `false` to skip compressing entirely. |
|
||||
Pass an object to specify custom [compressor options][compressor]. |
|
||||
|
|
||||
We could add more options to `UglifyJS.minify` — if you need additional |
|
||||
functionality please suggest! |
|
||||
|
|
||||
### The hard way |
|
||||
|
|
||||
Following there's more detailed API info, in case the `minify` function is |
|
||||
too simple for your needs. |
|
||||
|
|
||||
#### The parser |
|
||||
```javascript |
|
||||
var toplevel_ast = UglifyJS.parse(code, options); |
|
||||
``` |
|
||||
|
|
||||
`options` is optional and if present it must be an object. The following |
|
||||
properties are available: |
|
||||
|
|
||||
- `strict` — disable automatic semicolon insertion and support for trailing |
|
||||
comma in arrays and objects |
|
||||
- `filename` — the name of the file where this code is coming from |
|
||||
- `toplevel` — a `toplevel` node (as returned by a previous invocation of |
|
||||
`parse`) |
|
||||
|
|
||||
The last two options are useful when you'd like to minify multiple files and |
|
||||
get a single file as the output and a proper source map. Our CLI tool does |
|
||||
something like this: |
|
||||
```javascript |
|
||||
var toplevel = null; |
|
||||
files.forEach(function(file){ |
|
||||
var code = fs.readFileSync(file, "utf8"); |
|
||||
toplevel = UglifyJS.parse(code, { |
|
||||
filename: file, |
|
||||
toplevel: toplevel |
|
||||
}); |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
After this, we have in `toplevel` a big AST containing all our files, with |
|
||||
each token having proper information about where it came from. |
|
||||
|
|
||||
#### Scope information |
|
||||
|
|
||||
UglifyJS contains a scope analyzer that you need to call manually before |
|
||||
compressing or mangling. Basically it augments various nodes in the AST |
|
||||
with information about where is a name defined, how many times is a name |
|
||||
referenced, if it is a global or not, if a function is using `eval` or the |
|
||||
`with` statement etc. I will discuss this some place else, for now what's |
|
||||
important to know is that you need to call the following before doing |
|
||||
anything with the tree: |
|
||||
```javascript |
|
||||
toplevel.figure_out_scope() |
|
||||
``` |
|
||||
|
|
||||
#### Compression |
|
||||
|
|
||||
Like this: |
|
||||
```javascript |
|
||||
var compressor = UglifyJS.Compressor(options); |
|
||||
var compressed_ast = toplevel.transform(compressor); |
|
||||
``` |
|
||||
|
|
||||
The `options` can be missing. Available options are discussed above in |
|
||||
“Compressor options”. Defaults should lead to best compression in most |
|
||||
scripts. |
|
||||
|
|
||||
The compressor is destructive, so don't rely that `toplevel` remains the |
|
||||
original tree. |
|
||||
|
|
||||
#### Mangling |
|
||||
|
|
||||
After compression it is a good idea to call again `figure_out_scope` (since |
|
||||
the compressor might drop unused variables / unreachable code and this might |
|
||||
change the number of identifiers or their position). Optionally, you can |
|
||||
call a trick that helps after Gzip (counting character frequency in |
|
||||
non-mangleable words). Example: |
|
||||
```javascript |
|
||||
compressed_ast.figure_out_scope(); |
|
||||
compressed_ast.compute_char_frequency(); |
|
||||
compressed_ast.mangle_names(); |
|
||||
``` |
|
||||
|
|
||||
#### Generating output |
|
||||
|
|
||||
AST nodes have a `print` method that takes an output stream. Essentially, |
|
||||
to generate code you do this: |
|
||||
```javascript |
|
||||
var stream = UglifyJS.OutputStream(options); |
|
||||
compressed_ast.print(stream); |
|
||||
var code = stream.toString(); // this is your minified code |
|
||||
``` |
|
||||
|
|
||||
or, for a shortcut you can do: |
|
||||
```javascript |
|
||||
var code = compressed_ast.print_to_string(options); |
|
||||
``` |
|
||||
|
|
||||
As usual, `options` is optional. The output stream accepts a lot of otions, |
|
||||
most of them documented above in section “Beautifier options”. The two |
|
||||
which we care about here are `source_map` and `comments`. |
|
||||
|
|
||||
#### Keeping comments in the output |
|
||||
|
|
||||
In order to keep certain comments in the output you need to pass the |
|
||||
`comments` option. Pass a RegExp or a function. If you pass a RegExp, only |
|
||||
those comments whose body matches the regexp will be kept. Note that body |
|
||||
means without the initial `//` or `/*`. If you pass a function, it will be |
|
||||
called for every comment in the tree and will receive two arguments: the |
|
||||
node that the comment is attached to, and the comment token itself. |
|
||||
|
|
||||
The comment token has these properties: |
|
||||
|
|
||||
- `type`: "comment1" for single-line comments or "comment2" for multi-line |
|
||||
comments |
|
||||
- `value`: the comment body |
|
||||
- `pos` and `endpos`: the start/end positions (zero-based indexes) in the |
|
||||
original code where this comment appears |
|
||||
- `line` and `col`: the line and column where this comment appears in the |
|
||||
original code |
|
||||
- `file` — the file name of the original file |
|
||||
- `nlb` — true if there was a newline before this comment in the original |
|
||||
code, or if this comment contains a newline. |
|
||||
|
|
||||
Your function should return `true` to keep the comment, or a falsy value |
|
||||
otherwise. |
|
||||
|
|
||||
#### Generating a source mapping |
|
||||
|
|
||||
You need to pass the `source_map` argument when calling `print`. It needs |
|
||||
to be a `SourceMap` object (which is a thin wrapper on top of the |
|
||||
[source-map][source-map] library). |
|
||||
|
|
||||
Example: |
|
||||
```javascript |
|
||||
var source_map = UglifyJS.SourceMap(source_map_options); |
|
||||
var stream = UglifyJS.OutputStream({ |
|
||||
... |
|
||||
source_map: source_map |
|
||||
}); |
|
||||
compressed_ast.print(stream); |
|
||||
|
|
||||
var code = stream.toString(); |
|
||||
var map = source_map.toString(); // json output for your source map |
|
||||
``` |
|
||||
|
|
||||
The `source_map_options` (optional) can contain the following properties: |
|
||||
|
|
||||
- `file`: the name of the JavaScript output file that this mapping refers to |
|
||||
- `root`: the `sourceRoot` property (see the [spec][sm-spec]) |
|
||||
- `orig`: the "original source map", handy when you compress generated JS |
|
||||
and want to map the minified output back to the original code where it |
|
||||
came from. It can be simply a string in JSON, or a JSON object containing |
|
||||
the original source map. |
|
||||
|
|
||||
[acorn]: https://github.com/marijnh/acorn |
|
||||
[source-map]: https://github.com/mozilla/source-map |
|
||||
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit |
|
||||
[codegen]: http://lisperator.net/uglifyjs/codegen |
|
||||
[compressor]: http://lisperator.net/uglifyjs/compress |
|
@ -1,984 +0,0 @@ |
|||||
/*********************************************************************** |
|
||||
|
|
||||
A JavaScript tokenizer / parser / beautifier / compressor. |
|
||||
https://github.com/mishoo/UglifyJS2
|
|
||||
|
|
||||
-------------------------------- (C) --------------------------------- |
|
||||
|
|
||||
Author: Mihai Bazon |
|
||||
<mihai.bazon@gmail.com> |
|
||||
http://mihai.bazon.net/blog
|
|
||||
|
|
||||
Distributed under the BSD license: |
|
||||
|
|
||||
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions |
|
||||
are met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above |
|
||||
copyright notice, this list of conditions and the following |
|
||||
disclaimer. |
|
||||
|
|
||||
* 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. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “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 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. |
|
||||
|
|
||||
***********************************************************************/ |
|
||||
|
|
||||
"use strict"; |
|
||||
|
|
||||
function DEFNODE(type, props, methods, base) { |
|
||||
if (arguments.length < 4) base = AST_Node; |
|
||||
if (!props) props = []; |
|
||||
else props = props.split(/\s+/); |
|
||||
var self_props = props; |
|
||||
if (base && base.PROPS) |
|
||||
props = props.concat(base.PROPS); |
|
||||
var code = "return function AST_" + type + "(props){ if (props) { "; |
|
||||
for (var i = props.length; --i >= 0;) { |
|
||||
code += "this." + props[i] + " = props." + props[i] + ";"; |
|
||||
} |
|
||||
var proto = base && new base; |
|
||||
if (proto && proto.initialize || (methods && methods.initialize)) |
|
||||
code += "this.initialize();"; |
|
||||
code += "}}"; |
|
||||
var ctor = new Function(code)(); |
|
||||
if (proto) { |
|
||||
ctor.prototype = proto; |
|
||||
ctor.BASE = base; |
|
||||
} |
|
||||
if (base) base.SUBCLASSES.push(ctor); |
|
||||
ctor.prototype.CTOR = ctor; |
|
||||
ctor.PROPS = props || null; |
|
||||
ctor.SELF_PROPS = self_props; |
|
||||
ctor.SUBCLASSES = []; |
|
||||
if (type) { |
|
||||
ctor.prototype.TYPE = ctor.TYPE = type; |
|
||||
} |
|
||||
if (methods) for (i in methods) if (methods.hasOwnProperty(i)) { |
|
||||
if (/^\$/.test(i)) { |
|
||||
ctor[i.substr(1)] = methods[i]; |
|
||||
} else { |
|
||||
ctor.prototype[i] = methods[i]; |
|
||||
} |
|
||||
} |
|
||||
ctor.DEFMETHOD = function(name, method) { |
|
||||
this.prototype[name] = method; |
|
||||
}; |
|
||||
return ctor; |
|
||||
}; |
|
||||
|
|
||||
var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_before file", { |
|
||||
}, null); |
|
||||
|
|
||||
var AST_Node = DEFNODE("Node", "start end", { |
|
||||
clone: function() { |
|
||||
return new this.CTOR(this); |
|
||||
}, |
|
||||
$documentation: "Base class of all AST nodes", |
|
||||
$propdoc: { |
|
||||
start: "[AST_Token] The first token of this node", |
|
||||
end: "[AST_Token] The last token of this node" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this); |
|
||||
}, |
|
||||
walk: function(visitor) { |
|
||||
return this._walk(visitor); // not sure the indirection will be any help
|
|
||||
} |
|
||||
}, null); |
|
||||
|
|
||||
AST_Node.warn_function = null; |
|
||||
AST_Node.warn = function(txt, props) { |
|
||||
if (AST_Node.warn_function) |
|
||||
AST_Node.warn_function(string_template(txt, props)); |
|
||||
}; |
|
||||
|
|
||||
/* -----[ statements ]----- */ |
|
||||
|
|
||||
var AST_Statement = DEFNODE("Statement", null, { |
|
||||
$documentation: "Base class of all statements", |
|
||||
}); |
|
||||
|
|
||||
var AST_Debugger = DEFNODE("Debugger", null, { |
|
||||
$documentation: "Represents a debugger statement", |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_Directive = DEFNODE("Directive", "value scope", { |
|
||||
$documentation: "Represents a directive, like \"use strict\";", |
|
||||
$propdoc: { |
|
||||
value: "[string] The value of this directive as a plain string (it's not an AST_String!)", |
|
||||
scope: "[AST_Scope/S] The scope that this directive affects" |
|
||||
}, |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { |
|
||||
$documentation: "A statement consisting of an expression, i.e. a = 1 + 2", |
|
||||
$propdoc: { |
|
||||
body: "[AST_Node] an expression node (should not be instanceof AST_Statement)" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
function walk_body(node, visitor) { |
|
||||
if (node.body instanceof AST_Statement) { |
|
||||
node.body._walk(visitor); |
|
||||
} |
|
||||
else node.body.forEach(function(stat){ |
|
||||
stat._walk(visitor); |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
var AST_Block = DEFNODE("Block", "body", { |
|
||||
$documentation: "A body of statements (usually bracketed)", |
|
||||
$propdoc: { |
|
||||
body: "[AST_Statement*] an array of statements" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
walk_body(this, visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_BlockStatement = DEFNODE("BlockStatement", null, { |
|
||||
$documentation: "A block statement", |
|
||||
}, AST_Block); |
|
||||
|
|
||||
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { |
|
||||
$documentation: "The empty statement (empty block or simply a semicolon)", |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this); |
|
||||
} |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { |
|
||||
$documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", |
|
||||
$propdoc: { |
|
||||
body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { |
|
||||
$documentation: "Statement with a label", |
|
||||
$propdoc: { |
|
||||
label: "[AST_Label] a label definition" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.label._walk(visitor); |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_StatementWithBody); |
|
||||
|
|
||||
var AST_IterationStatement = DEFNODE("IterationStatement", null, { |
|
||||
$documentation: "Internal class. All loops inherit from it." |
|
||||
}, AST_StatementWithBody); |
|
||||
|
|
||||
var AST_DWLoop = DEFNODE("DWLoop", "condition", { |
|
||||
$documentation: "Base class for do/while statements", |
|
||||
$propdoc: { |
|
||||
condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.condition._walk(visitor); |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_IterationStatement); |
|
||||
|
|
||||
var AST_Do = DEFNODE("Do", null, { |
|
||||
$documentation: "A `do` statement", |
|
||||
}, AST_DWLoop); |
|
||||
|
|
||||
var AST_While = DEFNODE("While", null, { |
|
||||
$documentation: "A `while` statement", |
|
||||
}, AST_DWLoop); |
|
||||
|
|
||||
var AST_For = DEFNODE("For", "init condition step", { |
|
||||
$documentation: "A `for` statement", |
|
||||
$propdoc: { |
|
||||
init: "[AST_Node?] the `for` initialization code, or null if empty", |
|
||||
condition: "[AST_Node?] the `for` termination clause, or null if empty", |
|
||||
step: "[AST_Node?] the `for` update clause, or null if empty" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
if (this.init) this.init._walk(visitor); |
|
||||
if (this.condition) this.condition._walk(visitor); |
|
||||
if (this.step) this.step._walk(visitor); |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_IterationStatement); |
|
||||
|
|
||||
var AST_ForIn = DEFNODE("ForIn", "init name object", { |
|
||||
$documentation: "A `for ... in` statement", |
|
||||
$propdoc: { |
|
||||
init: "[AST_Node] the `for/in` initialization code", |
|
||||
name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", |
|
||||
object: "[AST_Node] the object that we're looping through" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.init._walk(visitor); |
|
||||
this.object._walk(visitor); |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_IterationStatement); |
|
||||
|
|
||||
var AST_With = DEFNODE("With", "expression", { |
|
||||
$documentation: "A `with` statement", |
|
||||
$propdoc: { |
|
||||
expression: "[AST_Node] the `with` expression" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
this.body._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_StatementWithBody); |
|
||||
|
|
||||
/* -----[ scope and functions ]----- */ |
|
||||
|
|
||||
var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", { |
|
||||
$documentation: "Base class for all statements introducing a lexical scope", |
|
||||
$propdoc: { |
|
||||
directives: "[string*/S] an array of directives declared in this scope", |
|
||||
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope", |
|
||||
functions: "[Object/S] like `variables`, but only lists function declarations", |
|
||||
uses_with: "[boolean/S] tells whether this scope uses the `with` statement", |
|
||||
uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`", |
|
||||
parent_scope: "[AST_Scope?/S] link to the parent scope", |
|
||||
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", |
|
||||
cname: "[integer/S] current index for mangling variables (used internally by the mangler)", |
|
||||
}, |
|
||||
}, AST_Block); |
|
||||
|
|
||||
var AST_Toplevel = DEFNODE("Toplevel", "globals", { |
|
||||
$documentation: "The toplevel scope", |
|
||||
$propdoc: { |
|
||||
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", |
|
||||
}, |
|
||||
wrap_enclose: function(arg_parameter_pairs) { |
|
||||
var self = this; |
|
||||
var args = []; |
|
||||
var parameters = []; |
|
||||
|
|
||||
arg_parameter_pairs.forEach(function(pair) { |
|
||||
var split = pair.split(":"); |
|
||||
|
|
||||
args.push(split[0]); |
|
||||
parameters.push(split[1]); |
|
||||
}); |
|
||||
|
|
||||
var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")"; |
|
||||
wrapped_tl = parse(wrapped_tl); |
|
||||
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ |
|
||||
if (node instanceof AST_Directive && node.value == "$ORIG") { |
|
||||
return MAP.splice(self.body); |
|
||||
} |
|
||||
})); |
|
||||
return wrapped_tl; |
|
||||
}, |
|
||||
wrap_commonjs: function(name, export_all) { |
|
||||
var self = this; |
|
||||
var to_export = []; |
|
||||
if (export_all) { |
|
||||
self.figure_out_scope(); |
|
||||
self.walk(new TreeWalker(function(node){ |
|
||||
if (node instanceof AST_SymbolDeclaration && node.definition().global) { |
|
||||
if (!find_if(function(n){ return n.name == node.name }, to_export)) |
|
||||
to_export.push(node); |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
var wrapped_tl = "(function(exports, global){ global['" + name + "'] = exports; '$ORIG'; '$EXPORTS'; }({}, (function(){return this}())))"; |
|
||||
wrapped_tl = parse(wrapped_tl); |
|
||||
wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ |
|
||||
if (node instanceof AST_SimpleStatement) { |
|
||||
node = node.body; |
|
||||
if (node instanceof AST_String) switch (node.getValue()) { |
|
||||
case "$ORIG": |
|
||||
return MAP.splice(self.body); |
|
||||
case "$EXPORTS": |
|
||||
var body = []; |
|
||||
to_export.forEach(function(sym){ |
|
||||
body.push(new AST_SimpleStatement({ |
|
||||
body: new AST_Assign({ |
|
||||
left: new AST_Sub({ |
|
||||
expression: new AST_SymbolRef({ name: "exports" }), |
|
||||
property: new AST_String({ value: sym.name }), |
|
||||
}), |
|
||||
operator: "=", |
|
||||
right: new AST_SymbolRef(sym), |
|
||||
}), |
|
||||
})); |
|
||||
}); |
|
||||
return MAP.splice(body); |
|
||||
} |
|
||||
} |
|
||||
})); |
|
||||
return wrapped_tl; |
|
||||
} |
|
||||
}, AST_Scope); |
|
||||
|
|
||||
var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { |
|
||||
$documentation: "Base class for functions", |
|
||||
$propdoc: { |
|
||||
name: "[AST_SymbolDeclaration?] the name of this function", |
|
||||
argnames: "[AST_SymbolFunarg*] array of function arguments", |
|
||||
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
if (this.name) this.name._walk(visitor); |
|
||||
this.argnames.forEach(function(arg){ |
|
||||
arg._walk(visitor); |
|
||||
}); |
|
||||
walk_body(this, visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Scope); |
|
||||
|
|
||||
var AST_Accessor = DEFNODE("Accessor", null, { |
|
||||
$documentation: "A setter/getter function. The `name` property is always null." |
|
||||
}, AST_Lambda); |
|
||||
|
|
||||
var AST_Function = DEFNODE("Function", null, { |
|
||||
$documentation: "A function expression" |
|
||||
}, AST_Lambda); |
|
||||
|
|
||||
var AST_Defun = DEFNODE("Defun", null, { |
|
||||
$documentation: "A function definition" |
|
||||
}, AST_Lambda); |
|
||||
|
|
||||
/* -----[ JUMPS ]----- */ |
|
||||
|
|
||||
var AST_Jump = DEFNODE("Jump", null, { |
|
||||
$documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)" |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_Exit = DEFNODE("Exit", "value", { |
|
||||
$documentation: "Base class for “exits” (`return` and `throw`)", |
|
||||
$propdoc: { |
|
||||
value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, this.value && function(){ |
|
||||
this.value._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Jump); |
|
||||
|
|
||||
var AST_Return = DEFNODE("Return", null, { |
|
||||
$documentation: "A `return` statement" |
|
||||
}, AST_Exit); |
|
||||
|
|
||||
var AST_Throw = DEFNODE("Throw", null, { |
|
||||
$documentation: "A `throw` statement" |
|
||||
}, AST_Exit); |
|
||||
|
|
||||
var AST_LoopControl = DEFNODE("LoopControl", "label", { |
|
||||
$documentation: "Base class for loop control statements (`break` and `continue`)", |
|
||||
$propdoc: { |
|
||||
label: "[AST_LabelRef?] the label, or null if none", |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, this.label && function(){ |
|
||||
this.label._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Jump); |
|
||||
|
|
||||
var AST_Break = DEFNODE("Break", null, { |
|
||||
$documentation: "A `break` statement" |
|
||||
}, AST_LoopControl); |
|
||||
|
|
||||
var AST_Continue = DEFNODE("Continue", null, { |
|
||||
$documentation: "A `continue` statement" |
|
||||
}, AST_LoopControl); |
|
||||
|
|
||||
/* -----[ IF ]----- */ |
|
||||
|
|
||||
var AST_If = DEFNODE("If", "condition alternative", { |
|
||||
$documentation: "A `if` statement", |
|
||||
$propdoc: { |
|
||||
condition: "[AST_Node] the `if` condition", |
|
||||
alternative: "[AST_Statement?] the `else` part, or null if not present" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.condition._walk(visitor); |
|
||||
this.body._walk(visitor); |
|
||||
if (this.alternative) this.alternative._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_StatementWithBody); |
|
||||
|
|
||||
/* -----[ SWITCH ]----- */ |
|
||||
|
|
||||
var AST_Switch = DEFNODE("Switch", "expression", { |
|
||||
$documentation: "A `switch` statement", |
|
||||
$propdoc: { |
|
||||
expression: "[AST_Node] the `switch` “discriminant”" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
walk_body(this, visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Block); |
|
||||
|
|
||||
var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { |
|
||||
$documentation: "Base class for `switch` branches", |
|
||||
}, AST_Block); |
|
||||
|
|
||||
var AST_Default = DEFNODE("Default", null, { |
|
||||
$documentation: "A `default` switch branch", |
|
||||
}, AST_SwitchBranch); |
|
||||
|
|
||||
var AST_Case = DEFNODE("Case", "expression", { |
|
||||
$documentation: "A `case` switch branch", |
|
||||
$propdoc: { |
|
||||
expression: "[AST_Node] the `case` expression" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
walk_body(this, visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_SwitchBranch); |
|
||||
|
|
||||
/* -----[ EXCEPTIONS ]----- */ |
|
||||
|
|
||||
var AST_Try = DEFNODE("Try", "bcatch bfinally", { |
|
||||
$documentation: "A `try` statement", |
|
||||
$propdoc: { |
|
||||
bcatch: "[AST_Catch?] the catch block, or null if not present", |
|
||||
bfinally: "[AST_Finally?] the finally block, or null if not present" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
walk_body(this, visitor); |
|
||||
if (this.bcatch) this.bcatch._walk(visitor); |
|
||||
if (this.bfinally) this.bfinally._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Block); |
|
||||
|
|
||||
var AST_Catch = DEFNODE("Catch", "argname", { |
|
||||
$documentation: "A `catch` node; only makes sense as part of a `try` statement", |
|
||||
$propdoc: { |
|
||||
argname: "[AST_SymbolCatch] symbol for the exception" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.argname._walk(visitor); |
|
||||
walk_body(this, visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Block); |
|
||||
|
|
||||
var AST_Finally = DEFNODE("Finally", null, { |
|
||||
$documentation: "A `finally` node; only makes sense as part of a `try` statement" |
|
||||
}, AST_Block); |
|
||||
|
|
||||
/* -----[ VAR/CONST ]----- */ |
|
||||
|
|
||||
var AST_Definitions = DEFNODE("Definitions", "definitions", { |
|
||||
$documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", |
|
||||
$propdoc: { |
|
||||
definitions: "[AST_VarDef*] array of variable definitions" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.definitions.forEach(function(def){ |
|
||||
def._walk(visitor); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_Statement); |
|
||||
|
|
||||
var AST_Var = DEFNODE("Var", null, { |
|
||||
$documentation: "A `var` statement" |
|
||||
}, AST_Definitions); |
|
||||
|
|
||||
var AST_Const = DEFNODE("Const", null, { |
|
||||
$documentation: "A `const` statement" |
|
||||
}, AST_Definitions); |
|
||||
|
|
||||
var AST_VarDef = DEFNODE("VarDef", "name value", { |
|
||||
$documentation: "A variable declaration; only appears in a AST_Definitions node", |
|
||||
$propdoc: { |
|
||||
name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", |
|
||||
value: "[AST_Node?] initializer, or null of there's no initializer" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.name._walk(visitor); |
|
||||
if (this.value) this.value._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
/* -----[ OTHER ]----- */ |
|
||||
|
|
||||
var AST_Call = DEFNODE("Call", "expression args", { |
|
||||
$documentation: "A function call expression", |
|
||||
$propdoc: { |
|
||||
expression: "[AST_Node] expression to invoke as function", |
|
||||
args: "[AST_Node*] array of arguments" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
this.args.forEach(function(arg){ |
|
||||
arg._walk(visitor); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_New = DEFNODE("New", null, { |
|
||||
$documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" |
|
||||
}, AST_Call); |
|
||||
|
|
||||
var AST_Seq = DEFNODE("Seq", "car cdr", { |
|
||||
$documentation: "A sequence expression (two comma-separated expressions)", |
|
||||
$propdoc: { |
|
||||
car: "[AST_Node] first element in sequence", |
|
||||
cdr: "[AST_Node] second element in sequence" |
|
||||
}, |
|
||||
$cons: function(x, y) { |
|
||||
var seq = new AST_Seq(x); |
|
||||
seq.car = x; |
|
||||
seq.cdr = y; |
|
||||
return seq; |
|
||||
}, |
|
||||
$from_array: function(array) { |
|
||||
if (array.length == 0) return null; |
|
||||
if (array.length == 1) return array[0].clone(); |
|
||||
var list = null; |
|
||||
for (var i = array.length; --i >= 0;) { |
|
||||
list = AST_Seq.cons(array[i], list); |
|
||||
} |
|
||||
var p = list; |
|
||||
while (p) { |
|
||||
if (p.cdr && !p.cdr.cdr) { |
|
||||
p.cdr = p.cdr.car; |
|
||||
break; |
|
||||
} |
|
||||
p = p.cdr; |
|
||||
} |
|
||||
return list; |
|
||||
}, |
|
||||
to_array: function() { |
|
||||
var p = this, a = []; |
|
||||
while (p) { |
|
||||
a.push(p.car); |
|
||||
if (p.cdr && !(p.cdr instanceof AST_Seq)) { |
|
||||
a.push(p.cdr); |
|
||||
break; |
|
||||
} |
|
||||
p = p.cdr; |
|
||||
} |
|
||||
return a; |
|
||||
}, |
|
||||
add: function(node) { |
|
||||
var p = this; |
|
||||
while (p) { |
|
||||
if (!(p.cdr instanceof AST_Seq)) { |
|
||||
var cell = AST_Seq.cons(p.cdr, node); |
|
||||
return p.cdr = cell; |
|
||||
} |
|
||||
p = p.cdr; |
|
||||
} |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.car._walk(visitor); |
|
||||
if (this.cdr) this.cdr._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_PropAccess = DEFNODE("PropAccess", "expression property", { |
|
||||
$documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`", |
|
||||
$propdoc: { |
|
||||
expression: "[AST_Node] the “container” expression", |
|
||||
property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node" |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_Dot = DEFNODE("Dot", null, { |
|
||||
$documentation: "A dotted property access expression", |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_PropAccess); |
|
||||
|
|
||||
var AST_Sub = DEFNODE("Sub", null, { |
|
||||
$documentation: "Index-style property access, i.e. `a[\"foo\"]`", |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
this.property._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}, AST_PropAccess); |
|
||||
|
|
||||
var AST_Unary = DEFNODE("Unary", "operator expression", { |
|
||||
$documentation: "Base class for unary expressions", |
|
||||
$propdoc: { |
|
||||
operator: "[string] the operator", |
|
||||
expression: "[AST_Node] expression that this unary operator applies to" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.expression._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, { |
|
||||
$documentation: "Unary prefix expression, i.e. `typeof i` or `++i`" |
|
||||
}, AST_Unary); |
|
||||
|
|
||||
var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { |
|
||||
$documentation: "Unary postfix expression, i.e. `i++`" |
|
||||
}, AST_Unary); |
|
||||
|
|
||||
var AST_Binary = DEFNODE("Binary", "left operator right", { |
|
||||
$documentation: "Binary expression, i.e. `a + b`", |
|
||||
$propdoc: { |
|
||||
left: "[AST_Node] left-hand side expression", |
|
||||
operator: "[string] the operator", |
|
||||
right: "[AST_Node] right-hand side expression" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.left._walk(visitor); |
|
||||
this.right._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", { |
|
||||
$documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`", |
|
||||
$propdoc: { |
|
||||
condition: "[AST_Node]", |
|
||||
consequent: "[AST_Node]", |
|
||||
alternative: "[AST_Node]" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.condition._walk(visitor); |
|
||||
this.consequent._walk(visitor); |
|
||||
this.alternative._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_Assign = DEFNODE("Assign", null, { |
|
||||
$documentation: "An assignment expression — `a = b + 5`", |
|
||||
}, AST_Binary); |
|
||||
|
|
||||
/* -----[ LITERALS ]----- */ |
|
||||
|
|
||||
var AST_Array = DEFNODE("Array", "elements", { |
|
||||
$documentation: "An array literal", |
|
||||
$propdoc: { |
|
||||
elements: "[AST_Node*] array of elements" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.elements.forEach(function(el){ |
|
||||
el._walk(visitor); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_Object = DEFNODE("Object", "properties", { |
|
||||
$documentation: "An object literal", |
|
||||
$propdoc: { |
|
||||
properties: "[AST_ObjectProperty*] array of properties" |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.properties.forEach(function(prop){ |
|
||||
prop._walk(visitor); |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { |
|
||||
$documentation: "Base class for literal object properties", |
|
||||
$propdoc: { |
|
||||
key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.", |
|
||||
value: "[AST_Node] property value. For setters and getters this is an AST_Function." |
|
||||
}, |
|
||||
_walk: function(visitor) { |
|
||||
return visitor._visit(this, function(){ |
|
||||
this.value._walk(visitor); |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, { |
|
||||
$documentation: "A key: value object property", |
|
||||
}, AST_ObjectProperty); |
|
||||
|
|
||||
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { |
|
||||
$documentation: "An object setter property", |
|
||||
}, AST_ObjectProperty); |
|
||||
|
|
||||
var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { |
|
||||
$documentation: "An object getter property", |
|
||||
}, AST_ObjectProperty); |
|
||||
|
|
||||
var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { |
|
||||
$propdoc: { |
|
||||
name: "[string] name of this symbol", |
|
||||
scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", |
|
||||
thedef: "[SymbolDef/S] the definition of this symbol" |
|
||||
}, |
|
||||
$documentation: "Base class for all symbols", |
|
||||
}); |
|
||||
|
|
||||
var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { |
|
||||
$documentation: "The name of a property accessor (setter/getter function)" |
|
||||
}, AST_Symbol); |
|
||||
|
|
||||
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { |
|
||||
$documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", |
|
||||
$propdoc: { |
|
||||
init: "[AST_Node*/S] array of initializers for this declaration." |
|
||||
} |
|
||||
}, AST_Symbol); |
|
||||
|
|
||||
var AST_SymbolVar = DEFNODE("SymbolVar", null, { |
|
||||
$documentation: "Symbol defining a variable", |
|
||||
}, AST_SymbolDeclaration); |
|
||||
|
|
||||
var AST_SymbolConst = DEFNODE("SymbolConst", null, { |
|
||||
$documentation: "A constant declaration" |
|
||||
}, AST_SymbolDeclaration); |
|
||||
|
|
||||
var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { |
|
||||
$documentation: "Symbol naming a function argument", |
|
||||
}, AST_SymbolVar); |
|
||||
|
|
||||
var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { |
|
||||
$documentation: "Symbol defining a function", |
|
||||
}, AST_SymbolDeclaration); |
|
||||
|
|
||||
var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { |
|
||||
$documentation: "Symbol naming a function expression", |
|
||||
}, AST_SymbolDeclaration); |
|
||||
|
|
||||
var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { |
|
||||
$documentation: "Symbol naming the exception in catch", |
|
||||
}, AST_SymbolDeclaration); |
|
||||
|
|
||||
var AST_Label = DEFNODE("Label", "references", { |
|
||||
$documentation: "Symbol naming a label (declaration)", |
|
||||
$propdoc: { |
|
||||
references: "[AST_LoopControl*] a list of nodes referring to this label" |
|
||||
}, |
|
||||
initialize: function() { |
|
||||
this.references = []; |
|
||||
this.thedef = this; |
|
||||
} |
|
||||
}, AST_Symbol); |
|
||||
|
|
||||
var AST_SymbolRef = DEFNODE("SymbolRef", null, { |
|
||||
$documentation: "Reference to some symbol (not definition/declaration)", |
|
||||
}, AST_Symbol); |
|
||||
|
|
||||
var AST_LabelRef = DEFNODE("LabelRef", null, { |
|
||||
$documentation: "Reference to a label symbol", |
|
||||
}, AST_Symbol); |
|
||||
|
|
||||
var AST_This = DEFNODE("This", null, { |
|
||||
$documentation: "The `this` symbol", |
|
||||
}, AST_Symbol); |
|
||||
|
|
||||
var AST_Constant = DEFNODE("Constant", null, { |
|
||||
$documentation: "Base class for all constants", |
|
||||
getValue: function() { |
|
||||
return this.value; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
var AST_String = DEFNODE("String", "value", { |
|
||||
$documentation: "A string literal", |
|
||||
$propdoc: { |
|
||||
value: "[string] the contents of this string" |
|
||||
} |
|
||||
}, AST_Constant); |
|
||||
|
|
||||
var AST_Number = DEFNODE("Number", "value", { |
|
||||
$documentation: "A number literal", |
|
||||
$propdoc: { |
|
||||
value: "[number] the numeric value" |
|
||||
} |
|
||||
}, AST_Constant); |
|
||||
|
|
||||
var AST_RegExp = DEFNODE("RegExp", "value", { |
|
||||
$documentation: "A regexp literal", |
|
||||
$propdoc: { |
|
||||
value: "[RegExp] the actual regexp" |
|
||||
} |
|
||||
}, AST_Constant); |
|
||||
|
|
||||
var AST_Atom = DEFNODE("Atom", null, { |
|
||||
$documentation: "Base class for atoms", |
|
||||
}, AST_Constant); |
|
||||
|
|
||||
var AST_Null = DEFNODE("Null", null, { |
|
||||
$documentation: "The `null` atom", |
|
||||
value: null |
|
||||
}, AST_Atom); |
|
||||
|
|
||||
var AST_NaN = DEFNODE("NaN", null, { |
|
||||
$documentation: "The impossible value", |
|
||||
value: 0/0 |
|
||||
}, AST_Atom); |
|
||||
|
|
||||
var AST_Undefined = DEFNODE("Undefined", null, { |
|
||||
$documentation: "The `undefined` value", |
|
||||
value: (function(){}()) |
|
||||
}, AST_Atom); |
|
||||
|
|
||||
var AST_Hole = DEFNODE("Hole", null, { |
|
||||
$documentation: "A hole in an array", |
|
||||
value: (function(){}()) |
|
||||
}, AST_Atom); |
|
||||
|
|
||||
var AST_Infinity = DEFNODE("Infinity", null, { |
|
||||
$documentation: "The `Infinity` value", |
|
||||
value: 1/0 |
|
||||
}, AST_Atom); |
|
||||
|
|
||||
var AST_Boolean = DEFNODE("Boolean", null, { |
|
||||
$documentation: "Base class for booleans", |
|
||||
}, AST_Atom); |
|
||||
|
|
||||
var AST_False = DEFNODE("False", null, { |
|
||||
$documentation: "The `false` atom", |
|
||||
value: false |
|
||||
}, AST_Boolean); |
|
||||
|
|
||||
var AST_True = DEFNODE("True", null, { |
|
||||
$documentation: "The `true` atom", |
|
||||
value: true |
|
||||
}, AST_Boolean); |
|
||||
|
|
||||
/* -----[ TreeWalker ]----- */ |
|
||||
|
|
||||
function TreeWalker(callback) { |
|
||||
this.visit = callback; |
|
||||
this.stack = []; |
|
||||
}; |
|
||||
TreeWalker.prototype = { |
|
||||
_visit: function(node, descend) { |
|
||||
this.stack.push(node); |
|
||||
var ret = this.visit(node, descend ? function(){ |
|
||||
descend.call(node); |
|
||||
} : noop); |
|
||||
if (!ret && descend) { |
|
||||
descend.call(node); |
|
||||
} |
|
||||
this.stack.pop(); |
|
||||
return ret; |
|
||||
}, |
|
||||
parent: function(n) { |
|
||||
return this.stack[this.stack.length - 2 - (n || 0)]; |
|
||||
}, |
|
||||
push: function (node) { |
|
||||
this.stack.push(node); |
|
||||
}, |
|
||||
pop: function() { |
|
||||
return this.stack.pop(); |
|
||||
}, |
|
||||
self: function() { |
|
||||
return this.stack[this.stack.length - 1]; |
|
||||
}, |
|
||||
find_parent: function(type) { |
|
||||
var stack = this.stack; |
|
||||
for (var i = stack.length; --i >= 0;) { |
|
||||
var x = stack[i]; |
|
||||
if (x instanceof type) return x; |
|
||||
} |
|
||||
}, |
|
||||
has_directive: function(type) { |
|
||||
return this.find_parent(AST_Scope).has_directive(type); |
|
||||
}, |
|
||||
in_boolean_context: function() { |
|
||||
var stack = this.stack; |
|
||||
var i = stack.length, self = stack[--i]; |
|
||||
while (i > 0) { |
|
||||
var p = stack[--i]; |
|
||||
if ((p instanceof AST_If && p.condition === self) || |
|
||||
(p instanceof AST_Conditional && p.condition === self) || |
|
||||
(p instanceof AST_DWLoop && p.condition === self) || |
|
||||
(p instanceof AST_For && p.condition === self) || |
|
||||
(p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||"))) |
|
||||
return false; |
|
||||
self = p; |
|
||||
} |
|
||||
}, |
|
||||
loopcontrol_target: function(label) { |
|
||||
var stack = this.stack; |
|
||||
if (label) for (var i = stack.length; --i >= 0;) { |
|
||||
var x = stack[i]; |
|
||||
if (x instanceof AST_LabeledStatement && x.label.name == label.name) { |
|
||||
return x.body; |
|
||||
} |
|
||||
} else for (var i = stack.length; --i >= 0;) { |
|
||||
var x = stack[i]; |
|
||||
if (x instanceof AST_Switch || x instanceof AST_IterationStatement) |
|
||||
return x; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
2358
node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/compress.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,267 +0,0 @@ |
|||||
/*********************************************************************** |
|
||||
|
|
||||
A JavaScript tokenizer / parser / beautifier / compressor. |
|
||||
https://github.com/mishoo/UglifyJS2
|
|
||||
|
|
||||
-------------------------------- (C) --------------------------------- |
|
||||
|
|
||||
Author: Mihai Bazon |
|
||||
<mihai.bazon@gmail.com> |
|
||||
http://mihai.bazon.net/blog
|
|
||||
|
|
||||
Distributed under the BSD license: |
|
||||
|
|
||||
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions |
|
||||
are met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above |
|
||||
copyright notice, this list of conditions and the following |
|
||||
disclaimer. |
|
||||
|
|
||||
* 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. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “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 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. |
|
||||
|
|
||||
***********************************************************************/ |
|
||||
|
|
||||
"use strict"; |
|
||||
|
|
||||
(function(){ |
|
||||
|
|
||||
var MOZ_TO_ME = { |
|
||||
TryStatement : function(M) { |
|
||||
return new AST_Try({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
body : from_moz(M.block).body, |
|
||||
bcatch : from_moz(M.handlers[0]), |
|
||||
bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null |
|
||||
}); |
|
||||
}, |
|
||||
CatchClause : function(M) { |
|
||||
return new AST_Catch({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
argname : from_moz(M.param), |
|
||||
body : from_moz(M.body).body |
|
||||
}); |
|
||||
}, |
|
||||
ObjectExpression : function(M) { |
|
||||
return new AST_Object({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
properties : M.properties.map(function(prop){ |
|
||||
var key = prop.key; |
|
||||
var name = key.type == "Identifier" ? key.name : key.value; |
|
||||
var args = { |
|
||||
start : my_start_token(key), |
|
||||
end : my_end_token(prop.value), |
|
||||
key : name, |
|
||||
value : from_moz(prop.value) |
|
||||
}; |
|
||||
switch (prop.kind) { |
|
||||
case "init": |
|
||||
return new AST_ObjectKeyVal(args); |
|
||||
case "set": |
|
||||
args.value.name = from_moz(key); |
|
||||
return new AST_ObjectSetter(args); |
|
||||
case "get": |
|
||||
args.value.name = from_moz(key); |
|
||||
return new AST_ObjectGetter(args); |
|
||||
} |
|
||||
}) |
|
||||
}); |
|
||||
}, |
|
||||
SequenceExpression : function(M) { |
|
||||
return AST_Seq.from_array(M.expressions.map(from_moz)); |
|
||||
}, |
|
||||
MemberExpression : function(M) { |
|
||||
return new (M.computed ? AST_Sub : AST_Dot)({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
property : M.computed ? from_moz(M.property) : M.property.name, |
|
||||
expression : from_moz(M.object) |
|
||||
}); |
|
||||
}, |
|
||||
SwitchCase : function(M) { |
|
||||
return new (M.test ? AST_Case : AST_Default)({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
expression : from_moz(M.test), |
|
||||
body : M.consequent.map(from_moz) |
|
||||
}); |
|
||||
}, |
|
||||
Literal : function(M) { |
|
||||
var val = M.value, args = { |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M) |
|
||||
}; |
|
||||
if (val === null) return new AST_Null(args); |
|
||||
switch (typeof val) { |
|
||||
case "string": |
|
||||
args.value = val; |
|
||||
return new AST_String(args); |
|
||||
case "number": |
|
||||
args.value = val; |
|
||||
return new AST_Number(args); |
|
||||
case "boolean": |
|
||||
return new (val ? AST_True : AST_False)(args); |
|
||||
default: |
|
||||
args.value = val; |
|
||||
return new AST_RegExp(args); |
|
||||
} |
|
||||
}, |
|
||||
UnaryExpression: From_Moz_Unary, |
|
||||
UpdateExpression: From_Moz_Unary, |
|
||||
Identifier: function(M) { |
|
||||
var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2]; |
|
||||
return new (M.name == "this" ? AST_This |
|
||||
: p.type == "LabeledStatement" ? AST_Label |
|
||||
: p.type == "VariableDeclarator" && p.id === M ? (p.kind == "const" ? AST_SymbolConst : AST_SymbolVar) |
|
||||
: p.type == "FunctionExpression" ? (p.id === M ? AST_SymbolLambda : AST_SymbolFunarg) |
|
||||
: p.type == "FunctionDeclaration" ? (p.id === M ? AST_SymbolDefun : AST_SymbolFunarg) |
|
||||
: p.type == "CatchClause" ? AST_SymbolCatch |
|
||||
: p.type == "BreakStatement" || p.type == "ContinueStatement" ? AST_LabelRef |
|
||||
: AST_SymbolRef)({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
name : M.name |
|
||||
}); |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
function From_Moz_Unary(M) { |
|
||||
var prefix = "prefix" in M ? M.prefix |
|
||||
: M.type == "UnaryExpression" ? true : false; |
|
||||
return new (prefix ? AST_UnaryPrefix : AST_UnaryPostfix)({ |
|
||||
start : my_start_token(M), |
|
||||
end : my_end_token(M), |
|
||||
operator : M.operator, |
|
||||
expression : from_moz(M.argument) |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
var ME_TO_MOZ = {}; |
|
||||
|
|
||||
map("Node", AST_Node); |
|
||||
map("Program", AST_Toplevel, "body@body"); |
|
||||
map("Function", AST_Function, "id>name, params@argnames, body%body"); |
|
||||
map("EmptyStatement", AST_EmptyStatement); |
|
||||
map("BlockStatement", AST_BlockStatement, "body@body"); |
|
||||
map("ExpressionStatement", AST_SimpleStatement, "expression>body"); |
|
||||
map("IfStatement", AST_If, "test>condition, consequent>body, alternate>alternative"); |
|
||||
map("LabeledStatement", AST_LabeledStatement, "label>label, body>body"); |
|
||||
map("BreakStatement", AST_Break, "label>label"); |
|
||||
map("ContinueStatement", AST_Continue, "label>label"); |
|
||||
map("WithStatement", AST_With, "object>expression, body>body"); |
|
||||
map("SwitchStatement", AST_Switch, "discriminant>expression, cases@body"); |
|
||||
map("ReturnStatement", AST_Return, "argument>value"); |
|
||||
map("ThrowStatement", AST_Throw, "argument>value"); |
|
||||
map("WhileStatement", AST_While, "test>condition, body>body"); |
|
||||
map("DoWhileStatement", AST_Do, "test>condition, body>body"); |
|
||||
map("ForStatement", AST_For, "init>init, test>condition, update>step, body>body"); |
|
||||
map("ForInStatement", AST_ForIn, "left>init, right>object, body>body"); |
|
||||
map("DebuggerStatement", AST_Debugger); |
|
||||
map("FunctionDeclaration", AST_Defun, "id>name, params@argnames, body%body"); |
|
||||
map("VariableDeclaration", AST_Var, "declarations@definitions"); |
|
||||
map("VariableDeclarator", AST_VarDef, "id>name, init>value"); |
|
||||
|
|
||||
map("ThisExpression", AST_This); |
|
||||
map("ArrayExpression", AST_Array, "elements@elements"); |
|
||||
map("FunctionExpression", AST_Function, "id>name, params@argnames, body%body"); |
|
||||
map("BinaryExpression", AST_Binary, "operator=operator, left>left, right>right"); |
|
||||
map("AssignmentExpression", AST_Assign, "operator=operator, left>left, right>right"); |
|
||||
map("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right"); |
|
||||
map("ConditionalExpression", AST_Conditional, "test>condition, consequent>consequent, alternate>alternative"); |
|
||||
map("NewExpression", AST_New, "callee>expression, arguments@args"); |
|
||||
map("CallExpression", AST_Call, "callee>expression, arguments@args"); |
|
||||
|
|
||||
/* -----[ tools ]----- */ |
|
||||
|
|
||||
function my_start_token(moznode) { |
|
||||
return new AST_Token({ |
|
||||
file : moznode.loc && moznode.loc.source, |
|
||||
line : moznode.loc && moznode.loc.start.line, |
|
||||
col : moznode.loc && moznode.loc.start.column, |
|
||||
pos : moznode.start, |
|
||||
endpos : moznode.start |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
function my_end_token(moznode) { |
|
||||
return new AST_Token({ |
|
||||
file : moznode.loc && moznode.loc.source, |
|
||||
line : moznode.loc && moznode.loc.end.line, |
|
||||
col : moznode.loc && moznode.loc.end.column, |
|
||||
pos : moznode.end, |
|
||||
endpos : moznode.end |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
function map(moztype, mytype, propmap) { |
|
||||
var moz_to_me = "function From_Moz_" + moztype + "(M){\n"; |
|
||||
moz_to_me += "return new mytype({\n" + |
|
||||
"start: my_start_token(M),\n" + |
|
||||
"end: my_end_token(M)"; |
|
||||
|
|
||||
if (propmap) propmap.split(/\s*,\s*/).forEach(function(prop){ |
|
||||
var m = /([a-z0-9$_]+)(=|@|>|%)([a-z0-9$_]+)/i.exec(prop); |
|
||||
if (!m) throw new Error("Can't understand property map: " + prop); |
|
||||
var moz = "M." + m[1], how = m[2], my = m[3]; |
|
||||
moz_to_me += ",\n" + my + ": "; |
|
||||
if (how == "@") { |
|
||||
moz_to_me += moz + ".map(from_moz)"; |
|
||||
} else if (how == ">") { |
|
||||
moz_to_me += "from_moz(" + moz + ")"; |
|
||||
} else if (how == "=") { |
|
||||
moz_to_me += moz; |
|
||||
} else if (how == "%") { |
|
||||
moz_to_me += "from_moz(" + moz + ").body"; |
|
||||
} else throw new Error("Can't understand operator in propmap: " + prop); |
|
||||
}); |
|
||||
moz_to_me += "\n})}"; |
|
||||
|
|
||||
// moz_to_me = parse(moz_to_me).print_to_string({ beautify: true });
|
|
||||
// console.log(moz_to_me);
|
|
||||
|
|
||||
moz_to_me = new Function("mytype", "my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")( |
|
||||
mytype, my_start_token, my_end_token, from_moz |
|
||||
); |
|
||||
return MOZ_TO_ME[moztype] = moz_to_me; |
|
||||
}; |
|
||||
|
|
||||
var FROM_MOZ_STACK = null; |
|
||||
|
|
||||
function from_moz(node) { |
|
||||
FROM_MOZ_STACK.push(node); |
|
||||
var ret = node != null ? MOZ_TO_ME[node.type](node) : null; |
|
||||
FROM_MOZ_STACK.pop(); |
|
||||
return ret; |
|
||||
}; |
|
||||
|
|
||||
AST_Node.from_mozilla_ast = function(node){ |
|
||||
var save_stack = FROM_MOZ_STACK; |
|
||||
FROM_MOZ_STACK = []; |
|
||||
var ast = from_moz(node); |
|
||||
FROM_MOZ_STACK = save_stack; |
|
||||
return ast; |
|
||||
}; |
|
||||
|
|
||||
})(); |
|
1300
node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/output.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1457
node_modules/jade/node_modules/constantinople/node_modules/uglify-js/lib/parse.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,563 +0,0 @@ |
|||||
/*********************************************************************** |
|
||||
|
|
||||
A JavaScript tokenizer / parser / beautifier / compressor. |
|
||||
https://github.com/mishoo/UglifyJS2
|
|
||||
|
|
||||
-------------------------------- (C) --------------------------------- |
|
||||
|
|
||||
Author: Mihai Bazon |
|
||||
<mihai.bazon@gmail.com> |
|
||||
http://mihai.bazon.net/blog
|
|
||||
|
|
||||
Distributed under the BSD license: |
|
||||
|
|
||||
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions |
|
||||
are met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above |
|
||||
copyright notice, this list of conditions and the following |
|
||||
disclaimer. |
|
||||
|
|
||||
* 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. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “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 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. |
|
||||
|
|
||||
***********************************************************************/ |
|
||||
|
|
||||
"use strict"; |
|
||||
|
|
||||
function SymbolDef(scope, index, orig) { |
|
||||
this.name = orig.name; |
|
||||
this.orig = [ orig ]; |
|
||||
this.scope = scope; |
|
||||
this.references = []; |
|
||||
this.global = false; |
|
||||
this.mangled_name = null; |
|
||||
this.undeclared = false; |
|
||||
this.constant = false; |
|
||||
this.index = index; |
|
||||
}; |
|
||||
|
|
||||
SymbolDef.prototype = { |
|
||||
unmangleable: function(options) { |
|
||||
return (this.global && !(options && options.toplevel)) |
|
||||
|| this.undeclared |
|
||||
|| (!(options && options.eval) && (this.scope.uses_eval || this.scope.uses_with)); |
|
||||
}, |
|
||||
mangle: function(options) { |
|
||||
if (!this.mangled_name && !this.unmangleable(options)) { |
|
||||
var s = this.scope; |
|
||||
if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda) |
|
||||
s = s.parent_scope; |
|
||||
this.mangled_name = s.next_mangled(options, this); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ |
|
||||
options = defaults(options, { |
|
||||
screw_ie8: false |
|
||||
}); |
|
||||
|
|
||||
// pass 1: setup scope chaining and handle definitions
|
|
||||
var self = this; |
|
||||
var scope = self.parent_scope = null; |
|
||||
var defun = null; |
|
||||
var nesting = 0; |
|
||||
var tw = new TreeWalker(function(node, descend){ |
|
||||
if (options.screw_ie8 && node instanceof AST_Catch) { |
|
||||
var save_scope = scope; |
|
||||
scope = new AST_Scope(node); |
|
||||
scope.init_scope_vars(nesting); |
|
||||
scope.parent_scope = save_scope; |
|
||||
descend(); |
|
||||
scope = save_scope; |
|
||||
return true; |
|
||||
} |
|
||||
if (node instanceof AST_Scope) { |
|
||||
node.init_scope_vars(nesting); |
|
||||
var save_scope = node.parent_scope = scope; |
|
||||
var save_defun = defun; |
|
||||
defun = scope = node; |
|
||||
++nesting; descend(); --nesting; |
|
||||
scope = save_scope; |
|
||||
defun = save_defun; |
|
||||
return true; // don't descend again in TreeWalker
|
|
||||
} |
|
||||
if (node instanceof AST_Directive) { |
|
||||
node.scope = scope; |
|
||||
push_uniq(scope.directives, node.value); |
|
||||
return true; |
|
||||
} |
|
||||
if (node instanceof AST_With) { |
|
||||
for (var s = scope; s; s = s.parent_scope) |
|
||||
s.uses_with = true; |
|
||||
return; |
|
||||
} |
|
||||
if (node instanceof AST_Symbol) { |
|
||||
node.scope = scope; |
|
||||
} |
|
||||
if (node instanceof AST_SymbolLambda) { |
|
||||
defun.def_function(node); |
|
||||
} |
|
||||
else if (node instanceof AST_SymbolDefun) { |
|
||||
// Careful here, the scope where this should be defined is
|
|
||||
// the parent scope. The reason is that we enter a new
|
|
||||
// scope when we encounter the AST_Defun node (which is
|
|
||||
// instanceof AST_Scope) but we get to the symbol a bit
|
|
||||
// later.
|
|
||||
(node.scope = defun.parent_scope).def_function(node); |
|
||||
} |
|
||||
else if (node instanceof AST_SymbolVar |
|
||||
|| node instanceof AST_SymbolConst) { |
|
||||
var def = defun.def_variable(node); |
|
||||
def.constant = node instanceof AST_SymbolConst; |
|
||||
def.init = tw.parent().value; |
|
||||
} |
|
||||
else if (node instanceof AST_SymbolCatch) { |
|
||||
(options.screw_ie8 ? scope : defun) |
|
||||
.def_variable(node); |
|
||||
} |
|
||||
}); |
|
||||
self.walk(tw); |
|
||||
|
|
||||
// pass 2: find back references and eval
|
|
||||
var func = null; |
|
||||
var globals = self.globals = new Dictionary(); |
|
||||
var tw = new TreeWalker(function(node, descend){ |
|
||||
if (node instanceof AST_Lambda) { |
|
||||
var prev_func = func; |
|
||||
func = node; |
|
||||
descend(); |
|
||||
func = prev_func; |
|
||||
return true; |
|
||||
} |
|
||||
if (node instanceof AST_SymbolRef) { |
|
||||
var name = node.name; |
|
||||
var sym = node.scope.find_variable(name); |
|
||||
if (!sym) { |
|
||||
var g; |
|
||||
if (globals.has(name)) { |
|
||||
g = globals.get(name); |
|
||||
} else { |
|
||||
g = new SymbolDef(self, globals.size(), node); |
|
||||
g.undeclared = true; |
|
||||
g.global = true; |
|
||||
globals.set(name, g); |
|
||||
} |
|
||||
node.thedef = g; |
|
||||
if (name == "eval" && tw.parent() instanceof AST_Call) { |
|
||||
for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) |
|
||||
s.uses_eval = true; |
|
||||
} |
|
||||
if (func && name == "arguments") { |
|
||||
func.uses_arguments = true; |
|
||||
} |
|
||||
} else { |
|
||||
node.thedef = sym; |
|
||||
} |
|
||||
node.reference(); |
|
||||
return true; |
|
||||
} |
|
||||
}); |
|
||||
self.walk(tw); |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ |
|
||||
this.directives = []; // contains the directives defined in this scope, i.e. "use strict"
|
|
||||
this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
|
|
||||
this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope)
|
|
||||
this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
|
|
||||
this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
|
|
||||
this.parent_scope = null; // the parent scope
|
|
||||
this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
|
|
||||
this.cname = -1; // the current index for mangling functions/variables
|
|
||||
this.nesting = nesting; // the nesting level of this scope (0 means toplevel)
|
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("strict", function(){ |
|
||||
return this.has_directive("use strict"); |
|
||||
}); |
|
||||
|
|
||||
AST_Lambda.DEFMETHOD("init_scope_vars", function(){ |
|
||||
AST_Scope.prototype.init_scope_vars.apply(this, arguments); |
|
||||
this.uses_arguments = false; |
|
||||
}); |
|
||||
|
|
||||
AST_SymbolRef.DEFMETHOD("reference", function() { |
|
||||
var def = this.definition(); |
|
||||
def.references.push(this); |
|
||||
var s = this.scope; |
|
||||
while (s) { |
|
||||
push_uniq(s.enclosed, def); |
|
||||
if (s === def.scope) break; |
|
||||
s = s.parent_scope; |
|
||||
} |
|
||||
this.frame = this.scope.nesting - def.scope.nesting; |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("find_variable", function(name){ |
|
||||
if (name instanceof AST_Symbol) name = name.name; |
|
||||
return this.variables.get(name) |
|
||||
|| (this.parent_scope && this.parent_scope.find_variable(name)); |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("has_directive", function(value){ |
|
||||
return this.parent_scope && this.parent_scope.has_directive(value) |
|
||||
|| (this.directives.indexOf(value) >= 0 ? this : null); |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("def_function", function(symbol){ |
|
||||
this.functions.set(symbol.name, this.def_variable(symbol)); |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("def_variable", function(symbol){ |
|
||||
var def; |
|
||||
if (!this.variables.has(symbol.name)) { |
|
||||
def = new SymbolDef(this, this.variables.size(), symbol); |
|
||||
this.variables.set(symbol.name, def); |
|
||||
def.global = !this.parent_scope; |
|
||||
} else { |
|
||||
def = this.variables.get(symbol.name); |
|
||||
def.orig.push(symbol); |
|
||||
} |
|
||||
return symbol.thedef = def; |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("next_mangled", function(options){ |
|
||||
var ext = this.enclosed; |
|
||||
out: while (true) { |
|
||||
var m = base54(++this.cname); |
|
||||
if (!is_identifier(m)) continue; // skip over "do"
|
|
||||
|
|
||||
// https://github.com/mishoo/UglifyJS2/issues/242 -- do not
|
|
||||
// shadow a name excepted from mangling.
|
|
||||
if (options.except.indexOf(m) >= 0) continue; |
|
||||
|
|
||||
// we must ensure that the mangled name does not shadow a name
|
|
||||
// from some parent scope that is referenced in this or in
|
|
||||
// inner scopes.
|
|
||||
for (var i = ext.length; --i >= 0;) { |
|
||||
var sym = ext[i]; |
|
||||
var name = sym.mangled_name || (sym.unmangleable(options) && sym.name); |
|
||||
if (m == name) continue out; |
|
||||
} |
|
||||
return m; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
AST_Function.DEFMETHOD("next_mangled", function(options, def){ |
|
||||
// #179, #326
|
|
||||
// in Safari strict mode, something like (function x(x){...}) is a syntax error;
|
|
||||
// a function expression's argument cannot shadow the function expression's name
|
|
||||
|
|
||||
var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition(); |
|
||||
while (true) { |
|
||||
var name = AST_Lambda.prototype.next_mangled.call(this, options, def); |
|
||||
if (!(tricky_def && tricky_def.mangled_name == name)) |
|
||||
return name; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
AST_Scope.DEFMETHOD("references", function(sym){ |
|
||||
if (sym instanceof AST_Symbol) sym = sym.definition(); |
|
||||
return this.enclosed.indexOf(sym) < 0 ? null : sym; |
|
||||
}); |
|
||||
|
|
||||
AST_Symbol.DEFMETHOD("unmangleable", function(options){ |
|
||||
return this.definition().unmangleable(options); |
|
||||
}); |
|
||||
|
|
||||
// property accessors are not mangleable
|
|
||||
AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){ |
|
||||
return true; |
|
||||
}); |
|
||||
|
|
||||
// labels are always mangleable
|
|
||||
AST_Label.DEFMETHOD("unmangleable", function(){ |
|
||||
return false; |
|
||||
}); |
|
||||
|
|
||||
AST_Symbol.DEFMETHOD("unreferenced", function(){ |
|
||||
return this.definition().references.length == 0 |
|
||||
&& !(this.scope.uses_eval || this.scope.uses_with); |
|
||||
}); |
|
||||
|
|
||||
AST_Symbol.DEFMETHOD("undeclared", function(){ |
|
||||
return this.definition().undeclared; |
|
||||
}); |
|
||||
|
|
||||
AST_LabelRef.DEFMETHOD("undeclared", function(){ |
|
||||
return false; |
|
||||
}); |
|
||||
|
|
||||
AST_Label.DEFMETHOD("undeclared", function(){ |
|
||||
return false; |
|
||||
}); |
|
||||
|
|
||||
AST_Symbol.DEFMETHOD("definition", function(){ |
|
||||
return this.thedef; |
|
||||
}); |
|
||||
|
|
||||
AST_Symbol.DEFMETHOD("global", function(){ |
|
||||
return this.definition().global; |
|
||||
}); |
|
||||
|
|
||||
AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ |
|
||||
return defaults(options, { |
|
||||
except : [], |
|
||||
eval : false, |
|
||||
sort : false, |
|
||||
toplevel : false, |
|
||||
screw_ie8 : false |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
AST_Toplevel.DEFMETHOD("mangle_names", function(options){ |
|
||||
options = this._default_mangler_options(options); |
|
||||
// We only need to mangle declaration nodes. Special logic wired
|
|
||||
// into the code generator will display the mangled name if it's
|
|
||||
// present (and for AST_SymbolRef-s it'll use the mangled name of
|
|
||||
// the AST_SymbolDeclaration that it points to).
|
|
||||
var lname = -1; |
|
||||
var to_mangle = []; |
|
||||
var tw = new TreeWalker(function(node, descend){ |
|
||||
if (node instanceof AST_LabeledStatement) { |
|
||||
// lname is incremented when we get to the AST_Label
|
|
||||
var save_nesting = lname; |
|
||||
descend(); |
|
||||
lname = save_nesting; |
|
||||
return true; // don't descend again in TreeWalker
|
|
||||
} |
|
||||
if (node instanceof AST_Scope) { |
|
||||
var p = tw.parent(), a = []; |
|
||||
node.variables.each(function(symbol){ |
|
||||
if (options.except.indexOf(symbol.name) < 0) { |
|
||||
a.push(symbol); |
|
||||
} |
|
||||
}); |
|
||||
if (options.sort) a.sort(function(a, b){ |
|
||||
return b.references.length - a.references.length; |
|
||||
}); |
|
||||
to_mangle.push.apply(to_mangle, a); |
|
||||
return; |
|
||||
} |
|
||||
if (node instanceof AST_Label) { |
|
||||
var name; |
|
||||
do name = base54(++lname); while (!is_identifier(name)); |
|
||||
node.mangled_name = name; |
|
||||
return true; |
|
||||
} |
|
||||
}); |
|
||||
this.walk(tw); |
|
||||
to_mangle.forEach(function(def){ def.mangle(options) }); |
|
||||
}); |
|
||||
|
|
||||
AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ |
|
||||
options = this._default_mangler_options(options); |
|
||||
var tw = new TreeWalker(function(node){ |
|
||||
if (node instanceof AST_Constant) |
|
||||
base54.consider(node.print_to_string()); |
|
||||
else if (node instanceof AST_Return) |
|
||||
base54.consider("return"); |
|
||||
else if (node instanceof AST_Throw) |
|
||||
base54.consider("throw"); |
|
||||
else if (node instanceof AST_Continue) |
|
||||
base54.consider("continue"); |
|
||||
else if (node instanceof AST_Break) |
|
||||
base54.consider("break"); |
|
||||
else if (node instanceof AST_Debugger) |
|
||||
base54.consider("debugger"); |
|
||||
else if (node instanceof AST_Directive) |
|
||||
base54.consider(node.value); |
|
||||
else if (node instanceof AST_While) |
|
||||
base54.consider("while"); |
|
||||
else if (node instanceof AST_Do) |
|
||||
base54.consider("do while"); |
|
||||
else if (node instanceof AST_If) { |
|
||||
base54.consider("if"); |
|
||||
if (node.alternative) base54.consider("else"); |
|
||||
} |
|
||||
else if (node instanceof AST_Var) |
|
||||
base54.consider("var"); |
|
||||
else if (node instanceof AST_Const) |
|
||||
base54.consider("const"); |
|
||||
else if (node instanceof AST_Lambda) |
|
||||
base54.consider("function"); |
|
||||
else if (node instanceof AST_For) |
|
||||
base54.consider("for"); |
|
||||
else if (node instanceof AST_ForIn) |
|
||||
base54.consider("for in"); |
|
||||
else if (node instanceof AST_Switch) |
|
||||
base54.consider("switch"); |
|
||||
else if (node instanceof AST_Case) |
|
||||
base54.consider("case"); |
|
||||
else if (node instanceof AST_Default) |
|
||||
base54.consider("default"); |
|
||||
else if (node instanceof AST_With) |
|
||||
base54.consider("with"); |
|
||||
else if (node instanceof AST_ObjectSetter) |
|
||||
base54.consider("set" + node.key); |
|
||||
else if (node instanceof AST_ObjectGetter) |
|
||||
base54.consider("get" + node.key); |
|
||||
else if (node instanceof AST_ObjectKeyVal) |
|
||||
base54.consider(node.key); |
|
||||
else if (node instanceof AST_New) |
|
||||
base54.consider("new"); |
|
||||
else if (node instanceof AST_This) |
|
||||
base54.consider("this"); |
|
||||
else if (node instanceof AST_Try) |
|
||||
base54.consider("try"); |
|
||||
else if (node instanceof AST_Catch) |
|
||||
base54.consider("catch"); |
|
||||
else if (node instanceof AST_Finally) |
|
||||
base54.consider("finally"); |
|
||||
else if (node instanceof AST_Symbol && node.unmangleable(options)) |
|
||||
base54.consider(node.name); |
|
||||
else if (node instanceof AST_Unary || node instanceof AST_Binary) |
|
||||
base54.consider(node.operator); |
|
||||
else if (node instanceof AST_Dot) |
|
||||
base54.consider(node.property); |
|
||||
}); |
|
||||
this.walk(tw); |
|
||||
base54.sort(); |
|
||||
}); |
|
||||
|
|
||||
var base54 = (function() { |
|
||||
var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; |
|
||||
var chars, frequency; |
|
||||
function reset() { |
|
||||
frequency = Object.create(null); |
|
||||
chars = string.split("").map(function(ch){ return ch.charCodeAt(0) }); |
|
||||
chars.forEach(function(ch){ frequency[ch] = 0 }); |
|
||||
} |
|
||||
base54.consider = function(str){ |
|
||||
for (var i = str.length; --i >= 0;) { |
|
||||
var code = str.charCodeAt(i); |
|
||||
if (code in frequency) ++frequency[code]; |
|
||||
} |
|
||||
}; |
|
||||
base54.sort = function() { |
|
||||
chars = mergeSort(chars, function(a, b){ |
|
||||
if (is_digit(a) && !is_digit(b)) return 1; |
|
||||
if (is_digit(b) && !is_digit(a)) return -1; |
|
||||
return frequency[b] - frequency[a]; |
|
||||
}); |
|
||||
}; |
|
||||
base54.reset = reset; |
|
||||
reset(); |
|
||||
base54.get = function(){ return chars }; |
|
||||
base54.freq = function(){ return frequency }; |
|
||||
function base54(num) { |
|
||||
var ret = "", base = 54; |
|
||||
do { |
|
||||
ret += String.fromCharCode(chars[num % base]); |
|
||||
num = Math.floor(num / base); |
|
||||
base = 64; |
|
||||
} while (num > 0); |
|
||||
return ret; |
|
||||
}; |
|
||||
return base54; |
|
||||
})(); |
|
||||
|
|
||||
AST_Toplevel.DEFMETHOD("scope_warnings", function(options){ |
|
||||
options = defaults(options, { |
|
||||
undeclared : false, // this makes a lot of noise
|
|
||||
unreferenced : true, |
|
||||
assign_to_global : true, |
|
||||
func_arguments : true, |
|
||||
nested_defuns : true, |
|
||||
eval : true |
|
||||
}); |
|
||||
var tw = new TreeWalker(function(node){ |
|
||||
if (options.undeclared |
|
||||
&& node instanceof AST_SymbolRef |
|
||||
&& node.undeclared()) |
|
||||
{ |
|
||||
// XXX: this also warns about JS standard names,
|
|
||||
// i.e. Object, Array, parseInt etc. Should add a list of
|
|
||||
// exceptions.
|
|
||||
AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", { |
|
||||
name: node.name, |
|
||||
file: node.start.file, |
|
||||
line: node.start.line, |
|
||||
col: node.start.col |
|
||||
}); |
|
||||
} |
|
||||
if (options.assign_to_global) |
|
||||
{ |
|
||||
var sym = null; |
|
||||
if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef) |
|
||||
sym = node.left; |
|
||||
else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef) |
|
||||
sym = node.init; |
|
||||
if (sym |
|
||||
&& (sym.undeclared() |
|
||||
|| (sym.global() && sym.scope !== sym.definition().scope))) { |
|
||||
AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", { |
|
||||
msg: sym.undeclared() ? "Accidental global?" : "Assignment to global", |
|
||||
name: sym.name, |
|
||||
file: sym.start.file, |
|
||||
line: sym.start.line, |
|
||||
col: sym.start.col |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
if (options.eval |
|
||||
&& node instanceof AST_SymbolRef |
|
||||
&& node.undeclared() |
|
||||
&& node.name == "eval") { |
|
||||
AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start); |
|
||||
} |
|
||||
if (options.unreferenced |
|
||||
&& (node instanceof AST_SymbolDeclaration || node instanceof AST_Label) |
|
||||
&& node.unreferenced()) { |
|
||||
AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", { |
|
||||
type: node instanceof AST_Label ? "Label" : "Symbol", |
|
||||
name: node.name, |
|
||||
file: node.start.file, |
|
||||
line: node.start.line, |
|
||||
col: node.start.col |
|
||||
}); |
|
||||
} |
|
||||
if (options.func_arguments |
|
||||
&& node instanceof AST_Lambda |
|
||||
&& node.uses_arguments) { |
|
||||
AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", { |
|
||||
name: node.name ? node.name.name : "anonymous", |
|
||||
file: node.start.file, |
|
||||
line: node.start.line, |
|
||||
col: node.start.col |
|
||||
}); |
|
||||
} |
|
||||
if (options.nested_defuns |
|
||||
&& node instanceof AST_Defun |
|
||||
&& !(tw.parent() instanceof AST_Scope)) { |
|
||||
AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", { |
|
||||
name: node.name.name, |
|
||||
type: tw.parent().TYPE, |
|
||||
file: node.start.file, |
|
||||
line: node.start.line, |
|
||||
col: node.start.col |
|
||||
}); |
|
||||
} |
|
||||
}); |
|
||||
this.walk(tw); |
|
||||
}); |
|
@ -1,84 +0,0 @@ |
|||||
/*********************************************************************** |
|
||||
|
|
||||
A JavaScript tokenizer / parser / beautifier / compressor. |
|
||||
https://github.com/mishoo/UglifyJS2
|
|
||||
|
|
||||
-------------------------------- (C) --------------------------------- |
|
||||
|
|
||||
Author: Mihai Bazon |
|
||||
<mihai.bazon@gmail.com> |
|
||||
http://mihai.bazon.net/blog
|
|
||||
|
|
||||
Distributed under the BSD license: |
|
||||
|
|
||||
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions |
|
||||
are met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above |
|
||||
copyright notice, this list of conditions and the following |
|
||||
disclaimer. |
|
||||
|
|
||||
* 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. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “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 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. |
|
||||
|
|
||||
***********************************************************************/ |
|
||||
|
|
||||
"use strict"; |
|
||||
|
|
||||
// a small wrapper around fitzgen's source-map library
|
|
||||
function SourceMap(options) { |
|
||||
options = defaults(options, { |
|
||||
file : null, |
|
||||
root : null, |
|
||||
orig : null, |
|
||||
|
|
||||
orig_line_diff : 0, |
|
||||
dest_line_diff : 0, |
|
||||
}); |
|
||||
var generator = new MOZ_SourceMap.SourceMapGenerator({ |
|
||||
file : options.file, |
|
||||
sourceRoot : options.root |
|
||||
}); |
|
||||
var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig); |
|
||||
function add(source, gen_line, gen_col, orig_line, orig_col, name) { |
|
||||
if (orig_map) { |
|
||||
var info = orig_map.originalPositionFor({ |
|
||||
line: orig_line, |
|
||||
column: orig_col |
|
||||
}); |
|
||||
source = info.source; |
|
||||
orig_line = info.line; |
|
||||
orig_col = info.column; |
|
||||
name = info.name; |
|
||||
} |
|
||||
generator.addMapping({ |
|
||||
generated : { line: gen_line + options.dest_line_diff, column: gen_col }, |
|
||||
original : { line: orig_line + options.orig_line_diff, column: orig_col }, |
|
||||
source : source, |
|
||||
name : name |
|
||||
}); |
|
||||
}; |
|
||||
return { |
|
||||
add : add, |
|
||||
get : function() { return generator }, |
|
||||
toString : function() { return generator.toString() } |
|
||||
}; |
|
||||
}; |
|
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue