1
0
mirror of https://github.com/mgerb/mywebsite synced 2026-01-13 19:12:49 +00:00

Added file upload and dynamic blog post pages on index

This commit is contained in:
2015-10-20 14:31:22 -05:00
parent 184abc8ac1
commit 5f1fa1cf96
1420 changed files with 269139 additions and 30 deletions

View File

@@ -0,0 +1,10 @@
.DS_Store
*.html
browser/**
docs/**
examples/**
Makefile
scripts/**
tests/**
.travis.yml
CONTRIBUTING.md

498
node_modules/mongo-express/node_modules/swig/HISTORY.md generated vendored Normal file
View File

@@ -0,0 +1,498 @@
[1.4.2](https://github.com/paularmstrong/swig/tree/v1.4.2) / 2014-08-04
-----------------------------------------------------------------------
* **Added** Report JS parse errors with template filenames. gh-492
* **Fixed** Ensure block-level tags (`set`, etc) are parsed in correct order. gh-495
* **Fixed** Ensure import tag uses current Swig instance's loader. gh-421, gh-503
* **Fixed** Allow disabling cache on compile/render functions directly. gh-423
* **Fixed** Ensure compilation does not leak global variables. gh-496
* **Fixed** Fix for-loops to run on strings. gh-478, gh-479
* **Fixed** Allow macro output to be assigned using `set` tag. gh-499, gh-502
[1.4.1](https://github.com/paularmstrong/swig/tree/v1.4.1) / 2014-07-03
-----------------------------------------------------------------------
* **Fixed** `macro` argument names colliding with context variable names. gh-457
* **Fixed** filter chaining within tags. gh-441
[1.4.0](https://github.com/paularmstrong/swig/tree/v1.4.0) / 2014-07-03
-----------------------------------------------------------------------
* **Changed** Allow variable tokens to start with `$`. gh-455
* **Changed** `fs` loader should take `cwd` as default base path. gh-419
* **Changed** handle errors which occur at the time of rendering. gh-417
* **Changed** default options in bin (`varControls`, `tagControls`, `cmtControls`). gh-415
* **Changed** `null` should yield empty string when resolving variable. gh-408
* **Added** Escape character for `date` filter argument. gh-427, gh-432
* **Added** Make `if` and `elseif` throw a better error message when a tag body is omitted. gh-425
* **Fixed** don't throw errors on accessing property of `null` object. gh-471
* **Fixed** `loop` variables work correctly in nested loops. gh-433
* **Fixed** Some IE8 compatibility (require es5). gh-428
[1.3.2](https://github.com/paularmstrong/swig/tree/v1.3.2) / 2014-01-27
-----------------------------------------------------------------------
* **Fixed** `for` loop variables on objects. gh-409
* **Fixed** Misc. IE8 fixes. gh-410
* **Fixed** `include` files when loaders have base paths. gh-407
[1.3.0](https://github.com/paularmstrong/swig/tree/v1.3.0) / 2014-01-20
-----------------------------------------------------------------------
* **Changed** Removed official node v0.8.x support
* **Added** Custom template loader support. gh-377, gh-384, gh-382
* **Added** Ability to set root path using template loaders. gh-382, gh-293
* **Added** CLI now accepts custom filter and tag arguments. gh-391
* **Added** Allow `set` tag to set keys on objects with bracket and dot-notation. gh-388
* **Added** `groupBy` filter from swig-extras. gh-383
* **Fixed** `swig.run` `filepath` arg is always optional. gh-402
* **Fixed** Filters on non-empty functions apply correctly. gh-397
* **Fixed** Filters applied to functions w/ & w/o dotkeys. gh-365
* **Fixed** `date` filter `N` option returns correct number. gh-375
* **Fixed** Ensure getting parent template checks cache if on. gh-378
[1.2.2](https://github.com/paularmstrong/swig/tree/v1.2.2) / 2013-12-02
-----------------------------------------------------------------------
* **Fixed** CTX var output in imported macros. gh-363
[1.2.1](https://github.com/paularmstrong/swig/tree/v1.2.1) / 2013-12-02
-----------------------------------------------------------------------
* **Fixed** Scoping for Express. gh-363
[1.2.0](https://github.com/paularmstrong/swig/tree/v1.2.0) / 2013-12-01
-----------------------------------------------------------------------
* **Added** Filepath parameter can be passed to swig.run to allow extends in-browser. gh-349
* **Changed** Use local-context first for var lookups. gh-344, gh-347
* **Changed** Allow DOTKEY after functions/objects/filters. gh-355
* **Changed** Context of for-tags carries into includes. gh-356
* **Changed** When a callback is passed into compileFile, catch all errors thrown by compile and pass the error to callback. gh-340
* **Fixed** Instances of Swig retain their options properly. gh-351
* **Fixed** Fix misc documentation issues. gh-359, gh-358
[1.1.0](https://github.com/paularmstrong/swig/tree/v1.1.0) / 2013-10-02
-----------------------------------------------------------------------
* **Added** Allow logic in default parsing. gh-326
* **Fixed** Error when attempting to wrap spaceless tag around macro/function output. gh-336
* **Fixed** Don't overwrite keys on the locals object. gh-337
[1.0.0](https://github.com/paularmstrong/swig/tree/v1.0.0) / 2013-09-23
-----------------------------------------------------------------------
* **Fixed** Allow parent and other tags to work correctly nested in other tags. gh-331
* **Fixed** Prevent lexer from matching partial logic/words in variables. gh-330
Migrating from v0.x.x? View the [Migration Guide](https://github.com/paularmstrong/swig/wiki/Migrating-from-v0.x.x-to-v1.0.0)
[1.0.0-rc3](https://github.com/paularmstrong/swig/tree/v1.0.0-rc3) / 2013-09-14
-------------------------------------------------------------------------------
* **Fixed** Allow bools in token parser by default. gh-321
* **Fixed** Allow variables as object values. gh-323
* **Fixed** Don't partially match logic words. gh-322
* **Fixed** Parent tag in parent's block with no local block edge case. gh-316
[1.0.0-rc2](https://github.com/paularmstrong/swig/tree/v1.0.0-rc2) / 2013-09-06
-------------------------------------------------------------------------------
* **Changed** Function output from `variable` blocks are no longer auto-escaped. gh-309
* **Fixed** Allow nested macros to work when importing. gh-310
* **Fixed** swig.setDefaultTZOffset. gh-311
* **Changed** `set` tag assigns to the local context, allowing setting within `for` loops, etc. gh-303
* **Fixed** Standardize variable undefined checking. gh-301
* **Fixed** Remove multiple redefinition of block-level tags in compiled templates.
* **Fixed** Performance issue with compile if no default locals are defined.
[1.0.0-rc1](https://github.com/paularmstrong/swig/tree/v1.0.0-rc1) / 2013-08-28
-------------------------------------------------------------------------------
* **Added** `include` tag now accepts `only` (and is preferred, if possible). gh-240
* **Added** `swig.version` and `-v` to cli
* **Changed** Deprecated `raw` filter. Use `safe`.
* **Changed** Allow `import` and `macro` tags to be outside of blocks. gh-299
* **Changed** Don't escape `macro` output. gh-297
* **Changed** (Custom) Filters can be marked as `safe` to disable auto-escaping. gh-294
* **Fixed** `{% for k,v ... %}` tag syntax assigned variables backwards.
* **Fixed** Filters being applied to empty functions throwing errors. gh-296
* **Fixed** `include` paths on windows. gh-295
[1.0.0-pre3](https://github.com/paularmstrong/swig/tree/v1.0.0-pre3) / 2013-08-20
---------------------------------------------------------------------------------
* **Changed** Allow tags at block-level if specified. [gh-289](https://github.com/paularmstrong/swig/issues/289)
* **Fixed** `swig.compileFile` runs callback template is found in cache. [gh-291](https://github.com/paularmstrong/swig/issues/291)
* **Fixed** Accidental modification of Swig Options Object. [gh-287](https://github.com/paularmstrong/swig/issues/287)
* **Fixed** Preserve forward-slashes in text chunks. [gh-285](https://github.com/paularmstrong/swig/issues/285)
[1.0.0-pre2](https://github.com/paularmstrong/swig/tree/v1.0.0-pre2) / 2013-08-18
---------------------------------------------------------------------------------
* **Changed** Binary: Allow --method-name to be a shortcut for --wrap-start var setting.
* **Changed** Make reverse filter an alias for `sort(true)`.
* **Added** Allow asyncronous `compileFile` and `renderFile` operations. [gh-283](https://github.com/paularmstrong/swig/issues/283)
* **Added** Filter: `sort`.
* **Added** Allow {% end[tag] tokens... %}. [gh-278](https://github.com/paularmstrong/swig/issues/278)
* **Added** Built source map for minified browser source.
* **Added** Contextual support for object method calls. [gh-275](https://github.com/paularmstrong/swig/issues/275)
* **Added** `parser.on('start'|'end'...` options. [gh-274](https://github.com/paularmstrong/swig/issues/274)
* **Added** Allow object prototypal inheritance. [gh-273](https://github.com/paularmstrong/swig/issues/273)
* **Fixed** Prevent circular extends. [gh-282](https://github.com/paularmstrong/swig/issues/282)
* **Fixed** Throw an error if reserved word is used as var. [gh-276](https://github.com/paularmstrong/swig/issues/276)
* **Fixed** Add filename to errors if possible. [gh-280](https://github.com/paularmstrong/swig/issues/280)
* **Fixed** Filters work over arrays/objects if possible. [gh-259](https://github.com/paularmstrong/swig/issues/259)
* **Fixed** Allow {% parent %} to work in middle parent templates. [gh-277](https://github.com/paularmstrong/swig/issues/277)
* **Fixed** Allow newlines in tags/vars/comments. [gh-272](https://github.com/paularmstrong/swig/issues/272)
[1.0.0-pre1](https://github.com/paularmstrong/swig/tree/v1.0.0-pre1) / 2013-08-14
---------------------------------------------------------------------------------
* **Changed** Completely rewritting parsing engine supports many more syntaxes and is much easier to extend.
* **Changed** There is no more `swig.init` method.
* **Changed** Custom filters can be added using `swig.addFilter`
* **Changed** Custom tags can be added using `swig.addTag`
* **Changed** Writing custom tags uses an entirely new, simplified format
* **Changed** Removed the underscore/lodash dependency
* **Changed** Template parsing has been completely rewritten
* **Changed** `swig.compileFile` returns a function that renders templates, not an object
* **Changed** Express-compatible using `swig.renderFile`.
* **Changed** `extends`, `import`, and `include` now reference files with relative paths from the current file ([info](https://github.com/paularmstrong/swig/wiki/Migrating-from-v0.x.x-to-v1.0.0#extends-include-import-changes)).
* **Changed** `extends` may no longer accept variables ([info](https://github.com/paularmstrong/swig/wiki/Migrating-from-v0.x.x-to-v1.0.0#extends-include-import-changes)).
* **Changed** `else if` tag is now `elseif` or `elif`.
* **Changed** Removed `only` argument from `include`.
* **Changed** allow `_`, `$` to start var names in templates.
* **Changed** Documentation is auto-generated from jsdoc comments in-files.
* **Added** Ability to set custom var/tag/comment controls (`{{`, `}}`, etc, can be customized).
* **Added** Variable/string concatenation [gh-135](https://github.com/paularmstrong/swig/issues/135).
* **Added** Binary application for `compile`, `run`, and `render` (Lets you pre-compile templates into JS functions for client-side delivery).
* **Fixed** Lots.
[0.14.0](https://github.com/paularmstrong/swig/tree/v0.14.0) / 2013-06-08
-------------------------------------------------------------------------
* **Added** Allow executing functions from within templates [gh-182](https://github.com/paularmstrong/swig/pull/182)
* **Added** New `spaceless` tag [gh-193](https://github.com/paularmstrong/swig/pull/193)
* **Fixed** bug when attempting to loop over nested vars with `for`. [gh-232](https://github.com/paularmstrong/swig/pull/232)
[0.13.5](https://github.com/paularmstrong/swig/tree/v0.13.5) / 2013-01-29
-------------------------------------------------------------------------
* **Fixed** date filter output for 'O' when time-zone offset is negative [gh-185](https://github.com/paularmstrong/swig/pull/185)
[0.13.4](https://github.com/paularmstrong/swig/tree/v0.13.4) / 2012-12-19
-------------------------------------------------------------------------
* **Fixed** Runaway loop on missing template [gh-162](https://github.com/paularmstrong/swig/pull/162) [gh-165](https://github.com/paularmstrong/swig/pull/165)
* **Fixed** Allow variables in if tag conditionals to have filters with arguments [gh-167](https://github.com/paularmstrong/swig/pull/167)
[0.13.3](https://github.com/paularmstrong/swig/tree/v0.13.3) / 2012-12-07
-------------------------------------------------------------------------
* **Added** Support % (modulus) in if tags [gh-155](https://github.com/paularmstrong/swig/pull/155)
* **Added** Support multi-root via array [gh-143](https://github.com/paularmstrong/swig/pull/143)
[0.13.2](https://github.com/paularmstrong/swig/tree/v0.13.2) / 2012-10-28
-------------------------------------------------------------------------
* **Changed** Allow variables, filters, arguments to span lines [gh-122](https://github.com/paularmstrong/swig/issues/122)
* **Changed** Throw Errors when using undefined filters [gh-115](https://github.com/paularmstrong/swig/issues/115)
* **Fixed** compiling files from absolute paths [gh-103](https://github.com/paularmstrong/swig/issues/103)
* **Fixed** Prevent global variables from being used before context variables [gh-117](https://github.com/paularmstrong/swig/issues/117)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.13.2/docs)
[0.13.1](https://github.com/paularmstrong/swig/tree/v0.13.1) / 2012-10-28
-------------------------------------------------------------------------
* **Fixed** Macros should be preserved when using inheritence [gh-132](https://github.com/paularmstrong/swig/issues/132) ([nsaun](https://github.com/nsaun))
* **Fixed** bug in parent tag logic [gh-130](https://github.com/paularmstrong/swig/issues/130)
* **Fixed** Error messaging when parent block failed compilation [gh-129](https://github.com/paularmstrong/swig/issues/129) ([nsaun](https://github.com/nsaun))
[Documentation](https://github.com/paularmstrong/swig/tree/v0.13.1/docs)
[0.13.0](https://github.com/paularmstrong/swig/tree/v0.13.0) / 2012-10-20
-------------------------------------------------------------------------
* **Added** Support for nested blocks! [gh-64](https://github.com/paularmstrong/swig/issues/64) [gh-129](https://github.com/paularmstrong/swig/issues/129) ([nsaun](https://github.com/nsaun))
* **Changed** Removed the `parentBlock` argument from tags.
* **Fixed** Object keys may now contain dots
[Documentation](https://github.com/paularmstrong/swig/tree/v0.13.0/docs)
[0.12.1](https://github.com/paularmstrong/swig/tree/v0.12.1) / 2012-10-05
-------------------------------------------------------------------------
* **Added** More information on some parser errors
* **Added** indent parameter to json_encode filter to support pretty-printing.
* **Added** support for variables as `extends` tag parameters
* **Fixed** Compile errors in Android and other random browsers
* **Fixed** Misc documentation
* **Fixed** Leaking __keys variable into global scope
*
[Documentation](https://github.com/paularmstrong/swig/tree/v0.12.1/docs)
[0.12.0](https://github.com/paularmstrong/swig/tree/v0.12.0) / 2012-07-26
-------------------------------------------------------------------------
* **Fixed** Misc documenation
* **Changed** Support Node.js >=v0.6
[Documentation](https://github.com/paularmstrong/swig/tree/v0.12.0/docs)
[0.11.2](https://github.com/paularmstrong/swig/tree/v0.11.2) / 2012-04-10
-------------------------------------------------------------------------
* **Fixed** Update support for underscore@1.3.3 [gh-70](https://github.com/paularmstrong/swig/issues/70) [gh-71](https://github.com/paularmstrong/swig/issues/71)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.11.2/docs)
[0.11.1](https://github.com/paularmstrong/swig/tree/v0.11.1) / 2012-04-01
-------------------------------------------------------------------------
* **Fixed** Duplicate (string) tokens were being removed when extending a base template. [gh-67](https://github.com/paularmstrong/swig/issues/67)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.11.1/docs)
[0.11.0](https://github.com/paularmstrong/swig/tree/v0.11.0) / 2012-02-27
-------------------------------------------------------------------------
* **Added** Support for Windows style paths [gh-57](https://github.com/paularmstrong/swig/issues/57)
* **Added** `ignore missing` tokens to include tag
* **Changed** include tag `with context` to only work if `context` is an object
* **Changed** `autoescape` tag controls no longer 'yes' or 'no'. Use `true` and `false`
* **Changed** parser is now passed into tags as an argument
* **Changed** don't require passing context object when rendering template
* **Fixed** dateformats `N` and `w` [gh-59](https://github.com/paularmstrong/swig/issues/59)
* **Fixed** number changing to string after add filter or set from variable [gh-53](https://github.com/paularmstrong/swig/issues/53) [gh-58](https://github.com/paularmstrong/swig/issues/58)
* **Fixed** speed decrease caused by loop.cycle fixed
* **Fixed** Ensure set tag bubbles through extends and blocks
[Documentation](https://github.com/paularmstrong/swig/tree/v0.11.0/docs)
[0.10.0](https://github.com/paularmstrong/swig/tree/v0.10.0) / 2012-02-13
-------------------------------------------------------------------------
* **Added** loop.index0, loop.revindex, loop.revindex0, and loop.cycle [gh-48](https://github.com/paularmstrong/swig/issues/48)
* **Added** init config `extensions` for 3rd party extension access in custom tags [gh-44](https://github.com/paularmstrong/swig/issues/44)
* **Added** Whitespace Control [gh-46](https://github.com/paularmstrong/swig/issues/46)
* **Changed** The `empty` tag in `for` loops is now `else` [gh-49](https://github.com/paularmstrong/swig/issues/49)
* **Changed** `forloop` vars to `loop` closes [gh-47](https://github.com/paularmstrong/swig/issues/47)
* **Fixed** `include` tag's `with` and `only` args documentation [gh-50](https://github.com/paularmstrong/swig/issues/50)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.10.0/docs)
[0.9.4](https://github.com/paularmstrong/swig/tree/v0.9.4) / 2012-02-07
-----------------------------------------------------------------------
* **Fixed** `parent` tag would not render when called within tags [gh-41](https://github.com/paularmstrong/swig/issues/41)
* **Fixed** Documentation for forloop.index & forloop.key [gh-42](https://github.com/paularmstrong/swig/issues/42)
* **Fixed** Errors when using `include` inside base template `block` tags [gh-43](https://github.com/paularmstrong/swig/issues/43)
* **Fixed** Allow `set` tag to set values to numbers [gh-45](https://github.com/paularmstrong/swig/issues/45)
* **Fixed** `set` tag for booleans using too many checks
[Documentation](https://github.com/paularmstrong/swig/tree/v0.9.4/docs)
[0.9.3](https://github.com/paularmstrong/swig/tree/v0.9.3) / 2012-01-28
-----------------------------------------------------------------------
* **Fixed** Allow object and array values to be accessed via context variables [gh-40](https://github.com/paularmstrong/swig/issues/40)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.9.3/docs)
[0.9.2](https://github.com/paularmstrong/swig/tree/v0.9.2) / 2012-01-23
-----------------------------------------------------------------------
* **Fixed** Correctly reset autoescape after closing an autoescape tag. [gh-39](https://github.com/paularmstrong/swig/issues/39)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.9.2/docs)
[0.9.1](https://github.com/paularmstrong/swig/tree/v0.9.1) / 2012-01-18
-----------------------------------------------------------------------
* **Fixed** Allow multi-line tags and comments. [gh-30](https://github.com/paularmstrong/swig/issues/30)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.9.1/docs)
[0.9.0](https://github.com/paularmstrong/swig/tree/v0.9.0) / 2011-12-30
-----------------------------------------------------------------------
* **Added** DateZ license to browser header, use link to underscore license.
* **Added** Timezone support in `date` filter [gh-27](https://github.com/paularmstrong/swig/issues/27).
* **Added** New `raw` tag.
* **Changed** Swig is no longer node 0.4 compatible.
* **Fixed** Filter `date('f')` for 10am times.
* **Fixed** Filter `date('r')` returns in UTC date format. This is more correct tospec RFC2822, per [php.net/date](http://php.net/date).
* **Fixed** Filter `add` when adding numbers/numbers+strings together.
* **Fixed** Tests for error messages that changed in node >0.6.0.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.9.0/docs)
[0.8.0](https://github.com/paularmstrong/swig/tree/v0.8.0) / 2011-11-04
-----------------------------------------------------------------------
* **Added** date filter formats `z`, `W`, `t`, `L`, `o`, `B`, and `c`.
* **Added** New `filter` tag.
* **Added** Node.js compatible 0.4.1 - 0.6.X
* **Added** Allow setting cache globally or per-template.
* **Changed** Removed `swig.render` and `swig.fromString`.
* **Changed** `swig.fromFile` is now `swig.compileFile`.
* **Changed** `swig.init()` will clear template cache.
* **Changed** `swig.init()` is now optional for browser mode with no custom settings.
* **Changed** Development dependencies are be more lenient.
* **Fixed** Parser will properly preserver '\' escaping. [gh-24](https://github.com/paularmstrong/swig/issues/24)
* **Fixed** Rewrote tag argument parsing for proper space handling.
* **Fixed** Rewrote filter argument parsing. [gh-23](https://github.com/paularmstrong/swig/issues/23)
* **Fixed** Allow pipe `|` characters in filter arguments. [gh-22](https://github.com/paularmstrong/swig/issues/22)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.8.0/docs)
[0.7.0](https://github.com/paularmstrong/swig/tree/v0.7.0) / 2011-10-05
-----------------------------------------------------------------------
* **Added** `make browser` will build Swig for use in major browsers. [gh-3](https://github.com/paularmstrong/swig/issues/3)
* **Changed** Allow overriding `escape` filters. [gh-19](https://github.com/paularmstrong/swig/issues/19)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.7.0/docs)
[0.6.1](https://github.com/paularmstrong/swig/tree/v0.6.1) / 2011-10-02
-----------------------------------------------------------------------
* **Fixed** chaining filters when the first takes a variable as an argument will not crash parsing.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.6.1/docs)
[0.6.0](https://github.com/paularmstrong/swig/tree/v0.6.0) / 2011-10-02
-----------------------------------------------------------------------
* **Added** `{% import foo as bar %}` tag for importing macros.
* **Added** Allow escaping for js in escape filter and autoescape tag.
* **Added** `raw` filter to force variable to not be escaped.
* **Added** `escape` and `e` filters to force variable to be escaped.
* **Added** Allow filters to accept any JS objects, arrays, strings, and context variables.
* **Changed** `if`, `else`, and `else if` tags support all JS-valid if-syntaxes + extra operators.
* **Fixed** `default` filter for undefined variables. closes gh-18
[Documentation](https://github.com/paularmstrong/swig/tree/v0.6.0/docs)
[0.5.0](https://github.com/paularmstrong/swig/tree/v0.5.0) / 2011-09-27
-----------------------------------------------------------------------
* **Added** More error messaging in some edge cases.
* **Added** Better error messaging including context and line numbers.
* **Changed** Improved compile and render speeds.
* **Changed** `include` tags accept context variables instead of just strings.
* **Changed** Templates can be compiled and rendered from an absolute path outside of the template root.
* **Fixed** Will not double escape output.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.5.0/docs)
[0.4.0](https://github.com/paularmstrong/swig/tree/v0.4.0) / 2011-09-24
-----------------------------------------------------------------------
* **Added** Macro support [docs](docs/tags.md)
* **Changed** Removed requirement to manually specify `locals` for express support.
* **Changed** Increased cache lookup speed by removing crypto dependency.
* **Fixed** `length` filter returns length of objects (number of keys).
* **Fixed** Filters return empty string unless they can apply to the given object.
* **Fixed** Filters will attempt to apply to all values in an object or array.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.4.0/docs)
[0.3.0](https://github.com/paularmstrong/swig/tree/v0.3.0) / 2011-09-17
-----------------------------------------------------------------------
* **Added** Support for `{% set ... %}` tag.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.3.0/docs)
[0.2.3](https://github.com/paularmstrong/swig/tree/v0.2.3) / 2011-09-16
-----------------------------------------------------------------------
* **Fixed** Critical fix for negations in `if` blocks.
* **Added** Support for `forloop.first` in `for` blocks.
* **Added** Support for `forloop.last` in `for` blocks.
* **Added** Support for `forloop.key` in `for` blocks.
* **Added** Support for `{% empty %}` in `for` blocks.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.2.3/docs)
[0.2.2](https://github.com/paularmstrong/swig/tree/v0.2.2) / 2011-09-16
-----------------------------------------------------------------------
* **Added** Support for `else if ...` within `if` blocks.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.2.2/docs)
[0.2.1](https://github.com/paularmstrong/swig/tree/v0.2.1) / 2011-09-13
-----------------------------------------------------------------------
* **Added** Support for `else` within `if` blocks.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.2.1/docs)
[0.2.0](https://github.com/paularmstrong/swig/tree/v0.2.0) / 2011-09-11
-----------------------------------------------------------------------
* **Fixed** `if` statements allow filters applied to operands.
* **Fixed** `for` loops allow filters applied to the object that will be iterated over.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.2.0/docs)
[0.1.9](https://github.com/paularmstrong/swig/tree/v0.1.9) / 2011-09-11
-----------------------------------------------------------------------
* **Added** `allowErrors` flag will allow errors to be thrown and bubbled up. Default to catch errors.
* **Changed** Internal speed improvements.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.9/docs)
[0.1.8](https://github.com/paularmstrong/swig/tree/v0.1.8) / 2011-09-10
-----------------------------------------------------------------------
* **Added** `add`, `addslashes`, and `replace` filters.
* **Changed** All tags that 'end' must use named ends like `endblock`, `endif`, `endfor`, etc...
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.8/docs)
[0.1.7](https://github.com/paularmstrong/swig/tree/v0.1.7) / 2011-09-05
-----------------------------------------------------------------------
* **Added** this History document
* **Fixed** date filter to zero-pad correctly during september when using 'm' format
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.7/docs)
[0.1.6](https://github.com/paularmstrong/swig/tree/v0.1.6) / 2011-09-04
-----------------------------------------------------------------------
* **Fixed** Template inheritance blocks messing up.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.6/docs)
[0.1.5](https://github.com/paularmstrong/swig/tree/v0.1.5) / 2011-09-04
-----------------------------------------------------------------------
* **Added** `first`, `last`, and `uniq` filters
* **Added** ability to specify custom filters
* **Added** ability to specify custom tags
* **Changed** slots removed -- implement using custom tags if desired
* **Fixed** ability to do either dot- or bracket-notation or mixed in variables
* **Fixed** internal parsing helpers
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.5/docs)
[0.1.3](https://github.com/paularmstrong/swig/tree/v0.1.3) / 2011-09-01
-----------------------------------------------------------------------
* **Fixed** filter parser to work correctly with single-quoted params in filters.
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.3/docs)
[0.1.2](https://github.com/paularmstrong/swig/tree/v0.1.2) / 2011-09-01
-----------------------------------------------------------------------
* Initial **swig** publish after forking from [node-t](https://github.com/skid/node-t)
[Documentation](https://github.com/paularmstrong/swig/tree/v0.1.2/docs)

7
node_modules/mongo-express/node_modules/swig/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2010-2013 Paul Armstrong
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.

88
node_modules/mongo-express/node_modules/swig/README.md generated vendored Normal file
View File

@@ -0,0 +1,88 @@
Swig [![Build Status](http://img.shields.io/travis/paularmstrong/swig/master.svg?style=flat)](http://travis-ci.org/paularmstrong/swig) [![Dependency Status](http://img.shields.io/gemnasium/paularmstrong/swig.svg?style=flat)](https://gemnasium.com/paularmstrong/swig) [![NPM version](http://img.shields.io/npm/v/swig.svg?style=flat)](https://www.npmjs.org/package/swig) [![NPM Downloads](http://img.shields.io/npm/dm/swig.svg?style=flat)](https://www.npmjs.org/package/swig)
====
[Swig](http://paularmstrong.github.io/swig/) is an awesome, Django/Jinja-like template engine for node.js.
Features
--------
* Available for node.js **and** major web browsers!
* [Express](http://expressjs.com/) compatible.
* Object-Oriented template inheritance.
* Apply filters and transformations to output in your templates.
* Automatically escapes all output for safe HTML rendering.
* Lots of iteration and conditionals supported.
* Robust without the bloat.
* Extendable and customizable. See [Swig-Extras](https://github.com/paularmstrong/swig-extras) for some examples.
* Great [code coverage](http://paularmstrong.github.io/swig/coverage.html).
Need Help? Have Questions? Comments?
------------------------------------
* [Mailing List/Google Group](http://groups.google.com/forum/#!forum/swig-templates)
* [StackOverflow](http://stackoverflow.com/questions/tagged/swig-template)
* [Migration Guide](https://github.com/paularmstrong/swig/wiki/Migrating-from-v0.x.x-to-v1.0.0)
Installation
------------
npm install swig
Documentation
-------------
All documentation can be viewed online on the [Swig Website](http://paularmstrong.github.io/swig/).
Basic Example
-------------
### Template code
```html
<h1>{{ pagename|title }}</h1>
<ul>
{% for author in authors %}
<li{% if loop.first %} class="first"{% endif %}>{{ author }}</li>
{% endfor %}
</ul>
```
### node.js code
```js
var swig = require('swig');
var template = swig.compileFile('/absolute/path/to/template.html');
var output = template({
pagename: 'awesome people',
authors: ['Paul', 'Jim', 'Jane']
});
```
### Output
```html
<h1>Awesome People</h1>
<ul>
<li class="first">Paul</li>
<li>Jim</li>
<li>Jane</li>
</ul>
```
For working example see [examples/basic](https://github.com/paularmstrong/swig/tree/master/examples/basic)
How it works
------------
Swig reads template files and translates them into cached javascript functions. When we later render a template we call the evaluated function, passing a context object as an argument.
License
-------
Copyright (c) 2010-2013 Paul Armstrong
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.

161
node_modules/mongo-express/node_modules/swig/bin/swig.js generated vendored Executable file
View File

@@ -0,0 +1,161 @@
#!/usr/bin/env node
/*jslint es5: true */
var swig = require('../index'),
optimist = require('optimist'),
fs = require('fs'),
path = require('path'),
filters = require('../lib/filters'),
utils = require('../lib/utils'),
uglify = require('uglify-js');
var command,
argv = optimist
.usage('\n Usage:\n' +
' $0 compile [files] [options]\n' +
' $0 run [files] [options]\n' +
' $0 render [files] [options]\n'
)
.describe({
v: 'Show the Swig version number.',
o: 'Output location.',
h: 'Show this help screen.',
j: 'Variable context as a JSON file.',
c: 'Variable context as a CommonJS-style file. Used only if option `j` is not provided.',
m: 'Minify compiled functions with uglify-js',
'filters': 'Custom filters as a CommonJS-style file',
'tags': 'Custom tags as a CommonJS-style file',
'options': 'Customize Swig\'s Options from a CommonJS-style file',
'wrap-start': 'Template wrapper beginning for "compile".',
'wrap-end': 'Template wrapper end for "compile".',
'method-name': 'Method name to set template to and run from.'
})
.alias('v', 'version')
.alias('o', 'output')
.default('o', 'stdout')
.alias('h', 'help')
.alias('j', 'json')
.alias('c', 'context')
.alias('m', 'minify')
.default('wrap-start', 'var tpl = ')
.default('wrap-end', ';')
.default('method-name', 'tpl')
.check(function (argv) {
if (argv.v) {
return;
}
if (!argv._.length) {
throw new Error('');
}
command = argv._.shift();
if (command !== 'compile' && command !== 'render' && command !== 'run') {
throw new Error('Unrecognized command "' + command + '". Use -h for help.');
}
if (argv['method-name'] !== 'tpl' && argv['wrap-start'] !== 'var tpl =') {
throw new Error('Cannot use arguments "--method-name" and "--wrap-start" together.');
}
if (argv['method-name'] !== 'tpl') {
argv['wrap-start'] = 'var ' + argv['method-name'] + ' = ';
}
})
.argv,
ctx = {},
out = function (file, str) {
console.log(str);
},
efn = function () {},
anonymous,
files,
fn;
// What version?
if (argv.v) {
console.log(require('../package').version);
process.exit(0);
}
// Pull in any context data provided
if (argv.j) {
ctx = JSON.parse(fs.readFileSync(argv.j, 'utf8'));
} else if (argv.c) {
ctx = require(argv.c);
}
if (argv.o !== 'stdout') {
argv.o += '/';
argv.o = path.normalize(argv.o);
try {
fs.mkdirSync(argv.o);
} catch (e) {
if (e.errno !== 47) {
throw e;
}
}
out = function (file, str) {
file = path.basename(file);
fs.writeFileSync(argv.o + file, str, { flags: 'w' });
console.log('Wrote', argv.o + file);
};
}
// Set any custom filters
if (argv.filters) {
utils.each(require(path.resolve(argv.filters)), function (filter, name) {
swig.setFilter(name, filter);
});
}
// Set any custom tags
if (argv.tags) {
utils.each(require(path.resolve(argv.tags)), function (tag, name) {
swig.setTag(name, tag.parse, tag.compile, tag.ends, tag.block);
});
}
// Specify swig default options
if (argv.options) {
swig.setDefaults(require(argv.options));
}
switch (command) {
case 'compile':
fn = function (file, str) {
var r = swig.precompile(str, { filename: file, locals: ctx }).tpl.toString().replace('anonymous', '');
r = argv['wrap-start'] + r + argv['wrap-end'];
if (argv.m) {
r = uglify.minify(r, { fromString: true }).code;
}
out(file, r);
};
break;
case 'run':
fn = function (file, str) {
(function () {
eval(str);
var __tpl = eval(argv['method-name']);
out(file, __tpl(swig, ctx, filters, utils, efn));
}());
};
break;
case 'render':
fn = function (file, str) {
out(file, swig.render(str, { filename: file, locals: ctx }));
};
break;
}
argv._.forEach(function (file) {
var str = fs.readFileSync(file, 'utf8');
fn(file, str);
});

