text: /*!
   * Less - middleware (adapted from the stylus middleware)
   *
   * Copyright(c) 2013 Randy Merrill <Zoramite+github@gmail.com>
   * MIT Licensed
   */
  
  var less = require('less'),
      fs = require('fs'),
      url = require('url'),
      path = require('path'),
      mkdirp = require('mkdirp'),
      determine_imports = require('./determine-imports.js');
  
  // Import map
  var imports = {};
  
  /**
   * Return Connect middleware with the given `options`.
   *
   * Options:
   *
   *    `force`           Always re-compile
   *    `once`            Only re-compile the one time
   *    `debug`           Output debugging information
   *    `src`             Source directory used to find .less files
   *    `dest`            Destination directory used to output .css files
   *                      when undefined defaults to `src`.
   *    `prefix`          Path which should be stripped from `pathname`.
   *    `compress`        Whether the output .css files should be compressed
   *    `yuicompress`     Same as `compress`, but uses YUI Compressor
   *    `optimization`    The desired value of the less optimization option (0, 1, or 2. 0 is default)
   *    `dumpLineNumbers` Add line tracking to the compiled css. ('comments' or 'mediaquery')
   *
   * Examples:
   *
   * Pass the middleware to Connect, grabbing .less files from this directory
   * and saving .css files to _./public_. Also supplying our custom `compile` function.
   *
   * Following that we have a `static` layer setup to serve the .css
   * files generated by Less.
   *
   *      var server = connect.createServer(
   *          less.middleware({
   *              src: __dirname + '/public',
   *              compress: true
   *          })
   *        , connect.static(__dirname + '/public')
   *      );
   *
   * @param {Object} options
   * @return {Function}
   * @api public
   */
  module.exports = less.middleware = function(options){
    var regex = {
      handle: /\.css$/,
      compress: /(\.|-)min\.css$/
    };
  
    options = options || {};
  
    // Accept src/dest dir
    if ('string' === typeof options) {
      options = { src: options };
    }
  
    // Only log if in debug mode
    var log = function(key, val, type) {
      if(options.debug || type === 'error') {
        switch(type) {
          case 'log':
          case 'info':
          case 'error':
          case 'warn':
            break;
          default:
            type = 'log';
        }
  
        console[type]('  \033[90m%s :\033[0m \033[36m%s\033[0m', key, val);
      }
    };
  
    var lessError = function(err) {
      log("LESS " + err.type + ' error', err.message, 'error');
      log("LESS File", err.filename + ' ' + err.line + ':' + err.column, 'error');
    };
  
    // Check imports for changes
    var checkImports = function(path, next) {
      var nodes = imports[path];
  
      if (!nodes || !nodes.length) {
        return next();
      }
  
      var pending = nodes.length;
      var changed = [];
  
      nodes.forEach(function(imported){
        fs.stat(imported.path, function(err, stat) {
          // error or newer mtime
          if (err || !imported.mtime || stat.mtime > imported.mtime) {
            changed.push(imported.path);
          }
  
          --pending || next(changed);
        });
      });
    };
  
    // Once option
    options.once = options.once || false;
  
    // Compress option
    options.compress = typeof options.compress === 'undefined' ? 'auto' : options.compress;
  
    // YUI Compress option
    options.yuicompress = typeof options.yuicompress === 'undefined' ? false : options.yuicompress;
  
    // Optimization option
    options.optimization = options.optimization || 0;
  
    // Line Number Tracking
    options.dumpLineNumbers = options.dumpLineNumbers || 0;
  
    // Source dir required
    var src = options.src;
    if (!src) { throw new Error('less.middleware() requires "src" directory'); }
  
    // Default dest dir to source
    var dest = options.dest ? options.dest : src;
  
    if (options.paths){
      if (!(options.paths instanceof Array)) {
        options.paths = [options.paths];
      }
    } else {
      options.paths = [];
    }
  
    // Default compile callback
    options.render = options.render || function(str, lessPath, cssPath, callback) {
  
      var paths = [ path.dirname(lessPath) ];
      options.paths.forEach(function(p){ paths.push(p); });
  
      var parser = new less.Parser({
        paths: paths,
        filename: lessPath,
        optimization: options.optimization,
        dumpLineNumbers: options.dumpLineNumbers
      });
  
      parser.parse(str, function(err, tree) {
          if(err) {
            return callback(err);
          }
  
          try {
            var css = tree.toCSS({
              compress: (options.compress == 'auto' ? regex.compress.test(cssPath) : options.compress),
              yuicompress: options.yuicompress
            });
  
            // Store the less import paths
            imports[lessPath] = determine_imports(tree, lessPath, options.paths);
  
            callback(err, css);
          } catch(parseError) {
            callback(parseError, null);
          }
      });
    };
  
    // Middleware
    return function(req, res, next) {
      if ('GET' != req.method.toUpperCase() && 'HEAD' != req.method.toUpperCase()) { return next(); }
  
      var pathname = url.parse(req.url).pathname;
  
      // Only handle the matching files
      if (regex.handle.test(pathname)) {
        if (options.prefix && 0 === pathname.indexOf(options.prefix)) {
          pathname = pathname.substring(options.prefix.length);
        }
  
        var cssPath = path.join(dest, pathname);
        var lessPath = path.join(src, (
          regex.compress.test(pathname)
          ? pathname.replace(regex.compress, '.less')
          : pathname.replace('.css', '.less'
        )));
  
        log('source', lessPath);
        log('dest', cssPath);
  
        // Ignore ENOENT to fall through as 404
        var error = function(err) {
          return next('ENOENT' == err.code ? null : err);
        };
  
        // Compile to cssPath
        var compile = function() {
          log('read', lessPath);
  
          fs.readFile(lessPath, 'utf8', function(err, str){
            if (err) { return error(err); }
  
            delete imports[lessPath];
  
            try {
              options.render(str, lessPath, cssPath, function(err, css){
                if (err) {
                  lessError(err);
  
                  return next(err);
                }
  
                log('render', lessPath);
  
                mkdirp(path.dirname(cssPath), 0777, function(err){
                  if (err) return error(err);
  
                  fs.writeFile(cssPath, css, 'utf8', next);
                });
              });
            } catch (err) {
              lessError(err);
  
              return next(err);
            }
          });
        };
  
        // Force
        if (options.force) { return compile(); }
  
        // Re-compile on server restart, disregarding
        // mtimes since we need to map imports
        if (!imports[lessPath]) { return compile(); }
  
        // Only check/recompile if it has not been done at before
        if (options.once && imports[lessPath]) { return next(); }
  
        // Compare mtimes
        fs.stat(lessPath, function(err, lessStats){
          if (err) { return error(err); }
  
          fs.stat(cssPath, function(err, cssStats){
            // CSS has not been compiled, compile it!
            if (err) {
              if ('ENOENT' == err.code) {
                log('not found', cssPath);
  
                // No CSS file found in dest
                return compile();
              } else {
                return next(err);
              }
            } else if (lessStats.mtime > cssStats.mtime) {
              // Source has changed, compile it
              log('modified', cssPath);
  
              return compile();
            } else {
              // Check if any of the less imports were changed
              checkImports(lessPath, function(changed){
                if(typeof changed != "undefined" && changed.length) {
                  log('modified import', changed);
  
                  return compile();
                }
  
                return next();
              });
            }
          });
        });
      } else {
        return next();
      }
    };
  };
  