code:
# Consolidate.js

  Template engine consolidation library.

## Installation

    $ npm install consolidate

## Supported template engines

  - [atpl](https://github.com/soywiz/atpl.js)
  - [bracket](https://github.com/danlevan/bracket-template)
  - [doT.js](https://github.com/olado/doT) [(website)](http://olado.github.io/doT/)
  - [dust (unmaintained)](https://github.com/akdubya/dustjs) [(website)](http://akdubya.github.com/dustjs/)
  - [dustjs-linkedin (maintained fork of dust)](https://github.com/linkedin/dustjs) [(website)](http://linkedin.github.io/dustjs/)
  - [eco](https://github.com/sstephenson/eco)
  - [ect](https://github.com/baryshev/ect) [(website)](http://ectjs.com/)
  - [ejs](https://github.com/mde/ejs) [(website)](http://ejs.co/)
  - [haml](https://github.com/visionmedia/haml.js)
  - [haml-coffee](https://github.com/9elements/haml-coffee)
  - [hamlet](https://github.com/gregwebs/hamlet.js)
  - [handlebars](https://github.com/wycats/handlebars.js/) [(website)](http://handlebarsjs.com/)
  - [hogan](https://github.com/twitter/hogan.js) [(website)](http://twitter.github.com/hogan.js/)
  - [htmling](https://github.com/codemix/htmling)
  - [jade](https://github.com/visionmedia/jade) [(website)](http://jade-lang.com/)
  - [jazz](https://github.com/shinetech/jazz)
  - [jqtpl](https://github.com/kof/jqtpl)
  - [JUST](https://github.com/baryshev/just)
  - [liquor](https://github.com/chjj/liquor)
  - [lodash](https://github.com/bestiejs/lodash) [(website)](http://lodash.com/)
  - [marko](https://github.com/marko-js/marko) [(website)](http://markojs.com)
  - [mote](https://github.com/satchmorun/mote) [(website)](http://satchmorun.github.io/mote/)
  - [mustache](https://github.com/janl/mustache.js)
  - [nunjucks](https://github.com/mozilla/nunjucks) [(website)](https://mozilla.github.io/nunjucks)
  - [plates](https://github.com/flatiron/plates)
  - [pug (formerly jade)](https://github.com/pugjs/pug) [(website)](http://jade-lang.com/)
  - [QEJS](https://github.com/jepso/QEJS)
  - [ractive](https://github.com/Rich-Harris/Ractive)
  - [react](https://github.com/facebook/react)
  - [slm](https://github.com/slm-lang/slm)
  - [swig (unmaintained)](https://github.com/paularmstrong/swig)
  - [swig (maintained fork)](https://github.com/node-swig/swig-templates)
  - [teacup](https://github.com/goodeggs/teacup)
  - [templayed](http://archan937.github.com/templayed.js/)
  - [twig](https://github.com/justjohn/twig.js)
  - [liquid](https://github.com/leizongmin/tinyliquid) [(website)](http://liquidmarkup.org/)
  - [toffee](https://github.com/malgorithms/toffee)
  - [underscore](https://github.com/documentcloud/underscore) [(website)](http://underscorejs.org/#template)
  - [vash](https://github.com/kirbysayshi/vash)
  - [walrus](https://github.com/jeremyruppel/walrus) [(website)](http://documentup.com/jeremyruppel/walrus/)
  - [whiskers](https://github.com/gsf/whiskers.js)

__NOTE__: you must still install the engines you wish to use, add them to your package.json dependencies.

## API

  All templates supported by this library may be rendered using the signature `(path[, locals], callback)` as shown below, which happens to be the signature that Express 3.x supports so any of these engines may be used within Express.

__NOTE__: All this example code uses cons.swig for the swig template engine. Replace swig with whatever templating you are using. For example, use cons.hogan for hogan.js, cons.jade for jade, etc. `console.log(cons)` for the full list of identifiers.

```js
var cons = require('consolidate');
cons.swig('views/page.html', { user: 'tobi' }, function(err, html){
  if (err) throw err;
  console.log(html);
});
```

  Or without options / local variables:

```js
var cons = require('consolidate');
cons.swig('views/page.html', function(err, html){
  if (err) throw err;
  console.log(html);
});
```

  To dynamically pass the engine, simply use the subscript operator and a variable:

```js
var cons = require('consolidate')
  , name = 'swig';

cons[name]('views/page.html', { user: 'tobi' }, function(err, html){
  if (err) throw err;
  console.log(html);
});
```

### Promises

  Additionally, all templates optionally return a promise if no callback function is provided. The promise represents the eventual result of the template function which will either resolve to a string, compiled from the template, or be rejected. Promises expose a `then` method which registers callbacks to receive the promise’s eventual value and a `catch` method which the reason why the promise could not be fulfilled. Promises allow more synchronous-like code structure and solve issues like race conditions.

```js
var cons = require('consolidate');

cons.swig('views/page.html', { user: 'tobi' })
  .then(function (html) {
    console.log(html);
  })
  .catch(function (err) {
    throw err;
  });
```

## Caching

 To enable caching simply pass `{ cache: true }`. Engines _may_ use this option to cache things reading the file contents, compiled `Function`s etc. Engines which do _not_ support this may simply ignore it. All engines that consolidate.js implements I/O for will cache the file contents, ideal for production environments.
 When using consolidate directly: `cons.swig('views/page.html', { user: 'tobi', cache:true }, callback);`
 Using Express 3 or higher: `app.locals.cache = true` or set NODE_ENV to 'production' and Express will do this for you.

## Express 3.x example

```js
var express = require('express')
  , cons = require('consolidate')
  , app = express();

// assign the swig engine to .html files
app.engine('html', cons.swig);

// set .html as the default extension
app.set('view engine', 'html');
app.set('views', __dirname + '/views');

var users = [];
users.push({ name: 'tobi' });
users.push({ name: 'loki' });
users.push({ name: 'jane' });

app.get('/', function(req, res){
  res.render('index', {
    title: 'Consolidate.js'
  });
});

app.get('/users', function(req, res){
  res.render('users', {
    title: 'Users',
    users: users
  });
});

app.listen(3000);
console.log('Express server listening on port 3000');
```

## Template Engine Instances

Template engines are exposed via the `cons.requires` object, but they are not instantiated until you've called the `cons[engine].render()` method. You can instantiate them manually beforehand if you want to add filters, globals, mixins, or other engine features.

```js
var cons = require('consolidate'),
  nunjucks = require('nunjucks');

// add nunjucks to requires so filters can be
// added and the same instance will be used inside the render method
cons.requires.nunjucks = nunjucks.configure();

cons.requires.nunjucks.addFilter('foo', function () {
  return 'bar';
});
```

## Notes

* If you're using Nunjucks, please take a look at the `exports.nunjucks.render` function in `lib.consolidate.js`.  You can pass your own engine/environment via `options.nunjucksEnv`, or if you want to support Express you can pass `options.settings.views`, or if you have another use case, pass `options.nunjucks` (see the code for more insight).
* You can pass **partials** with `options.partials`
* For using **template inheritance** with nunjucks, you can pass a loader
  with `options.loader`.
* To use **filters** with tinyliquid, use `options.filters` and specify an array of properties, each of which is a named filter function. A filter function takes a string as a parameter and returns a modified version of it.
* To use **custom tags** with tinyliquid, use `options.customTags` to specify an array of tag functions that follow the tinyliquid [custom tag](https://github.com/leizongmin/tinyliquid/wiki/Custom-Tag) definition.
* The default directory used with the **include** tag with tinyliquid is the current working directory. To override this, use `options.includeDir`.
* `React` To render content into a html base template (eg. `index.html` of your React app), pass the path of the template with `options.base`.

## Running tests

  Install dev deps:

    $ npm install -d

  Run the tests:

    $ make test

## License

(The MIT License)

Copyright (c) 2011-2016 TJ Holowaychuk <tj@vision-media.ca>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


code:
'use strict';
/*
 * Engines which do not support caching of their file contents
 * should use the `read()` function defined in consolidate.js
 * On top of this, when an engine compiles to a `Function`,
 * these functions should either be cached within consolidate.js
 * or the engine itself via `options.cache`. This will allow
 * users and frameworks to pass `options.cache = true` for
 * `NODE_ENV=production`, however edit the file(s) without
 * re-loading the application in development.
 */

/**
 * Module dependencies.
 */

var fs = require('fs');
var path = require('path');
var Promise = require('bluebird');

var join = path.join;
var resolve = path.resolve;
var extname = path.extname;
var dirname = path.dirname;
var isAbsolute = path.isAbsolute;

var readCache = {};

/**
 * Require cache.
 */

var cacheStore = {};

/**
 * Require cache.
 */

var requires = {};

/**
 * Clear the cache.
 *
 * @api public
 */

exports.clearCache = function() {
  readCache = {};
  cacheStore = {};
};

/**
 * Conditionally cache `compiled` template based
 * on the `options` filename and `.cache` boolean.
 *
 * @param {Object} options
 * @param {Function} compiled
 * @return {Function}
 * @api private
 */

function cache(options, compiled) {
  // cachable
  if (compiled && options.filename && options.cache) {
    delete readCache[options.filename];
    cacheStore[options.filename] = compiled;
    return compiled;
  }

  // check cache
  if (options.filename && options.cache) {
    return cacheStore[options.filename];
  }

  return compiled;
}

/**
 * Read `path` with `options` with
 * callback `(err, str)`. When `options.cache`
 * is true the template string will be cached.
 *
 * @param {String} options
 * @param {Function} cb
 * @api private
 */

function read(path, options, cb) {
  var str = readCache[path];
  var cached = options.cache && str && typeof str === 'string';

  // cached (only if cached is a string and not a compiled template function)
  if (cached) return cb(null, str);

  // read
  fs.readFile(path, 'utf8', function(err, str) {
    if (err) return cb(err);
    // remove extraneous utf8 BOM marker
    str = str.replace(/^\uFEFF/, '');
    if (options.cache) readCache[path] = str;
    cb(null, str);
  });
}

/**
 * Read `path` with `options` with
 * callback `(err, str)`. When `options.cache`
 * is true the partial string will be cached.
 *
 * @param {String} options
 * @param {Function} fn
 * @api private
 */

function readPartials(path, options, cb) {
  if (!options.partials) return cb();
  var partials = options.partials;
  var keys = Object.keys(partials);

  function next(index) {
    if (index === keys.length) return cb(null);
    var key = keys[index];
    var partialPath = partials[key];

    if (partialPath === undefined || partialPath === null || partialPath === false) {
      return next(++index);
    }

    var file;
    if (isAbsolute(partialPath)) {
      if (extname(partialPath) !== '') {
        file = partialPath;
      } else {
        file = join(partialPath + extname(path));
      }
    } else {
      file = join(dirname(path), partialPath + extname(path));
    }

    read(file, options, function(err, str) {
      if (err) return cb(err);
      options.partials[key] = str;
      next(++index);
    });
  }

  next(0);
}

/**
 * promisify
 */
function promisify(cb, fn) {
  return new Promise(function(resolve, reject) {
    cb = cb || function(err, html) {
      if (err) {
        return reject(err);
      }
      resolve(html);
    };
    fn(cb);
  });
}

/**
 * fromStringRenderer
 */

function fromStringRenderer(name) {
  return function(path, options, cb) {
    options.filename = path;

    return promisify(cb, function(cb) {
      readPartials(path, options, function(err) {
        if (err) return cb(err);
        if (cache(options)) {
          exports[name].render('', options, cb);
        } else {
          read(path, options, function(err, str) {
            if (err) return cb(err);
            exports[name].render(str, options, cb);
          });
        }
      });
    });
  };
}

/**
 * velocity support.
 */

exports.velocityjs = fromStringRenderer('velocityjs');

/**
 * velocity string support.
 */

exports.velocityjs.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.velocityjs || (requires.velocityjs = require('velocityjs'));
    try {
      options.locals = options;
      cb(null, engine.render(str, options).trimLeft());
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Liquid support.
 */

exports.liquid = fromStringRenderer('liquid');

/**
 * Liquid string support.
 */

/**
 * Note that in order to get filters and custom tags we've had to push
 * all user-defined locals down into @locals. However, just to make things
 * backwards-compatible, any property of `options` that is left after
 * processing and removing `locals`, `meta`, `filters`, `customTags` and
 * `includeDir` will also become a local.
 */

function _renderTinyliquid(engine, str, options, cb) {
  var context = engine.newContext();
  var k;

  /**
   * Note that there's a bug in the library that doesn't allow us to pass
   * the locals to newContext(), hence looping through the keys:
   */

  if (options.locals) {
    for (k in options.locals) {
      context.setLocals(k, options.locals[k]);
    }
    delete options.locals;
  }

  if (options.meta) {
    context.setLocals('page', options.meta);
    delete options.meta;
  }

  /**
   * Add any defined filters:
   */

  if (options.filters) {
    for (k in options.filters) {
      context.setFilter(k, options.filters[k]);
    }
    delete options.filters;
  }

  /**
   * Set up a callback for the include directory:
   */

  var includeDir = options.includeDir || process.cwd();

  context.onInclude(function(name, callback) {
    var extname = path.extname(name) ? '' : '.liquid';
    var filename = path.resolve(includeDir, name + extname);

    fs.readFile(filename, {encoding: 'utf8'}, function(err, data) {
      if (err) return callback(err);
      callback(null, engine.parse(data));
    });
  });
  delete options.includeDir;

  /**
   * The custom tag functions need to have their results pushed back
   * through the parser, so set up a shim before calling the provided
   * callback:
   */

  var compileOptions = {
    customTags: {}
  };

  if (options.customTags) {
    var tagFunctions = options.customTags;

    for (k in options.customTags) {
      /*Tell jshint there's no problem with having this function in the loop */
      /*jshint -W083 */
      compileOptions.customTags[k] = function(context, name, body) {
        var tpl = tagFunctions[name](body.trim());
        context.astStack.push(engine.parse(tpl));
      };
      /*jshint +W083 */
    }
    delete options.customTags;
  }

  /**
   * Now anything left in `options` becomes a local:
   */

  for (k in options) {
    context.setLocals(k, options[k]);
  }

  /**
   * Finally, execute the template:
   */

  var tmpl = cache(context) || cache(context, engine.compile(str, compileOptions));
  tmpl(context, cb);
}

exports.liquid.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.liquid;
    var Liquid;

    try {
      // set up tinyliquid engine
      engine = requires.liquid = require('tinyliquid');

      // use tinyliquid engine
      _renderTinyliquid(engine, str, options, cb);

      return;

    } catch (err) {

      // set up liquid-node engine
      try {
        Liquid = requires.liquid = require('liquid-node');
        engine = new Liquid.Engine();
      } catch (err) {
        throw err;
      }

    }

    // use liquid-node engine
    try {
      var locals = options.locals || {};

      if (options.meta) {
        locals.pages = options.meta;
        delete options.meta;
      }

      /**
       * Add any defined filters:
       */

      if (options.filters) {
        engine.registerFilters(options.filters);
        delete options.filters;
      }

      /**
       * Set up a callback for the include directory:
       */

      var includeDir = options.includeDir || process.cwd();
      engine.fileSystem = new Liquid.LocalFileSystem(includeDir, 'liquid');
      delete options.includeDir;

      /**
       * The custom tag functions need to have their results pushed back
       * through the parser, so set up a shim before calling the provided
       * callback:
       */

      if (options.customTags) {
        var tagFunctions = options.customTags;

        for (k in options.customTags) {
          engine.registerTag(k, tagFunctions[k]);
        }
        delete options.customTags;
      }

      /**
       * Now anything left in `options` becomes a local:
       */

      for (var k in options) {
        locals[k] = options[k];
      }

      /**
       * Finally, execute the template:
       */

      return engine
        .parseAndRender(str, locals)
        .nodeify(function(err, result) {
          if (err) {
            throw new Error(err);
          } else {
            return cb(null, result);
          }
        });

    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Jade support.
 */

exports.jade = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.jade;
    if (!engine) {
      try {
        engine = requires.jade = require('jade');
      } catch (err) {
        try {
          engine = requires.jade = require('then-jade');
        } catch (otherError) {
          throw err;
        }
      }
    }

    try {
      var tmpl = cache(options) || cache(options, engine.compileFile(path, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Jade string support.
 */

exports.jade.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.jade;
    if (!engine) {
      try {
        engine = requires.jade = require('jade');
      } catch (err) {
        try {
          engine = requires.jade = require('then-jade');
        } catch (otherError) {
          throw err;
        }
      }
    }

    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Dust support.
 */

exports.dust = fromStringRenderer('dust');

/**
 * Dust string support.
 */

exports.dust.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.dust;
    if (!engine) {
      try {
        engine = requires.dust = require('dust');
      } catch (err) {
        try {
          engine = requires.dust = require('dustjs-helpers');
        } catch (err) {
          engine = requires.dust = require('dustjs-linkedin');
        }
      }
    }

    var ext = 'dust';
    var views = '.';

    if (options) {
      if (options.ext) ext = options.ext;
      if (options.views) views = options.views;
      if (options.settings && options.settings.views) views = options.settings.views;
    }
    if (!options || (options && !options.cache)) engine.cache = {};

    engine.onLoad = function(path, callback) {
      if (extname(path) === '') path += '.' + ext;
      if (path[0] !== '/') path = views + '/' + path;
      read(path, options, callback);
    };

    try {
      var templateName;
      if (options.filename) {
        templateName = options.filename.replace(new RegExp('^' + views + '/'), '').replace(new RegExp('\\.' + ext), '');
      }

      var tmpl = cache(options) || cache(options, engine.compileFn(str, templateName));
      tmpl(options, cb);
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Swig support.
 */

exports.swig = fromStringRenderer('swig');

/**
 * Swig string support.
 */

exports.swig.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.swig;
    if (!engine) {
      try {
        engine = requires.swig = require('swig');
      } catch (err) {
        try {
          engine = requires.swig = require('swig-templates');
        } catch (otherError) {
          throw err;
        }
      }
    }

    try {
      if (options.cache === true) options.cache = 'memory';
      engine.setDefaults({ cache: options.cache });
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Atpl support.
 */

exports.atpl = fromStringRenderer('atpl');

/**
 * Atpl string support.
 */

exports.atpl.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.atpl || (requires.atpl = require('atpl'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Liquor support,
 */

exports.liquor = fromStringRenderer('liquor');

/**
 * Liquor string support.
 */

exports.liquor.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.liquor || (requires.liquor = require('liquor'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Twig support.
 */

exports.twig = fromStringRenderer('twig');

/**
 * Twig string support.
 */

exports.twig.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.twig || (requires.twig = require('twig').twig);
    var templateData = {
      data: str
    };
    try {
      var tmpl = cache(templateData) || cache(templateData, engine(templateData));
      cb(null, tmpl.render(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * EJS support.
 */

exports.ejs = fromStringRenderer('ejs');

/**
 * EJS string support.
 */

exports.ejs.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.ejs || (requires.ejs = require('ejs'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Eco support.
 */

exports.eco = fromStringRenderer('eco');

/**
 * Eco string support.
 */

exports.eco.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.eco || (requires.eco = require('eco'));
    try {
      cb(null, engine.render(str, options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Jazz support.
 */

exports.jazz = fromStringRenderer('jazz');

/**
 * Jazz string support.
 */

exports.jazz.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.jazz || (requires.jazz = require('jazz'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      tmpl.eval(options, function(str) {
        cb(null, str);
      });
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * JQTPL support.
 */

exports.jqtpl = fromStringRenderer('jqtpl');

/**
 * JQTPL string support.
 */

exports.jqtpl.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.jqtpl || (requires.jqtpl = require('jqtpl'));
    try {
      engine.template(str, str);
      cb(null, engine.tmpl(str, options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Haml support.
 */

exports.haml = fromStringRenderer('haml');

/**
 * Haml string support.
 */

exports.haml.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.haml || (requires.haml = require('hamljs'));
    try {
      options.locals = options;
      cb(null, engine.render(str, options).trimLeft());
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Hamlet support.
 */

exports.hamlet = fromStringRenderer('hamlet');

/**
 * Hamlet string support.
 */

exports.hamlet.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.hamlet || (requires.hamlet = require('hamlet'));
    try {
      options.locals = options;
      cb(null, engine.render(str, options).trimLeft());
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Whiskers support.
 */

exports.whiskers = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
    engine.__express(path, options, cb);
  });
};

/**
 * Whiskers string support.
 */

exports.whiskers.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.whiskers || (requires.whiskers = require('whiskers'));
    try {
      cb(null, engine.render(str, options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Coffee-HAML support.
 */

exports['haml-coffee'] = fromStringRenderer('haml-coffee');

/**
 * Coffee-HAML string support.
 */

exports['haml-coffee'].render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires['haml-coffee'] || (requires['haml-coffee'] = require('haml-coffee'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Hogan support.
 */

exports.hogan = fromStringRenderer('hogan');

/**
 * Hogan string support.
 */

exports.hogan.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.hogan || (requires.hogan = require('hogan.js'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl.render(options, options.partials));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * templayed.js support.
 */

exports.templayed = fromStringRenderer('templayed');

/**
 * templayed.js string support.
 */

exports.templayed.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.templayed || (requires.templayed = require('templayed'));
    try {
      var tmpl = cache(options) || cache(options, engine(str));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Handlebars support.
 */

exports.handlebars = fromStringRenderer('handlebars');

/**
 * Handlebars string support.
 */

exports.handlebars.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.handlebars || (requires.handlebars = require('handlebars'));
    try {
      for (var partial in options.partials) {
        engine.registerPartial(partial, options.partials[partial]);
      }
      for (var helper in options.helpers) {
        engine.registerHelper(helper, options.helpers[helper]);
      }
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Underscore support.
 */

exports.underscore = fromStringRenderer('underscore');

/**
 * Underscore string support.
 */

exports.underscore.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.underscore || (requires.underscore = require('underscore'));
    try {
      for (var partial in options.partials) {
        options.partials[partial] = engine.template(options.partials[partial]);
      }
      var tmpl = cache(options) || cache(options, engine.template(str, null, options));
      cb(null, tmpl(options).replace(/\n$/, ''));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Lodash support.
 */

exports.lodash = fromStringRenderer('lodash');

/**
 * Lodash string support.
 */

exports.lodash.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.lodash || (requires.lodash = require('lodash'));
    try {
      var tmpl = cache(options) || cache(options, engine.template(str, options));
      cb(null, tmpl(options).replace(/\n$/, ''));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Pug support. (formerly Jade)
 */

exports.pug = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.pug;
    if (!engine) {
      try {
        engine = requires.pug = require('pug');
      } catch (err) {
        try {
          engine = requires.pug = require('then-pug');
        } catch (otherError) {
          throw err;
        }
      }
    }

    try {
      var tmpl = cache(options) || cache(options, engine.compileFile(path, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Pug string support.
 */

exports.pug.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.pug;
    if (!engine) {
      try {
        engine = requires.pug = require('pug');
      } catch (err) {
        try {
          engine = requires.pug = require('then-pug');
        } catch (otherError) {
          throw err;
        }
      }
    }

    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * QEJS support.
 */

exports.qejs = fromStringRenderer('qejs');

/**
 * QEJS string support.
 */

exports.qejs.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    try {
      var engine = requires.qejs || (requires.qejs = require('qejs'));
      engine.render(str, options).then(function(result) {
        cb(null, result);
      }, function(err) {
        cb(err);
      }).done();
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Walrus support.
 */

exports.walrus = fromStringRenderer('walrus');

/**
 * Walrus string support.
 */

exports.walrus.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.walrus || (requires.walrus = require('walrus'));
    try {
      var tmpl = cache(options) || cache(options, engine.parse(str));
      cb(null, tmpl.compile(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Mustache support.
 */

exports.mustache = fromStringRenderer('mustache');

/**
 * Mustache string support.
 */

exports.mustache.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.mustache || (requires.mustache = require('mustache'));
    try {
      cb(null, engine.to_html(str, options, options.partials));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Just support.
 */

exports.just = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.just;
    if (!engine) {
      var JUST = require('just');
      engine = requires.just = new JUST();
    }
    engine.configure({ useCache: options.cache });
    engine.render(path, options, cb);
  });
};

/**
 * Just string support.
 */

exports.just.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var JUST = require('just');
    var engine = new JUST({ root: { page: str }});
    engine.render('page', options, cb);
  });
};

/**
 * ECT support.
 */

exports.ect = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.ect;
    if (!engine) {
      var ECT = require('ect');
      engine = requires.ect = new ECT(options);
    }
    engine.configure({ cache: options.cache });
    engine.render(path, options, cb);
  });
};

/**
 * ECT string support.
 */

exports.ect.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var ECT = require('ect');
    var engine = new ECT({ root: { page: str }});
    engine.render('page', options, cb);
  });
};

/**
 * mote support.
 */

exports.mote = fromStringRenderer('mote');

/**
 * mote string support.
 */

exports.mote.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.mote || (requires.mote = require('mote'));
    try {
      var tmpl = cache(options) || cache(options, engine.compile(str));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Toffee support.
 */

exports.toffee = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var toffee = requires.toffee || (requires.toffee = require('toffee'));
    toffee.__consolidate_engine_render(path, options, cb);
  });
};

/**
 * Toffee string support.
 */

exports.toffee.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.toffee || (requires.toffee = require('toffee'));
    try {
      engine.str_render(str, options, cb);
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * doT support.
 */

exports.dot = fromStringRenderer('dot');

/**
 * doT string support.
 */

exports.dot.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.dot || (requires.dot = require('dot'));
    var extend = (requires.extend || (requires.extend = require('util')._extend));
    try {
      var settings = {};
      settings = extend(settings, engine.templateSettings);
      settings = extend(settings, options ? options.dot : {});
      var tmpl = cache(options) || cache(options, engine.template(str, settings, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * bracket support.
 */

exports.bracket = fromStringRenderer('bracket');

/**
 * bracket string support.
 */

exports.bracket.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.bracket || (requires.bracket = require('bracket-template'));
    try {
      var tmpl = cache(options) || cache(options, engine.default.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Ractive support.
 */

exports.ractive = fromStringRenderer('ractive');

/**
 * Ractive string support.
 */

exports.ractive.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var Engine = requires.ractive || (requires.ractive = require('ractive'));

    var template = cache(options) || cache(options, Engine.parse(str));
    options.template = template;

    if (options.data === null || options.data === undefined) {
      var extend = (requires.extend || (requires.extend = require('util')._extend));

      // Shallow clone the options object
      options.data = extend({}, options);

      // Remove consolidate-specific properties from the clone
      var i;
      var length;
      var properties = ['template', 'filename', 'cache', 'partials'];
      for (i = 0, length = properties.length; i < length; i++) {
        var property = properties[i];
        delete options.data[property];
      }
    }

    try {
      cb(null, new Engine(options).toHTML());
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Nunjucks support.
 */

exports.nunjucks = fromStringRenderer('nunjucks');

/**
 * Nunjucks string support.
 */

exports.nunjucks.render = function(str, options, cb) {
  return promisify(cb, function(cb) {

    try {

      var engine = options.nunjucksEnv || requires.nunjucks || (requires.nunjucks = require('nunjucks'));

      var env = engine;

      // deprecated fallback support for express
      // <https://github.com/tj/consolidate.js/pull/152>
      // <https://github.com/tj/consolidate.js/pull/224>
      if (options.settings && options.settings.views) {
        env = engine.configure(options.settings.views);
      } else if (options.nunjucks && options.nunjucks.configure) {
        env = engine.configure.apply(engine, options.nunjucks.configure);
      }

      //
      // because `renderString` does not initiate loaders
      // we must manually create a loader for it based off
      // either `options.settings.views` or `options.nunjucks` or `options.nunjucks.root`
      //
      // <https://github.com/mozilla/nunjucks/issues/730>
      // <https://github.com/crocodilejs/node-email-templates/issues/182>
      //

      // so instead we simply check if we passed a custom loader
      // otherwise we create a simple file based loader
      if (options.loader) {
        env = new engine.Environment(options.loader);
      } else if (options.settings && options.settings.views) {
        env = new engine.Environment(
          new engine.FileSystemLoader(options.settings.views)
        );
      } else if (options.nunjucks && options.nunjucks.loader) {
        if (typeof options.nunjucks.loader === 'string') {
          env = new engine.Environment(new engine.FileSystemLoader(options.nunjucks.loader));
        } else {
          env = new engine.Environment(
            new engine.FileSystemLoader(
              options.nunjucks.loader[0],
              options.nunjucks.loader[1]
            )
          );
        }
      }

      env.renderString(str, options, cb);
    } catch (err) {
      throw cb(err);
    }
  });
};

/**
 * HTMLing support.
 */

exports.htmling = fromStringRenderer('htmling');

/**
 * HTMLing string support.
 */

exports.htmling.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.htmling || (requires.htmling = require('htmling'));
    try {
      var tmpl = cache(options) || cache(options, engine.string(str));
      cb(null, tmpl.render(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 *  Rendering function
 */
function requireReact(module, filename) {
  var babel = requires.babel || (requires.babel = require('babel-core'));

  var compiled = babel.transformFileSync(filename, { presets: [ 'react' ] }).code;

  return module._compile(compiled, filename);
}

exports.requireReact = requireReact;

/**
 *  Converting a string into a node module.
 */
function requireReactString(src, filename) {
  var babel = requires.babel || (requires.babel = require('babel-core'));

  if (!filename) filename = '';
  var m = new module.constructor();
  filename = filename || '';

  // Compile Using React
  var compiled = babel.transform(src, { presets: [ 'react' ] }).code;

  // Compile as a module
  m.paths = module.paths;
  m._compile(compiled, filename);

  return m.exports;
}

/**
 * A naive helper to replace {{tags}} with options.tags content
 */
function reactBaseTmpl(data, options) {

  var exp;
  var regex;

  // Iterates through the keys in file object
  // and interpolate / replace {{key}} with it's value
  for (var k in options) {
    if (options.hasOwnProperty(k)) {
      exp = '{{' + k + '}}';
      regex = new RegExp(exp, 'g');
      if (data.match(regex)) {
        data = data.replace(regex, options[k]);
      }
    }
  }

  return data;
}

/**
* Plates Support.
*/

exports.plates = fromStringRenderer('plates');

/**
* Plates string support.
*/

exports.plates.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.plates || (requires.plates = require('plates'));
    var map = options.map || undefined;
    try {
      var tmpl = engine.bind(str, options, map);
      cb(null, tmpl);
    } catch (err) {
      cb(err);
    }
  });
};

/**
 *  The main render parser for React bsaed templates
 */
function reactRenderer(type) {

  if (require.extensions) {

    // Ensure JSX is transformed on require
    if (!require.extensions['.jsx']) {
      require.extensions['.jsx'] = requireReact;
    }

    // Supporting .react extension as well as test cases
    // Using .react extension is not recommended.
    if (!require.extensions['.react']) {
      require.extensions['.react'] = requireReact;
    }

  }

  // Return rendering fx
  return function(str, options, cb) {
    return promisify(cb, function(cb) {
      // React Import
      var ReactDOM = requires.ReactDOM || (requires.ReactDOM = require('react-dom/server'));
      var react = requires.react || (requires.react = require('react'));

      // Assign HTML Base
      var base = options.base;
      delete options.base;

      var enableCache = options.cache;
      delete options.cache;

      var isNonStatic = options.isNonStatic;
      delete options.isNonStatic;

      // Start Conversion
      try {

        var Code;
        var Factory;

        var baseStr;
        var content;
        var parsed;

        if (!cache(options)) {
          // Parsing
          if (type === 'path') {
            var path = resolve(str);
            delete require.cache[path];
            Code = require(path);
          } else {
            Code = requireReactString(str);
          }
          Factory = cache(options, react.createFactory(Code));

        } else {
          Factory = cache(options);
        }

        parsed = new Factory(options);
        content = (isNonStatic) ? ReactDOM.renderToString(parsed) : ReactDOM.renderToStaticMarkup(parsed);

        if (base) {
          baseStr = readCache[str] || fs.readFileSync(resolve(base), 'utf8');

          if (enableCache) {
            readCache[str] = baseStr;
          }

          options.content = content;
          content = reactBaseTmpl(baseStr, options);
        }

        cb(null, content);

      } catch (err) {
        cb(err);
      }
    });
  };
}

/**
 * React JS Support
 */
exports.react = reactRenderer('path');

/**
 * React JS string support.
 */
exports.react.render = reactRenderer('string');

/**
 * ARC-templates support.
 */

exports['arc-templates'] = fromStringRenderer('arc-templates');

/**
 * ARC-templates string support.
 */

exports['arc-templates'].render = function(str, options, cb) {
  var readFileWithOptions = Promise.promisify(read);
  var consolidateFileSystem = {};
  consolidateFileSystem.readFile = function(path) {
    return readFileWithOptions(path, options);
  };

  return promisify(cb, function(cb) {
    try {
      var engine = requires['arc-templates'];
      if (!engine) {
        var Engine = require('arc-templates/dist/es5');
        engine = requires['arc-templates'] = new Engine({ filesystem: consolidateFileSystem });
      }

      var compiler = cache(options) || cache(options, engine.compileString(str, options.filename));
      compiler.then(function(func) { return func(options); })
        .then(function(result) { cb(null, result.content); })
        .catch(cb);
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Vash support
 */
exports.vash = fromStringRenderer('vash');

/**
 * Vash string support
 */
exports.vash.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.vash || (requires.vash = require('vash'));

    try {
      // helper system : https://github.com/kirbysayshi/vash#helper-system
      if (options.helpers) {
        for (var key in options.helpers) {
          if (!options.helpers.hasOwnProperty(key) || typeof options.helpers[key] !== 'function') {
            continue;
          }
          engine.helpers[key] = options.helpers[key];
        }
      }

      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      tmpl(options, function sealLayout(err, ctx) {
        if (err) cb(err);
        ctx.finishLayout();
        cb(null, ctx.toString().replace(/\n$/, ''));
      });
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Slm support.
 */

exports.slm = fromStringRenderer('slm');

/**
 * Slm string support.
 */

exports.slm.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.slm || (requires.slm = require('slm'));

    try {
      var tmpl = cache(options) || cache(options, engine.compile(str, options));
      cb(null, tmpl(options));
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Marko support.
 */

exports.marko = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.marko || (requires.marko = require('marko'));
    options.writeToDisk = !!options.cache;

    try {
      var tmpl = cache(options) || cache(options, engine.load(path, options));
      tmpl.renderToString(options, cb);
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Marko string support.
 */

exports.marko.render = function(str, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.marko || (requires.marko = require('marko'));
    options.writeToDisk = !!options.cache;
    options.filename = options.filename || 'string.marko';

    try {
      var tmpl = cache(options) || cache(options, engine.load(options.filename, str, options));
      tmpl.renderToString(options, cb);
    } catch (err) {
      cb(err);
    }
  });
};

/**
 * Teacup support.
 */
exports.teacup = function(path, options, cb) {
  return promisify(cb, function(cb) {
    var engine = requires.teacup || (requires.teacup = require('teacup/lib/express'));
    require.extensions['.teacup'] = require.extensions['.coffee'];
    if (path[0] !== '/') {
      path = join(process.cwd(), path);
    }
    if (!options.cache) {
      var callback = cb;
      cb = function() {
        delete require.cache[path];
        callback.apply(this, arguments);
      };
    }
    engine.renderFile(path, options, cb);
  });
};

/**
 * Teacup string support.
 */
exports.teacup.render = function(str, options, cb) {
  var coffee = require('coffee-script');
  var vm = require('vm');
  var sandbox = {
    module: {exports: {}},
    require: require
  };
  return promisify(cb, function(cb) {
    vm.runInNewContext(coffee.compile(str), sandbox);
    var tmpl = sandbox.module.exports;
    cb(null, tmpl(options));
  });
};

/**
 * expose the instance of the engine
 */
exports.requires = requires;

license: mit


consolidate

description: Template engine consolidation library

author: TJ Holowaychuk <tj@vision-media.ca>

license: MIT

lint: eslint .

pretest: npm run lint

test: mocha

keywords: engine template view


code:
# content-disposition

[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]

Create and parse HTTP `Content-Disposition` header

## Installation

```sh
$ npm install content-disposition
```

## API

```js
var contentDisposition = require('content-disposition')
```

### contentDisposition(filename, options)

Create an attachment `Content-Disposition` header value using the given file name,
if supplied. The `filename` is optional and if no file name is desired, but you
want to specify `options`, set `filename` to `undefined`.

```js
res.setHeader('Content-Disposition', contentDisposition('∫ maths.pdf'))
```

**note** HTTP headers are of the ISO-8859-1 character set. If you are writing this
header through a means different from `setHeader` in Node.js, you'll want to specify
the `'binary'` encoding in Node.js.

#### Options

`contentDisposition` accepts these properties in the options object.

##### fallback

If the `filename` option is outside ISO-8859-1, then the file name is actually
stored in a supplemental field for clients that support Unicode file names and
a ISO-8859-1 version of the file name is automatically generated.

This specifies the ISO-8859-1 file name to override the automatic generation or
disables the generation all together, defaults to `true`.

  - A string will specify the ISO-8859-1 file name to use in place of automatic
    generation.
  - `false` will disable including a ISO-8859-1 file name and only include the
    Unicode version (unless the file name is already ISO-8859-1).
  - `true` will enable automatic generation if the file name is outside ISO-8859-1.

If the `filename` option is ISO-8859-1 and this option is specified and has a
different value, then the `filename` option is encoded in the extended field
and this set as the fallback field, even though they are both ISO-8859-1.

##### type

Specifies the disposition type, defaults to `"attachment"`. This can also be
`"inline"`, or any other value (all values except inline are treated like
`attachment`, but can convey additional information if both parties agree to
it). The type is normalized to lower-case.

### contentDisposition.parse(string)

```js
var disposition = contentDisposition.parse('attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt')
```

Parse a `Content-Disposition` header string. This automatically handles extended
("Unicode") parameters by decoding them and providing them under the standard
parameter name. This will return an object with the following properties (examples
are shown for the string `'attachment; filename="EURO rates.txt"; filename*=UTF-8\'\'%e2%82%ac%20rates.txt'`):

 - `type`: The disposition type (always lower case). Example: `'attachment'`

 - `parameters`: An object of the parameters in the disposition (name of parameter
   always lower case and extended versions replace non-extended versions). Example:
   `{filename: "€ rates.txt"}`

## Examples

### Send a file for download

```js
var contentDisposition = require('content-disposition')
var destroy = require('destroy')
var fs = require('fs')
var http = require('http')
var onFinished = require('on-finished')

var filePath = '/path/to/public/plans.pdf'

http.createServer(function onRequest (req, res) {
  // set headers
  res.setHeader('Content-Type', 'application/pdf')
  res.setHeader('Content-Disposition', contentDisposition(filePath))

  // send file
  var stream = fs.createReadStream(filePath)
  stream.pipe(res)
  onFinished(res, function () {
    destroy(stream)
  })
})
```

## Testing

```sh
$ npm test
```

## References

- [RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1][rfc-2616]
- [RFC 5987: Character Set and Language Encoding for Hypertext Transfer Protocol (HTTP) Header Field Parameters][rfc-5987]
- [RFC 6266: Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)][rfc-6266]
- [Test Cases for HTTP Content-Disposition header field (RFC 6266) and the Encodings defined in RFCs 2047, 2231 and 5987][tc-2231]

[rfc-2616]: https://tools.ietf.org/html/rfc2616
[rfc-5987]: https://tools.ietf.org/html/rfc5987
[rfc-6266]: https://tools.ietf.org/html/rfc6266
[tc-2231]: http://greenbytes.de/tech/tc2231/

## License

[MIT](LICENSE)

[npm-image]: https://img.shields.io/npm/v/content-disposition.svg
[npm-url]: https://npmjs.org/package/content-disposition
[node-version-image]: https://img.shields.io/node/v/content-disposition.svg
[node-version-url]: https://nodejs.org/en/download
[coveralls-image]: https://img.shields.io/coveralls/jshttp/content-disposition.svg
[coveralls-url]: https://coveralls.io/r/jshttp/content-disposition?branch=master
[downloads-image]: https://img.shields.io/npm/dm/content-disposition.svg
[downloads-url]: https://npmjs.org/package/content-disposition
[github-actions-ci-image]: https://img.shields.io/github/workflow/status/jshttp/content-disposition/ci/master?label=ci
[github-actions-ci-url]: https://github.com/jshttp/content-disposition?query=workflow%3Aci


code:
/*!
 * content-disposition
 * Copyright(c) 2014-2017 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict'

/**
 * Module exports.
 * @public
 */

module.exports = contentDisposition
module.exports.parse = parse

/**
 * Module dependencies.
 * @private
 */

var basename = require('path').basename
var Buffer = require('safe-buffer').Buffer

/**
 * RegExp to match non attr-char, *after* encodeURIComponent (i.e. not including "%")
 * @private
 */

var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g // eslint-disable-line no-control-regex

/**
 * RegExp to match percent encoding escape.
 * @private
 */

var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/
var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g

/**
 * RegExp to match non-latin1 characters.
 * @private
 */

var NON_LATIN1_REGEXP = /[^\x20-\x7e\xa0-\xff]/g

/**
 * RegExp to match quoted-pair in RFC 2616
 *
 * quoted-pair = "\" CHAR
 * CHAR        = <any US-ASCII character (octets 0 - 127)>
 * @private
 */

var QESC_REGEXP = /\\([\u0000-\u007f])/g // eslint-disable-line no-control-regex

/**
 * RegExp to match chars that must be quoted-pair in RFC 2616
 * @private
 */

var QUOTE_REGEXP = /([\\"])/g

/**
 * RegExp for various RFC 2616 grammar
 *
 * parameter     = token "=" ( token | quoted-string )
 * token         = 1*<any CHAR except CTLs or separators>
 * separators    = "(" | ")" | "<" | ">" | "@"
 *               | "," | ";" | ":" | "\" | <">
 *               | "/" | "[" | "]" | "?" | "="
 *               | "{" | "}" | SP | HT
 * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
 * qdtext        = <any TEXT except <">>
 * quoted-pair   = "\" CHAR
 * CHAR          = <any US-ASCII character (octets 0 - 127)>
 * TEXT          = <any OCTET except CTLs, but including LWS>
 * LWS           = [CRLF] 1*( SP | HT )
 * CRLF          = CR LF
 * CR            = <US-ASCII CR, carriage return (13)>
 * LF            = <US-ASCII LF, linefeed (10)>
 * SP            = <US-ASCII SP, space (32)>
 * HT            = <US-ASCII HT, horizontal-tab (9)>
 * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
 * OCTET         = <any 8-bit sequence of data>
 * @private
 */

var PARAM_REGEXP = /;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g // eslint-disable-line no-control-regex
var TEXT_REGEXP = /^[\x20-\x7e\x80-\xff]+$/
var TOKEN_REGEXP = /^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/

/**
 * RegExp for various RFC 5987 grammar
 *
 * ext-value     = charset  "'" [ language ] "'" value-chars
 * charset       = "UTF-8" / "ISO-8859-1" / mime-charset
 * mime-charset  = 1*mime-charsetc
 * mime-charsetc = ALPHA / DIGIT
 *               / "!" / "#" / "$" / "%" / "&"
 *               / "+" / "-" / "^" / "_" / "`"
 *               / "{" / "}" / "~"
 * language      = ( 2*3ALPHA [ extlang ] )
 *               / 4ALPHA
 *               / 5*8ALPHA
 * extlang       = *3( "-" 3ALPHA )
 * value-chars   = *( pct-encoded / attr-char )
 * pct-encoded   = "%" HEXDIG HEXDIG
 * attr-char     = ALPHA / DIGIT
 *               / "!" / "#" / "$" / "&" / "+" / "-" / "."
 *               / "^" / "_" / "`" / "|" / "~"
 * @private
 */

var EXT_VALUE_REGEXP = /^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/

/**
 * RegExp for various RFC 6266 grammar
 *
 * disposition-type = "inline" | "attachment" | disp-ext-type
 * disp-ext-type    = token
 * disposition-parm = filename-parm | disp-ext-parm
 * filename-parm    = "filename" "=" value
 *                  | "filename*" "=" ext-value
 * disp-ext-parm    = token "=" value
 *                  | ext-token "=" ext-value
 * ext-token        = <the characters in token, followed by "*">
 * @private
 */

var DISPOSITION_TYPE_REGEXP = /^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/ // eslint-disable-line no-control-regex

/**
 * Create an attachment Content-Disposition header.
 *
 * @param {string} [filename]
 * @param {object} [options]
 * @param {string} [options.type=attachment]
 * @param {string|boolean} [options.fallback=true]
 * @return {string}
 * @public
 */

function contentDisposition (filename, options) {
  var opts = options || {}

  // get type
  var type = opts.type || 'attachment'

  // get parameters
  var params = createparams(filename, opts.fallback)

  // format into string
  return format(new ContentDisposition(type, params))
}

/**
 * Create parameters object from filename and fallback.
 *
 * @param {string} [filename]
 * @param {string|boolean} [fallback=true]
 * @return {object}
 * @private
 */

function createparams (filename, fallback) {
  if (filename === undefined) {
    return
  }

  var params = {}

  if (typeof filename !== 'string') {
    throw new TypeError('filename must be a string')
  }

  // fallback defaults to true
  if (fallback === undefined) {
    fallback = true
  }

  if (typeof fallback !== 'string' && typeof fallback !== 'boolean') {
    throw new TypeError('fallback must be a string or boolean')
  }

  if (typeof fallback === 'string' && NON_LATIN1_REGEXP.test(fallback)) {
    throw new TypeError('fallback must be ISO-8859-1 string')
  }

  // restrict to file base name
  var name = basename(filename)

  // determine if name is suitable for quoted string
  var isQuotedString = TEXT_REGEXP.test(name)

  // generate fallback name
  var fallbackName = typeof fallback !== 'string'
    ? fallback && getlatin1(name)
    : basename(fallback)
  var hasFallback = typeof fallbackName === 'string' && fallbackName !== name

  // set extended filename parameter
  if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
    params['filename*'] = name
  }

  // set filename parameter
  if (isQuotedString || hasFallback) {
    params.filename = hasFallback
      ? fallbackName
      : name
  }

  return params
}

/**
 * Format object to Content-Disposition header.
 *
 * @param {object} obj
 * @param {string} obj.type
 * @param {object} [obj.parameters]
 * @return {string}
 * @private
 */

function format (obj) {
  var parameters = obj.parameters
  var type = obj.type

  if (!type || typeof type !== 'string' || !TOKEN_REGEXP.test(type)) {
    throw new TypeError('invalid type')
  }

  // start with normalized type
  var string = String(type).toLowerCase()

  // append parameters
  if (parameters && typeof parameters === 'object') {
    var param
    var params = Object.keys(parameters).sort()

    for (var i = 0; i < params.length; i++) {
      param = params[i]

      var val = param.substr(-1) === '*'
        ? ustring(parameters[param])
        : qstring(parameters[param])

      string += '; ' + param + '=' + val
    }
  }

  return string
}

/**
 * Decode a RFC 5987 field value (gracefully).
 *
 * @param {string} str
 * @return {string}
 * @private
 */

function decodefield (str) {
  var match = EXT_VALUE_REGEXP.exec(str)

  if (!match) {
    throw new TypeError('invalid extended field value')
  }

  var charset = match[1].toLowerCase()
  var encoded = match[2]
  var value

  // to binary string
  var binary = encoded.replace(HEX_ESCAPE_REPLACE_REGEXP, pdecode)

  switch (charset) {
    case 'iso-8859-1':
      value = getlatin1(binary)
      break
    case 'utf-8':
      value = Buffer.from(binary, 'binary').toString('utf8')
      break
    default:
      throw new TypeError('unsupported charset in extended field')
  }

  return value
}

/**
 * Get ISO-8859-1 version of string.
 *
 * @param {string} val
 * @return {string}
 * @private
 */

function getlatin1 (val) {
  // simple Unicode -> ISO-8859-1 transformation
  return String(val).replace(NON_LATIN1_REGEXP, '?')
}

/**
 * Parse Content-Disposition header string.
 *
 * @param {string} string
 * @return {object}
 * @public
 */

function parse (string) {
  if (!string || typeof string !== 'string') {
    throw new TypeError('argument string is required')
  }

  var match = DISPOSITION_TYPE_REGEXP.exec(string)

  if (!match) {
    throw new TypeError('invalid type format')
  }

  // normalize type
  var index = match[0].length
  var type = match[1].toLowerCase()

  var key
  var names = []
  var params = {}
  var value

  // calculate index to start at
  index = PARAM_REGEXP.lastIndex = match[0].substr(-1) === ';'
    ? index - 1
    : index

  // match parameters
  while ((match = PARAM_REGEXP.exec(string))) {
    if (match.index !== index) {
      throw new TypeError('invalid parameter format')
    }

    index += match[0].length
    key = match[1].toLowerCase()
    value = match[2]

    if (names.indexOf(key) !== -1) {
      throw new TypeError('invalid duplicate parameter')
    }

    names.push(key)

    if (key.indexOf('*') + 1 === key.length) {
      // decode extended value
      key = key.slice(0, -1)
      value = decodefield(value)

      // overwrite existing value
      params[key] = value
      continue
    }

    if (typeof params[key] === 'string') {
      continue
    }

    if (value[0] === '"') {
      // remove quotes and escapes
      value = value
        .substr(1, value.length - 2)
        .replace(QESC_REGEXP, '$1')
    }

    params[key] = value
  }

  if (index !== -1 && index !== string.length) {
    throw new TypeError('invalid parameter format')
  }

  return new ContentDisposition(type, params)
}

/**
 * Percent decode a single character.
 *
 * @param {string} str
 * @param {string} hex
 * @return {string}
 * @private
 */

function pdecode (str, hex) {
  return String.fromCharCode(parseInt(hex, 16))
}

/**
 * Percent encode a single character.
 *
 * @param {string} char
 * @return {string}
 * @private
 */

function pencode (char) {
  return '%' + String(char)
    .charCodeAt(0)
    .toString(16)
    .toUpperCase()
}

/**
 * Quote a string for HTTP.
 *
 * @param {string} val
 * @return {string}
 * @private
 */

function qstring (val) {
  var str = String(val)

  return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
}

/**
 * Encode a Unicode string for HTTP (RFC 5987).
 *
 * @param {string} val
 * @return {string}
 * @private
 */

function ustring (val) {
  var str = String(val)

  // percent encode as UTF-8
  var encoded = encodeURIComponent(str)
    .replace(ENCODE_URL_ATTR_CHAR_REGEXP, pencode)

  return 'UTF-8\'\'' + encoded
}

/**
 * Class for parsed Content-Disposition header for v8 optimization
 *
 * @public
 * @param {string} type
 * @param {object} parameters
 * @constructor
 */

function ContentDisposition (type, parameters) {
  this.type = type
  this.parameters = parameters
}


content-disposition

description: Create and parse Content-Disposition header

author: Douglas Christopher Wilson <doug@somethingdoug.com>

keywords: content-disposition http rfc6266 res

repository: jshttp/content-disposition

test: mocha --reporter spec --bail --check-leaks test/

test-ci: istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/

test-cov: istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/


code:
# content-type

[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-image]][node-url]
[![Build Status][ci-image]][ci-url]
[![Coverage Status][coveralls-image]][coveralls-url]

Create and parse HTTP Content-Type header according to RFC 7231

## Installation

```sh
$ npm install content-type
```

## API

```js
var contentType = require('content-type')
```

### contentType.parse(string)

```js
var obj = contentType.parse('image/svg+xml; charset=utf-8')
```

Parse a `Content-Type` header. This will return an object with the following
properties (examples are shown for the string `'image/svg+xml; charset=utf-8'`):

 - `type`: The media type (the type and subtype, always lower case).
   Example: `'image/svg+xml'`

 - `parameters`: An object of the parameters in the media type (name of parameter
   always lower case). Example: `{charset: 'utf-8'}`

Throws a `TypeError` if the string is missing or invalid.

### contentType.parse(req)

```js
var obj = contentType.parse(req)
```

Parse the `Content-Type` header from the given `req`. Short-cut for
`contentType.parse(req.headers['content-type'])`.

Throws a `TypeError` if the `Content-Type` header is missing or invalid.

### contentType.parse(res)

```js
var obj = contentType.parse(res)
```

Parse the `Content-Type` header set on the given `res`. Short-cut for
`contentType.parse(res.getHeader('content-type'))`.

Throws a `TypeError` if the `Content-Type` header is missing or invalid.

### contentType.format(obj)

```js
var str = contentType.format({
  type: 'image/svg+xml',
  parameters: { charset: 'utf-8' }
})
```

Format an object into a `Content-Type` header. This will return a string of the
content type for the given object with the following properties (examples are
shown that produce the string `'image/svg+xml; charset=utf-8'`):

 - `type`: The media type (will be lower-cased). Example: `'image/svg+xml'`

 - `parameters`: An object of the parameters in the media type (name of the
   parameter will be lower-cased). Example: `{charset: 'utf-8'}`

Throws a `TypeError` if the object contains an invalid type or parameter names.

## License

[MIT](LICENSE)

[ci-image]: https://badgen.net/github/checks/jshttp/content-type/master?label=ci
[ci-url]: https://github.com/jshttp/content-type/actions/workflows/ci.yml
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/content-type/master
[coveralls-url]: https://coveralls.io/r/jshttp/content-type?branch=master
[node-image]: https://badgen.net/npm/node/content-type
[node-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/content-type
[npm-url]: https://npmjs.org/package/content-type
[npm-version-image]: https://badgen.net/npm/v/content-type


code:
/*!
 * content-type
 * Copyright(c) 2015 Douglas Christopher Wilson
 * MIT Licensed
 */

'use strict'

/**
 * RegExp to match *( ";" parameter ) in RFC 7231 sec 3.1.1.1
 *
 * parameter     = token "=" ( token / quoted-string )
 * token         = 1*tchar
 * tchar         = "!" / "#" / "$" / "%" / "&" / "'" / "*"
 *               / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
 *               / DIGIT / ALPHA
 *               ; any VCHAR, except delimiters
 * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
 * qdtext        = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
 * obs-text      = %x80-FF
 * quoted-pair   = "\" ( HTAB / SP / VCHAR / obs-text )
 */
var PARAM_REGEXP = /; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g // eslint-disable-line no-control-regex
var TEXT_REGEXP = /^[\u000b\u0020-\u007e\u0080-\u00ff]+$/ // eslint-disable-line no-control-regex
var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/

/**
 * RegExp to match quoted-pair in RFC 7230 sec 3.2.6
 *
 * quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
 * obs-text    = %x80-FF
 */
var QESC_REGEXP = /\\([\u000b\u0020-\u00ff])/g // eslint-disable-line no-control-regex

/**
 * RegExp to match chars that must be quoted-pair in RFC 7230 sec 3.2.6
 */
var QUOTE_REGEXP = /([\\"])/g

/**
 * RegExp to match type in RFC 7231 sec 3.1.1.1
 *
 * media-type = type "/" subtype
 * type       = token
 * subtype    = token
 */
var TYPE_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/

/**
 * Module exports.
 * @public
 */

exports.format = format
exports.parse = parse

/**
 * Format object to media type.
 *
 * @param {object} obj
 * @return {string}
 * @public
 */

function format (obj) {
  if (!obj || typeof obj !== 'object') {
    throw new TypeError('argument obj is required')
  }

  var parameters = obj.parameters
  var type = obj.type

  if (!type || !TYPE_REGEXP.test(type)) {
    throw new TypeError('invalid type')
  }

  var string = type

  // append parameters
  if (parameters && typeof parameters === 'object') {
    var param
    var params = Object.keys(parameters).sort()

    for (var i = 0; i < params.length; i++) {
      param = params[i]

      if (!TOKEN_REGEXP.test(param)) {
        throw new TypeError('invalid parameter name')
      }

      string += '; ' + param + '=' + qstring(parameters[param])
    }
  }

  return string
}

/**
 * Parse media type to object.
 *
 * @param {string|object} string
 * @return {Object}
 * @public
 */

function parse (string) {
  if (!string) {
    throw new TypeError('argument string is required')
  }

  // support req/res-like objects as argument
  var header = typeof string === 'object'
    ? getcontenttype(string)
    : string

  if (typeof header !== 'string') {
    throw new TypeError('argument string is required to be a string')
  }

  var index = header.indexOf(';')
  var type = index !== -1
    ? header.slice(0, index).trim()
    : header.trim()

  if (!TYPE_REGEXP.test(type)) {
    throw new TypeError('invalid media type')
  }

  var obj = new ContentType(type.toLowerCase())

  // parse parameters
  if (index !== -1) {
    var key
    var match
    var value

    PARAM_REGEXP.lastIndex = index

    while ((match = PARAM_REGEXP.exec(header))) {
      if (match.index !== index) {
        throw new TypeError('invalid parameter format')
      }

      index += match[0].length
      key = match[1].toLowerCase()
      value = match[2]

      if (value.charCodeAt(0) === 0x22 /* " */) {
        // remove quotes
        value = value.slice(1, -1)

        // remove escapes
        if (value.indexOf('\\') !== -1) {
          value = value.replace(QESC_REGEXP, '$1')
        }
      }

      obj.parameters[key] = value
    }

    if (index !== header.length) {
      throw new TypeError('invalid parameter format')
    }
  }

  return obj
}

/**
 * Get content-type from req/res objects.
 *
 * @param {object}
 * @return {Object}
 * @private
 */

function getcontenttype (obj) {
  var header

  if (typeof obj.getHeader === 'function') {
    // res-like
    header = obj.getHeader('content-type')
  } else if (typeof obj.headers === 'object') {
    // req-like
    header = obj.headers && obj.headers['content-type']
  }

  if (typeof header !== 'string') {
    throw new TypeError('content-type header is missing from object')
  }

  return header
}

/**
 * Quote a string if necessary.
 *
 * @param {string} val
 * @return {string}
 * @private
 */

function qstring (val) {
  var str = String(val)

  // no need to quote tokens
  if (TOKEN_REGEXP.test(str)) {
    return str
  }

  if (str.length > 0 && !TEXT_REGEXP.test(str)) {
    throw new TypeError('invalid parameter value')
  }

  return '"' + str.replace(QUOTE_REGEXP, '\\$1') + '"'
}

/**
 * Class to represent a content type.
 * @private
 */
function ContentType (type) {
  this.parameters = Object.create(null)
  this.type = type
}


content-type

description: Create and parse HTTP Content-Type header

keywords: content-type http req res rfc7231

repository: jshttp/content-type

test: mocha --reporter spec --check-leaks --bail test/

test-ci: nyc --reporter=lcovonly --reporter=text npm test

test-cov: nyc --reporter=html --reporter=text npm test

version: node scripts/version-history.js && git add HISTORY.md


code:
# convert-source-map [![Build Status][ci-image]][ci-url]

Converts a source-map from/to  different formats and allows adding/changing properties.

```js
var convert = require('convert-source-map');

var json = convert
  .fromComment('//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQvZm9vLm1pbi5qcyIsInNvdXJjZXMiOlsic3JjL2Zvby5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZVJvb3QiOiIvIn0=')
  .toJSON();

var modified = convert
  .fromComment('//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGQvZm9vLm1pbi5qcyIsInNvdXJjZXMiOlsic3JjL2Zvby5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZVJvb3QiOiIvIn0=')
  .setProperty('sources', [ 'SRC/FOO.JS' ])
  .toJSON();

console.log(json);
console.log(modified);
```

```json
{"version":3,"file":"build/foo.min.js","sources":["src/foo.js"],"names":[],"mappings":"AAAA","sourceRoot":"/"}
{"version":3,"file":"build/foo.min.js","sources":["SRC/FOO.JS"],"names":[],"mappings":"AAAA","sourceRoot":"/"}
```

## Upgrading

Prior to v2.0.0, the `fromMapFileComment` and `fromMapFileSource` functions took a String directory path and used that to resolve & read the source map file from the filesystem. However, this made the library limited to nodejs environments and broke on sources with querystrings.

In v2.0.0, you now need to pass a function that does the file reading. It will receive the source filename as a String that you can resolve to a filesystem path, URL, or anything else.

If you are using `convert-source-map` in nodejs and want the previous behavior, you'll use a function like such:

```diff
+ var fs = require('fs'); // Import the fs module to read a file
+ var path = require('path'); // Import the path module to resolve a path against your directory
- var conv = convert.fromMapFileSource(css, '../my-dir');
+ var conv = convert.fromMapFileSource(css, function (filename) {
+   return fs.readFileSync(path.resolve('../my-dir', filename), 'utf-8');
+ });
```

## API

### fromObject(obj)

Returns source map converter from given object.

### fromJSON(json)

Returns source map converter from given json string.

### fromURI(uri)

Returns source map converter from given uri encoded json string.

### fromBase64(base64)

Returns source map converter from given base64 encoded json string.

### fromComment(comment)

Returns source map converter from given base64 or uri encoded json string prefixed with `//# sourceMappingURL=...`.

### fromMapFileComment(comment, readMap)

Returns source map converter from given `filename` by parsing `//# sourceMappingURL=filename`.

`readMap` must be a function which receives the source map filename and returns either a String or Buffer of the source map (if read synchronously), or a `Promise` containing a String or Buffer of the source map (if read asynchronously).

If `readMap` doesn't return a `Promise`, `fromMapFileComment` will return a source map converter synchronously.

If `readMap` returns a `Promise`, `fromMapFileComment` will also return `Promise`. The `Promise` will be either resolved with the source map converter or rejected with an error.

#### Examples

**Synchronous read in Node.js:**

```js
var convert = require('convert-source-map');
var fs = require('fs');

function readMap(filename) {
  return fs.readFileSync(filename, 'utf8');
}

var json = convert
  .fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
  .toJSON();
console.log(json);
```

**Asynchronous read in Node.js:**

```js
var convert = require('convert-source-map');
var { promises: fs } = require('fs'); // Notice the `promises` import

function readMap(filename) {
  return fs.readFile(filename, 'utf8');
}

var converter = await convert.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
var json = converter.toJSON();
console.log(json);
```

**Asynchronous read in the browser:**

```js
var convert = require('convert-source-map');

async function readMap(url) {
  const res = await fetch(url);
  return res.text();
}

const converter = await convert.fromMapFileComment('//# sourceMappingURL=map-file-comment.css.map', readMap)
var json = converter.toJSON();
console.log(json);
```

### fromSource(source)

Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.

### fromMapFileSource(source, readMap)

Finds last sourcemap comment in file and returns source map converter or returns `null` if no source map comment was found.

`readMap` must be a function which receives the source map filename and returns either a String or Buffer of the source map (if read synchronously), or a `Promise` containing a String or Buffer of the source map (if read asynchronously).

If `readMap` doesn't return a `Promise`, `fromMapFileSource` will return a source map converter synchronously.

If `readMap` returns a `Promise`, `fromMapFileSource` will also return `Promise`. The `Promise` will be either resolved with the source map converter or rejected with an error.

### toObject()

Returns a copy of the underlying source map.

### toJSON([space])

Converts source map to json string. If `space` is given (optional), this will be passed to
[JSON.stringify](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify) when the
JSON string is generated.

### toURI()

Converts source map to uri encoded json string.

### toBase64()

Converts source map to base64 encoded json string.

### toComment([options])

Converts source map to an inline comment that can be appended to the source-file.

By default, the comment is formatted like: `//# sourceMappingURL=...`, which you would
normally see in a JS source file.

When `options.encoding == 'uri'`, the data will be uri encoded, otherwise they will be base64 encoded.

When `options.multiline == true`, the comment is formatted like: `/*# sourceMappingURL=... */`, which you would find in a CSS source file.

### addProperty(key, value)

Adds given property to the source map. Throws an error if property already exists.

### setProperty(key, value)

Sets given property to the source map. If property doesn't exist it is added, otherwise its value is updated.

### getProperty(key)

Gets given property of the source map.

### removeComments(src)

Returns `src` with all source map comments removed

### removeMapFileComments(src)

Returns `src` with all source map comments pointing to map files removed.

### commentRegex

Provides __a fresh__ RegExp each time it is accessed. Can be used to find source map comments.

Breaks down a source map comment into groups: Groups: 1: media type, 2: MIME type, 3: charset, 4: encoding, 5: data.

### mapFileCommentRegex

Provides __a fresh__ RegExp each time it is accessed. Can be used to find source map comments pointing to map files.

### generateMapFileComment(file, [options])

Returns a comment that links to an external source map via `file`.

By default, the comment is formatted like: `//# sourceMappingURL=...`, which you would normally see in a JS source file.

When `options.multiline == true`, the comment is formatted like: `/*# sourceMappingURL=... */`, which you would find in a CSS source file.

[ci-url]: https://github.com/thlorenz/convert-source-map/actions?query=workflow:ci
[ci-image]: https://img.shields.io/github/workflow/status/thlorenz/convert-source-map/CI?style=flat-square


code:
'use strict';

Object.defineProperty(exports, 'commentRegex', {
  get: function getCommentRegex () {
    // Groups: 1: media type, 2: MIME type, 3: charset, 4: encoding, 5: data.
    return /^\s*?\/[\/\*][@#]\s+?sourceMappingURL=data:(((?:application|text)\/json)(?:;charset=([^;,]+?)?)?)?(?:;(base64))?,(.*?)$/mg;
  }
});

Object.defineProperty(exports, 'mapFileCommentRegex', {
  get: function getMapFileCommentRegex () {
    // Matches sourceMappingURL in either // or /* comment styles.
    return /(?:\/\/[@#][ \t]+?sourceMappingURL=([^\s'"`]+?)[ \t]*?$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*?(?:\*\/){1}[ \t]*?$)/mg;
  }
});

var decodeBase64;
if (typeof Buffer !== 'undefined') {
  if (typeof Buffer.from === 'function') {
    decodeBase64 = decodeBase64WithBufferFrom;
  } else {
    decodeBase64 = decodeBase64WithNewBuffer;
  }
} else {
  decodeBase64 = decodeBase64WithAtob;
}

function decodeBase64WithBufferFrom(base64) {
  return Buffer.from(base64, 'base64').toString();
}

function decodeBase64WithNewBuffer(base64) {
  if (typeof value === 'number') {
    throw new TypeError('The value to decode must not be of type number.');
  }
  return new Buffer(base64, 'base64').toString();
}

function decodeBase64WithAtob(base64) {
  return decodeURIComponent(escape(atob(base64)));
}

function stripComment(sm) {
  return sm.split(',').pop();
}

function readFromFileMap(sm, read) {
  var r = exports.mapFileCommentRegex.exec(sm);
  // for some odd reason //# .. captures in 1 and /* .. */ in 2
  var filename = r[1] || r[2];

  try {
    var sm = read(filename);
    if (sm != null && typeof sm.catch === 'function') {
      return sm.catch(throwError);
    } else {
      return sm;
    }
  } catch (e) {
    throwError(e);
  }

  function throwError(e) {
    throw new Error('An error occurred while trying to read the map file at ' + filename + '\n' + e.stack);
  }
}

function Converter (sm, opts) {
  opts = opts || {};

  if (opts.hasComment) {
    sm = stripComment(sm);
  }

  if (opts.encoding === 'base64') {
    sm = decodeBase64(sm);
  } else if (opts.encoding === 'uri') {
    sm = decodeURIComponent(sm);
  }

  if (opts.isJSON || opts.encoding) {
    sm = JSON.parse(sm);
  }

  this.sourcemap = sm;
}

Converter.prototype.toJSON = function (space) {
  return JSON.stringify(this.sourcemap, null, space);
};

if (typeof Buffer !== 'undefined') {
  if (typeof Buffer.from === 'function') {
    Converter.prototype.toBase64 = encodeBase64WithBufferFrom;
  } else {
    Converter.prototype.toBase64 = encodeBase64WithNewBuffer;
  }
} else {
  Converter.prototype.toBase64 = encodeBase64WithBtoa;
}

function encodeBase64WithBufferFrom() {
  var json = this.toJSON();
  return Buffer.from(json, 'utf8').toString('base64');
}

function encodeBase64WithNewBuffer() {
  var json = this.toJSON();
  if (typeof json === 'number') {
    throw new TypeError('The json to encode must not be of type number.');
  }
  return new Buffer(json, 'utf8').toString('base64');
}

function encodeBase64WithBtoa() {
  var json = this.toJSON();
  return btoa(unescape(encodeURIComponent(json)));
}

Converter.prototype.toURI = function () {
  var json = this.toJSON();
  return encodeURIComponent(json);
};

Converter.prototype.toComment = function (options) {
  var encoding, content, data;
  if (options != null && options.encoding === 'uri') {
    encoding = '';
    content = this.toURI();
  } else {
    encoding = ';base64';
    content = this.toBase64();
  }
  data = 'sourceMappingURL=data:application/json;charset=utf-8' + encoding + ',' + content;
  return options != null && options.multiline ? '/*# ' + data + ' */' : '//# ' + data;
};

// returns copy instead of original
Converter.prototype.toObject = function () {
  return JSON.parse(this.toJSON());
};

Converter.prototype.addProperty = function (key, value) {
  if (this.sourcemap.hasOwnProperty(key)) throw new Error('property "' + key + '" already exists on the sourcemap, use set property instead');
  return this.setProperty(key, value);
};

Converter.prototype.setProperty = function (key, value) {
  this.sourcemap[key] = value;
  return this;
};

Converter.prototype.getProperty = function (key) {
  return this.sourcemap[key];
};

exports.fromObject = function (obj) {
  return new Converter(obj);
};

exports.fromJSON = function (json) {
  return new Converter(json, { isJSON: true });
};

exports.fromURI = function (uri) {
  return new Converter(uri, { encoding: 'uri' });
};

exports.fromBase64 = function (base64) {
  return new Converter(base64, { encoding: 'base64' });
};

exports.fromComment = function (comment) {
  var m, encoding;
  comment = comment
    .replace(/^\/\*/g, '//')
    .replace(/\*\/$/g, '');
  m = exports.commentRegex.exec(comment);
  encoding = m && m[4] || 'uri';
  return new Converter(comment, { encoding: encoding, hasComment: true });
};

function makeConverter(sm) {
  return new Converter(sm, { isJSON: true });
}

exports.fromMapFileComment = function (comment, read) {
  if (typeof read === 'string') {
    throw new Error(
      'String directory paths are no longer supported with `fromMapFileComment`\n' +
      'Please review the Upgrading documentation at https://github.com/thlorenz/convert-source-map#upgrading'
    )
  }

  var sm = readFromFileMap(comment, read);
  if (sm != null && typeof sm.then === 'function') {
    return sm.then(makeConverter);
  } else {
    return makeConverter(sm);
  }
};

// Finds last sourcemap comment in file or returns null if none was found
exports.fromSource = function (content) {
  var m = content.match(exports.commentRegex);
  return m ? exports.fromComment(m.pop()) : null;
};

// Finds last sourcemap comment in file or returns null if none was found
exports.fromMapFileSource = function (content, read) {
  if (typeof read === 'string') {
    throw new Error(
      'String directory paths are no longer supported with `fromMapFileSource`\n' +
      'Please review the Upgrading documentation at https://github.com/thlorenz/convert-source-map#upgrading'
    )
  }
  var m = content.match(exports.mapFileCommentRegex);
  return m ? exports.fromMapFileComment(m.pop(), read) : null;
};

exports.removeComments = function (src) {
  return src.replace(exports.commentRegex, '');
};

exports.removeMapFileComments = function (src) {
  return src.replace(exports.mapFileCommentRegex, '');
};

exports.generateMapFileComment = function (file, options) {
  var data = 'sourceMappingURL=' + file;
  return options && options.multiline ? '/*# ' + data + ' */' : '//# ' + data;
};


convert-source-map

description: Converts a source-map from/to  different formats and allows adding/changing properties.

test: tap test/*.js --color

type: git

keywords: convert sourcemap source map browser debug


code:
1.0.6 / 2015-02-03
==================

* use `npm test` instead of `make test` to run tests
* clearer assertion messages when checking input

1.0.5 / 2014-09-05
==================

* add license to package.json

1.0.4 / 2014-06-25
==================

 * corrected avoidance of timing attacks (thanks @tenbits!)

1.0.3 / 2014-01-28
==================

 * [incorrect] fix for timing attacks

1.0.2 / 2014-01-28
==================

 * fix missing repository warning
 * fix typo in test

1.0.1 / 2013-04-15
==================

  * Revert "Changed underlying HMAC algo. to sha512."
  * Revert "Fix for timing attacks on MAC verification."

0.0.1 / 2010-01-03
==================

  * Initial release


code:
# cookie-signature

  Sign and unsign cookies.

## Example

```js
var cookie = require('cookie-signature');

var val = cookie.sign('hello', 'tobiiscool');
val.should.equal('hello.DGDUkGlIkCzPz+C0B064FNgHdEjox7ch8tOBGslZ5QI');

var val = cookie.sign('hello', 'tobiiscool');
cookie.unsign(val, 'tobiiscool').should.equal('hello');
cookie.unsign(val, 'luna').should.be.false;
```

## License

(The MIT License)

Copyright (c) 2012 LearnBoost <tj@learnboost.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.


code:
/**
 * Module dependencies.
 */

var crypto = require('crypto');

/**
 * Sign the given `val` with `secret`.
 *
 * @param {String} val
 * @param {String} secret
 * @return {String}
 * @api private
 */

exports.sign = function(val, secret){
  if ('string' != typeof val) throw new TypeError("Cookie value must be provided as a string.");
  if ('string' != typeof secret) throw new TypeError("Secret string must be provided.");
  return val + '.' + crypto
    .createHmac('sha256', secret)
    .update(val)
    .digest('base64')
    .replace(/\=+$/, '');
};

/**
 * Unsign and decode the given `val` with `secret`,
 * returning `false` if the signature is invalid.
 *
 * @param {String} val
 * @param {String} secret
 * @return {String|Boolean}
 * @api private
 */

exports.unsign = function(val, secret){
  if ('string' != typeof val) throw new TypeError("Signed cookie string must be provided.");
  if ('string' != typeof secret) throw new TypeError("Secret string must be provided.");
  var str = val.slice(0, val.lastIndexOf('.'))
    , mac = exports.sign(str, secret);

  return sha1(mac) == sha1(val) ? str : false;
};

/**
 * Private
 */

function sha1(str){
  return crypto.createHash('sha1').update(str).digest('hex');
}


cookie-signature

description: Sign and unsign cookies

keywords: cookie sign unsign

author: TJ Holowaychuk <tj@learnboost.com>

test: mocha --require should --reporter spec

main: index


code:
0.6.0 / 2023-11-06
==================

  * Add `partitioned` option

0.5.0 / 2022-04-11
==================

  * Add `priority` option
  * Fix `expires` option to reject invalid dates
  * perf: improve default decode speed
  * perf: remove slow string split in parse

0.4.2 / 2022-02-02
==================

  * perf: read value only when assigning in parse
  * perf: remove unnecessary regexp in parse

0.4.1 / 2020-04-21
==================

  * Fix `maxAge` option to reject invalid values

0.4.0 / 2019-05-15
==================

  * Add `SameSite=None` support

0.3.1 / 2016-05-26
==================

  * Fix `sameSite: true` to work with draft-7 clients
    - `true` now sends `SameSite=Strict` instead of `SameSite`

0.3.0 / 2016-05-26
==================

  * Add `sameSite` option
    - Replaces `firstPartyOnly` option, never implemented by browsers
  * Improve error message when `encode` is not a function
  * Improve error message when `expires` is not a `Date`

0.2.4 / 2016-05-20
==================

  * perf: enable strict mode
  * perf: use for loop in parse
  * perf: use string concatenation for serialization

0.2.3 / 2015-10-25
==================

  * Fix cookie `Max-Age` to never be a floating point number

0.2.2 / 2015-09-17
==================

  * Fix regression when setting empty cookie value
    - Ease the new restriction, which is just basic header-level validation
  * Fix typo in invalid value errors

0.2.1 / 2015-09-17
==================

  * Throw on invalid values provided to `serialize`
    - Ensures the resulting string is a valid HTTP header value

0.2.0 / 2015-08-13
==================

  * Add `firstPartyOnly` option
  * Throw better error for invalid argument to parse
  * perf: hoist regular expression

0.1.5 / 2015-09-17
==================

  * Fix regression when setting empty cookie value
    - Ease the new restriction, which is just basic header-level validation
  * Fix typo in invalid value errors

0.1.4 / 2015-09-17
==================

  * Throw better error for invalid argument to parse
  * Throw on invalid values provided to `serialize`
    - Ensures the resulting string is a valid HTTP header value

0.1.3 / 2015-05-19
==================

  * Reduce the scope of try-catch deopt
  * Remove argument reassignments

0.1.2 / 2014-04-16
==================

  * Remove unnecessary files from npm package

0.1.1 / 2014-02-23
==================

  * Fix bad parse when cookie value contained a comma
  * Fix support for `maxAge` of `0`

0.1.0 / 2013-05-01
==================

  * Add `decode` option
  * Add `encode` option

0.0.6 / 2013-04-08
==================

  * Ignore cookie parts missing `=`

0.0.5 / 2012-10-29
==================

  * Return raw cookie value if value unescape errors

0.0.4 / 2012-06-21
==================

  * Use encode/decodeURIComponent for cookie encoding/decoding
    - Improve server/client interoperability

0.0.3 / 2012-06-06
==================

  * Only escape special characters per the cookie RFC

0.0.2 / 2012-06-01
==================

  * Fix `maxAge` option to not throw error

0.0.1 / 2012-05-28
==================

  * Add more tests

0.0.0 / 2012-05-28
==================

  * Initial release