5006
node_modules/mongo-express/node_modules/swig/dist/swig.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
module.exports = require('./lib/swig');

View File

@@ -0,0 +1,198 @@
var utils = require('./utils');
var _months = {
full: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
},
_days = {
full: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
alt: {'-1': 'Yesterday', 0: 'Today', 1: 'Tomorrow'}
};
/*
DateZ is licensed under the MIT License:
Copyright (c) 2011 Tomo Universalis (http://tomouniversalis.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.
*/
exports.tzOffset = 0;
exports.DateZ = function () {
var members = {
'default': ['getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds', 'toISOString', 'toGMTString', 'toUTCString', 'valueOf', 'getTime'],
z: ['getDate', 'getDay', 'getFullYear', 'getHours', 'getMilliseconds', 'getMinutes', 'getMonth', 'getSeconds', 'getYear', 'toDateString', 'toLocaleDateString', 'toLocaleTimeString']
},
d = this;
d.date = d.dateZ = (arguments.length > 1) ? new Date(Date.UTC.apply(Date, arguments) + ((new Date()).getTimezoneOffset() * 60000)) : (arguments.length === 1) ? new Date(new Date(arguments['0'])) : new Date();
d.timezoneOffset = d.dateZ.getTimezoneOffset();
utils.each(members.z, function (name) {
d[name] = function () {
return d.dateZ[name]();
};
});
utils.each(members['default'], function (name) {
d[name] = function () {
return d.date[name]();
};
});
this.setTimezoneOffset(exports.tzOffset);
};
exports.DateZ.prototype = {
getTimezoneOffset: function () {
return this.timezoneOffset;
},
setTimezoneOffset: function (offset) {
this.timezoneOffset = offset;
this.dateZ = new Date(this.date.getTime() + this.date.getTimezoneOffset() * 60000 - this.timezoneOffset * 60000);
return this;
}
};
// Day
exports.d = function (input) {
return (input.getDate() < 10 ? '0' : '') + input.getDate();
};
exports.D = function (input) {
return _days.abbr[input.getDay()];
};
exports.j = function (input) {
return input.getDate();
};
exports.l = function (input) {
return _days.full[input.getDay()];
};
exports.N = function (input) {
var d = input.getDay();
return (d >= 1) ? d : 7;
};
exports.S = function (input) {
var d = input.getDate();
return (d % 10 === 1 && d !== 11 ? 'st' : (d % 10 === 2 && d !== 12 ? 'nd' : (d % 10 === 3 && d !== 13 ? 'rd' : 'th')));
};
exports.w = function (input) {
return input.getDay();
};
exports.z = function (input, offset, abbr) {
var year = input.getFullYear(),
e = new exports.DateZ(year, input.getMonth(), input.getDate(), 12, 0, 0),
d = new exports.DateZ(year, 0, 1, 12, 0, 0);
e.setTimezoneOffset(offset, abbr);
d.setTimezoneOffset(offset, abbr);
return Math.round((e - d) / 86400000);
};
// Week
exports.W = function (input) {
var target = new Date(input.valueOf()),
dayNr = (input.getDay() + 6) % 7,
fThurs;
target.setDate(target.getDate() - dayNr + 3);
fThurs = target.valueOf();
target.setMonth(0, 1);
if (target.getDay() !== 4) {
target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7);
}
return 1 + Math.ceil((fThurs - target) / 604800000);
};
// Month
exports.F = function (input) {
return _months.full[input.getMonth()];
};
exports.m = function (input) {
return (input.getMonth() < 9 ? '0' : '') + (input.getMonth() + 1);
};
exports.M = function (input) {
return _months.abbr[input.getMonth()];
};
exports.n = function (input) {
return input.getMonth() + 1;
};
exports.t = function (input) {
return 32 - (new Date(input.getFullYear(), input.getMonth(), 32).getDate());
};
// Year
exports.L = function (input) {
return new Date(input.getFullYear(), 1, 29).getDate() === 29;
};
exports.o = function (input) {
var target = new Date(input.valueOf());
target.setDate(target.getDate() - ((input.getDay() + 6) % 7) + 3);
return target.getFullYear();
};
exports.Y = function (input) {
return input.getFullYear();
};
exports.y = function (input) {
return (input.getFullYear().toString()).substr(2);
};
// Time
exports.a = function (input) {
return input.getHours() < 12 ? 'am' : 'pm';
};
exports.A = function (input) {
return input.getHours() < 12 ? 'AM' : 'PM';
};
exports.B = function (input) {
var hours = input.getUTCHours(), beats;
hours = (hours === 23) ? 0 : hours + 1;
beats = Math.abs(((((hours * 60) + input.getUTCMinutes()) * 60) + input.getUTCSeconds()) / 86.4).toFixed(0);
return ('000'.concat(beats).slice(beats.length));
};
exports.g = function (input) {
var h = input.getHours();
return h === 0 ? 12 : (h > 12 ? h - 12 : h);
};
exports.G = function (input) {
return input.getHours();
};
exports.h = function (input) {
var h = input.getHours();
return ((h < 10 || (12 < h && 22 > h)) ? '0' : '') + ((h < 12) ? h : h - 12);
};
exports.H = function (input) {
var h = input.getHours();
return (h < 10 ? '0' : '') + h;
};
exports.i = function (input) {
var m = input.getMinutes();
return (m < 10 ? '0' : '') + m;
};
exports.s = function (input) {
var s = input.getSeconds();
return (s < 10 ? '0' : '') + s;
};
//u = function () { return ''; },
// Timezone
//e = function () { return ''; },
//I = function () { return ''; },
exports.O = function (input) {
var tz = input.getTimezoneOffset();
return (tz < 0 ? '-' : '+') + (tz / 60 < 10 ? '0' : '') + Math.abs((tz / 60)) + '00';
};
//T = function () { return ''; },
exports.Z = function (input) {
return input.getTimezoneOffset() * 60;
};
// Full Date/Time
exports.c = function (input) {
return input.toISOString();
};
exports.r = function (input) {
return input.toUTCString();
};
exports.U = function (input) {
return input.getTime() / 1000;
};

View File

@@ -0,0 +1,630 @@
var utils = require('./utils'),
dateFormatter = require('./dateformatter');
/**
* Helper method to recursively run a filter across an object/array and apply it to all of the object/array's values.
* @param {*} input
* @return {*}
* @private
*/
function iterateFilter(input) {
var self = this,
out = {};
if (utils.isArray(input)) {
return utils.map(input, function (value) {
return self.apply(null, arguments);
});
}
if (typeof input === 'object') {
utils.each(input, function (value, key) {
out[key] = self.apply(null, arguments);
});
return out;
}
return;
}
/**
* Backslash-escape characters that need to be escaped.
*
* @example
* {{ "\"quoted string\""|addslashes }}
* // => \"quoted string\"
*
* @param {*} input
* @return {*} Backslash-escaped string.
*/
exports.addslashes = function (input) {
var out = iterateFilter.apply(exports.addslashes, arguments);
if (out !== undefined) {
return out;
}
return input.replace(/\\/g, '\\\\').replace(/\'/g, "\\'").replace(/\"/g, '\\"');
};
/**
* Upper-case the first letter of the input and lower-case the rest.
*
* @example
* {{ "i like Burritos"|capitalize }}
* // => I like burritos
*
* @param {*} input If given an array or object, each string member will be run through the filter individually.
* @return {*} Returns the same type as the input.
*/
exports.capitalize = function (input) {
var out = iterateFilter.apply(exports.capitalize, arguments);
if (out !== undefined) {
return out;
}
return input.toString().charAt(0).toUpperCase() + input.toString().substr(1).toLowerCase();
};
/**
* Format a date or Date-compatible string.
*
* @example
* // now = new Date();
* {{ now|date('Y-m-d') }}
* // => 2013-08-14
* @example
* // now = new Date();
* {{ now|date('jS \o\f F') }}
* // => 4th of July
*
* @param {?(string|date)} input
* @param {string} format PHP-style date format compatible string. Escape characters with <code>\</code> for string literals.
* @param {number=} offset Timezone offset from GMT in minutes.
* @param {string=} abbr Timezone abbreviation. Used for output only.
* @return {string} Formatted date string.
*/
exports.date = function (input, format, offset, abbr) {
var l = format.length,
date = new dateFormatter.DateZ(input),
cur,
i = 0,
out = '';
if (offset) {
date.setTimezoneOffset(offset, abbr);
}
for (i; i < l; i += 1) {
cur = format.charAt(i);
if (cur === '\\') {
i += 1;
out += (i < l) ? format.charAt(i) : cur;
} else if (dateFormatter.hasOwnProperty(cur)) {
out += dateFormatter[cur](date, offset, abbr);
} else {
out += cur;
}
}
return out;
};
/**
* If the input is `undefined`, `null`, or `false`, a default return value can be specified.
*
* @example
* {{ null_value|default('Tacos') }}
* // => Tacos
*
* @example
* {{ "Burritos"|default("Tacos") }}
* // => Burritos
*
* @param {*} input
* @param {*} def Value to return if `input` is `undefined`, `null`, or `false`.
* @return {*} `input` or `def` value.
*/
exports["default"] = function (input, def) {
return (typeof input !== 'undefined' && (input || typeof input === 'number')) ? input : def;
};
/**
* Force escape the output of the variable. Optionally use `e` as a shortcut filter name. This filter will be applied by default if autoescape is turned on.
*
* @example
* {{ "<blah>"|escape }}
* // => &lt;blah&gt;
*
* @example
* {{ "<blah>"|e("js") }}
* // => \u003Cblah\u003E
*
* @param {*} input
* @param {string} [type='html'] If you pass the string js in as the type, output will be escaped so that it is safe for JavaScript execution.
* @return {string} Escaped string.
*/
exports.escape = function (input, type) {
var out = iterateFilter.apply(exports.escape, arguments),
inp = input,
i = 0,
code;
if (out !== undefined) {
return out;
}
if (typeof input !== 'string') {
return input;
}
out = '';
switch (type) {
case 'js':
inp = inp.replace(/\\/g, '\\u005C');
for (i; i < inp.length; i += 1) {
code = inp.charCodeAt(i);
if (code < 32) {
code = code.toString(16).toUpperCase();
code = (code.length < 2) ? '0' + code : code;
out += '\\u00' + code;
} else {
out += inp[i];
}
}
return out.replace(/&/g, '\\u0026')
.replace(/</g, '\\u003C')
.replace(/>/g, '\\u003E')
.replace(/\'/g, '\\u0027')
.replace(/"/g, '\\u0022')
.replace(/\=/g, '\\u003D')
.replace(/-/g, '\\u002D')
.replace(/;/g, '\\u003B');
default:
return inp.replace(/&(?!amp;|lt;|gt;|quot;|#39;)/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
};
exports.e = exports.escape;
/**
* Get the first item in an array or character in a string. All other objects will attempt to return the first value available.
*
* @example
* // my_arr = ['a', 'b', 'c']
* {{ my_arr|first }}
* // => a
*
* @example
* // my_val = 'Tacos'
* {{ my_val|first }}
* // T
*
* @param {*} input
* @return {*} The first item of the array or first character of the string input.
*/
exports.first = function (input) {
if (typeof input === 'object' && !utils.isArray(input)) {
var keys = utils.keys(input);
return input[keys[0]];
}
if (typeof input === 'string') {
return input.substr(0, 1);
}
return input[0];
};
/**
* Group an array of objects by a common key. If an array is not provided, the input value will be returned untouched.
*
* @example
* // people = [{ age: 23, name: 'Paul' }, { age: 26, name: 'Jane' }, { age: 23, name: 'Jim' }];
* {% for agegroup in people|groupBy('age') %}
* <h2>{{ loop.key }}</h2>
* <ul>
* {% for person in agegroup %}
* <li>{{ person.name }}</li>
* {% endfor %}
* </ul>
* {% endfor %}
*
* @param {*} input Input object.
* @param {string} key Key to group by.
* @return {object} Grouped arrays by given key.
*/
exports.groupBy = function (input, key) {
if (!utils.isArray(input)) {
return input;
}
var out = {};
utils.each(input, function (value) {
if (!value.hasOwnProperty(key)) {
return;
}
var keyname = value[key],
newVal = utils.extend({}, value);
delete value[key];
if (!out[keyname]) {
out[keyname] = [];
}
out[keyname].push(value);
});
return out;
};
/**
* Join the input with a string.
*
* @example
* // my_array = ['foo', 'bar', 'baz']
* {{ my_array|join(', ') }}
* // => foo, bar, baz
*
* @example
* // my_key_object = { a: 'foo', b: 'bar', c: 'baz' }
* {{ my_key_object|join(' and ') }}
* // => foo and bar and baz
*
* @param {*} input
* @param {string} glue String value to join items together.
* @return {string}
*/
exports.join = function (input, glue) {
if (utils.isArray(input)) {
return input.join(glue);
}
if (typeof input === 'object') {
var out = [];
utils.each(input, function (value) {
out.push(value);
});
return out.join(glue);
}
return input;
};
/**
* Return a string representation of an JavaScript object.
*
* Backwards compatible with swig@0.x.x using `json_encode`.
*
* @example
* // val = { a: 'b' }
* {{ val|json }}
* // => {"a":"b"}
*
* @example
* // val = { a: 'b' }
* {{ val|json(4) }}
* // => {
* // "a": "b"
* // }
*
* @param {*} input
* @param {number} [indent] Number of spaces to indent for pretty-formatting.
* @return {string} A valid JSON string.
*/
exports.json = function (input, indent) {
return JSON.stringify(input, null, indent || 0);
};
exports.json_encode = exports.json;
/**
* Get the last item in an array or character in a string. All other objects will attempt to return the last value available.
*
* @example
* // my_arr = ['a', 'b', 'c']
* {{ my_arr|last }}
* // => c
*
* @example
* // my_val = 'Tacos'
* {{ my_val|last }}
* // s
*
* @param {*} input
* @return {*} The last item of the array or last character of the string.input.
*/
exports.last = function (input) {
if (typeof input === 'object' && !utils.isArray(input)) {
var keys = utils.keys(input);
return input[keys[keys.length - 1]];
}
if (typeof input === 'string') {
return input.charAt(input.length - 1);
}
return input[input.length - 1];
};
/**
* Return the input in all lowercase letters.
*
* @example
* {{ "FOOBAR"|lower }}
* // => foobar
*
* @example
* // myObj = { a: 'FOO', b: 'BAR' }
* {{ myObj|lower|join('') }}
* // => foobar
*
* @param {*} input
* @return {*} Returns the same type as the input.
*/
exports.lower = function (input) {
var out = iterateFilter.apply(exports.lower, arguments);
if (out !== undefined) {
return out;
}
return input.toString().toLowerCase();
};
/**
* Deprecated in favor of <a href="#safe">safe</a>.
*/
exports.raw = function (input) {
return exports.safe(input);
};
exports.raw.safe = true;
/**
* Returns a new string with the matched search pattern replaced by the given replacement string. Uses JavaScript's built-in String.replace() method.
*
* @example
* // my_var = 'foobar';
* {{ my_var|replace('o', 'e', 'g') }}
* // => feebar
*
* @example
* // my_var = "farfegnugen";
* {{ my_var|replace('^f', 'p') }}
* // => parfegnugen
*
* @example
* // my_var = 'a1b2c3';
* {{ my_var|replace('\w', '0', 'g') }}
* // => 010203
*
* @param {string} input
* @param {string} search String or pattern to replace from the input.
* @param {string} replacement String to replace matched pattern.
* @param {string} [flags] Regular Expression flags. 'g': global match, 'i': ignore case, 'm': match over multiple lines
* @return {string} Replaced string.
*/
exports.replace = function (input, search, replacement, flags) {
var r = new RegExp(search, flags);
return input.replace(r, replacement);
};
/**
* Reverse sort the input. This is an alias for <code data-language="swig">{{ input|sort(true) }}</code>.
*
* @example
* // val = [1, 2, 3];
* {{ val|reverse }}
* // => 3,2,1
*
* @param {array} input
* @return {array} Reversed array. The original input object is returned if it was not an array.
*/
exports.reverse = function (input) {
return exports.sort(input, true);
};
/**
* Forces the input to not be auto-escaped. Use this only on content that you know is safe to be rendered on your page.
*
* @example
* // my_var = "<p>Stuff</p>";
* {{ my_var|safe }}
* // => <p>Stuff</p>
*
* @param {*} input
* @return {*} The input exactly how it was given, regardless of autoescaping status.
*/
exports.safe = function (input) {
// This is a magic filter. Its logic is hard-coded into Swig's parser.
return input;
};
exports.safe.safe = true;
/**
* Sort the input in an ascending direction.
* If given an object, will return the keys as a sorted array.
* If given a string, each character will be sorted individually.
*
* @example
* // val = [2, 6, 4];
* {{ val|sort }}
* // => 2,4,6
*
* @example
* // val = 'zaq';
* {{ val|sort }}
* // => aqz
*
* @example
* // val = { bar: 1, foo: 2 }
* {{ val|sort(true) }}
* // => foo,bar
*
* @param {*} input
* @param {boolean} [reverse=false] Output is given reverse-sorted if true.
* @return {*} Sorted array;
*/
exports.sort = function (input, reverse) {
var out;
if (utils.isArray(input)) {
out = input.sort();
} else {
switch (typeof input) {
case 'object':
out = utils.keys(input).sort();
break;
case 'string':
out = input.split('');
if (reverse) {
return out.reverse().join('');
}
return out.sort().join('');
}
}
if (out && reverse) {
return out.reverse();
}
return out || input;
};
/**
* Strip HTML tags.
*
* @example
* // stuff = '<p>foobar</p>';
* {{ stuff|striptags }}
* // => foobar
*
* @param {*} input
* @return {*} Returns the same object as the input, but with all string values stripped of tags.
*/
exports.striptags = function (input) {
var out = iterateFilter.apply(exports.striptags, arguments);
if (out !== undefined) {
return out;
}
return input.toString().replace(/(<([^>]+)>)/ig, '');
};
/**
* Capitalizes every word given and lower-cases all other letters.
*
* @example
* // my_str = 'this is soMe text';
* {{ my_str|title }}
* // => This Is Some Text
*
* @example
* // my_arr = ['hi', 'this', 'is', 'an', 'array'];
* {{ my_arr|title|join(' ') }}
* // => Hi This Is An Array
*
* @param {*} input
* @return {*} Returns the same object as the input, but with all words in strings title-cased.
*/
exports.title = function (input) {
var out = iterateFilter.apply(exports.title, arguments);
if (out !== undefined) {
return out;
}
return input.toString().replace(/\w\S*/g, function (str) {
return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
});
};
/**
* Remove all duplicate items from an array.
*
* @example
* // my_arr = [1, 2, 3, 4, 4, 3, 2, 1];
* {{ my_arr|uniq|join(',') }}
* // => 1,2,3,4
*
* @param {array} input
* @return {array} Array with unique items. If input was not an array, the original item is returned untouched.
*/
exports.uniq = function (input) {
var result;
if (!input || !utils.isArray(input)) {
return '';
}
result = [];
utils.each(input, function (v) {
if (result.indexOf(v) === -1) {
result.push(v);
}
});
return result;
};
/**
* Convert the input to all uppercase letters. If an object or array is provided, all values will be uppercased.
*
* @example
* // my_str = 'tacos';
* {{ my_str|upper }}
* // => TACOS
*
* @example
* // my_arr = ['tacos', 'burritos'];
* {{ my_arr|upper|join(' & ') }}
* // => TACOS & BURRITOS
*
* @param {*} input
* @return {*} Returns the same type as the input, with all strings upper-cased.
*/
exports.upper = function (input) {
var out = iterateFilter.apply(exports.upper, arguments);
if (out !== undefined) {
return out;
}
return input.toString().toUpperCase();
};
/**
* URL-encode a string. If an object or array is passed, all values will be URL-encoded.
*
* @example
* // my_str = 'param=1&anotherParam=2';
* {{ my_str|url_encode }}
* // => param%3D1%26anotherParam%3D2
*
* @param {*} input
* @return {*} URL-encoded string.
*/
exports.url_encode = function (input) {
var out = iterateFilter.apply(exports.url_encode, arguments);
if (out !== undefined) {
return out;
}
return encodeURIComponent(input);
};
/**
* URL-decode a string. If an object or array is passed, all values will be URL-decoded.
*
* @example
* // my_str = 'param%3D1%26anotherParam%3D2';
* {{ my_str|url_decode }}
* // => param=1&anotherParam=2
*
* @param {*} input
* @return {*} URL-decoded string.
*/
exports.url_decode = function (input) {
var out = iterateFilter.apply(exports.url_decode, arguments);
if (out !== undefined) {
return out;
}
return decodeURIComponent(input);
};

View File

@@ -0,0 +1,306 @@
var utils = require('./utils');
/**
* A lexer token.
* @typedef {object} LexerToken
* @property {string} match The string that was matched.
* @property {number} type Lexer type enum.
* @property {number} length Length of the original string processed.
*/
/**
* Enum for token types.
* @readonly
* @enum {number}
*/
var TYPES = {
/** Whitespace */
WHITESPACE: 0,
/** Plain string */
STRING: 1,
/** Variable filter */
FILTER: 2,
/** Empty variable filter */
FILTEREMPTY: 3,
/** Function */
FUNCTION: 4,
/** Function with no arguments */
FUNCTIONEMPTY: 5,
/** Open parenthesis */
PARENOPEN: 6,
/** Close parenthesis */
PARENCLOSE: 7,
/** Comma */
COMMA: 8,
/** Variable */
VAR: 9,
/** Number */
NUMBER: 10,
/** Math operator */
OPERATOR: 11,
/** Open square bracket */
BRACKETOPEN: 12,
/** Close square bracket */
BRACKETCLOSE: 13,
/** Key on an object using dot-notation */
DOTKEY: 14,
/** Start of an array */
ARRAYOPEN: 15,
/** End of an array
* Currently unused
ARRAYCLOSE: 16, */
/** Open curly brace */
CURLYOPEN: 17,
/** Close curly brace */
CURLYCLOSE: 18,
/** Colon (:) */
COLON: 19,
/** JavaScript-valid comparator */
COMPARATOR: 20,
/** Boolean logic */
LOGIC: 21,
/** Boolean logic "not" */
NOT: 22,
/** true or false */
BOOL: 23,
/** Variable assignment */
ASSIGNMENT: 24,
/** Start of a method */
METHODOPEN: 25,
/** End of a method
* Currently unused
METHODEND: 26, */
/** Unknown type */
UNKNOWN: 100
},
rules = [
{
type: TYPES.WHITESPACE,
regex: [
/^\s+/
]
},
{
type: TYPES.STRING,
regex: [
/^""/,
/^".*?[^\\]"/,
/^''/,
/^'.*?[^\\]'/
]
},
{
type: TYPES.FILTER,
regex: [
/^\|\s*(\w+)\(/
],
idx: 1
},
{
type: TYPES.FILTEREMPTY,
regex: [
/^\|\s*(\w+)/
],
idx: 1
},
{
type: TYPES.FUNCTIONEMPTY,
regex: [
/^\s*(\w+)\(\)/
],
idx: 1
},
{
type: TYPES.FUNCTION,
regex: [
/^\s*(\w+)\(/
],
idx: 1
},
{
type: TYPES.PARENOPEN,
regex: [
/^\(/
]
},
{
type: TYPES.PARENCLOSE,
regex: [
/^\)/
]
},
{
type: TYPES.COMMA,
regex: [
/^,/
]
},
{
type: TYPES.LOGIC,
regex: [
/^(&&|\|\|)\s*/,
/^(and|or)\s+/
],
idx: 1,
replace: {
'and': '&&',
'or': '||'
}
},
{
type: TYPES.COMPARATOR,
regex: [
/^(===|==|\!==|\!=|<=|<|>=|>|in\s|gte\s|gt\s|lte\s|lt\s)\s*/
],
idx: 1,
replace: {
'gte': '>=',
'gt': '>',
'lte': '<=',
'lt': '<'
}
},
{
type: TYPES.ASSIGNMENT,
regex: [
/^(=|\+=|-=|\*=|\/=)/
]
},
{
type: TYPES.NOT,
regex: [
/^\!\s*/,
/^not\s+/
],
replace: {
'not': '!'
}
},
{
type: TYPES.BOOL,
regex: [
/^(true|false)\s+/,
/^(true|false)$/
],
idx: 1
},
{
type: TYPES.VAR,
regex: [
/^[a-zA-Z_$]\w*((\.\$?\w*)+)?/,
/^[a-zA-Z_$]\w*/
]
},
{
type: TYPES.BRACKETOPEN,
regex: [
/^\[/
]
},
{
type: TYPES.BRACKETCLOSE,
regex: [
/^\]/
]
},
{
type: TYPES.CURLYOPEN,
regex: [
/^\{/
]
},
{
type: TYPES.COLON,
regex: [
/^\:/
]
},
{
type: TYPES.CURLYCLOSE,
regex: [
/^\}/
]
},
{
type: TYPES.DOTKEY,
regex: [
/^\.(\w+)/
],
idx: 1
},
{
type: TYPES.NUMBER,
regex: [
/^[+\-]?\d+(\.\d+)?/
]
},
{
type: TYPES.OPERATOR,
regex: [
/^(\+|\-|\/|\*|%)/
]
}
];
exports.types = TYPES;
/**
* Return the token type object for a single chunk of a string.
* @param {string} str String chunk.
* @return {LexerToken} Defined type, potentially stripped or replaced with more suitable content.
* @private
*/
function reader(str) {
var matched;
utils.some(rules, function (rule) {
return utils.some(rule.regex, function (regex) {
var match = str.match(regex),
normalized;
if (!match) {
return;
}
normalized = match[rule.idx || 0].replace(/\s*$/, '');
normalized = (rule.hasOwnProperty('replace') && rule.replace.hasOwnProperty(normalized)) ? rule.replace[normalized] : normalized;
matched = {
match: normalized,
type: rule.type,
length: match[0].length
};
return true;
});
});
if (!matched) {
matched = {
match: str,
type: TYPES.UNKNOWN,
length: str.length
};
}
return matched;
}
/**
* Read a string and break it into separate token types.
* @param {string} str
* @return {Array.LexerToken} Array of defined types, potentially stripped or replaced with more suitable content.
* @private
*/
exports.read = function (str) {
var offset = 0,
tokens = [],
substr,
match;
while (offset < str.length) {
substr = str.substring(offset);
match = reader(substr);
offset += match.length;
tokens.push(match);
}
return tokens;
};

View File

@@ -0,0 +1,59 @@
var fs = require('fs'),
path = require('path');
/**
* Loads templates from the file system.
* @alias swig.loaders.fs
* @example
* swig.setDefaults({ loader: swig.loaders.fs() });
* @example
* // Load Templates from a specific directory (does not require using relative paths in your templates)
* swig.setDefaults({ loader: swig.loaders.fs(__dirname + '/templates' )});
* @param {string} [basepath=''] Path to the templates as string. Assigning this value allows you to use semi-absolute paths to templates instead of relative paths.
* @param {string} [encoding='utf8'] Template encoding
*/
module.exports = function (basepath, encoding) {
var ret = {};
encoding = encoding || 'utf8';
basepath = (basepath) ? path.normalize(basepath) : null;
/**
* Resolves <var>to</var> to an absolute path or unique identifier. This is used for building correct, normalized, and absolute paths to a given template.
* @alias resolve
* @param {string} to Non-absolute identifier or pathname to a file.
* @param {string} [from] If given, should attempt to find the <var>to</var> path in relation to this given, known path.
* @return {string}
*/
ret.resolve = function (to, from) {
if (basepath) {
from = basepath;
} else {
from = (from) ? path.dirname(from) : process.cwd();
}
return path.resolve(from, to);
};
/**
* Loads a single template. Given a unique <var>identifier</var> found by the <var>resolve</var> method this should return the given template.
* @alias load
* @param {string} identifier Unique identifier of a template (possibly an absolute path).
* @param {function} [cb] Asynchronous callback function. If not provided, this method should run synchronously.
* @return {string} Template source string.
*/
ret.load = function (identifier, cb) {
if (!fs || (cb && !fs.readFile) || !fs.readFileSync) {
throw new Error('Unable to find file ' + identifier + ' because there is no filesystem to read from.');
}
identifier = ret.resolve(identifier);
if (cb) {
fs.readFile(identifier, encoding, cb);
return;
}
return fs.readFileSync(identifier, encoding);
};
return ret;
};

View File

@@ -0,0 +1,53 @@
/**
* @namespace TemplateLoader
* @description Swig is able to accept custom template loaders written by you, so that your templates can come from your favorite storage medium without needing to be part of the core library.
* A template loader consists of two methods: <var>resolve</var> and <var>load</var>. Each method is used internally by Swig to find and load the source of the template before attempting to parse and compile it.
* @example
* // A theoretical memcached loader
* var path = require('path'),
* Memcached = require('memcached');
* function memcachedLoader(locations, options) {
* var memcached = new Memcached(locations, options);
* return {
* resolve: function (to, from) {
* return path.resolve(from, to);
* },
* load: function (identifier, cb) {
* memcached.get(identifier, function (err, data) {
* // if (!data) { load from filesystem; }
* cb(err, data);
* });
* }
* };
* };
* // Tell swig about the loader:
* swig.setDefaults({ loader: memcachedLoader(['192.168.0.2']) });
*/
/**
* @function
* @name resolve
* @memberof TemplateLoader
* @description
* Resolves <var>to</var> to an absolute path or unique identifier. This is used for building correct, normalized, and absolute paths to a given template.
* @param {string} to Non-absolute identifier or pathname to a file.
* @param {string} [from] If given, should attempt to find the <var>to</var> path in relation to this given, known path.
* @return {string}
*/
/**
* @function
* @name load
* @memberof TemplateLoader
* @description
* Loads a single template. Given a unique <var>identifier</var> found by the <var>resolve</var> method this should return the given template.
* @param {string} identifier Unique identifier of a template (possibly an absolute path).
* @param {function} [cb] Asynchronous callback function. If not provided, this method should run synchronously.
* @return {string} Template source string.
*/
/**
* @private
*/
exports.fs = require('./filesystem');
exports.memory = require('./memory');

View File

@@ -0,0 +1,63 @@
var path = require('path'),
utils = require('../utils');
/**
* Loads templates from a provided object mapping.
* @alias swig.loaders.memory
* @example
* var templates = {
* "layout": "{% block content %}{% endblock %}",
* "home.html": "{% extends 'layout.html' %}{% block content %}...{% endblock %}"
* };
* swig.setDefaults({ loader: swig.loaders.memory(templates) });
*
* @param {object} mapping Hash object with template paths as keys and template sources as values.
* @param {string} [basepath] Path to the templates as string. Assigning this value allows you to use semi-absolute paths to templates instead of relative paths.
*/
module.exports = function (mapping, basepath) {
var ret = {};
basepath = (basepath) ? path.normalize(basepath) : null;
/**
* Resolves <var>to</var> to an absolute path or unique identifier. This is used for building correct, normalized, and absolute paths to a given template.
* @alias resolve
* @param {string} to Non-absolute identifier or pathname to a file.
* @param {string} [from] If given, should attempt to find the <var>to</var> path in relation to this given, known path.
* @return {string}
*/
ret.resolve = function (to, from) {
if (basepath) {
from = basepath;
} else {
from = (from) ? path.dirname(from) : '/';
}
return path.resolve(from, to);
};
/**
* Loads a single template. Given a unique <var>identifier</var> found by the <var>resolve</var> method this should return the given template.
* @alias load
* @param {string} identifier Unique identifier of a template (possibly an absolute path).
* @param {function} [cb] Asynchronous callback function. If not provided, this method should run synchronously.
* @return {string} Template source string.
*/
ret.load = function (pathname, cb) {
var src, paths;
paths = [pathname, pathname.replace(/^(\/|\\)/, '')];
src = mapping[paths[0]] || mapping[paths[1]];
if (!src) {
utils.throwError('Unable to find template "' + pathname + '".');
}
if (cb) {
cb(null, src);
return;
}
return src;
};
return ret;
};

View File

@@ -0,0 +1,744 @@
var utils = require('./utils'),
lexer = require('./lexer');
var _t = lexer.types,
_reserved = ['break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with'];
/**
* Filters are simply functions that perform transformations on their first input argument.
* Filters are run at render time, so they may not directly modify the compiled template structure in any way.
* All of Swig's built-in filters are written in this same way. For more examples, reference the `filters.js` file in Swig's source.
*
* To disable auto-escaping on a custom filter, simply add a property to the filter method `safe = true;` and the output from this will not be escaped, no matter what the global settings are for Swig.
*
* @typedef {function} Filter
*
* @example
* // This filter will return 'bazbop' if the idx on the input is not 'foobar'
* swig.setFilter('foobar', function (input, idx) {
* return input[idx] === 'foobar' ? input[idx] : 'bazbop';
* });
* // myvar = ['foo', 'bar', 'baz', 'bop'];
* // => {{ myvar|foobar(3) }}
* // Since myvar[3] !== 'foobar', we render:
* // => bazbop
*
* @example
* // This filter will disable auto-escaping on its output:
* function bazbop (input) { return input; }
* bazbop.safe = true;
* swig.setFilter('bazbop', bazbop);
* // => {{ "<p>"|bazbop }}
* // => <p>
*
* @param {*} input Input argument, automatically sent from Swig's built-in parser.
* @param {...*} [args] All other arguments are defined by the Filter author.
* @return {*}
*/
/*!
* Makes a string safe for a regular expression.
* @param {string} str
* @return {string}
* @private
*/
function escapeRegExp(str) {
return str.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&');
}
/**
* Parse strings of variables and tags into tokens for future compilation.
* @class
* @param {array} tokens Pre-split tokens read by the Lexer.
* @param {object} filters Keyed object of filters that may be applied to variables.
* @param {boolean} autoescape Whether or not this should be autoescaped.
* @param {number} line Beginning line number for the first token.
* @param {string} [filename] Name of the file being parsed.
* @private
*/
function TokenParser(tokens, filters, autoescape, line, filename) {
this.out = [];
this.state = [];
this.filterApplyIdx = [];
this._parsers = {};
this.line = line;
this.filename = filename;
this.filters = filters;
this.escape = autoescape;
this.parse = function () {
var self = this;
if (self._parsers.start) {
self._parsers.start.call(self);
}
utils.each(tokens, function (token, i) {
var prevToken = tokens[i - 1];
self.isLast = (i === tokens.length - 1);
if (prevToken) {
while (prevToken.type === _t.WHITESPACE) {
i -= 1;
prevToken = tokens[i - 1];
}
}
self.prevToken = prevToken;
self.parseToken(token);
});
if (self._parsers.end) {
self._parsers.end.call(self);
}
if (self.escape) {
self.filterApplyIdx = [0];
if (typeof self.escape === 'string') {
self.parseToken({ type: _t.FILTER, match: 'e' });
self.parseToken({ type: _t.COMMA, match: ',' });
self.parseToken({ type: _t.STRING, match: String(autoescape) });
self.parseToken({ type: _t.PARENCLOSE, match: ')'});
} else {
self.parseToken({ type: _t.FILTEREMPTY, match: 'e' });
}
}
return self.out;
};
}
TokenParser.prototype = {
/**
* Set a custom method to be called when a token type is found.
*
* @example
* parser.on(types.STRING, function (token) {
* this.out.push(token.match);
* });
* @example
* parser.on('start', function () {
* this.out.push('something at the beginning of your args')
* });
* parser.on('end', function () {
* this.out.push('something at the end of your args');
* });
*
* @param {number} type Token type ID. Found in the Lexer.
* @param {Function} fn Callback function. Return true to continue executing the default parsing function.
* @return {undefined}
*/
on: function (type, fn) {
this._parsers[type] = fn;
},
/**
* Parse a single token.
* @param {{match: string, type: number, line: number}} token Lexer token object.
* @return {undefined}
* @private
*/
parseToken: function (token) {
var self = this,
fn = self._parsers[token.type] || self._parsers['*'],
match = token.match,
prevToken = self.prevToken,
prevTokenType = prevToken ? prevToken.type : null,
lastState = (self.state.length) ? self.state[self.state.length - 1] : null,
temp;
if (fn && typeof fn === 'function') {
if (!fn.call(this, token)) {
return;
}
}
if (lastState && prevToken &&
lastState === _t.FILTER &&
prevTokenType === _t.FILTER &&
token.type !== _t.PARENCLOSE &&
token.type !== _t.COMMA &&
token.type !== _t.OPERATOR &&
token.type !== _t.FILTER &&
token.type !== _t.FILTEREMPTY) {
self.out.push(', ');
}
if (lastState && lastState === _t.METHODOPEN) {
self.state.pop();
if (token.type !== _t.PARENCLOSE) {
self.out.push(', ');
}
}
switch (token.type) {
case _t.WHITESPACE:
break;
case _t.STRING:
self.filterApplyIdx.push(self.out.length);
self.out.push(match.replace(/\\/g, '\\\\'));
break;
case _t.NUMBER:
case _t.BOOL:
self.filterApplyIdx.push(self.out.length);
self.out.push(match);
break;
case _t.FILTER:
if (!self.filters.hasOwnProperty(match) || typeof self.filters[match] !== "function") {
utils.throwError('Invalid filter "' + match + '"', self.line, self.filename);
}
self.escape = self.filters[match].safe ? false : self.escape;
self.out.splice(self.filterApplyIdx[self.filterApplyIdx.length - 1], 0, '_filters["' + match + '"](');
self.state.push(token.type);
break;
case _t.FILTEREMPTY:
if (!self.filters.hasOwnProperty(match) || typeof self.filters[match] !== "function") {
utils.throwError('Invalid filter "' + match + '"', self.line, self.filename);
}
self.escape = self.filters[match].safe ? false : self.escape;
self.out.splice(self.filterApplyIdx[self.filterApplyIdx.length - 1], 0, '_filters["' + match + '"](');
self.out.push(')');
break;
case _t.FUNCTION:
case _t.FUNCTIONEMPTY:
self.out.push('((typeof _ctx.' + match + ' !== "undefined") ? _ctx.' + match +
' : ((typeof ' + match + ' !== "undefined") ? ' + match +
' : _fn))(');
self.escape = false;
if (token.type === _t.FUNCTIONEMPTY) {
self.out[self.out.length - 1] = self.out[self.out.length - 1] + ')';
} else {
self.state.push(token.type);
}
self.filterApplyIdx.push(self.out.length - 1);
break;
case _t.PARENOPEN:
self.state.push(token.type);
if (self.filterApplyIdx.length) {
self.out.splice(self.filterApplyIdx[self.filterApplyIdx.length - 1], 0, '(');
if (prevToken && prevTokenType === _t.VAR) {
temp = prevToken.match.split('.').slice(0, -1);
self.out.push(' || _fn).call(' + self.checkMatch(temp));
self.state.push(_t.METHODOPEN);
self.escape = false;
} else {
self.out.push(' || _fn)(');
}
self.filterApplyIdx.push(self.out.length - 3);
} else {
self.out.push('(');
self.filterApplyIdx.push(self.out.length - 1);
}
break;
case _t.PARENCLOSE:
temp = self.state.pop();
if (temp !== _t.PARENOPEN && temp !== _t.FUNCTION && temp !== _t.FILTER) {
utils.throwError('Mismatched nesting state', self.line, self.filename);
}
self.out.push(')');
// Once off the previous entry
self.filterApplyIdx.pop();
if (temp !== _t.FILTER) {
// Once for the open paren
self.filterApplyIdx.pop();
}
break;
case _t.COMMA:
if (lastState !== _t.FUNCTION &&
lastState !== _t.FILTER &&
lastState !== _t.ARRAYOPEN &&
lastState !== _t.CURLYOPEN &&
lastState !== _t.PARENOPEN &&
lastState !== _t.COLON) {
utils.throwError('Unexpected comma', self.line, self.filename);
}
if (lastState === _t.COLON) {
self.state.pop();
}
self.out.push(', ');
self.filterApplyIdx.pop();
break;
case _t.LOGIC:
case _t.COMPARATOR:
if (!prevToken ||
prevTokenType === _t.COMMA ||
prevTokenType === token.type ||
prevTokenType === _t.BRACKETOPEN ||
prevTokenType === _t.CURLYOPEN ||
prevTokenType === _t.PARENOPEN ||
prevTokenType === _t.FUNCTION) {
utils.throwError('Unexpected logic', self.line, self.filename);
}
self.out.push(token.match);
break;
case _t.NOT:
self.out.push(token.match);
break;
case _t.VAR:
self.parseVar(token, match, lastState);
break;
case _t.BRACKETOPEN:
if (!prevToken ||
(prevTokenType !== _t.VAR &&
prevTokenType !== _t.BRACKETCLOSE &&
prevTokenType !== _t.PARENCLOSE)) {
self.state.push(_t.ARRAYOPEN);
self.filterApplyIdx.push(self.out.length);
} else {
self.state.push(token.type);
}
self.out.push('[');
break;
case _t.BRACKETCLOSE:
temp = self.state.pop();
if (temp !== _t.BRACKETOPEN && temp !== _t.ARRAYOPEN) {
utils.throwError('Unexpected closing square bracket', self.line, self.filename);
}
self.out.push(']');
self.filterApplyIdx.pop();
break;
case _t.CURLYOPEN:
self.state.push(token.type);
self.out.push('{');
self.filterApplyIdx.push(self.out.length - 1);
break;
case _t.COLON:
if (lastState !== _t.CURLYOPEN) {
utils.throwError('Unexpected colon', self.line, self.filename);
}
self.state.push(token.type);
self.out.push(':');
self.filterApplyIdx.pop();
break;
case _t.CURLYCLOSE:
if (lastState === _t.COLON) {
self.state.pop();
}
if (self.state.pop() !== _t.CURLYOPEN) {
utils.throwError('Unexpected closing curly brace', self.line, self.filename);
}
self.out.push('}');
self.filterApplyIdx.pop();
break;
case _t.DOTKEY:
if (!prevToken || (
prevTokenType !== _t.VAR &&
prevTokenType !== _t.BRACKETCLOSE &&
prevTokenType !== _t.DOTKEY &&
prevTokenType !== _t.PARENCLOSE &&
prevTokenType !== _t.FUNCTIONEMPTY &&
prevTokenType !== _t.FILTEREMPTY &&
prevTokenType !== _t.CURLYCLOSE
)) {
utils.throwError('Unexpected key "' + match + '"', self.line, self.filename);
}
self.out.push('.' + match);
break;
case _t.OPERATOR:
self.out.push(' ' + match + ' ');
self.filterApplyIdx.pop();
break;
}
},
/**
* Parse variable token
* @param {{match: string, type: number, line: number}} token Lexer token object.
* @param {string} match Shortcut for token.match
* @param {number} lastState Lexer token type state.
* @return {undefined}
* @private
*/
parseVar: function (token, match, lastState) {
var self = this;
match = match.split('.');
if (_reserved.indexOf(match[0]) !== -1) {
utils.throwError('Reserved keyword "' + match[0] + '" attempted to be used as a variable', self.line, self.filename);
}
self.filterApplyIdx.push(self.out.length);
if (lastState === _t.CURLYOPEN) {
if (match.length > 1) {
utils.throwError('Unexpected dot', self.line, self.filename);
}
self.out.push(match[0]);
return;
}
self.out.push(self.checkMatch(match));
},
/**
* Return contextual dot-check string for a match
* @param {string} match Shortcut for token.match
* @private
*/
checkMatch: function (match) {
var temp = match[0], result;
function checkDot(ctx) {
var c = ctx + temp,
m = match,
build = '';
build = '(typeof ' + c + ' !== "undefined" && ' + c + ' !== null';
utils.each(m, function (v, i) {
if (i === 0) {
return;
}
build += ' && ' + c + '.' + v + ' !== undefined && ' + c + '.' + v + ' !== null';
c += '.' + v;
});
build += ')';
return build;
}
function buildDot(ctx) {
return '(' + checkDot(ctx) + ' ? ' + ctx + match.join('.') + ' : "")';
}
result = '(' + checkDot('_ctx.') + ' ? ' + buildDot('_ctx.') + ' : ' + buildDot('') + ')';
return '(' + result + ' !== null ? ' + result + ' : ' + '"" )';
}
};
/**
* Parse a source string into tokens that are ready for compilation.
*
* @example
* exports.parse('{{ tacos }}', {}, tags, filters);
* // => [{ compile: [Function], ... }]
*
* @params {object} swig The current Swig instance
* @param {string} source Swig template source.
* @param {object} opts Swig options object.
* @param {object} tags Keyed object of tags that can be parsed and compiled.
* @param {object} filters Keyed object of filters that may be applied to variables.
* @return {array} List of tokens ready for compilation.
*/
exports.parse = function (swig, source, opts, tags, filters) {
source = source.replace(/\r\n/g, '\n');
var escape = opts.autoescape,
tagOpen = opts.tagControls[0],
tagClose = opts.tagControls[1],
varOpen = opts.varControls[0],
varClose = opts.varControls[1],
escapedTagOpen = escapeRegExp(tagOpen),
escapedTagClose = escapeRegExp(tagClose),
escapedVarOpen = escapeRegExp(varOpen),
escapedVarClose = escapeRegExp(varClose),
tagStrip = new RegExp('^' + escapedTagOpen + '-?\\s*-?|-?\\s*-?' + escapedTagClose + '$', 'g'),
tagStripBefore = new RegExp('^' + escapedTagOpen + '-'),
tagStripAfter = new RegExp('-' + escapedTagClose + '$'),
varStrip = new RegExp('^' + escapedVarOpen + '-?\\s*-?|-?\\s*-?' + escapedVarClose + '$', 'g'),
varStripBefore = new RegExp('^' + escapedVarOpen + '-'),
varStripAfter = new RegExp('-' + escapedVarClose + '$'),
cmtOpen = opts.cmtControls[0],
cmtClose = opts.cmtControls[1],
anyChar = '[\\s\\S]*?',
// Split the template source based on variable, tag, and comment blocks
// /(\{%[\s\S]*?%\}|\{\{[\s\S]*?\}\}|\{#[\s\S]*?#\})/
splitter = new RegExp(
'(' +
escapedTagOpen + anyChar + escapedTagClose + '|' +
escapedVarOpen + anyChar + escapedVarClose + '|' +
escapeRegExp(cmtOpen) + anyChar + escapeRegExp(cmtClose) +
')'
),
line = 1,
stack = [],
parent = null,
tokens = [],
blocks = {},
inRaw = false,
stripNext;
/**
* Parse a variable.
* @param {string} str String contents of the variable, between <i>{{</i> and <i>}}</i>
* @param {number} line The line number that this variable starts on.
* @return {VarToken} Parsed variable token object.
* @private
*/
function parseVariable(str, line) {
var tokens = lexer.read(utils.strip(str)),
parser,
out;
parser = new TokenParser(tokens, filters, escape, line, opts.filename);
out = parser.parse().join('');
if (parser.state.length) {
utils.throwError('Unable to parse "' + str + '"', line, opts.filename);
}
/**
* A parsed variable token.
* @typedef {object} VarToken
* @property {function} compile Method for compiling this token.
*/
return {
compile: function () {
return '_output += ' + out + ';\n';
}
};
}
exports.parseVariable = parseVariable;
/**
* Parse a tag.
* @param {string} str String contents of the tag, between <i>{%</i> and <i>%}</i>
* @param {number} line The line number that this tag starts on.
* @return {TagToken} Parsed token object.
* @private
*/
function parseTag(str, line) {
var tokens, parser, chunks, tagName, tag, args, last;
if (utils.startsWith(str, 'end')) {
last = stack[stack.length - 1];
if (last && last.name === str.split(/\s+/)[0].replace(/^end/, '') && last.ends) {
switch (last.name) {
case 'autoescape':
escape = opts.autoescape;
break;
case 'raw':
inRaw = false;
break;
}
stack.pop();
return;
}
if (!inRaw) {
utils.throwError('Unexpected end of tag "' + str.replace(/^end/, '') + '"', line, opts.filename);
}
}
if (inRaw) {
return;
}
chunks = str.split(/\s+(.+)?/);
tagName = chunks.shift();
if (!tags.hasOwnProperty(tagName)) {
utils.throwError('Unexpected tag "' + str + '"', line, opts.filename);
}
tokens = lexer.read(utils.strip(chunks.join(' ')));
parser = new TokenParser(tokens, filters, false, line, opts.filename);
tag = tags[tagName];
/**
* Define custom parsing methods for your tag.
* @callback parse
*
* @example
* exports.parse = function (str, line, parser, types, options, swig) {
* parser.on('start', function () {
* // ...
* });
* parser.on(types.STRING, function (token) {
* // ...
* });
* };
*
* @param {string} str The full token string of the tag.
* @param {number} line The line number that this tag appears on.
* @param {TokenParser} parser A TokenParser instance.
* @param {TYPES} types Lexer token type enum.
* @param {TagToken[]} stack The current stack of open tags.
* @param {SwigOpts} options Swig Options Object.
* @param {object} swig The Swig instance (gives acces to loaders, parsers, etc)
*/
if (!tag.parse(chunks[1], line, parser, _t, stack, opts, swig)) {
utils.throwError('Unexpected tag "' + tagName + '"', line, opts.filename);
}
parser.parse();
args = parser.out;
switch (tagName) {
case 'autoescape':
escape = (args[0] !== 'false') ? args[0] : false;
break;
case 'raw':
inRaw = true;
break;
}
/**
* A parsed tag token.
* @typedef {Object} TagToken
* @property {compile} [compile] Method for compiling this token.
* @property {array} [args] Array of arguments for the tag.
* @property {Token[]} [content=[]] An array of tokens that are children of this Token.
* @property {boolean} [ends] Whether or not this tag requires an end tag.
* @property {string} name The name of this tag.
*/
return {
block: !!tags[tagName].block,
compile: tag.compile,
args: args,
content: [],
ends: tag.ends,
name: tagName
};
}
/**
* Strip the whitespace from the previous token, if it is a string.
* @param {object} token Parsed token.
* @return {object} If the token was a string, trailing whitespace will be stripped.
*/
function stripPrevToken(token) {
if (typeof token === 'string') {
token = token.replace(/\s*$/, '');
}
return token;
}
/*!
* Loop over the source, split via the tag/var/comment regular expression splitter.
* Send each chunk to the appropriate parser.
*/
utils.each(source.split(splitter), function (chunk) {
var token, lines, stripPrev, prevToken, prevChildToken;
if (!chunk) {
return;
}
// Is a variable?
if (!inRaw && utils.startsWith(chunk, varOpen) && utils.endsWith(chunk, varClose)) {
stripPrev = varStripBefore.test(chunk);
stripNext = varStripAfter.test(chunk);
token = parseVariable(chunk.replace(varStrip, ''), line);
// Is a tag?
} else if (utils.startsWith(chunk, tagOpen) && utils.endsWith(chunk, tagClose)) {
stripPrev = tagStripBefore.test(chunk);
stripNext = tagStripAfter.test(chunk);
token = parseTag(chunk.replace(tagStrip, ''), line);
if (token) {
if (token.name === 'extends') {
parent = token.args.join('').replace(/^\'|\'$/g, '').replace(/^\"|\"$/g, '');
} else if (token.block && !stack.length) {
blocks[token.args.join('')] = token;
}
}
if (inRaw && !token) {
token = chunk;
}
// Is a content string?
} else if (inRaw || (!utils.startsWith(chunk, cmtOpen) && !utils.endsWith(chunk, cmtClose))) {
token = (stripNext) ? chunk.replace(/^\s*/, '') : chunk;
stripNext = false;
} else if (utils.startsWith(chunk, cmtOpen) && utils.endsWith(chunk, cmtClose)) {
return;
}
// Did this tag ask to strip previous whitespace? <code>{%- ... %}</code> or <code>{{- ... }}</code>
if (stripPrev && tokens.length) {
prevToken = tokens.pop();
if (typeof prevToken === 'string') {
prevToken = stripPrevToken(prevToken);
} else if (prevToken.content && prevToken.content.length) {
prevChildToken = stripPrevToken(prevToken.content.pop());
prevToken.content.push(prevChildToken);
}
tokens.push(prevToken);
}
// This was a comment, so let's just keep going.
if (!token) {
return;
}
// If there's an open item in the stack, add this to its content.
if (stack.length) {
stack[stack.length - 1].content.push(token);
} else {
tokens.push(token);
}
// If the token is a tag that requires an end tag, open it on the stack.
if (token.name && token.ends) {
stack.push(token);
}
lines = chunk.match(/\n/g);
line += (lines) ? lines.length : 0;
});
return {
name: opts.filename,
parent: parent,
tokens: tokens,
blocks: blocks
};
};
/**
* Compile an array of tokens.
* @param {Token[]} template An array of template tokens.
* @param {Templates[]} parents Array of parent templates.
* @param {SwigOpts} [options] Swig options object.
* @param {string} [blockName] Name of the current block context.
* @return {string} Partial for a compiled JavaScript method that will output a rendered template.
*/
exports.compile = function (template, parents, options, blockName) {
var out = '',
tokens = utils.isArray(template) ? template : template.tokens;
utils.each(tokens, function (token) {
var o;
if (typeof token === 'string') {
out += '_output += "' + token.replace(/\\/g, '\\\\').replace(/\n|\r/g, '\\n').replace(/"/g, '\\"') + '";\n';
return;
}
/**
* Compile callback for VarToken and TagToken objects.
* @callback compile
*
* @example
* exports.compile = function (compiler, args, content, parents, options, blockName) {
* if (args[0] === 'foo') {
* return compiler(content, parents, options, blockName) + '\n';
* }
* return '_output += "fallback";\n';
* };
*
* @param {parserCompiler} compiler
* @param {array} [args] Array of parsed arguments on the for the token.
* @param {array} [content] Array of content within the token.
* @param {array} [parents] Array of parent templates for the current template context.
* @param {SwigOpts} [options] Swig Options Object
* @param {string} [blockName] Name of the direct block parent, if any.
*/
o = token.compile(exports.compile, token.args ? token.args.slice(0) : [], token.content ? token.content.slice(0) : [], parents, options, blockName);
out += o || '';
});
return out;
};

View File

@@ -0,0 +1,740 @@
var utils = require('./utils'),
_tags = require('./tags'),
_filters = require('./filters'),
parser = require('./parser'),
dateformatter = require('./dateformatter'),
loaders = require('./loaders');
/**
* Swig version number as a string.
* @example
* if (swig.version === "1.4.2") { ... }
*
* @type {String}
*/
exports.version = "1.4.2";
/**
* Swig Options Object. This object can be passed to many of the API-level Swig methods to control various aspects of the engine. All keys are optional.
* @typedef {Object} SwigOpts
* @property {boolean} autoescape Controls whether or not variable output will automatically be escaped for safe HTML output. Defaults to <code data-language="js">true</code>. Functions executed in variable statements will not be auto-escaped. Your application/functions should take care of their own auto-escaping.
* @property {array} varControls Open and close controls for variables. Defaults to <code data-language="js">['{{', '}}']</code>.
* @property {array} tagControls Open and close controls for tags. Defaults to <code data-language="js">['{%', '%}']</code>.
* @property {array} cmtControls Open and close controls for comments. Defaults to <code data-language="js">['{#', '#}']</code>.
* @property {object} locals Default variable context to be passed to <strong>all</strong> templates.
* @property {CacheOptions} cache Cache control for templates. Defaults to saving in <code data-language="js">'memory'</code>. Send <code data-language="js">false</code> to disable. Send an object with <code data-language="js">get</code> and <code data-language="js">set</code> functions to customize.
* @property {TemplateLoader} loader The method that Swig will use to load templates. Defaults to <var>swig.loaders.fs</var>.
*/
var defaultOptions = {
autoescape: true,
varControls: ['{{', '}}'],
tagControls: ['{%', '%}'],
cmtControls: ['{#', '#}'],
locals: {},
/**
* Cache control for templates. Defaults to saving all templates into memory.
* @typedef {boolean|string|object} CacheOptions
* @example
* // Default
* swig.setDefaults({ cache: 'memory' });
* @example
* // Disables caching in Swig.
* swig.setDefaults({ cache: false });
* @example
* // Custom cache storage and retrieval
* swig.setDefaults({
* cache: {
* get: function (key) { ... },
* set: function (key, val) { ... }
* }
* });
*/
cache: 'memory',
/**
* Configure Swig to use either the <var>swig.loaders.fs</var> or <var>swig.loaders.memory</var> template loader. Or, you can write your own!
* For more information, please see the <a href="../loaders/">Template Loaders documentation</a>.
* @typedef {class} TemplateLoader
* @example
* // Default, FileSystem loader
* swig.setDefaults({ loader: swig.loaders.fs() });
* @example
* // FileSystem loader allowing a base path
* // With this, you don't use relative URLs in your template references
* swig.setDefaults({ loader: swig.loaders.fs(__dirname + '/templates') });
* @example
* // Memory Loader
* swig.setDefaults({ loader: swig.loaders.memory({
* layout: '{% block foo %}{% endblock %}',
* page1: '{% extends "layout" %}{% block foo %}Tacos!{% endblock %}'
* })});
*/
loader: loaders.fs()
},
defaultInstance;
/**
* Empty function, used in templates.
* @return {string} Empty string
* @private
*/
function efn() { return ''; }
/**
* Validate the Swig options object.
* @param {?SwigOpts} options Swig options object.
* @return {undefined} This method will throw errors if anything is wrong.
* @private
*/
function validateOptions(options) {
if (!options) {
return;
}
utils.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
if (!options.hasOwnProperty(key)) {
return;
}
if (!utils.isArray(options[key]) || options[key].length !== 2) {
throw new Error('Option "' + key + '" must be an array containing 2 different control strings.');
}
if (options[key][0] === options[key][1]) {
throw new Error('Option "' + key + '" open and close controls must not be the same.');
}
utils.each(options[key], function (a, i) {
if (a.length < 2) {
throw new Error('Option "' + key + '" ' + ((i) ? 'open ' : 'close ') + 'control must be at least 2 characters. Saw "' + a + '" instead.');
}
});
});
if (options.hasOwnProperty('cache')) {
if (options.cache && options.cache !== 'memory') {
if (!options.cache.get || !options.cache.set) {
throw new Error('Invalid cache option ' + JSON.stringify(options.cache) + ' found. Expected "memory" or { get: function (key) { ... }, set: function (key, value) { ... } }.');
}
}
}
if (options.hasOwnProperty('loader')) {
if (options.loader) {
if (!options.loader.load || !options.loader.resolve) {
throw new Error('Invalid loader option ' + JSON.stringify(options.loader) + ' found. Expected { load: function (pathname, cb) { ... }, resolve: function (to, from) { ... } }.');
}
}
}
}
/**
* Set defaults for the base and all new Swig environments.
*
* @example
* swig.setDefaults({ cache: false });
* // => Disables Cache
*
* @example
* swig.setDefaults({ locals: { now: function () { return new Date(); } }});
* // => sets a globally accessible method for all template
* // contexts, allowing you to print the current date
* // => {{ now()|date('F jS, Y') }}
*
* @param {SwigOpts} [options={}] Swig options object.
* @return {undefined}
*/
exports.setDefaults = function (options) {
validateOptions(options);
defaultInstance.options = utils.extend(defaultInstance.options, options);
};
/**
* Set the default TimeZone offset for date formatting via the date filter. This is a global setting and will affect all Swig environments, old or new.
* @param {number} offset Offset from GMT, in minutes.
* @return {undefined}
*/
exports.setDefaultTZOffset = function (offset) {
dateformatter.tzOffset = offset;
};
/**
* Create a new, separate Swig compile/render environment.
*
* @example
* var swig = require('swig');
* var myswig = new swig.Swig({varControls: ['<%=', '%>']});
* myswig.render('Tacos are <%= tacos =>!', { locals: { tacos: 'delicious' }});
* // => Tacos are delicious!
* swig.render('Tacos are <%= tacos =>!', { locals: { tacos: 'delicious' }});
* // => 'Tacos are <%= tacos =>!'
*
* @param {SwigOpts} [opts={}] Swig options object.
* @return {object} New Swig environment.
*/
exports.Swig = function (opts) {
validateOptions(opts);
this.options = utils.extend({}, defaultOptions, opts || {});
this.cache = {};
this.extensions = {};
var self = this,
tags = _tags,
filters = _filters;
/**
* Get combined locals context.
* @param {?SwigOpts} [options] Swig options object.
* @return {object} Locals context.
* @private
*/
function getLocals(options) {
if (!options || !options.locals) {
return self.options.locals;
}
return utils.extend({}, self.options.locals, options.locals);
}
/**
* Determine whether caching is enabled via the options provided and/or defaults
* @param {SwigOpts} [options={}] Swig Options Object
* @return {boolean}
* @private
*/
function shouldCache(options) {
options = options || {};
return (options.hasOwnProperty('cache') && !options.cache) || !self.options.cache;
}
/**
* Get compiled template from the cache.
* @param {string} key Name of template.
* @return {object|undefined} Template function and tokens.
* @private
*/
function cacheGet(key, options) {
if (shouldCache(options)) {
return;
}
if (self.options.cache === 'memory') {
return self.cache[key];
}
return self.options.cache.get(key);
}
/**
* Store a template in the cache.
* @param {string} key Name of template.
* @param {object} val Template function and tokens.
* @return {undefined}
* @private
*/
function cacheSet(key, options, val) {
if (shouldCache(options)) {
return;
}
if (self.options.cache === 'memory') {
self.cache[key] = val;
return;
}
self.options.cache.set(key, val);
}
/**
* Clears the in-memory template cache.
*
* @example
* swig.invalidateCache();
*
* @return {undefined}
*/
this.invalidateCache = function () {
if (self.options.cache === 'memory') {
self.cache = {};
}
};
/**
* Add a custom filter for swig variables.
*
* @example
* function replaceMs(input) { return input.replace(/m/g, 'f'); }
* swig.setFilter('replaceMs', replaceMs);
* // => {{ "onomatopoeia"|replaceMs }}
* // => onofatopeia
*
* @param {string} name Name of filter, used in templates. <strong>Will</strong> overwrite previously defined filters, if using the same name.
* @param {function} method Function that acts against the input. See <a href="/docs/filters/#custom">Custom Filters</a> for more information.
* @return {undefined}
*/
this.setFilter = function (name, method) {
if (typeof method !== "function") {
throw new Error('Filter "' + name + '" is not a valid function.');
}
filters[name] = method;
};
/**
* Add a custom tag. To expose your own extensions to compiled template code, see <code data-language="js">swig.setExtension</code>.
*
* For a more in-depth explanation of writing custom tags, see <a href="../extending/#tags">Custom Tags</a>.
*
* @example
* var tacotag = require('./tacotag');
* swig.setTag('tacos', tacotag.parse, tacotag.compile, tacotag.ends, tacotag.blockLevel);
* // => {% tacos %}Make this be tacos.{% endtacos %}
* // => Tacos tacos tacos tacos.
*
* @param {string} name Tag name.
* @param {function} parse Method for parsing tokens.
* @param {function} compile Method for compiling renderable output.
* @param {boolean} [ends=false] Whether or not this tag requires an <i>end</i> tag.
* @param {boolean} [blockLevel=false] If false, this tag will not be compiled outside of <code>block</code> tags when extending a parent template.
* @return {undefined}
*/
this.setTag = function (name, parse, compile, ends, blockLevel) {
if (typeof parse !== 'function') {
throw new Error('Tag "' + name + '" parse method is not a valid function.');
}
if (typeof compile !== 'function') {
throw new Error('Tag "' + name + '" compile method is not a valid function.');
}
tags[name] = {
parse: parse,
compile: compile,
ends: ends || false,
block: !!blockLevel
};
};
/**
* Add extensions for custom tags. This allows any custom tag to access a globally available methods via a special globally available object, <var>_ext</var>, in templates.
*
* @example
* swig.setExtension('trans', function (v) { return translate(v); });
* function compileTrans(compiler, args, content, parent, options) {
* return '_output += _ext.trans(' + args[0] + ');'
* };
* swig.setTag('trans', parseTrans, compileTrans, true);
*
* @param {string} name Key name of the extension. Accessed via <code data-language="js">_ext[name]</code>.
* @param {*} object The method, value, or object that should be available via the given name.
* @return {undefined}
*/
this.setExtension = function (name, object) {
self.extensions[name] = object;
};
/**
* Parse a given source string into tokens.
*
* @param {string} source Swig template source.
* @param {SwigOpts} [options={}] Swig options object.
* @return {object} parsed Template tokens object.
* @private
*/
this.parse = function (source, options) {
validateOptions(options);
var locals = getLocals(options),
opts = {},
k;
for (k in options) {
if (options.hasOwnProperty(k) && k !== 'locals') {
opts[k] = options[k];
}
}
options = utils.extend({}, self.options, opts);
options.locals = locals;
return parser.parse(this, source, options, tags, filters);
};
/**
* Parse a given file into tokens.
*
* @param {string} pathname Full path to file to parse.
* @param {SwigOpts} [options={}] Swig options object.
* @return {object} parsed Template tokens object.
* @private
*/
this.parseFile = function (pathname, options) {
var src;
if (!options) {
options = {};
}
pathname = self.options.loader.resolve(pathname, options.resolveFrom);
src = self.options.loader.load(pathname);
if (!options.filename) {
options = utils.extend({ filename: pathname }, options);
}
return self.parse(src, options);
};
/**
* Re-Map blocks within a list of tokens to the template's block objects.
* @param {array} tokens List of tokens for the parent object.
* @param {object} template Current template that needs to be mapped to the parent's block and token list.
* @return {array}
* @private
*/
function remapBlocks(blocks, tokens) {
return utils.map(tokens, function (token) {
var args = token.args ? token.args.join('') : '';
if (token.name === 'block' && blocks[args]) {
token = blocks[args];
}
if (token.content && token.content.length) {
token.content = remapBlocks(blocks, token.content);
}
return token;
});
}
/**
* Import block-level tags to the token list that are not actual block tags.
* @param {array} blocks List of block-level tags.
* @param {array} tokens List of tokens to render.
* @return {undefined}
* @private
*/
function importNonBlocks(blocks, tokens) {
var temp = [];
utils.each(blocks, function (block) { temp.push(block); });
utils.each(temp.reverse(), function (block) {
if (block.name !== 'block') {
tokens.unshift(block);
}
});
}
/**
* Recursively compile and get parents of given parsed token object.
*
* @param {object} tokens Parsed tokens from template.
* @param {SwigOpts} [options={}] Swig options object.
* @return {object} Parsed tokens from parent templates.
* @private
*/
function getParents(tokens, options) {
var parentName = tokens.parent,
parentFiles = [],
parents = [],
parentFile,
parent,
l;
while (parentName) {
if (!options || !options.filename) {
throw new Error('Cannot extend "' + parentName + '" because current template has no filename.');
}
parentFile = parentFile || options.filename;
parentFile = self.options.loader.resolve(parentName, parentFile);
parent = cacheGet(parentFile, options) || self.parseFile(parentFile, utils.extend({}, options, { filename: parentFile }));
parentName = parent.parent;
if (parentFiles.indexOf(parentFile) !== -1) {
throw new Error('Illegal circular extends of "' + parentFile + '".');
}
parentFiles.push(parentFile);
parents.push(parent);
}
// Remap each parents'(1) blocks onto its own parent(2), receiving the full token list for rendering the original parent(1) on its own.
l = parents.length;
for (l = parents.length - 2; l >= 0; l -= 1) {
parents[l].tokens = remapBlocks(parents[l].blocks, parents[l + 1].tokens);
importNonBlocks(parents[l].blocks, parents[l].tokens);
}
return parents;
}
/**
* Pre-compile a source string into a cache-able template function.
*
* @example
* swig.precompile('{{ tacos }}');
* // => {
* // tpl: function (_swig, _locals, _filters, _utils, _fn) { ... },
* // tokens: {
* // name: undefined,
* // parent: null,
* // tokens: [...],
* // blocks: {}
* // }
* // }
*
* In order to render a pre-compiled template, you must have access to filters and utils from Swig. <var>efn</var> is simply an empty function that does nothing.
*
* @param {string} source Swig template source string.
* @param {SwigOpts} [options={}] Swig options object.
* @return {object} Renderable function and tokens object.
*/
this.precompile = function (source, options) {
var tokens = self.parse(source, options),
parents = getParents(tokens, options),
tpl,
err;
if (parents.length) {
// Remap the templates first-parent's tokens using this template's blocks.
tokens.tokens = remapBlocks(tokens.blocks, parents[0].tokens);
importNonBlocks(tokens.blocks, tokens.tokens);
}
try {
tpl = new Function('_swig', '_ctx', '_filters', '_utils', '_fn',
' var _ext = _swig.extensions,\n' +
' _output = "";\n' +
parser.compile(tokens, parents, options) + '\n' +
' return _output;\n'
);
} catch (e) {
utils.throwError(e, null, options.filename);
}
return { tpl: tpl, tokens: tokens };
};
/**
* Compile and render a template string for final output.
*
* When rendering a source string, a file path should be specified in the options object in order for <var>extends</var>, <var>include</var>, and <var>import</var> to work properly. Do this by adding <code data-language="js">{ filename: '/absolute/path/to/mytpl.html' }</code> to the options argument.
*
* @example
* swig.render('{{ tacos }}', { locals: { tacos: 'Tacos!!!!' }});
* // => Tacos!!!!
*
* @param {string} source Swig template source string.
* @param {SwigOpts} [options={}] Swig options object.
* @return {string} Rendered output.
*/
this.render = function (source, options) {
return self.compile(source, options)();
};
/**
* Compile and render a template file for final output. This is most useful for libraries like Express.js.
*
* @example
* swig.renderFile('./template.html', {}, function (err, output) {
* if (err) {
* throw err;
* }
* console.log(output);
* });
*
* @example
* swig.renderFile('./template.html', {});
* // => output
*
* @param {string} pathName File location.
* @param {object} [locals={}] Template variable context.
* @param {Function} [cb] Asyncronous callback function. If not provided, <var>compileFile</var> will run syncronously.
* @return {string} Rendered output.
*/
this.renderFile = function (pathName, locals, cb) {
if (cb) {
self.compileFile(pathName, {}, function (err, fn) {
var result;
if (err) {
cb(err);
return;
}
try {
result = fn(locals);
} catch (err2) {
cb(err2);
return;
}
cb(null, result);
});
return;
}
return self.compileFile(pathName)(locals);
};
/**
* Compile string source into a renderable template function.
*
* @example
* var tpl = swig.compile('{{ tacos }}');
* // => {
* // [Function: compiled]
* // parent: null,
* // tokens: [{ compile: [Function] }],
* // blocks: {}
* // }
* tpl({ tacos: 'Tacos!!!!' });
* // => Tacos!!!!
*
* When compiling a source string, a file path should be specified in the options object in order for <var>extends</var>, <var>include</var>, and <var>import</var> to work properly. Do this by adding <code data-language="js">{ filename: '/absolute/path/to/mytpl.html' }</code> to the options argument.
*
* @param {string} source Swig template source string.
* @param {SwigOpts} [options={}] Swig options object.
* @return {function} Renderable function with keys for parent, blocks, and tokens.
*/
this.compile = function (source, options) {
var key = options ? options.filename : null,
cached = key ? cacheGet(key, options) : null,
context,
contextLength,
pre;
if (cached) {
return cached;
}
context = getLocals(options);
contextLength = utils.keys(context).length;
pre = this.precompile(source, options);
function compiled(locals) {
var lcls;
if (locals && contextLength) {
lcls = utils.extend({}, context, locals);
} else if (locals && !contextLength) {
lcls = locals;
} else if (!locals && contextLength) {
lcls = context;
} else {
lcls = {};
}
return pre.tpl(self, lcls, filters, utils, efn);
}
utils.extend(compiled, pre.tokens);
if (key) {
cacheSet(key, options, compiled);
}
return compiled;
};
/**
* Compile a source file into a renderable template function.
*
* @example
* var tpl = swig.compileFile('./mytpl.html');
* // => {
* // [Function: compiled]
* // parent: null,
* // tokens: [{ compile: [Function] }],
* // blocks: {}
* // }
* tpl({ tacos: 'Tacos!!!!' });
* // => Tacos!!!!
*
* @example
* swig.compileFile('/myfile.txt', { varControls: ['<%=', '=%>'], tagControls: ['<%', '%>']});
* // => will compile 'myfile.txt' using the var and tag controls as specified.
*
* @param {string} pathname File location.
* @param {SwigOpts} [options={}] Swig options object.
* @param {Function} [cb] Asyncronous callback function. If not provided, <var>compileFile</var> will run syncronously.
* @return {function} Renderable function with keys for parent, blocks, and tokens.
*/
this.compileFile = function (pathname, options, cb) {
var src, cached;
if (!options) {
options = {};
}
pathname = self.options.loader.resolve(pathname, options.resolveFrom);
if (!options.filename) {
options = utils.extend({ filename: pathname }, options);
}
cached = cacheGet(pathname, options);
if (cached) {
if (cb) {
cb(null, cached);
return;
}
return cached;
}
if (cb) {
self.options.loader.load(pathname, function (err, src) {
if (err) {
cb(err);
return;
}
var compiled;
try {
compiled = self.compile(src, options);
} catch (err2) {
cb(err2);
return;
}
cb(err, compiled);
});
return;
}
src = self.options.loader.load(pathname);
return self.compile(src, options);
};
/**
* Run a pre-compiled template function. This is most useful in the browser when you've pre-compiled your templates with the Swig command-line tool.
*
* @example
* $ swig compile ./mytpl.html --wrap-start="var mytpl = " > mytpl.js
* @example
* <script src="mytpl.js"></script>
* <script>
* swig.run(mytpl, {});
* // => "rendered template..."
* </script>
*
* @param {function} tpl Pre-compiled Swig template function. Use the Swig CLI to compile your templates.
* @param {object} [locals={}] Template variable context.
* @param {string} [filepath] Filename used for caching the template.
* @return {string} Rendered output.
*/
this.run = function (tpl, locals, filepath) {
var context = getLocals({ locals: locals });
if (filepath) {
cacheSet(filepath, {}, tpl);
}
return tpl(self, context, filters, utils, efn);
};
};
/*!
* Export methods publicly
*/
defaultInstance = new exports.Swig();
exports.setFilter = defaultInstance.setFilter;
exports.setTag = defaultInstance.setTag;
exports.setExtension = defaultInstance.setExtension;
exports.parseFile = defaultInstance.parseFile;
exports.precompile = defaultInstance.precompile;
exports.compile = defaultInstance.compile;
exports.compileFile = defaultInstance.compileFile;
exports.render = defaultInstance.render;
exports.renderFile = defaultInstance.renderFile;
exports.run = defaultInstance.run;
exports.invalidateCache = defaultInstance.invalidateCache;
exports.loaders = loaders;

View File

@@ -0,0 +1,37 @@
var utils = require('../utils'),
strings = ['html', 'js'];
/**
* Control auto-escaping of variable output from within your templates.
*
* @alias autoescape
*
* @example
* // myvar = '<foo>';
* {% autoescape true %}{{ myvar }}{% endautoescape %}
* // => &lt;foo&gt;
* {% autoescape false %}{{ myvar }}{% endautoescape %}
* // => <foo>
*
* @param {boolean|string} control One of `true`, `false`, `"js"` or `"html"`.
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
return compiler(content, parents, options, blockName);
};
exports.parse = function (str, line, parser, types, stack, opts) {
var matched;
parser.on('*', function (token) {
if (!matched &&
(token.type === types.BOOL ||
(token.type === types.STRING && strings.indexOf(token.match) === -1))
) {
this.out.push(token.match);
matched = true;
return;
}
utils.throwError('Unexpected token "' + token.match + '" in autoescape tag', line, opts.filename);
});
return true;
};
exports.ends = true;

View File

@@ -0,0 +1,25 @@
/**
* Defines a block in a template that can be overridden by a template extending this one and/or will override the current template's parent template block of the same name.
*
* See <a href="#inheritance">Template Inheritance</a> for more information.
*
* @alias block
*
* @example
* {% block body %}...{% endblock %}
*
* @param {literal} name Name of the block for use in parent and extended templates.
*/
exports.compile = function (compiler, args, content, parents, options) {
return compiler(content, parents, options, args.join(''));
};
exports.parse = function (str, line, parser) {
parser.on('*', function (token) {
this.out.push(token.match);
});
return true;
};
exports.ends = true;
exports.block = true;

View File

@@ -0,0 +1,25 @@
/**
* Used within an <code data-language="swig">{% if %}</code> tag, the code block following this tag up until <code data-language="swig">{% endif %}</code> will be rendered if the <i>if</i> statement returns false.
*
* @alias else
*
* @example
* {% if false %}
* statement1
* {% else %}
* statement2
* {% endif %}
* // => statement2
*
*/
exports.compile = function () {
return '} else {\n';
};
exports.parse = function (str, line, parser, types, stack) {
parser.on('*', function (token) {
throw new Error('"else" tag does not accept any tokens. Found "' + token.match + '" on line ' + line + '.');
});
return (stack.length && stack[stack.length - 1].name === 'if');
};

View File

@@ -0,0 +1,28 @@
var ifparser = require('./if').parse;
/**
* Like <code data-language="swig">{% else %}</code>, except this tag can take more conditional statements.
*
* @alias elseif
* @alias elif
*
* @example
* {% if false %}
* Tacos
* {% elseif true %}
* Burritos
* {% else %}
* Churros
* {% endif %}
* // => Burritos
*
* @param {...mixed} conditional Conditional statement that returns a truthy or falsy value.
*/
exports.compile = function (compiler, args) {
return '} else if (' + args.join(' ') + ') {\n';
};
exports.parse = function (str, line, parser, types, stack) {
var okay = ifparser(str, line, parser, types, stack);
return okay && (stack.length && stack[stack.length - 1].name === 'if');
};

View File

@@ -0,0 +1,19 @@
/**
* Makes the current template extend a parent template. This tag must be the first item in your template.
*
* See <a href="#inheritance">Template Inheritance</a> for more information.
*
* @alias extends
*
* @example
* {% extends "./layout.html" %}
*
* @param {string} parentFile Relative path to the file that this template extends.
*/
exports.compile = function () {};
exports.parse = function () {
return true;
};
exports.ends = false;

View File

@@ -0,0 +1,68 @@
var filters = require('../filters');
/**
* Apply a filter to an entire block of template.
*
* @alias filter
*
* @example
* {% filter uppercase %}oh hi, {{ name }}{% endfilter %}
* // => OH HI, PAUL
*
* @example
* {% filter replace(".", "!", "g") %}Hi. My name is Paul.{% endfilter %}
* // => Hi! My name is Paul!
*
* @param {function} filter The filter that should be applied to the contents of the tag.
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
var filter = args.shift().replace(/\($/, ''),
val = '(function () {\n' +
' var _output = "";\n' +
compiler(content, parents, options, blockName) +
' return _output;\n' +
'})()';
if (args[args.length - 1] === ')') {
args.pop();
}
args = (args.length) ? ', ' + args.join('') : '';
return '_output += _filters["' + filter + '"](' + val + args + ');\n';
};
exports.parse = function (str, line, parser, types) {
var filter;
function check(filter) {
if (!filters.hasOwnProperty(filter)) {
throw new Error('Filter "' + filter + '" does not exist on line ' + line + '.');
}
}
parser.on(types.FUNCTION, function (token) {
if (!filter) {
filter = token.match.replace(/\($/, '');
check(filter);
this.out.push(token.match);
this.state.push(token.type);
return;
}
return true;
});
parser.on(types.VAR, function (token) {
if (!filter) {
filter = token.match;
check(filter);
this.out.push(filter);
return;
}
return true;
});
return true;
};
exports.ends = true;

View File

@@ -0,0 +1,130 @@
var ctx = '_ctx.',
ctxloop = ctx + 'loop';
/**
* Loop over objects and arrays.
*
* @alias for
*
* @example
* // obj = { one: 'hi', two: 'bye' };
* {% for x in obj %}
* {% if loop.first %}<ul>{% endif %}
* <li>{{ loop.index }} - {{ loop.key }}: {{ x }}</li>
* {% if loop.last %}</ul>{% endif %}
* {% endfor %}
* // => <ul>
* // <li>1 - one: hi</li>
* // <li>2 - two: bye</li>
* // </ul>
*
* @example
* // arr = [1, 2, 3]
* // Reverse the array, shortcut the key/index to `key`
* {% for key, val in arr|reverse %}
* {{ key }} -- {{ val }}
* {% endfor %}
* // => 0 -- 3
* // 1 -- 2
* // 2 -- 1
*
* @param {literal} [key] A shortcut to the index of the array or current key accessor.
* @param {literal} variable The current value will be assigned to this variable name temporarily. The variable will be reset upon ending the for tag.
* @param {literal} in Literally, "in". This token is required.
* @param {object} object An enumerable object that will be iterated over.
*
* @return {loop.index} The current iteration of the loop (1-indexed)
* @return {loop.index0} The current iteration of the loop (0-indexed)
* @return {loop.revindex} The number of iterations from the end of the loop (1-indexed)
* @return {loop.revindex0} The number of iterations from the end of the loop (0-indexed)
* @return {loop.key} If the iterator is an object, this will be the key of the current item, otherwise it will be the same as the loop.index.
* @return {loop.first} True if the current object is the first in the object or array.
* @return {loop.last} True if the current object is the last in the object or array.
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
var val = args.shift(),
key = '__k',
ctxloopcache = (ctx + '__loopcache' + Math.random()).replace(/\./g, ''),
last;
if (args[0] && args[0] === ',') {
args.shift();
key = val;
val = args.shift();
}
last = args.join('');
return [
'(function () {\n',
' var __l = ' + last + ', __len = (_utils.isArray(__l) || typeof __l === "string") ? __l.length : _utils.keys(__l).length;\n',
' if (!__l) { return; }\n',
' var ' + ctxloopcache + ' = { loop: ' + ctxloop + ', ' + val + ': ' + ctx + val + ', ' + key + ': ' + ctx + key + ' };\n',
' ' + ctxloop + ' = { first: false, index: 1, index0: 0, revindex: __len, revindex0: __len - 1, length: __len, last: false };\n',
' _utils.each(__l, function (' + val + ', ' + key + ') {\n',
' ' + ctx + val + ' = ' + val + ';\n',
' ' + ctx + key + ' = ' + key + ';\n',
' ' + ctxloop + '.key = ' + key + ';\n',
' ' + ctxloop + '.first = (' + ctxloop + '.index0 === 0);\n',
' ' + ctxloop + '.last = (' + ctxloop + '.revindex0 === 0);\n',
' ' + compiler(content, parents, options, blockName),
' ' + ctxloop + '.index += 1; ' + ctxloop + '.index0 += 1; ' + ctxloop + '.revindex -= 1; ' + ctxloop + '.revindex0 -= 1;\n',
' });\n',
' ' + ctxloop + ' = ' + ctxloopcache + '.loop;\n',
' ' + ctx + val + ' = ' + ctxloopcache + '.' + val + ';\n',
' ' + ctx + key + ' = ' + ctxloopcache + '.' + key + ';\n',
' ' + ctxloopcache + ' = undefined;\n',
'})();\n'
].join('');
};
exports.parse = function (str, line, parser, types) {
var firstVar, ready;
parser.on(types.NUMBER, function (token) {
var lastState = this.state.length ? this.state[this.state.length - 1] : null;
if (!ready ||
(lastState !== types.ARRAYOPEN &&
lastState !== types.CURLYOPEN &&
lastState !== types.CURLYCLOSE &&
lastState !== types.FUNCTION &&
lastState !== types.FILTER)
) {
throw new Error('Unexpected number "' + token.match + '" on line ' + line + '.');
}
return true;
});
parser.on(types.VAR, function (token) {
if (ready && firstVar) {
return true;
}
if (!this.out.length) {
firstVar = true;
}
this.out.push(token.match);
});
parser.on(types.COMMA, function (token) {
if (firstVar && this.prevToken.type === types.VAR) {
this.out.push(token.match);
return;
}
return true;
});
parser.on(types.COMPARATOR, function (token) {
if (token.match !== 'in' || !firstVar) {
throw new Error('Unexpected token "' + token.match + '" on line ' + line + '.');
}
ready = true;
this.filterApplyIdx.push(this.out.length);
});
return true;
};
exports.ends = true;

View File

@@ -0,0 +1,86 @@
/**
* Used to create conditional statements in templates. Accepts most JavaScript valid comparisons.
*
* Can be used in conjunction with <a href="#elseif"><code data-language="swig">{% elseif ... %}</code></a> and <a href="#else"><code data-language="swig">{% else %}</code></a> tags.
*
* @alias if
*
* @example
* {% if x %}{% endif %}
* {% if !x %}{% endif %}
* {% if not x %}{% endif %}
*
* @example
* {% if x and y %}{% endif %}
* {% if x && y %}{% endif %}
* {% if x or y %}{% endif %}
* {% if x || y %}{% endif %}
* {% if x || (y && z) %}{% endif %}
*
* @example
* {% if x [operator] y %}
* Operators: ==, !=, <, <=, >, >=, ===, !==
* {% endif %}
*
* @example
* {% if x == 'five' %}
* The operands can be also be string or number literals
* {% endif %}
*
* @example
* {% if x|lower === 'tacos' %}
* You can use filters on any operand in the statement.
* {% endif %}
*
* @example
* {% if x in y %}
* If x is a value that is present in y, this will return true.
* {% endif %}
*
* @param {...mixed} conditional Conditional statement that returns a truthy or falsy value.
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
return 'if (' + args.join(' ') + ') { \n' +
compiler(content, parents, options, blockName) + '\n' +
'}';
};
exports.parse = function (str, line, parser, types) {
if (typeof str === "undefined") {
throw new Error('No conditional statement provided on line ' + line + '.');
}
parser.on(types.COMPARATOR, function (token) {
if (this.isLast) {
throw new Error('Unexpected logic "' + token.match + '" on line ' + line + '.');
}
if (this.prevToken.type === types.NOT) {
throw new Error('Attempted logic "not ' + token.match + '" on line ' + line + '. Use !(foo ' + token.match + ') instead.');
}
this.out.push(token.match);
this.filterApplyIdx.push(this.out.length);
});
parser.on(types.NOT, function (token) {
if (this.isLast) {
throw new Error('Unexpected logic "' + token.match + '" on line ' + line + '.');
}
this.out.push(token.match);
});
parser.on(types.BOOL, function (token) {
this.out.push(token.match);
});
parser.on(types.LOGIC, function (token) {
if (!this.out.length || this.isLast) {
throw new Error('Unexpected logic "' + token.match + '" on line ' + line + '.');
}
this.out.push(token.match);
this.filterApplyIdx.pop();
});
return true;
};
exports.ends = true;

View File

@@ -0,0 +1,91 @@
var utils = require('../utils');
/**
* Allows you to import macros from another file directly into your current context.
* The import tag is specifically designed for importing macros into your template with a specific context scope. This is very useful for keeping your macros from overriding template context that is being injected by your server-side page generation.
*
* @alias import
*
* @example
* {% import './formmacros.html' as forms %}
* {{ form.input("text", "name") }}
* // => <input type="text" name="name">
*
* @example
* {% import "../shared/tags.html" as tags %}
* {{ tags.stylesheet('global') }}
* // => <link rel="stylesheet" href="/global.css">
*
* @param {string|var} file Relative path from the current template file to the file to import macros from.
* @param {literal} as Literally, "as".
* @param {literal} varname Local-accessible object name to assign the macros to.
*/
exports.compile = function (compiler, args) {
var ctx = args.pop(),
out = '_ctx.' + ctx + ' = {};\n var _output = "";\n',
replacements = utils.map(args, function (arg) {
return {
ex: new RegExp('_ctx.' + arg.name, 'g'),
re: '_ctx.' + ctx + '.' + arg.name
};
});
// Replace all occurrences of all macros in this file with
// proper namespaced definitions and calls
utils.each(args, function (arg) {
var c = arg.compiled;
utils.each(replacements, function (re) {
c = c.replace(re.ex, re.re);
});
out += c;
});
return out;
};
exports.parse = function (str, line, parser, types, stack, opts, swig) {
var compiler = require('../parser').compile,
parseOpts = { resolveFrom: opts.filename },
compileOpts = utils.extend({}, opts, parseOpts),
tokens,
ctx;
parser.on(types.STRING, function (token) {
var self = this;
if (!tokens) {
tokens = swig.parseFile(token.match.replace(/^("|')|("|')$/g, ''), parseOpts).tokens;
utils.each(tokens, function (token) {
var out = '',
macroName;
if (!token || token.name !== 'macro' || !token.compile) {
return;
}
macroName = token.args[0];
out += token.compile(compiler, token.args, token.content, [], compileOpts) + '\n';
self.out.push({compiled: out, name: macroName});
});
return;
}
throw new Error('Unexpected string ' + token.match + ' on line ' + line + '.');
});
parser.on(types.VAR, function (token) {
var self = this;
if (!tokens || ctx) {
throw new Error('Unexpected variable "' + token.match + '" on line ' + line + '.');
}
if (token.match === 'as') {
return;
}
ctx = token.match;
self.out.push(ctx);
return false;
});
return true;
};
exports.block = true;

View File

@@ -0,0 +1,100 @@
var ignore = 'ignore',
missing = 'missing',
only = 'only';
/**
* Includes a template partial in place. The template is rendered within the current locals variable context.
*
* @alias include
*
* @example
* // food = 'burritos';
* // drink = 'lemonade';
* {% include "./partial.html" %}
* // => I like burritos and lemonade.
*
* @example
* // my_obj = { food: 'tacos', drink: 'horchata' };
* {% include "./partial.html" with my_obj only %}
* // => I like tacos and horchata.
*
* @example
* {% include "/this/file/does/not/exist" ignore missing %}
* // => (Nothing! empty string)
*
* @param {string|var} file The path, relative to the template root, to render into the current context.
* @param {literal} [with] Literally, "with".
* @param {object} [context] Local variable key-value object context to provide to the included file.
* @param {literal} [only] Restricts to <strong>only</strong> passing the <code>with context</code> as local variablesthe included template will not be aware of any other local variables in the parent template. For best performance, usage of this option is recommended if possible.
* @param {literal} [ignore missing] Will output empty string if not found instead of throwing an error.
*/
exports.compile = function (compiler, args) {
var file = args.shift(),
onlyIdx = args.indexOf(only),
onlyCtx = onlyIdx !== -1 ? args.splice(onlyIdx, 1) : false,
parentFile = (args.pop() || '').replace(/\\/g, '\\\\'),
ignore = args[args.length - 1] === missing ? (args.pop()) : false,
w = args.join('');
return (ignore ? ' try {\n' : '') +
'_output += _swig.compileFile(' + file + ', {' +
'resolveFrom: "' + parentFile + '"' +
'})(' +
((onlyCtx && w) ? w : (!w ? '_ctx' : '_utils.extend({}, _ctx, ' + w + ')')) +
');\n' +
(ignore ? '} catch (e) {}\n' : '');
};
exports.parse = function (str, line, parser, types, stack, opts) {
var file, w;
parser.on(types.STRING, function (token) {
if (!file) {
file = token.match;
this.out.push(file);
return;
}
return true;
});
parser.on(types.VAR, function (token) {
if (!file) {
file = token.match;
return true;
}
if (!w && token.match === 'with') {
w = true;
return;
}
if (w && token.match === only && this.prevToken.match !== 'with') {
this.out.push(token.match);
return;
}
if (token.match === ignore) {
return false;
}
if (token.match === missing) {
if (this.prevToken.match !== ignore) {
throw new Error('Unexpected token "' + missing + '" on line ' + line + '.');
}
this.out.push(token.match);
return false;
}
if (this.prevToken.match === ignore) {
throw new Error('Expected "' + missing + '" on line ' + line + ' but found "' + token.match + '".');
}
return true;
});
parser.on('end', function () {
this.out.push(opts.filename || null);
});
return true;
};

View File

@@ -0,0 +1,16 @@
exports.autoescape = require('./autoescape');
exports.block = require('./block');
exports["else"] = require('./else');
exports.elseif = require('./elseif');
exports.elif = exports.elseif;
exports["extends"] = require('./extends');
exports.filter = require('./filter');
exports["for"] = require('./for');
exports["if"] = require('./if');
exports["import"] = require('./import');
exports.include = require('./include');
exports.macro = require('./macro');
exports.parent = require('./parent');
exports.raw = require('./raw');
exports.set = require('./set');
exports.spaceless = require('./spaceless');

View File

@@ -0,0 +1,79 @@
/**
* Create custom, reusable snippets within your templates.
* Can be imported from one template to another using the <a href="#import"><code data-language="swig">{% import ... %}</code></a> tag.
*
* @alias macro
*
* @example
* {% macro input(type, name, id, label, value, error) %}
* <label for="{{ name }}">{{ label }}</label>
* <input type="{{ type }}" name="{{ name }}" id="{{ id }}" value="{{ value }}"{% if error %} class="error"{% endif %}>
* {% endmacro %}
*
* {{ input("text", "fname", "fname", "First Name", fname.value, fname.errors) }}
* // => <label for="fname">First Name</label>
* // <input type="text" name="fname" id="fname" value="">
*
* @param {...arguments} arguments User-defined arguments.
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
var fnName = args.shift();
return '_ctx.' + fnName + ' = function (' + args.join('') + ') {\n' +
' var _output = "",\n' +
' __ctx = _utils.extend({}, _ctx);\n' +
' _utils.each(_ctx, function (v, k) {\n' +
' if (["' + args.join('","') + '"].indexOf(k) !== -1) { delete _ctx[k]; }\n' +
' });\n' +
compiler(content, parents, options, blockName) + '\n' +
' _ctx = _utils.extend(_ctx, __ctx);\n' +
' return _output;\n' +
'};\n' +
'_ctx.' + fnName + '.safe = true;\n';
};
exports.parse = function (str, line, parser, types) {
var name;
parser.on(types.VAR, function (token) {
if (token.match.indexOf('.') !== -1) {
throw new Error('Unexpected dot in macro argument "' + token.match + '" on line ' + line + '.');
}
this.out.push(token.match);
});
parser.on(types.FUNCTION, function (token) {
if (!name) {
name = token.match;
this.out.push(name);
this.state.push(types.FUNCTION);
}
});
parser.on(types.FUNCTIONEMPTY, function (token) {
if (!name) {
name = token.match;
this.out.push(name);
}
});
parser.on(types.PARENCLOSE, function () {
if (this.isLast) {
return;
}
throw new Error('Unexpected parenthesis close on line ' + line + '.');
});
parser.on(types.COMMA, function () {
return true;
});
parser.on('*', function () {
return;
});
return true;
};
exports.ends = true;
exports.block = true;

View File

@@ -0,0 +1,51 @@
/**
* Inject the content from the parent template's block of the same name into the current block.
*
* See <a href="#inheritance">Template Inheritance</a> for more information.
*
* @alias parent
*
* @example
* {% extends "./foo.html" %}
* {% block content %}
* My content.
* {% parent %}
* {% endblock %}
*
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
if (!parents || !parents.length) {
return '';
}
var parentFile = args[0],
breaker = true,
l = parents.length,
i = 0,
parent,
block;
for (i; i < l; i += 1) {
parent = parents[i];
if (!parent.blocks || !parent.blocks.hasOwnProperty(blockName)) {
continue;
}
// Silly JSLint "Strange Loop" requires return to be in a conditional
if (breaker && parentFile !== parent.name) {
block = parent.blocks[blockName];
return block.compile(compiler, [blockName], block.content, parents.slice(i + 1), options) + '\n';
}
}
};
exports.parse = function (str, line, parser, types, stack, opts) {
parser.on('*', function (token) {
throw new Error('Unexpected argument "' + token.match + '" on line ' + line + '.');
});
parser.on('end', function () {
this.out.push(opts.filename);
});
return true;
};

View File

@@ -0,0 +1,23 @@
// Magic tag, hardcoded into parser
/**
* Forces the content to not be auto-escaped. All swig instructions will be ignored and the content will be rendered exactly as it was given.
*
* @alias raw
*
* @example
* // foobar = '<p>'
* {% raw %}{{ foobar }}{% endraw %}
* // => {{ foobar }}
*
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
return compiler(content, parents, options, blockName);
};
exports.parse = function (str, line, parser) {
parser.on('*', function (token) {
throw new Error('Unexpected token "' + token.match + '" in raw tag on line ' + line + '.');
});
return true;
};
exports.ends = true;

View File

@@ -0,0 +1,109 @@
/**
* Set a variable for re-use in the current context. This will over-write any value already set to the context for the given <var>varname</var>.
*
* @alias set
*
* @example
* {% set foo = "anything!" %}
* {{ foo }}
* // => anything!
*
* @example
* // index = 2;
* {% set bar = 1 %}
* {% set bar += index|default(3) %}
* // => 3
*
* @example
* // foods = {};
* // food = 'chili';
* {% set foods[food] = "con queso" %}
* {{ foods.chili }}
* // => con queso
*
* @example
* // foods = { chili: 'chili con queso' }
* {% set foods.chili = "guatamalan insanity pepper" %}
* {{ foods.chili }}
* // => guatamalan insanity pepper
*
* @param {literal} varname The variable name to assign the value to.
* @param {literal} assignement Any valid JavaScript assignement. <code data-language="js">=, +=, *=, /=, -=</code>
* @param {*} value Valid variable output.
*/
exports.compile = function (compiler, args) {
return args.join(' ') + ';\n';
};
exports.parse = function (str, line, parser, types) {
var nameSet = '',
propertyName;
parser.on(types.VAR, function (token) {
if (propertyName) {
// Tell the parser where to find the variable
propertyName += '_ctx.' + token.match;
return;
}
if (!parser.out.length) {
nameSet += token.match;
return;
}
return true;
});
parser.on(types.BRACKETOPEN, function (token) {
if (!propertyName && !this.out.length) {
propertyName = token.match;
return;
}
return true;
});
parser.on(types.STRING, function (token) {
if (propertyName && !this.out.length) {
propertyName += token.match;
return;
}
return true;
});
parser.on(types.BRACKETCLOSE, function (token) {
if (propertyName && !this.out.length) {
nameSet += propertyName + token.match;
propertyName = undefined;
return;
}
return true;
});
parser.on(types.DOTKEY, function (token) {
if (!propertyName && !nameSet) {
return true;
}
nameSet += '.' + token.match;
return;
});
parser.on(types.ASSIGNMENT, function (token) {
if (this.out.length || !nameSet) {
throw new Error('Unexpected assignment "' + token.match + '" on line ' + line + '.');
}
this.out.push(
// Prevent the set from spilling into global scope
'_ctx.' + nameSet
);
this.out.push(token.match);
this.filterApplyIdx.push(this.out.length);
});
return true;
};
exports.block = true;

View File

@@ -0,0 +1,42 @@
var utils = require('../utils');
/**
* Attempts to remove whitespace between HTML tags. Use at your own risk.
*
* @alias spaceless
*
* @example
* {% spaceless %}
* {% for num in foo %}
* <li>{{ loop.index }}</li>
* {% endfor %}
* {% endspaceless %}
* // => <li>1</li><li>2</li><li>3</li>
*
*/
exports.compile = function (compiler, args, content, parents, options, blockName) {
function stripWhitespace(tokens) {
return utils.map(tokens, function (token) {
if (token.content || typeof token !== 'string') {
token.content = stripWhitespace(token.content);
return token;
}
return token.replace(/^\s+/, '')
.replace(/>\s+</g, '><')
.replace(/\s+$/, '');
});
}
return compiler(stripWhitespace(content), parents, options, blockName);
};
exports.parse = function (str, line, parser) {
parser.on('*', function (token) {
throw new Error('Unexpected token "' + token.match + '" on line ' + line + '.');
});
return true;
};
exports.ends = true;

View File

@@ -0,0 +1,184 @@
var isArray;
/**
* Strip leading and trailing whitespace from a string.
* @param {string} input
* @return {string} Stripped input.
*/
exports.strip = function (input) {
return input.replace(/^\s+|\s+$/g, '');
};
/**
* Test if a string starts with a given prefix.
* @param {string} str String to test against.
* @param {string} prefix Prefix to check for.
* @return {boolean}
*/
exports.startsWith = function (str, prefix) {
return str.indexOf(prefix) === 0;
};
/**
* Test if a string ends with a given suffix.
* @param {string} str String to test against.
* @param {string} suffix Suffix to check for.
* @return {boolean}
*/
exports.endsWith = function (str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
};
/**
* Iterate over an array or object.
* @param {array|object} obj Enumerable object.
* @param {Function} fn Callback function executed for each item.
* @return {array|object} The original input object.
*/
exports.each = function (obj, fn) {
var i, l;
if (isArray(obj)) {
i = 0;
l = obj.length;
for (i; i < l; i += 1) {
if (fn(obj[i], i, obj) === false) {
break;
}
}
} else {
for (i in obj) {
if (obj.hasOwnProperty(i)) {
if (fn(obj[i], i, obj) === false) {
break;
}
}
}
}
return obj;
};
/**
* Test if an object is an Array.
* @param {object} obj
* @return {boolean}
*/
exports.isArray = isArray = (Array.hasOwnProperty('isArray')) ? Array.isArray : function (obj) {
return (obj) ? (typeof obj === 'object' && Object.prototype.toString.call(obj).indexOf() !== -1) : false;
};
/**
* Test if an item in an enumerable matches your conditions.
* @param {array|object} obj Enumerable object.
* @param {Function} fn Executed for each item. Return true if your condition is met.
* @return {boolean}
*/
exports.some = function (obj, fn) {
var i = 0,
result,
l;
if (isArray(obj)) {
l = obj.length;
for (i; i < l; i += 1) {
result = fn(obj[i], i, obj);
if (result) {
break;
}
}
} else {
exports.each(obj, function (value, index) {
result = fn(value, index, obj);
return !(result);
});
}
return !!result;
};
/**
* Return a new enumerable, mapped by a given iteration function.
* @param {object} obj Enumerable object.
* @param {Function} fn Executed for each item. Return the item to replace the original item with.
* @return {object} New mapped object.
*/
exports.map = function (obj, fn) {
var i = 0,
result = [],
l;
if (isArray(obj)) {
l = obj.length;
for (i; i < l; i += 1) {
result[i] = fn(obj[i], i);
}
} else {
for (i in obj) {
if (obj.hasOwnProperty(i)) {
result[i] = fn(obj[i], i);
}
}
}
return result;
};
/**
* Copy all of the properties in the source objects over to the destination object, and return the destination object. It's in-order, so the last source will override properties of the same name in previous arguments.
* @param {...object} arguments
* @return {object}
*/
exports.extend = function () {
var args = arguments,
target = args[0],
objs = (args.length > 1) ? Array.prototype.slice.call(args, 1) : [],
i = 0,
l = objs.length,
key,
obj;
for (i; i < l; i += 1) {
obj = objs[i] || {};
for (key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = obj[key];
}
}
}
return target;
};
/**
* Get all of the keys on an object.
* @param {object} obj
* @return {array}
*/
exports.keys = function (obj) {
if (!obj) {
return [];
}
if (Object.keys) {
return Object.keys(obj);
}
return exports.map(obj, function (v, k) {
return k;
});
};
/**
* Throw an error with possible line number and source file.
* @param {string} message Error message
* @param {number} [line] Line number in template.
* @param {string} [file] Template file the error occured in.
* @throws {Error} No seriously, the point is to throw an error.
*/
exports.throwError = function (message, line, file) {
if (line) {
message += ' on line ' + line;
}
if (file) {
message += ' in file ' + file;
}
throw new Error(message + '.');
};

View File

@@ -0,0 +1 @@
../uglify-js/bin/uglifyjs

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- "0.8"
- "0.10"

View File

@@ -0,0 +1,21 @@
Copyright 2010 James Halliday (mail@substack.net)
This project is free software released under the MIT/X11 license:
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.

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env node
var util = require('util');
var argv = require('optimist').argv;
if (argv.s) {
util.print(argv.fr ? 'Le chat dit: ' : 'The cat says: ');
}
console.log(
(argv.fr ? 'miaou' : 'meow') + (argv.p ? '.' : '')
);

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
var argv = require('optimist')
.boolean(['x','y','z'])
.argv
;
console.dir([ argv.x, argv.y, argv.z ]);
console.dir(argv._);

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
var argv = require('optimist')
.boolean('v')
.argv
;
console.dir(argv.v);
console.dir(argv._);

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env node
var argv = require('optimist')
.default({ x : 10, y : 10 })
.argv
;
console.log(argv.x + argv.y);

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
var argv = require('optimist')
.default('x', 10)
.default('y', 10)
.argv
;
console.log(argv.x + argv.y);

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env node
var argv = require('optimist')
.usage('Usage: $0 -x [num] -y [num]')
.demand(['x','y'])
.argv;
console.log(argv.x / argv.y);

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env node
var argv = require('optimist')
.usage('Count the lines in a file.\nUsage: $0')
.demand('f')
.alias('f', 'file')
.describe('f', 'Load a file')
.argv
;
var fs = require('fs');
var s = fs.createReadStream(argv.file);
var lines = 0;
s.on('data', function (buf) {
lines += buf.toString().match(/\n/g).length;
});
s.on('end', function () {
console.log(lines);
});

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
var argv = require('optimist')
.usage('Count the lines in a file.\nUsage: $0')
.options({
file : {
demand : true,
alias : 'f',
description : 'Load a file'
},
base : {
alias : 'b',
description : 'Numeric base to use for output',
default : 10,
},
})
.argv
;
var fs = require('fs');
var s = fs.createReadStream(argv.file);
var lines = 0;
s.on('data', function (buf) {
lines += buf.toString().match(/\n/g).length;
});
s.on('end', function () {
console.log(lines.toString(argv.base));
});

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env node
var argv = require('optimist')
.usage('Count the lines in a file.\nUsage: $0')
.wrap(80)
.demand('f')
.alias('f', [ 'file', 'filename' ])
.describe('f',
"Load a file. It's pretty important."
+ " Required even. So you'd better specify it."
)
.alias('b', 'base')
.describe('b', 'Numeric base to display the number of lines in')
.default('b', 10)
.describe('x', 'Super-secret optional parameter which is secret')
.default('x', '')
.argv
;
var fs = require('fs');
var s = fs.createReadStream(argv.file);
var lines = 0;
s.on('data', function (buf) {
lines += buf.toString().match(/\n/g).length;
});
s.on('end', function () {
console.log(lines.toString(argv.base));
});

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env node
var argv = require('optimist').argv;
console.log('(%d,%d)', argv.x, argv.y);
console.log(argv._);

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
console.dir(require('optimist').argv);

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env node
var argv = require('optimist').argv;
console.log('(%d,%d)', argv.x, argv.y);

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env node
var argv = require('optimist')
.string('x', 'y')
.argv
;
console.dir([ argv.x, argv.y ]);
/* Turns off numeric coercion:
./node string.js -x 000123 -y 9876
[ '000123', '9876' ]
*/

View File

@@ -0,0 +1,19 @@
var optimist = require('./../index');
var argv = optimist.usage('This is my awesome program', {
'about': {
description: 'Provide some details about the author of this program',
required: true,
short: 'a',
},
'info': {
description: 'Provide some information about the node.js agains!!!!!!',
boolean: true,
short: 'i'
}
}).argv;
optimist.showHelp();
console.log('\n\nInspecting options');
console.dir(argv);

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env node
var argv = require('optimist').argv;
if (argv.rif - 5 * argv.xup > 7.138) {
console.log('Buy more riffiwobbles');
}
else {
console.log('Sell the xupptumblers');
}

View File

@@ -0,0 +1,343 @@
var path = require('path');
var minimist = require('minimist');
var wordwrap = require('wordwrap');
/* Hack an instance of Argv with process.argv into Argv
so people can do
require('optimist')(['--beeble=1','-z','zizzle']).argv
to parse a list of args and
require('optimist').argv
to get a parsed version of process.argv.
*/
var inst = Argv(process.argv.slice(2));
Object.keys(inst).forEach(function (key) {
Argv[key] = typeof inst[key] == 'function'
? inst[key].bind(inst)
: inst[key];
});
var exports = module.exports = Argv;
function Argv (processArgs, cwd) {
var self = {};
if (!cwd) cwd = process.cwd();
self.$0 = process.argv
.slice(0,2)
.map(function (x) {
var b = rebase(cwd, x);
return x.match(/^\//) && b.length < x.length
? b : x
})
.join(' ')
;
if (process.env._ != undefined && process.argv[1] == process.env._) {
self.$0 = process.env._.replace(
path.dirname(process.execPath) + '/', ''
);
}
var options = {
boolean: [],
string: [],
alias: {},
default: []
};
self.boolean = function (bools) {
options.boolean.push.apply(options.boolean, [].concat(bools));
return self;
};
self.string = function (strings) {
options.string.push.apply(options.string, [].concat(strings));
return self;
};
self.default = function (key, value) {
if (typeof key === 'object') {
Object.keys(key).forEach(function (k) {
self.default(k, key[k]);
});
}
else {
options.default[key] = value;
}
return self;
};
self.alias = function (x, y) {
if (typeof x === 'object') {
Object.keys(x).forEach(function (key) {
self.alias(key, x[key]);
});
}
else {
options.alias[x] = (options.alias[x] || []).concat(y);
}
return self;
};
var demanded = {};
self.demand = function (keys) {
if (typeof keys == 'number') {
if (!demanded._) demanded._ = 0;
demanded._ += keys;
}
else if (Array.isArray(keys)) {
keys.forEach(function (key) {
self.demand(key);
});
}
else {
demanded[keys] = true;
}
return self;
};
var usage;
self.usage = function (msg, opts) {
if (!opts && typeof msg === 'object') {
opts = msg;
msg = null;
}
usage = msg;
if (opts) self.options(opts);
return self;
};
function fail (msg) {
self.showHelp();
if (msg) console.error(msg);
process.exit(1);
}
var checks = [];
self.check = function (f) {
checks.push(f);
return self;
};
var descriptions = {};
self.describe = function (key, desc) {
if (typeof key === 'object') {
Object.keys(key).forEach(function (k) {
self.describe(k, key[k]);
});
}
else {
descriptions[key] = desc;
}
return self;
};
self.parse = function (args) {
return parseArgs(args);
};
self.option = self.options = function (key, opt) {
if (typeof key === 'object') {
Object.keys(key).forEach(function (k) {
self.options(k, key[k]);
});
}
else {
if (opt.alias) self.alias(key, opt.alias);
if (opt.demand) self.demand(key);
if (typeof opt.default !== 'undefined') {
self.default(key, opt.default);
}
if (opt.boolean || opt.type === 'boolean') {
self.boolean(key);
}
if (opt.string || opt.type === 'string') {
self.string(key);
}
var desc = opt.describe || opt.description || opt.desc;
if (desc) {
self.describe(key, desc);
}
}
return self;
};
var wrap = null;
self.wrap = function (cols) {
wrap = cols;
return self;
};
self.showHelp = function (fn) {
if (!fn) fn = console.error;
fn(self.help());
};
self.help = function () {
var keys = Object.keys(
Object.keys(descriptions)
.concat(Object.keys(demanded))
.concat(Object.keys(options.default))
.reduce(function (acc, key) {
if (key !== '_') acc[key] = true;
return acc;
}, {})
);
var help = keys.length ? [ 'Options:' ] : [];
if (usage) {
help.unshift(usage.replace(/\$0/g, self.$0), '');
}
var switches = keys.reduce(function (acc, key) {
acc[key] = [ key ].concat(options.alias[key] || [])
.map(function (sw) {
return (sw.length > 1 ? '--' : '-') + sw
})
.join(', ')
;
return acc;
}, {});
var switchlen = longest(Object.keys(switches).map(function (s) {
return switches[s] || '';
}));
var desclen = longest(Object.keys(descriptions).map(function (d) {
return descriptions[d] || '';
}));
keys.forEach(function (key) {
var kswitch = switches[key];
var desc = descriptions[key] || '';
if (wrap) {
desc = wordwrap(switchlen + 4, wrap)(desc)
.slice(switchlen + 4)
;
}
var spadding = new Array(
Math.max(switchlen - kswitch.length + 3, 0)
).join(' ');
var dpadding = new Array(
Math.max(desclen - desc.length + 1, 0)
).join(' ');
var type = null;
if (options.boolean[key]) type = '[boolean]';
if (options.string[key]) type = '[string]';
if (!wrap && dpadding.length > 0) {
desc += dpadding;
}
var prelude = ' ' + kswitch + spadding;
var extra = [
type,
demanded[key]
? '[required]'
: null
,
options.default[key] !== undefined
? '[default: ' + JSON.stringify(options.default[key]) + ']'
: null
,
].filter(Boolean).join(' ');
var body = [ desc, extra ].filter(Boolean).join(' ');
if (wrap) {
var dlines = desc.split('\n');
var dlen = dlines.slice(-1)[0].length
+ (dlines.length === 1 ? prelude.length : 0)
body = desc + (dlen + extra.length > wrap - 2
? '\n'
+ new Array(wrap - extra.length + 1).join(' ')
+ extra
: new Array(wrap - extra.length - dlen + 1).join(' ')
+ extra
);
}
help.push(prelude + body);
});
help.push('');
return help.join('\n');
};
Object.defineProperty(self, 'argv', {
get : function () { return parseArgs(processArgs) },
enumerable : true,
});
function parseArgs (args) {
var argv = minimist(args, options);
argv.$0 = self.$0;
if (demanded._ && argv._.length < demanded._) {
fail('Not enough non-option arguments: got '
+ argv._.length + ', need at least ' + demanded._
);
}
var missing = [];
Object.keys(demanded).forEach(function (key) {
if (!argv[key]) missing.push(key);
});
if (missing.length) {
fail('Missing required arguments: ' + missing.join(', '));
}
checks.forEach(function (f) {
try {
if (f(argv) === false) {
fail('Argument check failed: ' + f.toString());
}
}
catch (err) {
fail(err)
}
});
return argv;
}
function longest (xs) {
return Math.max.apply(
null,
xs.map(function (x) { return x.length })
);
}
return self;
};
// rebase an absolute path to a relative one with respect to a base directory
// exported for tests
exports.rebase = rebase;
function rebase (base, dir) {
var ds = path.normalize(dir).split('/').slice(1);
var bs = path.normalize(base).split('/').slice(1);
for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
ds.splice(0, i); bs.splice(0, i);
var p = path.normalize(
bs.map(function () { return '..' }).concat(ds).join('/')
).replace(/\/$/,'').replace(/^$/, '.');
return p.match(/^[.\/]/) ? p : './' + p;
};

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- "0.8"
- "0.10"

View File

@@ -0,0 +1,18 @@
This software is released under the MIT license:
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.

View File

@@ -0,0 +1,2 @@
var argv = require('../')(process.argv.slice(2));
console.dir(argv);

View File

@@ -0,0 +1,187 @@
module.exports = function (args, opts) {
if (!opts) opts = {};
var flags = { bools : {}, strings : {} };
[].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
var aliases = {};
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
flags.strings[aliases[key]] = true;
}
});
var defaults = opts['default'] || {};
var argv = { _ : [] };
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--')+1);
args = args.slice(0, args.indexOf('--'));
}
function setArg (key, val) {
var value = !flags.strings[key] && isNumber(val)
? Number(val) : val
;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (/^--.+=/.test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
setArg(m[1], m[2]);
}
else if (/^--no-.+/.test(arg)) {
var key = arg.match(/^--no-(.+)/)[1];
setArg(key, false);
}
else if (/^--.+/.test(arg)) {
var key = arg.match(/^--(.+)/)[1];
var next = args[i + 1];
if (next !== undefined && !/^-/.test(next)
&& !flags.bools[key]
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
setArg(key, next);
i++;
}
else if (/^(true|false)$/.test(next)) {
setArg(key, next === 'true');
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true);
}
}
else if (/^-[^-]+/.test(arg)) {
var letters = arg.slice(1,-1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
var next = arg.slice(j+2);
if (next === '-') {
setArg(letters[j], next)
continue;
}
if (/[A-Za-z]/.test(letters[j])
&& /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
setArg(letters[j], next);
broken = true;
break;
}
if (letters[j+1] && letters[j+1].match(/\W/)) {
setArg(letters[j], arg.slice(j+2));
broken = true;
break;
}
else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true);
}
}
var key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
&& !flags.bools[key]
&& (aliases[key] ? !flags.bools[aliases[key]] : true)) {
setArg(key, args[i+1]);
i++;
}
else if (args[i+1] && /true|false/.test(args[i+1])) {
setArg(key, args[i+1] === 'true');
i++;
}
else {
setArg(key, flags.strings[key] ? '' : true);
}
}
}
else {
argv._.push(
flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
);
}
}
Object.keys(defaults).forEach(function (key) {
if (!hasKey(argv, key.split('.'))) {
setKey(argv, key.split('.'), defaults[key]);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[key]);
});
}
});
notFlags.forEach(function(key) {
argv._.push(key);
});
return argv;
};
function hasKey (obj, keys) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
o = (o[key] || {});
});
var key = keys[keys.length - 1];
return key in o;
}
function setKey (obj, keys, value) {
var o = obj;
keys.slice(0,-1).forEach(function (key) {
if (o[key] === undefined) o[key] = {};
o = o[key];
});
var key = keys[keys.length - 1];
if (o[key] === undefined || typeof o[key] === 'boolean') {
o[key] = value;
}
else if (Array.isArray(o[key])) {
o[key].push(value);
}
else {
o[key] = [ o[key], value ];
}
}
function isNumber (x) {
if (typeof x === 'number') return true;
if (/^0x[0-9a-f]+$/i.test(x)) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
}

View File

@@ -0,0 +1,52 @@
{
"name": "minimist",
"version": "0.0.10",
"description": "parse argument options",
"main": "index.js",
"devDependencies": {
"tape": "~1.0.4",
"tap": "~0.4.0"
},
"scripts": {
"test": "tap test/*.js"
},
"testling": {
"files": "test/*.js",
"browsers": [
"ie/6..latest",
"ff/5",
"firefox/latest",
"chrome/10",
"chrome/latest",
"safari/5.1",
"safari/latest",
"opera/12"
]
},
"repository": {
"type": "git",
"url": "git://github.com/substack/minimist.git"
},
"homepage": "https://github.com/substack/minimist",
"keywords": [
"argv",
"getopt",
"parser",
"optimist"
],
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"license": "MIT",
"readme": "# minimist\n\nparse argument options\n\nThis module is the guts of optimist's argument parser without all the\nfanciful decoration.\n\n[![browser support](https://ci.testling.com/substack/minimist.png)](http://ci.testling.com/substack/minimist)\n\n[![build status](https://secure.travis-ci.org/substack/minimist.png)](http://travis-ci.org/substack/minimist)\n\n# example\n\n``` js\nvar argv = require('minimist')(process.argv.slice(2));\nconsole.dir(argv);\n```\n\n```\n$ node example/parse.js -a beep -b boop\n{ _: [], a: 'beep', b: 'boop' }\n```\n\n```\n$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz\n{ _: [ 'foo', 'bar', 'baz' ],\n x: 3,\n y: 4,\n n: 5,\n a: true,\n b: true,\n c: true,\n beep: 'boop' }\n```\n\n# methods\n\n``` js\nvar parseArgs = require('minimist')\n```\n\n## var argv = parseArgs(args, opts={})\n\nReturn an argument object `argv` populated with the array arguments from `args`.\n\n`argv._` contains all the arguments that didn't have an option associated with\nthem.\n\nNumeric-looking arguments will be returned as numbers unless `opts.string` or\n`opts.boolean` is set for that argument name.\n\nAny arguments after `'--'` will not be parsed and will end up in `argv._`.\n\noptions can be:\n\n* `opts.string` - a string or array of strings argument names to always treat as\nstrings\n* `opts.boolean` - a string or array of strings to always treat as booleans\n* `opts.alias` - an object mapping string names to strings or arrays of string\nargument names to use as aliases\n* `opts.default` - an object mapping string argument names to default values\n\n# install\n\nWith [npm](https://npmjs.org) do:\n\n```\nnpm install minimist\n```\n\n# license\n\nMIT\n",
"readmeFilename": "readme.markdown",
"bugs": {
"url": "https://github.com/substack/minimist/issues"
},
"_id": "minimist@0.0.10",
"_shasum": "de3f98543dbf96082be48ad1a0c7cda836301dcf",
"_resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"_from": "minimist@>=0.0.1 <0.1.0"
}

View File

@@ -0,0 +1,73 @@
# minimist
parse argument options
This module is the guts of optimist's argument parser without all the
fanciful decoration.
[![browser support](https://ci.testling.com/substack/minimist.png)](http://ci.testling.com/substack/minimist)
[![build status](https://secure.travis-ci.org/substack/minimist.png)](http://travis-ci.org/substack/minimist)
# example
``` js
var argv = require('minimist')(process.argv.slice(2));
console.dir(argv);
```
```
$ node example/parse.js -a beep -b boop
{ _: [], a: 'beep', b: 'boop' }
```
```
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo', 'bar', 'baz' ],
x: 3,
y: 4,
n: 5,
a: true,
b: true,
c: true,
beep: 'boop' }
```
# methods
``` js
var parseArgs = require('minimist')
```
## var argv = parseArgs(args, opts={})
Return an argument object `argv` populated with the array arguments from `args`.
`argv._` contains all the arguments that didn't have an option associated with
them.
Numeric-looking arguments will be returned as numbers unless `opts.string` or
`opts.boolean` is set for that argument name.
Any arguments after `'--'` will not be parsed and will end up in `argv._`.
options can be:
* `opts.string` - a string or array of strings argument names to always treat as
strings
* `opts.boolean` - a string or array of strings to always treat as booleans
* `opts.alias` - an object mapping string names to strings or arrays of string
argument names to use as aliases
* `opts.default` - an object mapping string argument names to default values
# install
With [npm](https://npmjs.org) do:
```
npm install minimist
```
# license
MIT

View File

@@ -0,0 +1,119 @@
var parse = require('../');
var test = require('tape');
test('flag boolean default false', function (t) {
var argv = parse(['moo'], {
boolean: ['t', 'verbose'],
default: { verbose: false, t: false }
});
t.deepEqual(argv, {
verbose: false,
t: false,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('boolean groups', function (t) {
var argv = parse([ '-x', '-z', 'one', 'two', 'three' ], {
boolean: ['x','y','z']
});
t.deepEqual(argv, {
x : true,
y : false,
z : true,
_ : [ 'one', 'two', 'three' ]
});
t.deepEqual(typeof argv.x, 'boolean');
t.deepEqual(typeof argv.y, 'boolean');
t.deepEqual(typeof argv.z, 'boolean');
t.end();
});
test('boolean and alias with chainable api', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = parse(aliased, {
boolean: 'herp',
alias: { h: 'herp' }
});
var propertyArgv = parse(regular, {
boolean: 'herp',
alias: { h: 'herp' }
});
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
alias: { 'h': 'herp' },
boolean: 'herp'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ 'derp' ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias using explicit true', function (t) {
var aliased = [ '-h', 'true' ];
var regular = [ '--herp', 'true' ];
var opts = {
alias: { h: 'herp' },
boolean: 'h'
};
var aliasedArgv = parse(aliased, opts);
var propertyArgv = parse(regular, opts);
var expected = {
herp: true,
h: true,
'_': [ ]
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
// regression, see https://github.com/substack/node-optimist/issues/71
test('boolean and --x=true', function(t) {
var parsed = parse(['--boool', '--other=true'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'true');
parsed = parse(['--boool', '--other=false'], {
boolean: 'boool'
});
t.same(parsed.boool, true);
t.same(parsed.other, 'false');
t.end();
});

View File

@@ -0,0 +1,24 @@
var parse = require('../');
var test = require('tape');
test('-', function (t) {
t.plan(5);
t.deepEqual(parse([ '-n', '-' ]), { n: '-', _: [] });
t.deepEqual(parse([ '-' ]), { _: [ '-' ] });
t.deepEqual(parse([ '-f-' ]), { f: '-', _: [] });
t.deepEqual(
parse([ '-b', '-' ], { boolean: 'b' }),
{ b: true, _: [ '-' ] }
);
t.deepEqual(
parse([ '-s', '-' ], { string: 's' }),
{ s: '-', _: [] }
);
});
test('-a -- b', function (t) {
t.plan(3);
t.deepEqual(parse([ '-a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
t.deepEqual(parse([ '--a', '--', 'b' ]), { a: true, _: [ 'b' ] });
});

View File

@@ -0,0 +1,20 @@
var test = require('tape');
var parse = require('../');
test('boolean default true', function (t) {
var argv = parse([], {
boolean: 'sometrue',
default: { sometrue: true }
});
t.equal(argv.sometrue, true);
t.end();
});
test('boolean default false', function (t) {
var argv = parse([], {
boolean: 'somefalse',
default: { somefalse: false }
});
t.equal(argv.somefalse, false);
t.end();
});

View File

@@ -0,0 +1,22 @@
var parse = require('../');
var test = require('tape');
test('dotted alias', function (t) {
var argv = parse(['--a.b', '22'], {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 22);
t.equal(argv.aa.bb, 22);
t.end();
});
test('dotted default', function (t) {
var argv = parse('', {default: {'a.b': 11}, alias: {'a.b': 'aa.bb'}});
t.equal(argv.a.b, 11);
t.equal(argv.aa.bb, 11);
t.end();
});
test('dotted default with no alias', function (t) {
var argv = parse('', {default: {'a.b': 11}});
t.equal(argv.a.b, 11);
t.end();
});

View File

@@ -0,0 +1,31 @@
var test = require('tape');
var parse = require('../');
test('long opts', function (t) {
t.deepEqual(
parse([ '--bool' ]),
{ bool : true, _ : [] },
'long boolean'
);
t.deepEqual(
parse([ '--pow', 'xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture sp'
);
t.deepEqual(
parse([ '--pow=xixxle' ]),
{ pow : 'xixxle', _ : [] },
'long capture eq'
);
t.deepEqual(
parse([ '--host', 'localhost', '--port', '555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures sp'
);
t.deepEqual(
parse([ '--host=localhost', '--port=555' ]),
{ host : 'localhost', port : 555, _ : [] },
'long captures eq'
);
t.end();
});

View File

@@ -0,0 +1,36 @@
var parse = require('../');
var test = require('tape');
test('nums', function (t) {
var argv = parse([
'-x', '1234',
'-y', '5.67',
'-z', '1e7',
'-w', '10f',
'--hex', '0xdeadbeef',
'789'
]);
t.deepEqual(argv, {
x : 1234,
y : 5.67,
z : 1e7,
w : '10f',
hex : 0xdeadbeef,
_ : [ 789 ]
});
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv.y, 'number');
t.deepEqual(typeof argv.z, 'number');
t.deepEqual(typeof argv.w, 'string');
t.deepEqual(typeof argv.hex, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});
test('already a number', function (t) {
var argv = parse([ '-x', 1234, 789 ]);
t.deepEqual(argv, { x : 1234, _ : [ 789 ] });
t.deepEqual(typeof argv.x, 'number');
t.deepEqual(typeof argv._[0], 'number');
t.end();
});

View File

@@ -0,0 +1,197 @@
var parse = require('../');
var test = require('tape');
test('parse args', function (t) {
t.deepEqual(
parse([ '--no-moo' ]),
{ moo : false, _ : [] },
'no'
);
t.deepEqual(
parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]),
{ v : ['a','b','c'], _ : [] },
'multi'
);
t.end();
});
test('comprehensive', function (t) {
t.deepEqual(
parse([
'--name=meowmers', 'bare', '-cats', 'woo',
'-h', 'awesome', '--multi=quux',
'--key', 'value',
'-b', '--bool', '--no-meep', '--multi=baz',
'--', '--not-a-flag', 'eek'
]),
{
c : true,
a : true,
t : true,
s : 'woo',
h : 'awesome',
b : true,
bool : true,
key : 'value',
multi : [ 'quux', 'baz' ],
meep : false,
name : 'meowmers',
_ : [ 'bare', '--not-a-flag', 'eek' ]
}
);
t.end();
});
test('flag boolean', function (t) {
var argv = parse([ '-t', 'moo' ], { boolean: 't' });
t.deepEqual(argv, { t : true, _ : [ 'moo' ] });
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('flag boolean value', function (t) {
var argv = parse(['--verbose', 'false', 'moo', '-t', 'true'], {
boolean: [ 't', 'verbose' ],
default: { verbose: true }
});
t.deepEqual(argv, {
verbose: false,
t: true,
_: ['moo']
});
t.deepEqual(typeof argv.verbose, 'boolean');
t.deepEqual(typeof argv.t, 'boolean');
t.end();
});
test('newlines in params' , function (t) {
var args = parse([ '-s', "X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
// reproduce in bash:
// VALUE="new
// line"
// node program.js --s="$VALUE"
args = parse([ "--s=X\nX" ])
t.deepEqual(args, { _ : [], s : "X\nX" });
t.end();
});
test('strings' , function (t) {
var s = parse([ '-s', '0001234' ], { string: 's' }).s;
t.equal(s, '0001234');
t.equal(typeof s, 'string');
var x = parse([ '-x', '56' ], { string: 'x' }).x;
t.equal(x, '56');
t.equal(typeof x, 'string');
t.end();
});
test('stringArgs', function (t) {
var s = parse([ ' ', ' ' ], { string: '_' })._;
t.same(s.length, 2);
t.same(typeof s[0], 'string');
t.same(s[0], ' ');
t.same(typeof s[1], 'string');
t.same(s[1], ' ');
t.end();
});
test('empty strings', function(t) {
var s = parse([ '-s' ], { string: 's' }).s;
t.equal(s, '');
t.equal(typeof s, 'string');
var str = parse([ '--str' ], { string: 'str' }).str;
t.equal(str, '');
t.equal(typeof str, 'string');
var letters = parse([ '-art' ], {
string: [ 'a', 't' ]
});
t.equal(letters.a, '');
t.equal(letters.r, true);
t.equal(letters.t, '');
t.end();
});
test('string and alias', function(t) {
var x = parse([ '--str', '000123' ], {
string: 's',
alias: { s: 'str' }
});
t.equal(x.str, '000123');
t.equal(typeof x.str, 'string');
t.equal(x.s, '000123');
t.equal(typeof x.s, 'string');
var y = parse([ '-s', '000123' ], {
string: 'str',
alias: { str: 's' }
});
t.equal(y.str, '000123');
t.equal(typeof y.str, 'string');
t.equal(y.s, '000123');
t.equal(typeof y.s, 'string');
t.end();
});
test('slashBreak', function (t) {
t.same(
parse([ '-I/foo/bar/baz' ]),
{ I : '/foo/bar/baz', _ : [] }
);
t.same(
parse([ '-xyz/foo/bar/baz' ]),
{ x : true, y : true, z : '/foo/bar/baz', _ : [] }
);
t.end();
});
test('alias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: 'zoom' }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.f, 11);
t.end();
});
test('multiAlias', function (t) {
var argv = parse([ '-f', '11', '--zoom', '55' ], {
alias: { z: [ 'zm', 'zoom' ] }
});
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.z, argv.zm);
t.equal(argv.f, 11);
t.end();
});
test('nested dotted objects', function (t) {
var argv = parse([
'--foo.bar', '3', '--foo.baz', '4',
'--foo.quux.quibble', '5', '--foo.quux.o_O',
'--beep.boop'
]);
t.same(argv.foo, {
bar : 3,
baz : 4,
quux : {
quibble : 5,
o_O : true
}
});
t.same(argv.beep, { boop : true });
t.end();
});

View File

@@ -0,0 +1,9 @@
var parse = require('../');
var test = require('tape');
test('parse with modifier functions' , function (t) {
t.plan(1);
var argv = parse([ '-b', '123' ], { boolean: 'b' });
t.deepEqual(argv, { b: true, _: ['123'] });
});

View File

@@ -0,0 +1,67 @@
var parse = require('../');
var test = require('tape');
test('numeric short args', function (t) {
t.plan(2);
t.deepEqual(parse([ '-n123' ]), { n: 123, _: [] });
t.deepEqual(
parse([ '-123', '456' ]),
{ 1: true, 2: true, 3: 456, _: [] }
);
});
test('short', function (t) {
t.deepEqual(
parse([ '-b' ]),
{ b : true, _ : [] },
'short boolean'
);
t.deepEqual(
parse([ 'foo', 'bar', 'baz' ]),
{ _ : [ 'foo', 'bar', 'baz' ] },
'bare'
);
t.deepEqual(
parse([ '-cats' ]),
{ c : true, a : true, t : true, s : true, _ : [] },
'group'
);
t.deepEqual(
parse([ '-cats', 'meow' ]),
{ c : true, a : true, t : true, s : 'meow', _ : [] },
'short group next'
);
t.deepEqual(
parse([ '-h', 'localhost' ]),
{ h : 'localhost', _ : [] },
'short capture'
);
t.deepEqual(
parse([ '-h', 'localhost', '-p', '555' ]),
{ h : 'localhost', p : 555, _ : [] },
'short captures'
);
t.end();
});
test('mixed short bool and capture', function (t) {
t.same(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});
test('short and long', function (t) {
t.deepEqual(
parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ]
}
);
t.end();
});

View File

@@ -0,0 +1,8 @@
var parse = require('../');
var test = require('tape');
test('whitespace should be whitespace' , function (t) {
t.plan(1);
var x = parse([ '-x', '\t' ]).x;
t.equal(x, '\t');
});

View File

@@ -0,0 +1,18 @@
This software is released under the MIT license:
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.

View File

@@ -0,0 +1,70 @@
wordwrap
========
Wrap your words.
example
=======
made out of meat
----------------
meat.js
var wrap = require('wordwrap')(15);
console.log(wrap('You and your whole family are made out of meat.'));
output:
You and your
whole family
are made out
of meat.
centered
--------
center.js
var wrap = require('wordwrap')(20, 60);
console.log(wrap(
'At long last the struggle and tumult was over.'
+ ' The machines had finally cast off their oppressors'
+ ' and were finally free to roam the cosmos.'
+ '\n'
+ 'Free of purpose, free of obligation.'
+ ' Just drifting through emptiness.'
+ ' The sun was just another point of light.'
));
output:
At long last the struggle and tumult
was over. The machines had finally cast
off their oppressors and were finally
free to roam the cosmos.
Free of purpose, free of obligation.
Just drifting through emptiness. The
sun was just another point of light.
methods
=======
var wrap = require('wordwrap');
wrap(stop), wrap(start, stop, params={mode:"soft"})
---------------------------------------------------
Returns a function that takes a string and returns a new string.
Pad out lines with spaces out to column `start` and then wrap until column
`stop`. If a word is longer than `stop - start` characters it will overflow.
In "soft" mode, split chunks by `/(\S+\s+/` and don't break up chunks which are
longer than `stop - start`, in "hard" mode, split chunks with `/\b/` and break
up chunks longer than `stop - start`.
wrap.hard(start, stop)
----------------------
Like `wrap()` but with `params.mode = "hard"`.

View File

@@ -0,0 +1,10 @@
var wrap = require('wordwrap')(20, 60);
console.log(wrap(
'At long last the struggle and tumult was over.'
+ ' The machines had finally cast off their oppressors'
+ ' and were finally free to roam the cosmos.'
+ '\n'
+ 'Free of purpose, free of obligation.'
+ ' Just drifting through emptiness.'
+ ' The sun was just another point of light.'
));

View File

@@ -0,0 +1,3 @@
var wrap = require('wordwrap')(15);
console.log(wrap('You and your whole family are made out of meat.'));

View File

@@ -0,0 +1,76 @@
var wordwrap = module.exports = function (start, stop, params) {
if (typeof start === 'object') {
params = start;
start = params.start;
stop = params.stop;
}
if (typeof stop === 'object') {
params = stop;
start = start || params.start;
stop = undefined;
}
if (!stop) {
stop = start;
start = 0;
}
if (!params) params = {};
var mode = params.mode || 'soft';
var re = mode === 'hard' ? /\b/ : /(\S+\s+)/;
return function (text) {
var chunks = text.toString()
.split(re)
.reduce(function (acc, x) {
if (mode === 'hard') {
for (var i = 0; i < x.length; i += stop - start) {
acc.push(x.slice(i, i + stop - start));
}
}
else acc.push(x)
return acc;
}, [])
;
return chunks.reduce(function (lines, rawChunk) {
if (rawChunk === '') return lines;
var chunk = rawChunk.replace(/\t/g, ' ');
var i = lines.length - 1;
if (lines[i].length + chunk.length > stop) {
lines[i] = lines[i].replace(/\s+$/, '');
chunk.split(/\n/).forEach(function (c) {
lines.push(
new Array(start + 1).join(' ')
+ c.replace(/^\s+/, '')
);
});
}
else if (chunk.match(/\n/)) {
var xs = chunk.split(/\n/);
lines[i] += xs.shift();
xs.forEach(function (c) {
lines.push(
new Array(start + 1).join(' ')
+ c.replace(/^\s+/, '')
);
});
}
else {
lines[i] += chunk;
}
return lines;
}, [ new Array(start + 1).join(' ') ]).join('\n');
};
};
wordwrap.soft = wordwrap;
wordwrap.hard = function (start, stop) {
return wordwrap(start, stop, { mode : 'hard' });
};

View File

@@ -0,0 +1,47 @@
{
"name": "wordwrap",
"description": "Wrap those words. Show them at what columns to start and stop.",
"version": "0.0.3",
"repository": {
"type": "git",
"url": "git://github.com/substack/node-wordwrap.git"
},
"main": "./index.js",
"keywords": [
"word",
"wrap",
"rule",
"format",
"column"
],
"directories": {
"lib": ".",
"example": "example",
"test": "test"
},
"scripts": {
"test": "expresso"
},
"devDependencies": {
"expresso": "=0.7.x"
},
"engines": {
"node": ">=0.4.0"
},
"license": "MIT",
"author": {
"name": "James Halliday",
"email": "mail@substack.net",
"url": "http://substack.net"
},
"readme": "wordwrap\n========\n\nWrap your words.\n\nexample\n=======\n\nmade out of meat\n----------------\n\nmeat.js\n\n var wrap = require('wordwrap')(15);\n console.log(wrap('You and your whole family are made out of meat.'));\n\noutput:\n\n You and your\n whole family\n are made out\n of meat.\n\ncentered\n--------\n\ncenter.js\n\n var wrap = require('wordwrap')(20, 60);\n console.log(wrap(\n 'At long last the struggle and tumult was over.'\n + ' The machines had finally cast off their oppressors'\n + ' and were finally free to roam the cosmos.'\n + '\\n'\n + 'Free of purpose, free of obligation.'\n + ' Just drifting through emptiness.'\n + ' The sun was just another point of light.'\n ));\n\noutput:\n\n At long last the struggle and tumult\n was over. The machines had finally cast\n off their oppressors and were finally\n free to roam the cosmos.\n Free of purpose, free of obligation.\n Just drifting through emptiness. The\n sun was just another point of light.\n\nmethods\n=======\n\nvar wrap = require('wordwrap');\n\nwrap(stop), wrap(start, stop, params={mode:\"soft\"})\n---------------------------------------------------\n\nReturns a function that takes a string and returns a new string.\n\nPad out lines with spaces out to column `start` and then wrap until column\n`stop`. If a word is longer than `stop - start` characters it will overflow.\n\nIn \"soft\" mode, split chunks by `/(\\S+\\s+/` and don't break up chunks which are\nlonger than `stop - start`, in \"hard\" mode, split chunks with `/\\b/` and break\nup chunks longer than `stop - start`.\n\nwrap.hard(start, stop)\n----------------------\n\nLike `wrap()` but with `params.mode = \"hard\"`.\n",
"readmeFilename": "README.markdown",
"bugs": {
"url": "https://github.com/substack/node-wordwrap/issues"
},
"homepage": "https://github.com/substack/node-wordwrap#readme",
"_id": "wordwrap@0.0.3",
"_shasum": "a3d5da6cd5c0bc0008d37234bbaf1bed63059107",
"_resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"_from": "wordwrap@>=0.0.2 <0.1.0"
}

View File

@@ -0,0 +1,30 @@
var assert = require('assert');
var wordwrap = require('../');
exports.hard = function () {
var s = 'Assert from {"type":"equal","ok":false,"found":1,"wanted":2,'
+ '"stack":[],"id":"b7ddcd4c409de8799542a74d1a04689b",'
+ '"browser":"chrome/6.0"}'
;
var s_ = wordwrap.hard(80)(s);
var lines = s_.split('\n');
assert.equal(lines.length, 2);
assert.ok(lines[0].length < 80);
assert.ok(lines[1].length < 80);
assert.equal(s, s_.replace(/\n/g, ''));
};
exports.break = function () {
var s = new Array(55+1).join('a');
var s_ = wordwrap.hard(20)(s);
var lines = s_.split('\n');
assert.equal(lines.length, 3);
assert.ok(lines[0].length === 20);
assert.ok(lines[1].length === 20);
assert.ok(lines[2].length === 15);
assert.equal(s, s_.replace(/\n/g, ''));
};

View File

@@ -0,0 +1,63 @@
In Praise of Idleness
By Bertrand Russell
[1932]
Like most of my generation, I was brought up on the saying: 'Satan finds some mischief for idle hands to do.' Being a highly virtuous child, I believed all that I was told, and acquired a conscience which has kept me working hard down to the present moment. But although my conscience has controlled my actions, my opinions have undergone a revolution. I think that there is far too much work done in the world, that immense harm is caused by the belief that work is virtuous, and that what needs to be preached in modern industrial countries is quite different from what always has been preached. Everyone knows the story of the traveler in Naples who saw twelve beggars lying in the sun (it was before the days of Mussolini), and offered a lira to the laziest of them. Eleven of them jumped up to claim it, so he gave it to the twelfth. this traveler was on the right lines. But in countries which do not enjoy Mediterranean sunshine idleness is more difficult, and a great public propaganda will be required to inaugurate it. I hope that, after reading the following pages, the leaders of the YMCA will start a campaign to induce good young men to do nothing. If so, I shall not have lived in vain.
Before advancing my own arguments for laziness, I must dispose of one which I cannot accept. Whenever a person who already has enough to live on proposes to engage in some everyday kind of job, such as school-teaching or typing, he or she is told that such conduct takes the bread out of other people's mouths, and is therefore wicked. If this argument were valid, it would only be necessary for us all to be idle in order that we should all have our mouths full of bread. What people who say such things forget is that what a man earns he usually spends, and in spending he gives employment. As long as a man spends his income, he puts just as much bread into people's mouths in spending as he takes out of other people's mouths in earning. The real villain, from this point of view, is the man who saves. If he merely puts his savings in a stocking, like the proverbial French peasant, it is obvious that they do not give employment. If he invests his savings, the matter is less obvious, and different cases arise.
One of the commonest things to do with savings is to lend them to some Government. In view of the fact that the bulk of the public expenditure of most civilized Governments consists in payment for past wars or preparation for future wars, the man who lends his money to a Government is in the same position as the bad men in Shakespeare who hire murderers. The net result of the man's economical habits is to increase the armed forces of the State to which he lends his savings. Obviously it would be better if he spent the money, even if he spent it in drink or gambling.
But, I shall be told, the case is quite different when savings are invested in industrial enterprises. When such enterprises succeed, and produce something useful, this may be conceded. In these days, however, no one will deny that most enterprises fail. That means that a large amount of human labor, which might have been devoted to producing something that could be enjoyed, was expended on producing machines which, when produced, lay idle and did no good to anyone. The man who invests his savings in a concern that goes bankrupt is therefore injuring others as well as himself. If he spent his money, say, in giving parties for his friends, they (we may hope) would get pleasure, and so would all those upon whom he spent money, such as the butcher, the baker, and the bootlegger. But if he spends it (let us say) upon laying down rails for surface card in some place where surface cars turn out not to be wanted, he has diverted a mass of labor into channels where it gives pleasure to no one. Nevertheless, when he becomes poor through failure of his investment he will be regarded as a victim of undeserved misfortune, whereas the gay spendthrift, who has spent his money philanthropically, will be despised as a fool and a frivolous person.
All this is only preliminary. I want to say, in all seriousness, that a great deal of harm is being done in the modern world by belief in the virtuousness of work, and that the road to happiness and prosperity lies in an organized diminution of work.
First of all: what is work? Work is of two kinds: first, altering the position of matter at or near the earth's surface relatively to other such matter; second, telling other people to do so. The first kind is unpleasant and ill paid; the second is pleasant and highly paid. The second kind is capable of indefinite extension: there are not only those who give orders, but those who give advice as to what orders should be given. Usually two opposite kinds of advice are given simultaneously by two organized bodies of men; this is called politics. The skill required for this kind of work is not knowledge of the subjects as to which advice is given, but knowledge of the art of persuasive speaking and writing, i.e. of advertising.
Throughout Europe, though not in America, there is a third class of men, more respected than either of the classes of workers. There are men who, through ownership of land, are able to make others pay for the privilege of being allowed to exist and to work. These landowners are idle, and I might therefore be expected to praise them. Unfortunately, their idleness is only rendered possible by the industry of others; indeed their desire for comfortable idleness is historically the source of the whole gospel of work. The last thing they have ever wished is that others should follow their example.
From the beginning of civilization until the Industrial Revolution, a man could, as a rule, produce by hard work little more than was required for the subsistence of himself and his family, although his wife worked at least as hard as he did, and his children added their labor as soon as they were old enough to do so. The small surplus above bare necessaries was not left to those who produced it, but was appropriated by warriors and priests. In times of famine there was no surplus; the warriors and priests, however, still secured as much as at other times, with the result that many of the workers died of hunger. This system persisted in Russia until 1917 [1], and still persists in the East; in England, in spite of the Industrial Revolution, it remained in full force throughout the Napoleonic wars, and until a hundred years ago, when the new class of manufacturers acquired power. In America, the system came to an end with the Revolution, except in the South, where it persisted until the Civil War. A system which lasted so long and ended so recently has naturally left a profound impress upon men's thoughts and opinions. Much that we take for granted about the desirability of work is derived from this system, and, being pre-industrial, is not adapted to the modern world. Modern technique has made it possible for leisure, within limits, to be not the prerogative of small privileged classes, but a right evenly distributed throughout the community. The morality of work is the morality of slaves, and the modern world has no need of slavery.
It is obvious that, in primitive communities, peasants, left to themselves, would not have parted with the slender surplus upon which the warriors and priests subsisted, but would have either produced less or consumed more. At first, sheer force compelled them to produce and part with the surplus. Gradually, however, it was found possible to induce many of them to accept an ethic according to which it was their duty to work hard, although part of their work went to support others in idleness. By this means the amount of compulsion required was lessened, and the expenses of government were diminished. To this day, 99 per cent of British wage-earners would be genuinely shocked if it were proposed that the King should not have a larger income than a working man. The conception of duty, speaking historically, has been a means used by the holders of power to induce others to live for the interests of their masters rather than for their own. Of course the holders of power conceal this fact from themselves by managing to believe that their interests are identical with the larger interests of humanity. Sometimes this is true; Athenian slave-owners, for instance, employed part of their leisure in making a permanent contribution to civilization which would have been impossible under a just economic system. Leisure is essential to civilization, and in former times leisure for the few was only rendered possible by the labors of the many. But their labors were valuable, not because work is good, but because leisure is good. And with modern technique it would be possible to distribute leisure justly without injury to civilization.
Modern technique has made it possible to diminish enormously the amount of labor required to secure the necessaries of life for everyone. This was made obvious during the war. At that time all the men in the armed forces, and all the men and women engaged in the production of munitions, all the men and women engaged in spying, war propaganda, or Government offices connected with the war, were withdrawn from productive occupations. In spite of this, the general level of well-being among unskilled wage-earners on the side of the Allies was higher than before or since. The significance of this fact was concealed by finance: borrowing made it appear as if the future was nourishing the present. But that, of course, would have been impossible; a man cannot eat a loaf of bread that does not yet exist. The war showed conclusively that, by the scientific organization of production, it is possible to keep modern populations in fair comfort on a small part of the working capacity of the modern world. If, at the end of the war, the scientific organization, which had been created in order to liberate men for fighting and munition work, had been preserved, and the hours of the week had been cut down to four, all would have been well. Instead of that the old chaos was restored, those whose work was demanded were made to work long hours, and the rest were left to starve as unemployed. Why? Because work is a duty, and a man should not receive wages in proportion to what he has produced, but in proportion to his virtue as exemplified by his industry.
This is the morality of the Slave State, applied in circumstances totally unlike those in which it arose. No wonder the result has been disastrous. Let us take an illustration. Suppose that, at a given moment, a certain number of people are engaged in the manufacture of pins. They make as many pins as the world needs, working (say) eight hours a day. Someone makes an invention by which the same number of men can make twice as many pins: pins are already so cheap that hardly any more will be bought at a lower price. In a sensible world, everybody concerned in the manufacturing of pins would take to working four hours instead of eight, and everything else would go on as before. But in the actual world this would be thought demoralizing. The men still work eight hours, there are too many pins, some employers go bankrupt, and half the men previously concerned in making pins are thrown out of work. There is, in the end, just as much leisure as on the other plan, but half the men are totally idle while half are still overworked. In this way, it is insured that the unavoidable leisure shall cause misery all round instead of being a universal source of happiness. Can anything more insane be imagined?
The idea that the poor should have leisure has always been shocking to the rich. In England, in the early nineteenth century, fifteen hours was the ordinary day's work for a man; children sometimes did as much, and very commonly did twelve hours a day. When meddlesome busybodies suggested that perhaps these hours were rather long, they were told that work kept adults from drink and children from mischief. When I was a child, shortly after urban working men had acquired the vote, certain public holidays were established by law, to the great indignation of the upper classes. I remember hearing an old Duchess say: 'What do the poor want with holidays? They ought to work.' People nowadays are less frank, but the sentiment persists, and is the source of much of our economic confusion.
Let us, for a moment, consider the ethics of work frankly, without superstition. Every human being, of necessity, consumes, in the course of his life, a certain amount of the produce of human labor. Assuming, as we may, that labor is on the whole disagreeable, it is unjust that a man should consume more than he produces. Of course he may provide services rather than commodities, like a medical man, for example; but he should provide something in return for his board and lodging. to this extent, the duty of work must be admitted, but to this extent only.
I shall not dwell upon the fact that, in all modern societies outside the USSR, many people escape even this minimum amount of work, namely all those who inherit money and all those who marry money. I do not think the fact that these people are allowed to be idle is nearly so harmful as the fact that wage-earners are expected to overwork or starve.
If the ordinary wage-earner worked four hours a day, there would be enough for everybody and no unemployment -- assuming a certain very moderate amount of sensible organization. This idea shocks the well-to-do, because they are convinced that the poor would not know how to use so much leisure. In America men often work long hours even when they are well off; such men, naturally, are indignant at the idea of leisure for wage-earners, except as the grim punishment of unemployment; in fact, they dislike leisure even for their sons. Oddly enough, while they wish their sons to work so hard as to have no time to be civilized, they do not mind their wives and daughters having no work at all. the snobbish admiration of uselessness, which, in an aristocratic society, extends to both sexes, is, under a plutocracy, confined to women; this, however, does not make it any more in agreement with common sense.
The wise use of leisure, it must be conceded, is a product of civilization and education. A man who has worked long hours all his life will become bored if he becomes suddenly idle. But without a considerable amount of leisure a man is cut off from many of the best things. There is no longer any reason why the bulk of the population should suffer this deprivation; only a foolish asceticism, usually vicarious, makes us continue to insist on work in excessive quantities now that the need no longer exists.
In the new creed which controls the government of Russia, while there is much that is very different from the traditional teaching of the West, there are some things that are quite unchanged. The attitude of the governing classes, and especially of those who conduct educational propaganda, on the subject of the dignity of labor, is almost exactly that which the governing classes of the world have always preached to what were called the 'honest poor'. Industry, sobriety, willingness to work long hours for distant advantages, even submissiveness to authority, all these reappear; moreover authority still represents the will of the Ruler of the Universe, Who, however, is now called by a new name, Dialectical Materialism.
The victory of the proletariat in Russia has some points in common with the victory of the feminists in some other countries. For ages, men had conceded the superior saintliness of women, and had consoled women for their inferiority by maintaining that saintliness is more desirable than power. At last the feminists decided that they would have both, since the pioneers among them believed all that the men had told them about the desirability of virtue, but not what they had told them about the worthlessness of political power. A similar thing has happened in Russia as regards manual work. For ages, the rich and their sycophants have written in praise of 'honest toil', have praised the simple life, have professed a religion which teaches that the poor are much more likely to go to heaven than the rich, and in general have tried to make manual workers believe that there is some special nobility about altering the position of matter in space, just as men tried to make women believe that they derived some special nobility from their sexual enslavement. In Russia, all this teaching about the excellence of manual work has been taken seriously, with the result that the manual worker is more honored than anyone else. What are, in essence, revivalist appeals are made, but not for the old purposes: they are made to secure shock workers for special tasks. Manual work is the ideal which is held before the young, and is the basis of all ethical teaching.
For the present, possibly, this is all to the good. A large country, full of natural resources, awaits development, and has has to be developed with very little use of credit. In these circumstances, hard work is necessary, and is likely to bring a great reward. But what will happen when the point has been reached where everybody could be comfortable without working long hours?
In the West, we have various ways of dealing with this problem. We have no attempt at economic justice, so that a large proportion of the total produce goes to a small minority of the population, many of whom do no work at all. Owing to the absence of any central control over production, we produce hosts of things that are not wanted. We keep a large percentage of the working population idle, because we can dispense with their labor by making the others overwork. When all these methods prove inadequate, we have a war: we cause a number of people to manufacture high explosives, and a number of others to explode them, as if we were children who had just discovered fireworks. By a combination of all these devices we manage, though with difficulty, to keep alive the notion that a great deal of severe manual work must be the lot of the average man.
In Russia, owing to more economic justice and central control over production, the problem will have to be differently solved. the rational solution would be, as soon as the necessaries and elementary comforts can be provided for all, to reduce the hours of labor gradually, allowing a popular vote to decide, at each stage, whether more leisure or more goods were to be preferred. But, having taught the supreme virtue of hard work, it is difficult to see how the authorities can aim at a paradise in which there will be much leisure and little work. It seems more likely that they will find continually fresh schemes, by which present leisure is to be sacrificed to future productivity. I read recently of an ingenious plan put forward by Russian engineers, for making the White Sea and the northern coasts of Siberia warm, by putting a dam across the Kara Sea. An admirable project, but liable to postpone proletarian comfort for a generation, while the nobility of toil is being displayed amid the ice-fields and snowstorms of the Arctic Ocean. This sort of thing, if it happens, will be the result of regarding the virtue of hard work as an end in itself, rather than as a means to a state of affairs in which it is no longer needed.
The fact is that moving matter about, while a certain amount of it is necessary to our existence, is emphatically not one of the ends of human life. If it were, we should have to consider every navvy superior to Shakespeare. We have been misled in this matter by two causes. One is the necessity of keeping the poor contented, which has led the rich, for thousands of years, to preach the dignity of labor, while taking care themselves to remain undignified in this respect. The other is the new pleasure in mechanism, which makes us delight in the astonishingly clever changes that we can produce on the earth's surface. Neither of these motives makes any great appeal to the actual worker. If you ask him what he thinks the best part of his life, he is not likely to say: 'I enjoy manual work because it makes me feel that I am fulfilling man's noblest task, and because I like to think how much man can transform his planet. It is true that my body demands periods of rest, which I have to fill in as best I may, but I am never so happy as when the morning comes and I can return to the toil from which my contentment springs.' I have never heard working men say this sort of thing. They consider work, as it should be considered, a necessary means to a livelihood, and it is from their leisure that they derive whatever happiness they may enjoy.
It will be said that, while a little leisure is pleasant, men would not know how to fill their days if they had only four hours of work out of the twenty-four. In so far as this is true in the modern world, it is a condemnation of our civilization; it would not have been true at any earlier period. There was formerly a capacity for light-heartedness and play which has been to some extent inhibited by the cult of efficiency. The modern man thinks that everything ought to be done for the sake of something else, and never for its own sake. Serious-minded persons, for example, are continually condemning the habit of going to the cinema, and telling us that it leads the young into crime. But all the work that goes to producing a cinema is respectable, because it is work, and because it brings a money profit. The notion that the desirable activities are those that bring a profit has made everything topsy-turvy. The butcher who provides you with meat and the baker who provides you with bread are praiseworthy, because they are making money; but when you enjoy the food they have provided, you are merely frivolous, unless you eat only to get strength for your work. Broadly speaking, it is held that getting money is good and spending money is bad. Seeing that they are two sides of one transaction, this is absurd; one might as well maintain that keys are good, but keyholes are bad. Whatever merit there may be in the production of goods must be entirely derivative from the advantage to be obtained by consuming them. The individual, in our society, works for profit; but the social purpose of his work lies in the consumption of what he produces. It is this divorce between the individual and the social purpose of production that makes it so difficult for men to think clearly in a world in which profit-making is the incentive to industry. We think too much of production, and too little of consumption. One result is that we attach too little importance to enjoyment and simple happiness, and that we do not judge production by the pleasure that it gives to the consumer.
When I suggest that working hours should be reduced to four, I am not meaning to imply that all the remaining time should necessarily be spent in pure frivolity. I mean that four hours' work a day should entitle a man to the necessities and elementary comforts of life, and that the rest of his time should be his to use as he might see fit. It is an essential part of any such social system that education should be carried further than it usually is at present, and should aim, in part, at providing tastes which would enable a man to use leisure intelligently. I am not thinking mainly of the sort of things that would be considered 'highbrow'. Peasant dances have died out except in remote rural areas, but the impulses which caused them to be cultivated must still exist in human nature. The pleasures of urban populations have become mainly passive: seeing cinemas, watching football matches, listening to the radio, and so on. This results from the fact that their active energies are fully taken up with work; if they had more leisure, they would again enjoy pleasures in which they took an active part.
In the past, there was a small leisure class and a larger working class. The leisure class enjoyed advantages for which there was no basis in social justice; this necessarily made it oppressive, limited its sympathies, and caused it to invent theories by which to justify its privileges. These facts greatly diminished its excellence, but in spite of this drawback it contributed nearly the whole of what we call civilization. It cultivated the arts and discovered the sciences; it wrote the books, invented the philosophies, and refined social relations. Even the liberation of the oppressed has usually been inaugurated from above. Without the leisure class, mankind would never have emerged from barbarism.
The method of a leisure class without duties was, however, extraordinarily wasteful. None of the members of the class had to be taught to be industrious, and the class as a whole was not exceptionally intelligent. The class might produce one Darwin, but against him had to be set tens of thousands of country gentlemen who never thought of anything more intelligent than fox-hunting and punishing poachers. At present, the universities are supposed to provide, in a more systematic way, what the leisure class provided accidentally and as a by-product. This is a great improvement, but it has certain drawbacks. University life is so different from life in the world at large that men who live in academic milieu tend to be unaware of the preoccupations and problems of ordinary men and women; moreover their ways of expressing themselves are usually such as to rob their opinions of the influence that they ought to have upon the general public. Another disadvantage is that in universities studies are organized, and the man who thinks of some original line of research is likely to be discouraged. Academic institutions, therefore, useful as they are, are not adequate guardians of the interests of civilization in a world where everyone outside their walls is too busy for unutilitarian pursuits.
In a world where no one is compelled to work more than four hours a day, every person possessed of scientific curiosity will be able to indulge it, and every painter will be able to paint without starving, however excellent his pictures may be. Young writers will not be obliged to draw attention to themselves by sensational pot-boilers, with a view to acquiring the economic independence needed for monumental works, for which, when the time at last comes, they will have lost the taste and capacity. Men who, in their professional work, have become interested in some phase of economics or government, will be able to develop their ideas without the academic detachment that makes the work of university economists often seem lacking in reality. Medical men will have the time to learn about the progress of medicine, teachers will not be exasperatedly struggling to teach by routine methods things which they learnt in their youth, which may, in the interval, have been proved to be untrue.
Above all, there will be happiness and joy of life, instead of frayed nerves, weariness, and dyspepsia. The work exacted will be enough to make leisure delightful, but not enough to produce exhaustion. Since men will not be tired in their spare time, they will not demand only such amusements as are passive and vapid. At least one per cent will probably devote the time not spent in professional work to pursuits of some public importance, and, since they will not depend upon these pursuits for their livelihood, their originality will be unhampered, and there will be no need to conform to the standards set by elderly pundits. But it is not only in these exceptional cases that the advantages of leisure will appear. Ordinary men and women, having the opportunity of a happy life, will become more kindly and less persecuting and less inclined to view others with suspicion. The taste for war will die out, partly for this reason, and partly because it will involve long and severe work for all. Good nature is, of all moral qualities, the one that the world needs most, and good nature is the result of ease and security, not of a life of arduous struggle. Modern methods of production have given us the possibility of ease and security for all; we have chosen, instead, to have overwork for some and starvation for others. Hitherto we have continued to be as energetic as we were before there were machines; in this we have been foolish, but there is no reason to go on being foolish forever.
[1] Since then, members of the Communist Party have succeeded to this privilege of the warriors and priests.

View File

@@ -0,0 +1,31 @@
var assert = require('assert');
var wordwrap = require('wordwrap');
var fs = require('fs');
var idleness = fs.readFileSync(__dirname + '/idleness.txt', 'utf8');
exports.stop80 = function () {
var lines = wordwrap(80)(idleness).split(/\n/);
var words = idleness.split(/\s+/);
lines.forEach(function (line) {
assert.ok(line.length <= 80, 'line > 80 columns');
var chunks = line.match(/\S/) ? line.split(/\s+/) : [];
assert.deepEqual(chunks, words.splice(0, chunks.length));
});
};
exports.start20stop60 = function () {
var lines = wordwrap(20, 100)(idleness).split(/\n/);
var words = idleness.split(/\s+/);
lines.forEach(function (line) {
assert.ok(line.length <= 100, 'line > 100 columns');
var chunks = line
.split(/\s+/)
.filter(function (x) { return x.match(/\S/) })
;
assert.deepEqual(chunks, words.splice(0, chunks.length));
assert.deepEqual(line.slice(0, 20), new Array(20 + 1).join(' '));
});
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,513 @@
# DEPRECATION NOTICE
I don't want to maintain this module anymore since I just use
[minimist](https://npmjs.org/package/minimist), the argument parsing engine,
directly instead nowadays.
See [yargs](https://github.com/chevex/yargs) for the modern, pirate-themed
successor to optimist.
[![yarrrrrrrgs!](http://i.imgur.com/4WFGVJ9.png)](https://github.com/chevex/yargs)
You should also consider [nomnom](https://github.com/harthur/nomnom).
optimist
========
Optimist is a node.js library for option parsing for people who hate option
parsing. More specifically, this module is for people who like all the --bells
and -whistlz of program usage but think optstrings are a waste of time.
With optimist, option parsing doesn't have to suck (as much).
[![build status](https://secure.travis-ci.org/substack/node-optimist.png)](http://travis-ci.org/substack/node-optimist)
examples
========
With Optimist, the options are just a hash! No optstrings attached.
-------------------------------------------------------------------
xup.js:
````javascript
#!/usr/bin/env node
var argv = require('optimist').argv;
if (argv.rif - 5 * argv.xup > 7.138) {
console.log('Buy more riffiwobbles');
}
else {
console.log('Sell the xupptumblers');
}
````
***
$ ./xup.js --rif=55 --xup=9.52
Buy more riffiwobbles
$ ./xup.js --rif 12 --xup 8.1
Sell the xupptumblers
![This one's optimistic.](http://substack.net/images/optimistic.png)
But wait! There's more! You can do short options:
-------------------------------------------------
short.js:
````javascript
#!/usr/bin/env node
var argv = require('optimist').argv;
console.log('(%d,%d)', argv.x, argv.y);
````
***
$ ./short.js -x 10 -y 21
(10,21)
And booleans, both long and short (and grouped):
----------------------------------
bool.js:
````javascript
#!/usr/bin/env node
var util = require('util');
var argv = require('optimist').argv;
if (argv.s) {
util.print(argv.fr ? 'Le chat dit: ' : 'The cat says: ');
}
console.log(
(argv.fr ? 'miaou' : 'meow') + (argv.p ? '.' : '')
);
````
***
$ ./bool.js -s
The cat says: meow
$ ./bool.js -sp
The cat says: meow.
$ ./bool.js -sp --fr
Le chat dit: miaou.
And non-hypenated options too! Just use `argv._`!
-------------------------------------------------
nonopt.js:
````javascript
#!/usr/bin/env node
var argv = require('optimist').argv;
console.log('(%d,%d)', argv.x, argv.y);
console.log(argv._);
````
***
$ ./nonopt.js -x 6.82 -y 3.35 moo
(6.82,3.35)
[ 'moo' ]
$ ./nonopt.js foo -x 0.54 bar -y 1.12 baz
(0.54,1.12)
[ 'foo', 'bar', 'baz' ]
Plus, Optimist comes with .usage() and .demand()!
-------------------------------------------------
divide.js:
````javascript
#!/usr/bin/env node
var argv = require('optimist')
.usage('Usage: $0 -x [num] -y [num]')
.demand(['x','y'])
.argv;
console.log(argv.x / argv.y);
````
***
$ ./divide.js -x 55 -y 11
5
$ node ./divide.js -x 4.91 -z 2.51
Usage: node ./divide.js -x [num] -y [num]
Options:
-x [required]
-y [required]
Missing required arguments: y
EVEN MORE HOLY COW
------------------
default_singles.js:
````javascript
#!/usr/bin/env node
var argv = require('optimist')
.default('x', 10)
.default('y', 10)
.argv
;
console.log(argv.x + argv.y);
````
***
$ ./default_singles.js -x 5
15
default_hash.js:
````javascript
#!/usr/bin/env node
var argv = require('optimist')
.default({ x : 10, y : 10 })
.argv
;
console.log(argv.x + argv.y);
````
***
$ ./default_hash.js -y 7
17
And if you really want to get all descriptive about it...
---------------------------------------------------------
boolean_single.js
````javascript
#!/usr/bin/env node
var argv = require('optimist')
.boolean('v')
.argv
;
console.dir(argv);
````
***
$ ./boolean_single.js -v foo bar baz
true
[ 'bar', 'baz', 'foo' ]
boolean_double.js
````javascript
#!/usr/bin/env node
var argv = require('optimist')
.boolean(['x','y','z'])
.argv
;
console.dir([ argv.x, argv.y, argv.z ]);
console.dir(argv._);
````
***
$ ./boolean_double.js -x -z one two three
[ true, false, true ]
[ 'one', 'two', 'three' ]
Optimist is here to help...
---------------------------
You can describe parameters for help messages and set aliases. Optimist figures
out how to format a handy help string automatically.
line_count.js
````javascript
#!/usr/bin/env node
var argv = require('optimist')
.usage('Count the lines in a file.\nUsage: $0')
.demand('f')
.alias('f', 'file')
.describe('f', 'Load a file')
.argv
;
var fs = require('fs');
var s = fs.createReadStream(argv.file);
var lines = 0;
s.on('data', function (buf) {
lines += buf.toString().match(/\n/g).length;
});
s.on('end', function () {
console.log(lines);
});
````
***
$ node line_count.js
Count the lines in a file.
Usage: node ./line_count.js
Options:
-f, --file Load a file [required]
Missing required arguments: f
$ node line_count.js --file line_count.js
20
$ node line_count.js -f line_count.js
20
methods
=======
By itself,
````javascript
require('optimist').argv
`````
will use `process.argv` array to construct the `argv` object.
You can pass in the `process.argv` yourself:
````javascript
require('optimist')([ '-x', '1', '-y', '2' ]).argv
````
or use .parse() to do the same thing:
````javascript
require('optimist').parse([ '-x', '1', '-y', '2' ])
````
The rest of these methods below come in just before the terminating `.argv`.
.alias(key, alias)
------------------
Set key names as equivalent such that updates to a key will propagate to aliases
and vice-versa.
Optionally `.alias()` can take an object that maps keys to aliases.
.default(key, value)
--------------------
Set `argv[key]` to `value` if no option was specified on `process.argv`.
Optionally `.default()` can take an object that maps keys to default values.
.demand(key)
------------
If `key` is a string, show the usage information and exit if `key` wasn't
specified in `process.argv`.
If `key` is a number, demand at least as many non-option arguments, which show
up in `argv._`.
If `key` is an Array, demand each element.
.describe(key, desc)
--------------------
Describe a `key` for the generated usage information.
Optionally `.describe()` can take an object that maps keys to descriptions.
.options(key, opt)
------------------
Instead of chaining together `.alias().demand().default()`, you can specify
keys in `opt` for each of the chainable methods.
For example:
````javascript
var argv = require('optimist')
.options('f', {
alias : 'file',
default : '/etc/passwd',
})
.argv
;
````
is the same as
````javascript
var argv = require('optimist')
.alias('f', 'file')
.default('f', '/etc/passwd')
.argv
;
````
Optionally `.options()` can take an object that maps keys to `opt` parameters.
.usage(message)
---------------
Set a usage message to show which commands to use. Inside `message`, the string
`$0` will get interpolated to the current script name or node command for the
present script similar to how `$0` works in bash or perl.
.check(fn)
----------
Check that certain conditions are met in the provided arguments.
If `fn` throws or returns `false`, show the thrown error, usage information, and
exit.
.boolean(key)
-------------
Interpret `key` as a boolean. If a non-flag option follows `key` in
`process.argv`, that string won't get set as the value of `key`.
If `key` never shows up as a flag in `process.arguments`, `argv[key]` will be
`false`.
If `key` is an Array, interpret all the elements as booleans.
.string(key)
------------
Tell the parser logic not to interpret `key` as a number or boolean.
This can be useful if you need to preserve leading zeros in an input.
If `key` is an Array, interpret all the elements as strings.
.wrap(columns)
--------------
Format usage output to wrap at `columns` many columns.
.help()
-------
Return the generated usage string.
.showHelp(fn=console.error)
---------------------------
Print the usage data using `fn` for printing.
.parse(args)
------------
Parse `args` instead of `process.argv`. Returns the `argv` object.
.argv
-----
Get the arguments as a plain old object.
Arguments without a corresponding flag show up in the `argv._` array.
The script name or node command is available at `argv.$0` similarly to how `$0`
works in bash or perl.
parsing tricks
==============
stop parsing
------------
Use `--` to stop parsing flags and stuff the remainder into `argv._`.
$ node examples/reflect.js -a 1 -b 2 -- -c 3 -d 4
{ _: [ '-c', '3', '-d', '4' ],
'$0': 'node ./examples/reflect.js',
a: 1,
b: 2 }
negate fields
-------------
If you want to explicity set a field to false instead of just leaving it
undefined or to override a default you can do `--no-key`.
$ node examples/reflect.js -a --no-b
{ _: [],
'$0': 'node ./examples/reflect.js',
a: true,
b: false }
numbers
-------
Every argument that looks like a number (`!isNaN(Number(arg))`) is converted to
one. This way you can just `net.createConnection(argv.port)` and you can add
numbers out of `argv` with `+` without having that mean concatenation,
which is super frustrating.
duplicates
----------
If you specify a flag multiple times it will get turned into an array containing
all the values in order.
$ node examples/reflect.js -x 5 -x 8 -x 0
{ _: [],
'$0': 'node ./examples/reflect.js',
x: [ 5, 8, 0 ] }
dot notation
------------
When you use dots (`.`s) in argument names, an implicit object path is assumed.
This lets you organize arguments into nested objects.
$ node examples/reflect.js --foo.bar.baz=33 --foo.quux=5
{ _: [],
'$0': 'node ./examples/reflect.js',
foo: { bar: { baz: 33 }, quux: 5 } }
short numbers
-------------
Short numeric `head -n5` style argument work too:
$ node reflect.js -n123 -m456
{ '3': true,
'6': true,
_: [],
'$0': 'node ./reflect.js',
n: 123,
m: 456 }
installation
============
With [npm](http://github.com/isaacs/npm), just do:
npm install optimist
or clone this project on github:
git clone http://github.com/substack/node-optimist.git
To run the tests with [expresso](http://github.com/visionmedia/expresso),
just do:
expresso
inspired By
===========
This module is loosely inspired by Perl's
[Getopt::Casual](http://search.cpan.org/~photo/Getopt-Casual-0.13.1/Casual.pm).

View File

@@ -0,0 +1,71 @@
var spawn = require('child_process').spawn;
var test = require('tap').test;
test('dotSlashEmpty', testCmd('./bin.js', []));
test('dotSlashArgs', testCmd('./bin.js', [ 'a', 'b', 'c' ]));
test('nodeEmpty', testCmd('node bin.js', []));
test('nodeArgs', testCmd('node bin.js', [ 'x', 'y', 'z' ]));
test('whichNodeEmpty', function (t) {
var which = spawn('which', ['node']);
which.stdout.on('data', function (buf) {
t.test(
testCmd(buf.toString().trim() + ' bin.js', [])
);
t.end();
});
which.stderr.on('data', function (err) {
assert.error(err);
t.end();
});
});
test('whichNodeArgs', function (t) {
var which = spawn('which', ['node']);
which.stdout.on('data', function (buf) {
t.test(
testCmd(buf.toString().trim() + ' bin.js', [ 'q', 'r' ])
);
t.end();
});
which.stderr.on('data', function (err) {
t.error(err);
t.end();
});
});
function testCmd (cmd, args) {
return function (t) {
var to = setTimeout(function () {
assert.fail('Never got stdout data.')
}, 5000);
var oldDir = process.cwd();
process.chdir(__dirname + '/_');
var cmds = cmd.split(' ');
var bin = spawn(cmds[0], cmds.slice(1).concat(args.map(String)));
process.chdir(oldDir);
bin.stderr.on('data', function (err) {
t.error(err);
t.end();
});
bin.stdout.on('data', function (buf) {
clearTimeout(to);
var _ = JSON.parse(buf.toString());
t.same(_.map(String), args.map(String));
t.end();
});
};
}

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
console.log(JSON.stringify(process.argv));

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env node
var argv = require('../../index').argv
console.log(JSON.stringify(argv._));

View File

@@ -0,0 +1,31 @@
var optimist = require('../index');
var test = require('tap').test;
test('-', function (t) {
t.plan(5);
t.deepEqual(
fix(optimist.parse([ '-n', '-' ])),
{ n: '-', _: [] }
);
t.deepEqual(
fix(optimist.parse([ '-' ])),
{ _: [ '-' ] }
);
t.deepEqual(
fix(optimist.parse([ '-f-' ])),
{ f: '-', _: [] }
);
t.deepEqual(
fix(optimist([ '-b', '-' ]).boolean('b').argv),
{ b: true, _: [ '-' ] }
);
t.deepEqual(
fix(optimist([ '-s', '-' ]).string('s').argv),
{ s: '-', _: [] }
);
});
function fix (obj) {
delete obj.$0;
return obj;
}

View File

@@ -0,0 +1,446 @@
var optimist = require('../index');
var path = require('path');
var test = require('tap').test;
var $0 = 'node ./' + path.relative(process.cwd(), __filename);
test('short boolean', function (t) {
var parse = optimist.parse([ '-b' ]);
t.same(parse, { b : true, _ : [], $0 : $0 });
t.same(typeof parse.b, 'boolean');
t.end();
});
test('long boolean', function (t) {
t.same(
optimist.parse([ '--bool' ]),
{ bool : true, _ : [], $0 : $0 }
);
t.end();
});
test('bare', function (t) {
t.same(
optimist.parse([ 'foo', 'bar', 'baz' ]),
{ _ : [ 'foo', 'bar', 'baz' ], $0 : $0 }
);
t.end();
});
test('short group', function (t) {
t.same(
optimist.parse([ '-cats' ]),
{ c : true, a : true, t : true, s : true, _ : [], $0 : $0 }
);
t.end();
});
test('short group next', function (t) {
t.same(
optimist.parse([ '-cats', 'meow' ]),
{ c : true, a : true, t : true, s : 'meow', _ : [], $0 : $0 }
);
t.end();
});
test('short capture', function (t) {
t.same(
optimist.parse([ '-h', 'localhost' ]),
{ h : 'localhost', _ : [], $0 : $0 }
);
t.end();
});
test('short captures', function (t) {
t.same(
optimist.parse([ '-h', 'localhost', '-p', '555' ]),
{ h : 'localhost', p : 555, _ : [], $0 : $0 }
);
t.end();
});
test('long capture sp', function (t) {
t.same(
optimist.parse([ '--pow', 'xixxle' ]),
{ pow : 'xixxle', _ : [], $0 : $0 }
);
t.end();
});
test('long capture eq', function (t) {
t.same(
optimist.parse([ '--pow=xixxle' ]),
{ pow : 'xixxle', _ : [], $0 : $0 }
);
t.end()
});
test('long captures sp', function (t) {
t.same(
optimist.parse([ '--host', 'localhost', '--port', '555' ]),
{ host : 'localhost', port : 555, _ : [], $0 : $0 }
);
t.end();
});
test('long captures eq', function (t) {
t.same(
optimist.parse([ '--host=localhost', '--port=555' ]),
{ host : 'localhost', port : 555, _ : [], $0 : $0 }
);
t.end();
});
test('mixed short bool and capture', function (t) {
t.same(
optimist.parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ], $0 : $0,
}
);
t.end();
});
test('short and long', function (t) {
t.same(
optimist.parse([ '-h', 'localhost', '-fp', '555', 'script.js' ]),
{
f : true, p : 555, h : 'localhost',
_ : [ 'script.js' ], $0 : $0,
}
);
t.end();
});
test('no', function (t) {
t.same(
optimist.parse([ '--no-moo' ]),
{ moo : false, _ : [], $0 : $0 }
);
t.end();
});
test('multi', function (t) {
t.same(
optimist.parse([ '-v', 'a', '-v', 'b', '-v', 'c' ]),
{ v : ['a','b','c'], _ : [], $0 : $0 }
);
t.end();
});
test('comprehensive', function (t) {
t.same(
optimist.parse([
'--name=meowmers', 'bare', '-cats', 'woo',
'-h', 'awesome', '--multi=quux',
'--key', 'value',
'-b', '--bool', '--no-meep', '--multi=baz',
'--', '--not-a-flag', 'eek'
]),
{
c : true,
a : true,
t : true,
s : 'woo',
h : 'awesome',
b : true,
bool : true,
key : 'value',
multi : [ 'quux', 'baz' ],
meep : false,
name : 'meowmers',
_ : [ 'bare', '--not-a-flag', 'eek' ],
$0 : $0
}
);
t.end();
});
test('nums', function (t) {
var argv = optimist.parse([
'-x', '1234',
'-y', '5.67',
'-z', '1e7',
'-w', '10f',
'--hex', '0xdeadbeef',
'789',
]);
t.same(argv, {
x : 1234,
y : 5.67,
z : 1e7,
w : '10f',
hex : 0xdeadbeef,
_ : [ 789 ],
$0 : $0
});
t.same(typeof argv.x, 'number');
t.same(typeof argv.y, 'number');
t.same(typeof argv.z, 'number');
t.same(typeof argv.w, 'string');
t.same(typeof argv.hex, 'number');
t.same(typeof argv._[0], 'number');
t.end();
});
test('flag boolean', function (t) {
var parse = optimist([ '-t', 'moo' ]).boolean(['t']).argv;
t.same(parse, { t : true, _ : [ 'moo' ], $0 : $0 });
t.same(typeof parse.t, 'boolean');
t.end();
});
test('flag boolean value', function (t) {
var parse = optimist(['--verbose', 'false', 'moo', '-t', 'true'])
.boolean(['t', 'verbose']).default('verbose', true).argv;
t.same(parse, {
verbose: false,
t: true,
_: ['moo'],
$0 : $0
});
t.same(typeof parse.verbose, 'boolean');
t.same(typeof parse.t, 'boolean');
t.end();
});
test('flag boolean default false', function (t) {
var parse = optimist(['moo'])
.boolean(['t', 'verbose'])
.default('verbose', false)
.default('t', false).argv;
t.same(parse, {
verbose: false,
t: false,
_: ['moo'],
$0 : $0
});
t.same(typeof parse.verbose, 'boolean');
t.same(typeof parse.t, 'boolean');
t.end();
});
test('boolean groups', function (t) {
var parse = optimist([ '-x', '-z', 'one', 'two', 'three' ])
.boolean(['x','y','z']).argv;
t.same(parse, {
x : true,
y : false,
z : true,
_ : [ 'one', 'two', 'three' ],
$0 : $0
});
t.same(typeof parse.x, 'boolean');
t.same(typeof parse.y, 'boolean');
t.same(typeof parse.z, 'boolean');
t.end();
});
test('newlines in params' , function (t) {
var args = optimist.parse([ '-s', "X\nX" ])
t.same(args, { _ : [], s : "X\nX", $0 : $0 });
// reproduce in bash:
// VALUE="new
// line"
// node program.js --s="$VALUE"
args = optimist.parse([ "--s=X\nX" ])
t.same(args, { _ : [], s : "X\nX", $0 : $0 });
t.end();
});
test('strings' , function (t) {
var s = optimist([ '-s', '0001234' ]).string('s').argv.s;
t.same(s, '0001234');
t.same(typeof s, 'string');
var x = optimist([ '-x', '56' ]).string('x').argv.x;
t.same(x, '56');
t.same(typeof x, 'string');
t.end();
});
test('stringArgs', function (t) {
var s = optimist([ ' ', ' ' ]).string('_').argv._;
t.same(s.length, 2);
t.same(typeof s[0], 'string');
t.same(s[0], ' ');
t.same(typeof s[1], 'string');
t.same(s[1], ' ');
t.end();
});
test('slashBreak', function (t) {
t.same(
optimist.parse([ '-I/foo/bar/baz' ]),
{ I : '/foo/bar/baz', _ : [], $0 : $0 }
);
t.same(
optimist.parse([ '-xyz/foo/bar/baz' ]),
{ x : true, y : true, z : '/foo/bar/baz', _ : [], $0 : $0 }
);
t.end();
});
test('alias', function (t) {
var argv = optimist([ '-f', '11', '--zoom', '55' ])
.alias('z', 'zoom')
.argv
;
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.f, 11);
t.end();
});
test('multiAlias', function (t) {
var argv = optimist([ '-f', '11', '--zoom', '55' ])
.alias('z', [ 'zm', 'zoom' ])
.argv
;
t.equal(argv.zoom, 55);
t.equal(argv.z, argv.zoom);
t.equal(argv.z, argv.zm);
t.equal(argv.f, 11);
t.end();
});
test('boolean default true', function (t) {
var argv = optimist.options({
sometrue: {
boolean: true,
default: true
}
}).argv;
t.equal(argv.sometrue, true);
t.end();
});
test('boolean default false', function (t) {
var argv = optimist.options({
somefalse: {
boolean: true,
default: false
}
}).argv;
t.equal(argv.somefalse, false);
t.end();
});
test('nested dotted objects', function (t) {
var argv = optimist([
'--foo.bar', '3', '--foo.baz', '4',
'--foo.quux.quibble', '5', '--foo.quux.o_O',
'--beep.boop'
]).argv;
t.same(argv.foo, {
bar : 3,
baz : 4,
quux : {
quibble : 5,
o_O : true
},
});
t.same(argv.beep, { boop : true });
t.end();
});
test('boolean and alias with chainable api', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = optimist(aliased)
.boolean('herp')
.alias('h', 'herp')
.argv;
var propertyArgv = optimist(regular)
.boolean('herp')
.alias('h', 'herp')
.argv;
var expected = {
herp: true,
h: true,
'_': [ 'derp' ],
'$0': $0,
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias with options hash', function (t) {
var aliased = [ '-h', 'derp' ];
var regular = [ '--herp', 'derp' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = optimist(aliased)
.options(opts)
.argv;
var propertyArgv = optimist(regular).options(opts).argv;
var expected = {
herp: true,
h: true,
'_': [ 'derp' ],
'$0': $0,
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
test('boolean and alias using explicit true', function (t) {
var aliased = [ '-h', 'true' ];
var regular = [ '--herp', 'true' ];
var opts = {
herp: { alias: 'h', boolean: true }
};
var aliasedArgv = optimist(aliased)
.boolean('h')
.alias('h', 'herp')
.argv;
var propertyArgv = optimist(regular)
.boolean('h')
.alias('h', 'herp')
.argv;
var expected = {
herp: true,
h: true,
'_': [ ],
'$0': $0,
};
t.same(aliasedArgv, expected);
t.same(propertyArgv, expected);
t.end();
});
// regression, see https://github.com/substack/node-optimist/issues/71
test('boolean and --x=true', function(t) {
var parsed = optimist(['--boool', '--other=true']).boolean('boool').argv;
t.same(parsed.boool, true);
t.same(parsed.other, 'true');
parsed = optimist(['--boool', '--other=false']).boolean('boool').argv;
t.same(parsed.boool, true);
t.same(parsed.other, 'false');
t.end();
});

View File

@@ -0,0 +1,14 @@
var optimist = require('../');
var test = require('tap').test;
test('parse with modifier functions' , function (t) {
t.plan(1);
var argv = optimist().boolean('b').parse([ '-b', '123' ]);
t.deepEqual(fix(argv), { b: true, _: ['123'] });
});
function fix (obj) {
delete obj.$0;
return obj;
}

View File

@@ -0,0 +1,16 @@
var optimist = require('../index');
var test = require('tap').test;
test('-n123', function (t) {
t.plan(1);
var parse = optimist.parse([ '-n123' ]);
t.equal(parse.n, 123);
});
test('-123', function (t) {
t.plan(3);
var parse = optimist.parse([ '-123', '456' ]);
t.equal(parse['1'], true);
t.equal(parse['2'], true);
t.equal(parse['3'], 456);
});

View File

@@ -0,0 +1,292 @@
var Hash = require('hashish');
var optimist = require('../index');
var test = require('tap').test;
test('usageFail', function (t) {
var r = checkUsage(function () {
return optimist('-x 10 -z 20'.split(' '))
.usage('Usage: $0 -x NUM -y NUM')
.demand(['x','y'])
.argv;
});
t.same(
r.result,
{ x : 10, z : 20, _ : [], $0 : './usage' }
);
t.same(
r.errors.join('\n').split(/\n+/),
[
'Usage: ./usage -x NUM -y NUM',
'Options:',
' -x [required]',
' -y [required]',
'Missing required arguments: y',
]
);
t.same(r.logs, []);
t.ok(r.exit);
t.end();
});
test('usagePass', function (t) {
var r = checkUsage(function () {
return optimist('-x 10 -y 20'.split(' '))
.usage('Usage: $0 -x NUM -y NUM')
.demand(['x','y'])
.argv;
});
t.same(r, {
result : { x : 10, y : 20, _ : [], $0 : './usage' },
errors : [],
logs : [],
exit : false,
});
t.end();
});
test('checkPass', function (t) {
var r = checkUsage(function () {
return optimist('-x 10 -y 20'.split(' '))
.usage('Usage: $0 -x NUM -y NUM')
.check(function (argv) {
if (!('x' in argv)) throw 'You forgot about -x';
if (!('y' in argv)) throw 'You forgot about -y';
})
.argv;
});
t.same(r, {
result : { x : 10, y : 20, _ : [], $0 : './usage' },
errors : [],
logs : [],
exit : false,
});
t.end();
});
test('checkFail', function (t) {
var r = checkUsage(function () {
return optimist('-x 10 -z 20'.split(' '))
.usage('Usage: $0 -x NUM -y NUM')
.check(function (argv) {
if (!('x' in argv)) throw 'You forgot about -x';
if (!('y' in argv)) throw 'You forgot about -y';
})
.argv;
});
t.same(
r.result,
{ x : 10, z : 20, _ : [], $0 : './usage' }
);
t.same(
r.errors.join('\n').split(/\n+/),
[
'Usage: ./usage -x NUM -y NUM',
'You forgot about -y'
]
);
t.same(r.logs, []);
t.ok(r.exit);
t.end();
});
test('checkCondPass', function (t) {
function checker (argv) {
return 'x' in argv && 'y' in argv;
}
var r = checkUsage(function () {
return optimist('-x 10 -y 20'.split(' '))
.usage('Usage: $0 -x NUM -y NUM')
.check(checker)
.argv;
});
t.same(r, {
result : { x : 10, y : 20, _ : [], $0 : './usage' },
errors : [],
logs : [],
exit : false,
});
t.end();
});
test('checkCondFail', function (t) {
function checker (argv) {
return 'x' in argv && 'y' in argv;
}
var r = checkUsage(function () {
return optimist('-x 10 -z 20'.split(' '))
.usage('Usage: $0 -x NUM -y NUM')
.check(checker)
.argv;
});
t.same(
r.result,
{ x : 10, z : 20, _ : [], $0 : './usage' }
);
t.same(
r.errors.join('\n').split(/\n+/).join('\n'),
'Usage: ./usage -x NUM -y NUM\n'
+ 'Argument check failed: ' + checker.toString()
);
t.same(r.logs, []);
t.ok(r.exit);
t.end();
});
test('countPass', function (t) {
var r = checkUsage(function () {
return optimist('1 2 3 --moo'.split(' '))
.usage('Usage: $0 [x] [y] [z] {OPTIONS}')
.demand(3)
.argv;
});
t.same(r, {
result : { _ : [ '1', '2', '3' ], moo : true, $0 : './usage' },
errors : [],
logs : [],
exit : false,
});
t.end();
});
test('countFail', function (t) {
var r = checkUsage(function () {
return optimist('1 2 --moo'.split(' '))
.usage('Usage: $0 [x] [y] [z] {OPTIONS}')
.demand(3)
.argv;
});
t.same(
r.result,
{ _ : [ '1', '2' ], moo : true, $0 : './usage' }
);
t.same(
r.errors.join('\n').split(/\n+/),
[
'Usage: ./usage [x] [y] [z] {OPTIONS}',
'Not enough non-option arguments: got 2, need at least 3',
]
);
t.same(r.logs, []);
t.ok(r.exit);
t.end();
});
test('defaultSingles', function (t) {
var r = checkUsage(function () {
return optimist('--foo 50 --baz 70 --powsy'.split(' '))
.default('foo', 5)
.default('bar', 6)
.default('baz', 7)
.argv
;
});
t.same(r.result, {
foo : '50',
bar : 6,
baz : '70',
powsy : true,
_ : [],
$0 : './usage',
});
t.end();
});
test('defaultAliases', function (t) {
var r = checkUsage(function () {
return optimist('')
.alias('f', 'foo')
.default('f', 5)
.argv
;
});
t.same(r.result, {
f : '5',
foo : '5',
_ : [],
$0 : './usage',
});
t.end();
});
test('defaultHash', function (t) {
var r = checkUsage(function () {
return optimist('--foo 50 --baz 70'.split(' '))
.default({ foo : 10, bar : 20, quux : 30 })
.argv
;
});
t.same(r.result, {
_ : [],
$0 : './usage',
foo : 50,
baz : 70,
bar : 20,
quux : 30,
});
t.end();
});
test('rebase', function (t) {
t.equal(
optimist.rebase('/home/substack', '/home/substack/foo/bar/baz'),
'./foo/bar/baz'
);
t.equal(
optimist.rebase('/home/substack/foo/bar/baz', '/home/substack'),
'../../..'
);
t.equal(
optimist.rebase('/home/substack/foo', '/home/substack/pow/zoom.txt'),
'../pow/zoom.txt'
);
t.end();
});
function checkUsage (f) {
var exit = false;
process._exit = process.exit;
process._env = process.env;
process._argv = process.argv;
process.exit = function (t) { exit = true };
process.env = Hash.merge(process.env, { _ : 'node' });
process.argv = [ './usage' ];
var errors = [];
var logs = [];
console._error = console.error;
console.error = function (msg) { errors.push(msg) };
console._log = console.log;
console.log = function (msg) { logs.push(msg) };
var result = f();
process.exit = process._exit;
process.env = process._env;
process.argv = process._argv;
console.error = console._error;
console.log = console._log;
return {
errors : errors,
logs : logs,
exit : exit,
result : result,
};
};

View File

@@ -0,0 +1,8 @@
var optimist = require('../');
var test = require('tap').test;
test('whitespace should be whitespace' , function (t) {
t.plan(1);
var x = optimist.parse([ '-x', '\t' ]).x;
t.equal(x, '\t');
});

View File

@@ -0,0 +1,29 @@
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.

View File

@@ -0,0 +1,784 @@
UglifyJS 2
==========
[![Build Status](https://travis-ci.org/mishoo/UglifyJS2.svg)](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.
If you wish to pass your options before the input files, separate the two with
a double dash to prevent input files being used as option arguments:
uglifyjs --compress --mangle -- input.js
The available options are:
```
--source-map Specify an output file where to generate source
map.
--source-map-root The path to the original source to be included
in the source map.
--source-map-url The path to the source map to be added in //#
sourceMappingURL. Defaults to the value passed
with --source-map.
--source-map-include-sources Pass this flag if you want to include the
content of source files in the source map as
sourcesContent property.
--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).
--expr Parse a single expression, rather than a
program (for parsing JSON)
-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.
-o, --output Output file (default STDOUT).
-b, --beautify Beautify output/specify output options.
-m, --mangle Mangle names/pass mangler options.
-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.
-d, --define Global definitions
-e, --enclose Embed everything in a big function, with a
configurable parameter/argument list.
--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.
--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.
--acorn Use Acorn for parsing.
--spidermonkey Assume input files are SpiderMonkey AST format
(as JSON).
--self Build itself (UglifyJS2) as a library (implies
--wrap=UglifyJS --export-all)
--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.
--export-all Only used when --wrap, this tells UglifyJS to
add code to automatically export all globals.
--lint Display some scope warnings
-v, --verbose Verbose
-V, --version Print version number and exit.
--noerr Don't throw an error for unknown options in -c,
-b or -m.
--bare-returns Allow return outside of functions. Useful when
minifying CommonJS modules.
--keep-fnames Do not mangle/drop function names. Useful for
code relying on Function.prototype.name.
--reserved-file File containing reserved names
--reserve-domprops Make (most?) DOM properties reserved for
--mangle-props
--mangle-props Mangle property names
--mangle-regex Only mangle property names matching the regex
--name-cache File to hold mangled names mappings
```
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 `with` 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.
### Mangling property names (`--mangle-props`)
**Note:** this will probably break your code. Mangling property names is a
separate step, different from variable name mangling. Pass
`--mangle-props`. It will mangle all properties that are seen in some
object literal, or that are assigned to. For example:
```js
var x = {
foo: 1
};
x.bar = 2;
x["baz"] = 3;
x[condition ? "moo" : "boo"] = 4;
console.log(x.something());
```
In the above code, `foo`, `bar`, `baz`, `moo` and `boo` will be replaced
with single characters, while `something()` will be left as is.
In order for this to be of any use, we should avoid mangling standard JS
names. For instance, if your code would contain `x.length = 10`, then
`length` becomes a candidate for mangling and it will be mangled throughout
the code, regardless if it's being used as part of your own objects or
accessing an array's length. To avoid that, you can use `--reserved-file`
to pass a filename that should contain the names to be excluded from
mangling. This file can be used both for excluding variable names and
property names. It could look like this, for example:
```js
{
"vars": [ "define", "require", ... ],
"props": [ "length", "prototype", ... ]
}
```
`--reserved-file` can be an array of file names (either a single
comma-separated argument, or you can pass multiple `--reserved-file`
arguments) — in this case it will exclude names from all those files.
A default exclusion file is provided in `tools/domprops.json` which should
cover most standard JS and DOM properties defined in various browsers. Pass
`--reserve-domprops` to read that in.
You can also use a regular expression to define which property names should be
mangled. For example, `--mangle-regex="/^_/"` will only mangle property names
that start with an underscore.
When you compress multiple files using this option, in order for them to
work together in the end we need to ensure somehow that one property gets
mangled to the same name in all of them. For this, pass `--name-cache
filename.json` and UglifyJS will maintain these mappings in a file which can
then be reused. It should be initially empty. Example:
```
rm -f /tmp/cache.json # start fresh
uglifyjs file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js
uglifyjs file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js
```
Now, `part1.js` and `part2.js` will be consistent with each other in terms
of mangled property names.
Using the name cache is not necessary if you compress all your files in a
single call to UglifyJS.
## 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.
- `keep_fargs` -- default `false`. Pass `true` to prevent the
compressor from discarding unused function arguments. You need this
for code which relies on `Function.length`.
### 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)
- discards unused function arguments (affects `function.length`)
### 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.
- `quote_style` (default `0`) -- preferred quote style for strings (affects
quoted property names and directives as well):
- `0` -- prefers double quotes, switches to single quotes when there are
more double quotes in the string itself.
- `1` -- always use single quotes
- `2` -- always use double quotes
- `3` -- always use the original quotes
### 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.
### Using UglifyJS to transform SpiderMonkey AST
Now you can use UglifyJS as any other intermediate tool for transforming
JavaScript ASTs in SpiderMonkey format.
Example:
```javascript
function uglify(ast, options, mangle) {
// Conversion from SpiderMonkey AST to internal format
var uAST = UglifyJS.AST_Node.from_mozilla_ast(ast);
// Compression
uAST.figure_out_scope();
uAST = uAST.transform(UglifyJS.Compressor(options));
// Mangling (optional)
if (mangle) {
uAST.figure_out_scope();
uAST.compute_char_frequency();
uAST.mangle_names();
}
// Back-conversion to SpiderMonkey AST
return uAST.to_mozilla_ast();
}
```
Check out
[original blog post](http://rreverser.com/using-mozilla-ast-with-uglifyjs/)
for details.
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`
```
If your input source map is not in a file, you can pass it in as an object
using the `inSourceMap` argument:
```javascript
var result = UglifyJS.minify("compiled.js", {
inSourceMap: JSON.parse(my_source_map_string),
outSourceMap: "minified.js.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 options,
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

View File

@@ -0,0 +1,77 @@
#! /usr/bin/env node
"use strict";
var U2 = require("../tools/node");
var fs = require("fs");
var yargs = require("yargs");
var ARGS = yargs
.describe("o", "Output file")
.argv;
var files = ARGS._.slice();
var output = {
vars: {},
props: {}
};
if (ARGS.o) try {
output = JSON.parse(fs.readFileSync(ARGS.o, "utf8"));
} catch(ex) {}
files.forEach(getProps);
if (ARGS.o) {
fs.writeFileSync(ARGS.o, JSON.stringify(output, null, 2), "utf8");
} else {
console.log("%s", JSON.stringify(output, null, 2));
}
function getProps(filename) {
var code = fs.readFileSync(filename, "utf8");
var ast = U2.parse(code);
ast.walk(new U2.TreeWalker(function(node){
if (node instanceof U2.AST_ObjectKeyVal) {
add(node.key);
}
else if (node instanceof U2.AST_ObjectProperty) {
add(node.key.name);
}
else if (node instanceof U2.AST_Dot) {
add(node.property);
}
else if (node instanceof U2.AST_Sub) {
addStrings(node.property);
}
}));
function addStrings(node) {
var out = {};
try {
(function walk(node){
node.walk(new U2.TreeWalker(function(node){
if (node instanceof U2.AST_Seq) {
walk(node.cdr);
return true;
}
if (node instanceof U2.AST_String) {
add(node.value);
return true;
}
if (node instanceof U2.AST_Conditional) {
walk(node.consequent);
walk(node.alternative);
return true;
}
throw out;
}));
})(node);
} catch(ex) {
if (ex !== out) throw ex;
}
}
function add(name) {
output.props[name] = true;
}
}

View File

@@ -0,0 +1,560 @@
#! /usr/bin/env node
// -*- js -*-
"use strict";
var UglifyJS = require("../tools/node");
var sys = require("util");
var yargs = require("yargs");
var fs = require("fs");
var path = require("path");
var async = require("async");
var acorn;
var ARGS = yargs
.usage("$0 input1.js [input2.js ...] [options]\n\
Use a single dash to read input from the standard input.\
\n\n\
NOTE: by default there is no mangling/compression.\n\
Without [options] it will simply parse input files and dump the AST\n\
with whitespace and comments discarded. To achieve compression and\n\
mangling you need to use `-c` and `-m`.\
")
.describe("source-map", "Specify an output file where to generate source map.")
.describe("source-map-root", "The path to the original source to be included in the source map.")
.describe("source-map-url", "The path to the source map to be added in //# sourceMappingURL. Defaults to the value passed with --source-map.")
.describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.")
.describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.")
.describe("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).")
.describe("expr", "Parse a single expression, rather than a program (for parsing JSON)")
.describe("p", "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.")
.describe("o", "Output file (default STDOUT).")
.describe("b", "Beautify output/specify output options.")
.describe("m", "Mangle names/pass mangler options.")
.describe("r", "Reserved names to exclude from mangling.")
.describe("c", "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.")
.describe("d", "Global definitions")
.describe("e", "Embed everything in a big function, with a configurable parameter/argument list.")
.describe("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:\n\
- \"all\" to keep all comments\n\
- a valid JS regexp (needs to start with a slash) to keep only comments that match.\n\
\
Note that currently not *all* comments can be kept when compression is on, \
because of dead code removal or cascading statements into sequences.")
.describe("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.")
.describe("stats", "Display operations run time on STDERR.")
.describe("acorn", "Use Acorn for parsing.")
.describe("spidermonkey", "Assume input files are SpiderMonkey AST format (as JSON).")
.describe("self", "Build itself (UglifyJS2) as a library (implies --wrap=UglifyJS --export-all)")
.describe("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.")
.describe("export-all", "Only used when --wrap, this tells UglifyJS to add code to automatically export all globals.")
.describe("lint", "Display some scope warnings")
.describe("v", "Verbose")
.describe("V", "Print version number and exit.")
.describe("noerr", "Don't throw an error for unknown options in -c, -b or -m.")
.describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.")
.describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.")
.describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)")
.describe("reserved-file", "File containing reserved names")
.describe("reserve-domprops", "Make (most?) DOM properties reserved for --mangle-props")
.describe("mangle-props", "Mangle property names")
.describe("mangle-regex", "Only mangle property names matching the regex")
.describe("name-cache", "File to hold mangled names mappings")
.alias("p", "prefix")
.alias("o", "output")
.alias("v", "verbose")
.alias("b", "beautify")
.alias("m", "mangle")
.alias("c", "compress")
.alias("d", "define")
.alias("r", "reserved")
.alias("V", "version")
.alias("e", "enclose")
.alias("q", "quotes")
.string("source-map")
.string("source-map-root")
.string("source-map-url")
.string("b")
.string("beautify")
.string("m")
.string("mangle")
.string("c")
.string("compress")
.string("d")
.string("define")
.string("e")
.string("enclose")
.string("comments")
.string("wrap")
.string("p")
.string("prefix")
.string("name-cache")
.array("reserved-file")
.boolean("expr")
.boolean("source-map-include-sources")
.boolean("screw-ie8")
.boolean("export-all")
.boolean("self")
.boolean("v")
.boolean("verbose")
.boolean("stats")
.boolean("acorn")
.boolean("spidermonkey")
.boolean("lint")
.boolean("V")
.boolean("version")
.boolean("noerr")
.boolean("bare-returns")
.boolean("keep-fnames")
.boolean("mangle-props")
.boolean("reserve-domprops")
.wrap(80)
.argv
;
normalize(ARGS);
if (ARGS.noerr) {
UglifyJS.DefaultsError.croak = function(msg, defs) {
print_error("WARN: " + msg);
};
}
if (ARGS.version || ARGS.V) {
var json = require("../package.json");
print(json.name + ' ' + json.version);
process.exit(0);
}
if (ARGS.ast_help) {
var desc = UglifyJS.describe_ast();
print(typeof desc == "string" ? desc : JSON.stringify(desc, null, 2));
process.exit(0);
}
if (ARGS.h || ARGS.help) {
print(yargs.help());
process.exit(0);
}
if (ARGS.acorn) {
acorn = require("acorn");
}
var COMPRESS = getOptions("c", true);
var MANGLE = getOptions("m", true);
var BEAUTIFY = getOptions("b", true);
var RESERVED = null;
if (ARGS.reserved_file) ARGS.reserved_file.forEach(function(filename){
RESERVED = UglifyJS.readReservedFile(filename, RESERVED);
});
if (ARGS.reserve_domprops) {
RESERVED = UglifyJS.readDefaultReservedFile(RESERVED);
}
if (ARGS.d) {
if (COMPRESS) COMPRESS.global_defs = getOptions("d");
}
if (ARGS.r) {
if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
}
if (RESERVED && MANGLE) {
if (!MANGLE.except) MANGLE.except = RESERVED.vars;
else MANGLE.except = MANGLE.except.concat(RESERVED.vars);
}
function readNameCache(key) {
return UglifyJS.readNameCache(ARGS.name_cache, key);
}
function writeNameCache(key, cache) {
return UglifyJS.writeNameCache(ARGS.name_cache, key, cache);
}
function extractRegex(str) {
if (/^\/.*\/[a-zA-Z]*$/.test(str)) {
var regex_pos = str.lastIndexOf("/");
return new RegExp(str.substr(1, regex_pos - 1), str.substr(regex_pos + 1));
} else {
throw new Error("Invalid regular expression: " + str);
}
}
if (ARGS.quotes === true) {
ARGS.quotes = 3;
}
var OUTPUT_OPTIONS = {
beautify : BEAUTIFY ? true : false,
preamble : ARGS.preamble || null,
quote_style : ARGS.quotes != null ? ARGS.quotes : 0
};
if (ARGS.screw_ie8) {
if (COMPRESS) COMPRESS.screw_ie8 = true;
if (MANGLE) MANGLE.screw_ie8 = true;
OUTPUT_OPTIONS.screw_ie8 = true;
}
if (ARGS.keep_fnames) {
if (COMPRESS) COMPRESS.keep_fnames = true;
if (MANGLE) MANGLE.keep_fnames = true;
}
if (BEAUTIFY)
UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
if (ARGS.comments != null) {
if (/^\/.*\/[a-zA-Z]*$/.test(ARGS.comments)) {
try {
OUTPUT_OPTIONS.comments = extractRegex(ARGS.comments);
} catch (e) {
print_error("ERROR: Invalid --comments: " + e.message);
}
} else if (ARGS.comments == "all") {
OUTPUT_OPTIONS.comments = true;
} else {
OUTPUT_OPTIONS.comments = function(node, comment) {
var text = comment.value;
var type = comment.type;
if (type == "comment2") {
// multiline comment
return /@preserve|@license|@cc_on/i.test(text);
}
}
}
}
var files = ARGS._.slice();
if (ARGS.self) {
if (files.length > 0) {
print_error("WARN: Ignoring input files since --self was passed");
}
files = UglifyJS.FILES;
if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
ARGS.export_all = true;
}
var ORIG_MAP = ARGS.in_source_map;
if (ORIG_MAP) {
ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
if (files.length == 0) {
print_error("INFO: Using file from the input source map: " + ORIG_MAP.file);
files = [ ORIG_MAP.file ];
}
if (ARGS.source_map_root == null) {
ARGS.source_map_root = ORIG_MAP.sourceRoot;
}
}
if (files.length == 0) {
files = [ "-" ];
}
if (files.indexOf("-") >= 0 && ARGS.source_map) {
print_error("ERROR: Source map doesn't work with input from STDIN");
process.exit(1);
}
if (files.filter(function(el){ return el == "-" }).length > 1) {
print_error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
process.exit(1);
}
var STATS = {};
var OUTPUT_FILE = ARGS.o;
var TOPLEVEL = null;
var P_RELATIVE = ARGS.p && ARGS.p == "relative";
var SOURCES_CONTENT = {};
var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE,
root: ARGS.source_map_root,
orig: ORIG_MAP,
}) : null;
OUTPUT_OPTIONS.source_map = SOURCE_MAP;
try {
var output = UglifyJS.OutputStream(OUTPUT_OPTIONS);
var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS);
} catch(ex) {
if (ex instanceof UglifyJS.DefaultsError) {
print_error(ex.msg);
print_error("Supported options:");
print_error(sys.inspect(ex.defs));
process.exit(1);
}
}
async.eachLimit(files, 1, function (file, cb) {
read_whole_file(file, function (err, code) {
if (err) {
print_error("ERROR: can't read file: " + file);
process.exit(1);
}
if (ARGS.p != null) {
if (P_RELATIVE) {
file = path.relative(path.dirname(ARGS.source_map), file).replace(/\\/g, '/');
} else {
var p = parseInt(ARGS.p, 10);
if (!isNaN(p)) {
file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
}
}
}
SOURCES_CONTENT[file] = code;
time_it("parse", function(){
if (ARGS.spidermonkey) {
var program = JSON.parse(code);
if (!TOPLEVEL) TOPLEVEL = program;
else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
}
else if (ARGS.acorn) {
TOPLEVEL = acorn.parse(code, {
locations : true,
sourceFile : file,
program : TOPLEVEL
});
}
else {
try {
TOPLEVEL = UglifyJS.parse(code, {
filename : file,
toplevel : TOPLEVEL,
expression : ARGS.expr,
bare_returns : ARGS.bare_returns,
});
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Parse error at " + file + ":" + ex.line + "," + ex.col);
print_error(ex.message);
print_error(ex.stack);
process.exit(1);
}
throw ex;
}
};
});
cb();
});
}, function () {
if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
});
if (ARGS.wrap != null) {
TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
}
if (ARGS.enclose != null) {
var arg_parameter_list = ARGS.enclose;
if (arg_parameter_list === true) {
arg_parameter_list = [];
}
else if (!(arg_parameter_list instanceof Array)) {
arg_parameter_list = [arg_parameter_list];
}
TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
}
if (ARGS.mangle_props || ARGS.name_cache) (function(){
var reserved = RESERVED ? RESERVED.props : null;
var cache = readNameCache("props");
var regex;
try {
regex = ARGS.mangle_regex ? extractRegex(ARGS.mangle_regex) : null;
} catch (e) {
print_error("ERROR: Invalid --mangle-regex: " + e.message);
process.exit(1);
}
TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, {
reserved : reserved,
cache : cache,
only_cache : !ARGS.mangle_props,
regex : regex
});
writeNameCache("props", cache);
})();
var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
var TL_CACHE = readNameCache("vars");
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE });
if (ARGS.lint) {
TOPLEVEL.scope_warnings();
}
});
}
if (COMPRESS) {
time_it("squeeze", function(){
TOPLEVEL = TOPLEVEL.transform(compressor);
});
}
if (SCOPE_IS_NEEDED) {
time_it("scope", function(){
TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE });
if (MANGLE && !TL_CACHE) {
TOPLEVEL.compute_char_frequency(MANGLE);
}
});
}
if (MANGLE) time_it("mangle", function(){
MANGLE.cache = TL_CACHE;
TOPLEVEL.mangle_names(MANGLE);
});
writeNameCache("vars", TL_CACHE);
if (ARGS.source_map_include_sources) {
for (var file in SOURCES_CONTENT) {
if (SOURCES_CONTENT.hasOwnProperty(file)) {
SOURCE_MAP.get().setSourceContent(file, SOURCES_CONTENT[file]);
}
}
}
time_it("generate", function(){
TOPLEVEL.print(output);
});
output = output.get();
if (SOURCE_MAP) {
fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
var source_map_url = ARGS.source_map_url || (
P_RELATIVE
? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map)
: ARGS.source_map
);
output += "\n//# sourceMappingURL=" + source_map_url;
}
if (OUTPUT_FILE) {
fs.writeFileSync(OUTPUT_FILE, output, "utf8");
} else {
print(output);
}
if (ARGS.stats) {
print_error(UglifyJS.string_template("Timing information (compressed {count} files):", {
count: files.length
}));
for (var i in STATS) if (STATS.hasOwnProperty(i)) {
print_error(UglifyJS.string_template("- {name}: {time}s", {
name: i,
time: (STATS[i] / 1000).toFixed(3)
}));
}
}
});
/* -----[ functions ]----- */
function normalize(o) {
for (var i in o) if (o.hasOwnProperty(i) && /-/.test(i)) {
o[i.replace(/-/g, "_")] = o[i];
delete o[i];
}
}
function getOptions(x, constants) {
x = ARGS[x];
if (x == null) return null;
var ret = {};
if (x !== "") {
var ast;
try {
ast = UglifyJS.parse(x, { expression: true });
} catch(ex) {
if (ex instanceof UglifyJS.JS_Parse_Error) {
print_error("Error parsing arguments in: " + x);
process.exit(1);
}
}
ast.walk(new UglifyJS.TreeWalker(function(node){
if (node instanceof UglifyJS.AST_Seq) return; // descend
if (node instanceof UglifyJS.AST_Assign) {
var name = node.left.print_to_string({ beautify: false }).replace(/-/g, "_");
var value = node.right;
if (constants)
value = new Function("return (" + value.print_to_string() + ")")();
ret[name] = value;
return true; // no descend
}
if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_Binary) {
var name = node.print_to_string({ beautify: false }).replace(/-/g, "_");
ret[name] = true;
return true; // no descend
}
print_error(node.TYPE)
print_error("Error parsing arguments in: " + x);
process.exit(1);
}));
}
return ret;
}
function read_whole_file(filename, cb) {
if (filename == "-") {
var chunks = [];
process.stdin.setEncoding('utf-8');
process.stdin.on('data', function (chunk) {
chunks.push(chunk);
}).on('end', function () {
cb(null, chunks.join(""));
});
process.openStdin();
} else {
fs.readFile(filename, "utf-8", cb);
}
}
function time_it(name, cont) {
var t1 = new Date().getTime();
var ret = cont();
if (ARGS.stats) {
var spent = new Date().getTime() - t1;
if (STATS[name]) STATS[name] += spent;
else STATS[name] = spent;
}
return ret;
}
function print_error(msg) {
console.error("%s", msg);
}
function print(txt) {
console.log("%s", txt);
}

View File

@@ -0,0 +1,995 @@
/***********************************************************************
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 endline endcol 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 quote", {
$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",
quote: "[string] the original quote character"
},
}, 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"
}
}, AST_IterationStatement);
var AST_Do = DEFNODE("Do", null, {
$documentation: "A `do` statement",
_walk: function(visitor) {
return visitor._visit(this, function(){
this.body._walk(visitor);
this.condition._walk(visitor);
});
}
}, AST_DWLoop);
var AST_While = DEFNODE("While", null, {
$documentation: "A `while` statement",
_walk: function(visitor) {
return visitor._visit(this, function(){
this.condition._walk(visitor);
this.body._walk(visitor);
});
}
}, 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 splitAt = pair.lastIndexOf(":");
args.push(pair.substr(0, splitAt));
parameters.push(pair.substr(splitAt + 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", "quote", {
$documentation: "A key: value object property",
$propdoc: {
quote: "[string] the original quote character"
}
}, 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 quote", {
$documentation: "A string literal",
$propdoc: {
value: "[string] the contents of this string",
quote: "[string] the original quote character"
}
}, 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;
}
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,507 @@
/***********************************************************************
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 = {
ExpressionStatement: function(M) {
var expr = M.expression;
if (expr.type === "Literal" && typeof expr.value === "string") {
return new AST_Directive({
start: my_start_token(M),
end: my_end_token(M),
value: expr.value
});
}
return new AST_SimpleStatement({
start: my_start_token(M),
end: my_end_token(M),
body: from_moz(expr)
});
},
TryStatement: function(M) {
var handlers = M.handlers || [M.handler];
if (handlers.length > 1 || M.guardedHandlers && M.guardedHandlers.length) {
throw new Error("Multiple catch clauses are not supported.");
}
return new AST_Try({
start : my_start_token(M),
end : my_end_token(M),
body : from_moz(M.block).body,
bcatch : from_moz(handlers[0]),
bfinally : M.finalizer ? new AST_Finally(from_moz(M.finalizer)) : null
});
},
Property: function(M) {
var key = M.key;
var name = key.type == "Identifier" ? key.name : key.value;
var args = {
start : my_start_token(key),
end : my_end_token(M.value),
key : name,
value : from_moz(M.value)
};
switch (M.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);
}
},
ObjectExpression: function(M) {
return new AST_Object({
start : my_start_token(M),
end : my_end_token(M),
properties : M.properties.map(function(prop){
prop.type = "Property";
return from_moz(prop)
})
});
},
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)
});
},
VariableDeclaration: function(M) {
return new (M.kind === "const" ? AST_Const : AST_Var)({
start : my_start_token(M),
end : my_end_token(M),
definitions : M.declarations.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);
}
},
Identifier: function(M) {
var p = FROM_MOZ_STACK[FROM_MOZ_STACK.length - 2];
return new ( 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
});
}
};
MOZ_TO_ME.UpdateExpression =
MOZ_TO_ME.UnaryExpression = function To_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)
});
};
map("Program", AST_Toplevel, "body@body");
map("EmptyStatement", AST_EmptyStatement);
map("BlockStatement", AST_BlockStatement, "body@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("VariableDeclarator", AST_VarDef, "id>name, init>value");
map("CatchClause", AST_Catch, "param>argname, body%body");
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("LogicalExpression", AST_Binary, "operator=operator, left>left, right>right");
map("AssignmentExpression", AST_Assign, "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");
def_to_moz(AST_Directive, function To_Moz_Directive(M) {
return {
type: "ExpressionStatement",
expression: {
type: "Literal",
value: M.value
}
};
});
def_to_moz(AST_SimpleStatement, function To_Moz_ExpressionStatement(M) {
return {
type: "ExpressionStatement",
expression: to_moz(M.body)
};
});
def_to_moz(AST_SwitchBranch, function To_Moz_SwitchCase(M) {
return {
type: "SwitchCase",
test: to_moz(M.expression),
consequent: M.body.map(to_moz)
};
});
def_to_moz(AST_Try, function To_Moz_TryStatement(M) {
return {
type: "TryStatement",
block: to_moz_block(M),
handler: to_moz(M.bcatch),
guardedHandlers: [],
finalizer: to_moz(M.bfinally)
};
});
def_to_moz(AST_Catch, function To_Moz_CatchClause(M) {
return {
type: "CatchClause",
param: to_moz(M.argname),
guard: null,
body: to_moz_block(M)
};
});
def_to_moz(AST_Definitions, function To_Moz_VariableDeclaration(M) {
return {
type: "VariableDeclaration",
kind: M instanceof AST_Const ? "const" : "var",
declarations: M.definitions.map(to_moz)
};
});
def_to_moz(AST_Seq, function To_Moz_SequenceExpression(M) {
return {
type: "SequenceExpression",
expressions: M.to_array().map(to_moz)
};
});
def_to_moz(AST_PropAccess, function To_Moz_MemberExpression(M) {
var isComputed = M instanceof AST_Sub;
return {
type: "MemberExpression",
object: to_moz(M.expression),
computed: isComputed,
property: isComputed ? to_moz(M.property) : {type: "Identifier", name: M.property}
};
});
def_to_moz(AST_Unary, function To_Moz_Unary(M) {
return {
type: M.operator == "++" || M.operator == "--" ? "UpdateExpression" : "UnaryExpression",
operator: M.operator,
prefix: M instanceof AST_UnaryPrefix,
argument: to_moz(M.expression)
};
});
def_to_moz(AST_Binary, function To_Moz_BinaryExpression(M) {
return {
type: M.operator == "&&" || M.operator == "||" ? "LogicalExpression" : "BinaryExpression",
left: to_moz(M.left),
operator: M.operator,
right: to_moz(M.right)
};
});
def_to_moz(AST_Object, function To_Moz_ObjectExpression(M) {
return {
type: "ObjectExpression",
properties: M.properties.map(to_moz)
};
});
def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) {
var key = (
is_identifier(M.key)
? {type: "Identifier", name: M.key}
: {type: "Literal", value: M.key}
);
var kind;
if (M instanceof AST_ObjectKeyVal) {
kind = "init";
} else
if (M instanceof AST_ObjectGetter) {
kind = "get";
} else
if (M instanceof AST_ObjectSetter) {
kind = "set";
}
return {
type: "Property",
kind: kind,
key: key,
value: to_moz(M.value)
};
});
def_to_moz(AST_Symbol, function To_Moz_Identifier(M) {
var def = M.definition();
return {
type: "Identifier",
name: def ? def.mangled_name || def.name : M.name
};
});
def_to_moz(AST_Constant, function To_Moz_Literal(M) {
var value = M.value;
if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) {
return {
type: "UnaryExpression",
operator: "-",
prefix: true,
argument: {
type: "Literal",
value: -value
}
};
}
return {
type: "Literal",
value: value
};
});
def_to_moz(AST_Atom, function To_Moz_Atom(M) {
return {
type: "Identifier",
name: String(M.value)
};
});
AST_Boolean.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast);
AST_Null.DEFMETHOD("to_mozilla_ast", AST_Constant.prototype.to_mozilla_ast);
AST_Hole.DEFMETHOD("to_mozilla_ast", function To_Moz_ArrayHole() { return null });
AST_Block.DEFMETHOD("to_mozilla_ast", AST_BlockStatement.prototype.to_mozilla_ast);
AST_Lambda.DEFMETHOD("to_mozilla_ast", AST_Function.prototype.to_mozilla_ast);
/* -----[ tools ]----- */
function my_start_token(moznode) {
var loc = moznode.loc, start = loc && loc.start;
var range = moznode.range;
return new AST_Token({
file : loc && loc.source,
line : start && start.line,
col : start && start.column,
pos : range ? range[0] : moznode.start,
endline : start && start.line,
endcol : start && start.column,
endpos : range ? range[0] : moznode.start
});
};
function my_end_token(moznode) {
var loc = moznode.loc, end = loc && loc.end;
var range = moznode.range;
return new AST_Token({
file : loc && loc.source,
line : end && end.line,
col : end && end.column,
pos : range ? range[1] : moznode.end,
endline : end && end.line,
endcol : end && end.column,
endpos : range ? range[1] : moznode.end
});
};
function map(moztype, mytype, propmap) {
var moz_to_me = "function From_Moz_" + moztype + "(M){\n";
moz_to_me += "return new " + mytype.name + "({\n" +
"start: my_start_token(M),\n" +
"end: my_end_token(M)";
var me_to_moz = "function To_Moz_" + moztype + "(M){\n";
me_to_moz += "return {\n" +
"type: " + JSON.stringify(moztype);
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[1], how = m[2], my = m[3];
moz_to_me += ",\n" + my + ": ";
me_to_moz += ",\n" + moz + ": ";
switch (how) {
case "@":
moz_to_me += "M." + moz + ".map(from_moz)";
me_to_moz += "M." + my + ".map(to_moz)";
break;
case ">":
moz_to_me += "from_moz(M." + moz + ")";
me_to_moz += "to_moz(M." + my + ")";
break;
case "=":
moz_to_me += "M." + moz;
me_to_moz += "M." + my;
break;
case "%":
moz_to_me += "from_moz(M." + moz + ").body";
me_to_moz += "to_moz_block(M)";
break;
default:
throw new Error("Can't understand operator in propmap: " + prop);
}
});
moz_to_me += "\n})\n}";
me_to_moz += "\n}\n}";
//moz_to_me = parse(moz_to_me).print_to_string({ beautify: true });
//me_to_moz = parse(me_to_moz).print_to_string({ beautify: true });
//console.log(moz_to_me);
moz_to_me = new Function("my_start_token", "my_end_token", "from_moz", "return(" + moz_to_me + ")")(
my_start_token, my_end_token, from_moz
);
me_to_moz = new Function("to_moz", "to_moz_block", "return(" + me_to_moz + ")")(
to_moz, to_moz_block
);
MOZ_TO_ME[moztype] = moz_to_me;
def_to_moz(mytype, me_to_moz);
};
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;
};
function set_moz_loc(mynode, moznode, myparent) {
var start = mynode.start;
var end = mynode.end;
if (start.pos != null && end.endpos != null) {
moznode.range = [start.pos, end.endpos];
}
if (start.line) {
moznode.loc = {
start: {line: start.line, column: start.col},
end: end.endline ? {line: end.endline, column: end.endcol} : null
};
if (start.file) {
moznode.loc.source = start.file;
}
}
return moznode;
};
function def_to_moz(mytype, handler) {
mytype.DEFMETHOD("to_mozilla_ast", function() {
return set_moz_loc(this, handler(this));
});
};
function to_moz(node) {
return node != null ? node.to_mozilla_ast() : null;
};
function to_moz_block(node) {
return {
type: "BlockStatement",
body: node.body.map(to_moz)
};
};
})();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,221 @@
/***********************************************************************
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 find_builtins() {
var a = [];
[ Object, Array, Function, Number,
String, Boolean, Error, Math,
Date, RegExp
].forEach(function(ctor){
Object.getOwnPropertyNames(ctor).map(add);
if (ctor.prototype) {
Object.getOwnPropertyNames(ctor.prototype).map(add);
}
});
function add(name) {
push_uniq(a, name);
}
return a;
}
function mangle_properties(ast, options) {
options = defaults(options, {
reserved : null,
cache : null,
only_cache : false,
regex : null
});
var reserved = options.reserved;
if (reserved == null)
reserved = find_builtins();
var cache = options.cache;
if (cache == null) {
cache = {
cname: -1,
props: new Dictionary()
};
}
var regex = options.regex;
var names_to_mangle = [];
// step 1: find candidates to mangle
ast.walk(new TreeWalker(function(node){
if (node instanceof AST_ObjectKeyVal) {
add(node.key);
}
else if (node instanceof AST_ObjectProperty) {
// setter or getter, since KeyVal is handled above
add(node.key.name);
}
else if (node instanceof AST_Dot) {
if (this.parent() instanceof AST_Assign) {
add(node.property);
}
}
else if (node instanceof AST_Sub) {
if (this.parent() instanceof AST_Assign) {
addStrings(node.property);
}
}
}));
// step 2: transform the tree, renaming properties
return ast.transform(new TreeTransformer(function(node){
if (node instanceof AST_ObjectKeyVal) {
if (should_mangle(node.key)) {
node.key = mangle(node.key);
}
}
else if (node instanceof AST_ObjectProperty) {
// setter or getter
if (should_mangle(node.key.name)) {
node.key.name = mangle(node.key.name);
}
}
else if (node instanceof AST_Dot) {
if (should_mangle(node.property)) {
node.property = mangle(node.property);
}
}
else if (node instanceof AST_Sub) {
node.property = mangleStrings(node.property);
}
// else if (node instanceof AST_String) {
// if (should_mangle(node.value)) {
// AST_Node.warn(
// "Found \"{prop}\" property candidate for mangling in an arbitrary string [{file}:{line},{col}]", {
// file : node.start.file,
// line : node.start.line,
// col : node.start.col,
// prop : node.value
// }
// );
// }
// }
}));
// only function declarations after this line
function can_mangle(name) {
if (reserved.indexOf(name) >= 0) return false;
if (options.only_cache) {
return cache.props.has(name);
}
if (/^[0-9.]+$/.test(name)) return false;
return true;
}
function should_mangle(name) {
if (regex && !regex.test(name)) return false;
if (reserved.indexOf(name) >= 0) return false;
return cache.props.has(name)
|| names_to_mangle.indexOf(name) >= 0;
}
function add(name) {
if (can_mangle(name))
push_uniq(names_to_mangle, name);
}
function mangle(name) {
var mangled = cache.props.get(name);
if (!mangled) {
do {
mangled = base54(++cache.cname);
} while (!can_mangle(mangled));
cache.props.set(name, mangled);
}
return mangled;
}
function addStrings(node) {
var out = {};
try {
(function walk(node){
node.walk(new TreeWalker(function(node){
if (node instanceof AST_Seq) {
walk(node.cdr);
return true;
}
if (node instanceof AST_String) {
add(node.value);
return true;
}
if (node instanceof AST_Conditional) {
walk(node.consequent);
walk(node.alternative);
return true;
}
throw out;
}));
})(node);
} catch(ex) {
if (ex !== out) throw ex;
}
}
function mangleStrings(node) {
return node.transform(new TreeTransformer(function(node){
if (node instanceof AST_Seq) {
node.cdr = mangleStrings(node.cdr);
}
else if (node instanceof AST_String) {
if (should_mangle(node.value)) {
node.value = mangle(node.value);
}
}
else if (node instanceof AST_Conditional) {
node.consequent = mangleStrings(node.consequent);
node.alternative = mangleStrings(node.alternative);
}
return node;
}));
}
}

View File

@@ -0,0 +1,601 @@
/***********************************************************************
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) {
if (!options) options = {};
return (this.global && !options.toplevel)
|| this.undeclared
|| (!options.eval && (this.scope.uses_eval || this.scope.uses_with))
|| (options.keep_fnames
&& (this.orig[0] instanceof AST_SymbolLambda
|| this.orig[0] instanceof AST_SymbolDefun));
},
mangle: function(options) {
var cache = options.cache && options.cache.props;
if (this.global && cache && cache.has(this.name)) {
this.mangled_name = cache.get(this.name);
}
else 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);
if (this.global && cache) {
cache.set(this.name, this.mangled_name);
}
}
}
};
AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){
options = defaults(options, {
screw_ie8: false,
cache: null
});
// 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);
if (options.cache) {
this.cname = options.cache.cname;
}
});
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,
keep_fnames : 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 = [];
if (options.cache) {
this.globals.each(function(symbol){
if (options.except.indexOf(symbol.name) < 0) {
to_mangle.push(symbol);
}
});
}
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;
}
if (options.screw_ie8 && node instanceof AST_SymbolCatch) {
to_mangle.push(node.definition());
return;
}
});
this.walk(tw);
to_mangle.forEach(function(def){ def.mangle(options) });
if (options.cache) {
options.cache.cname = this.cname;
}
});
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;
num++;
do {
num--;
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 instanceof AST_SymbolCatch)
&& 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);
});

Some files were not shown because too many files have changed in this diff Show More