<commit_before>"use strict";
const assert = require("assert");

function assertDoc(val) {
  assert(
    typeof val === "string" || val != null && typeof val.type === "string",
    "Value is a valid document"
  );
}

function fromString(text) {
  return "" + text;
}

function concat(parts) {
  parts.forEach(assertDoc);

  return { type: "concat", parts };
}

function indent(n, contents) {
  assertDoc(contents);

  return { type: "indent", contents, n };
}

function group(contents, opts) {
  opts = opts || {};

  assertDoc(contents);

  return {
    type: "group",
    contents: contents,
    break: !!opts.shouldBreak,
    expandedStates: opts.expandedStates
  };
}

function multilineGroup(contents, opts) {
  return group(
    contents,
    Object.assign(opts || {}, { shouldBreak: hasHardLine(contents) })
  );
}

function conditionalGroup(states, opts) {
  return group(
    states[(0)],
    Object.assign(opts || {}, { expandedStates: states })
  );
}

function ifBreak(contents) {
  return { type: "if-break", contents };
}

function iterDoc(topDoc, func) {
  const docs = [ topDoc ];
  while (docs.length !== 0) {
    const doc = docs.pop();
    let res = undefined;

    if (typeof doc === "string") {
      const res = func("string", doc);

      if (res) {
        return res;
      }
    } else {
      const res = func(doc.type, doc);

      if (res) {
        return res;
      }

      if (doc.type === "concat") {
        for (var i = doc.parts.length - 1; i >= 0; i--) {
          docs.push(doc.parts[i]);
        }
      } else if (doc.type !== "line") {
        docs.push(doc.contents);
      }
    }
  }
}

const line = { type: "line" };
const softline = { type: "line", soft: true };
const hardline = { type: "line", hard: true };
const literalline = { type: "line", hard: true, literal: true };

function isEmpty(n) {
  return typeof n === "string" && n.length === 0;
}

function join(sep, arr) {
  var res = [];

  for (var i = 0; i < arr.length; i++) {
    if (i !== 0) {
      res.push(sep);
    }

    res.push(arr[i]);
  }

  return concat(res);
}

function getFirstString(doc) {
  return iterDoc(doc, (type, doc) => {
    if (type === "string" && doc.trim().length !== 0) {
      return doc;
    }
  });
}

function hasHardLine(doc) {
  // TODO: If we hit a group, check if it's already marked as a
  // multiline group because they should be marked bottom-up.
  return !!iterDoc(doc, (type, doc) => {
    switch (type) {
      case "line":
        if (doc.hard) {
          return true;
        }

        break;
    }
  });
}

const MODE_BREAK = 1;
const MODE_FLAT = 2;

function fits(next, restCommands, width) {
  let restIdx = restCommands.length;
  const cmds = [ next ];
  while (width >= 0) {
    if (cmds.length === 0) {
      if (restIdx === 0) {
        return true;
      } else {
        cmds.push(restCommands[restIdx - 1]);

        restIdx--;

        continue;
      }
    }

    const x = cmds.pop();
    const ind = x[(0)];
    const mode = x[(1)];
    const doc = x[(2)];

    if (typeof doc === "string") {
      width -= doc.length;
    } else {
      switch (doc.type) {
        case "concat":
          for (var i = doc.parts.length - 1; i >= 0; i--) {
            cmds.push([ ind, mode, doc.parts[i] ]);
          }

          break;
        case "indent":
          cmds.push([ ind + doc.n, mode, doc.contents ]);

          break;
        case "group":
          cmds.push([ ind, doc.break ? MODE_BREAK : mode, doc.contents ]);

          break;
        case "if-break":
          if (mode === MODE_BREAK) {
            cmds.push([ ind, mode, doc.contents ]);
          }

          break;
        case "line":
          switch (mode) {
            // fallthrough
            case MODE_FLAT:
              if (!doc.hard) {
                if (!doc.soft) {
                  width -= 1;
                }

                break;
              }

            case MODE_BREAK:
              return true;
          }
          break;
      }
    }
  }
  return false;
}

function print(w, doc) {
  let pos = 0;
  // cmds is basically a stack. We've turned a recursive call into a
  // while loop which is much faster. The while loop below adds new
  // cmds to the array instead of recursively calling `print`.
  let cmds = [ [ 0, MODE_BREAK, doc ] ];
  let out = [];
  let shouldRemeasure = false;
  while (cmds.length !== 0) {
    const x = cmds.pop();
    const ind = x[(0)];
    const mode = x[(1)];
    const doc = x[(2)];

    if (typeof doc === "string") {
      out.push(doc);

      pos += doc.length;
    } else {
      switch (doc.type) {
        case "concat":
          for (var i = doc.parts.length - 1; i >= 0; i--) {
            cmds.push([ ind, mode, doc.parts[i] ]);
          }

          break;
        case "indent":
          cmds.push([ ind + doc.n, mode, doc.contents ]);

          break;
        case "group":
          switch (mode) {
            // fallthrough
            case MODE_FLAT:
              if (!shouldRemeasure) {
                cmds.push([
                  ind,
                  doc.break ? MODE_BREAK : MODE_FLAT,
                  doc.contents
                ]);

                break;
              }

            case MODE_BREAK:
              shouldRemeasure = false;

              const next = [ ind, MODE_FLAT, doc.contents ];
              let rem = w - pos;

              if (!doc.break && fits(next, cmds, rem)) {
                cmds.push(next);
              } else {
                // Expanded states are a rare case where a document
                // can manually provide multiple representations of
                // itself. It provides an array of documents
                // going from the least expanded (most flattened)
                // representation first to the most expanded. If a
                // group has these, we need to manually go through
                // these states and find the first one that fits.
                if (doc.expandedStates) {
                  const mostExpanded = doc.expandedStates[doc.expandedStates.length -
                    1];

                  if (doc.break) {
                    cmds.push([ ind, MODE_BREAK, mostExpanded ]);

                    break;
                  } else {
                    for (var i = 1; i < doc.expandedStates.length + 1; i++) {
                      if (i >= doc.expandedStates.length) {
                        cmds.push([ ind, MODE_BREAK, mostExpanded ]);

                        break;
                      } else {
                        const state = doc.expandedStates[i];
                        const cmd = [ ind, MODE_FLAT, state ];

                        if (fits(cmd, cmds, rem)) {
                          cmds.push(cmd);

                          break;
                        }
                      }
                    }
                  }
                } else {
                  cmds.push([ ind, MODE_BREAK, doc.contents ]);
                }
              }

              break;
          }
          break;
        case "if-break":
          if (mode === MODE_BREAK) {
            cmds.push([ ind, MODE_BREAK, doc.contents ]);
          }

          break;
        case "line":
          switch (mode) {
            // fallthrough
            case MODE_FLAT:
              if (!doc.hard) {
                if (!doc.soft) {
                  out.push(" ");

                  pos += 1;
                }

                break;
              } else {
                // This line was forced into the output even if we
                // were in flattened mode, so we need to tell the next
                // group that no matter what, it needs to remeasure
                // because the previous measurement didn't accurately
                // capture the entire expression (this is necessary
                // for nested groups)
                shouldRemeasure = true;
              }

            case MODE_BREAK:
              if (out.length > 0) {
                const lastString = out[out.length - 1];

                if (lastString.match(/^\s*\n\s*$/)) {
                  out[out.length - 1] = "\n";
                }
              }

              if (doc.literal) {
                out.push("\n");

                pos = 0;
              } else {
                out.push("\n" + " ".repeat(ind));

                pos = ind;
              }

              break;
          }
          break;
        default:
      }
    }
  }
  return out.join("");
}

module.exports = {
  fromString,
  concat,
  isEmpty,
  join,
  line,
  softline,
  hardline,
  literalline,
  group,
  multilineGroup,
  conditionalGroup,
  ifBreak,
  hasHardLine,
  indent,
  print,
  getFirstString
};
<commit_msg>Introduce second argument to ifBreak (#302)<commit_after>"use strict";
const assert = require("assert");

function assertDoc(val) {
  assert(
    typeof val === "string" || val != null && typeof val.type === "string",
    "Value is a valid document"
  );
}

function fromString(text) {
  return "" + text;
}

function concat(parts) {
  parts.forEach(assertDoc);

  return { type: "concat", parts };
}

function indent(n, contents) {
  assertDoc(contents);

  return { type: "indent", contents, n };
}

function group(contents, opts) {
  opts = opts || {};

  assertDoc(contents);

  return {
    type: "group",
    contents: contents,
    break: !!opts.shouldBreak,
    expandedStates: opts.expandedStates
  };
}

function multilineGroup(contents, opts) {
  return group(
    contents,
    Object.assign(opts || {}, { shouldBreak: hasHardLine(contents) })
  );
}

function conditionalGroup(states, opts) {
  return group(
    states[(0)],
    Object.assign(opts || {}, { expandedStates: states })
  );
}

function ifBreak(breakContents, flatContents) {
  if (breakContents) {
    assertDoc(breakContents);
  }
  if (flatContents) {
    assertDoc(flatContents);
  }

  return { type: "if-break", breakContents, flatContents };
}

function iterDoc(topDoc, func) {
  const docs = [ topDoc ];
  while (docs.length !== 0) {
    const doc = docs.pop();
    let res = undefined;

    if (typeof doc === "string") {
      const res = func("string", doc);

      if (res) {
        return res;
      }
    } else {
      const res = func(doc.type, doc);

      if (res) {
        return res;
      }

      if (doc.type === "concat") {
        for (var i = doc.parts.length - 1; i >= 0; i--) {
          docs.push(doc.parts[i]);
        }
      } else if (doc.type === "if-break") {
        if (doc.breakContents) {
          docs.push(doc.breakContents);
        }
        if (doc.flatContents) {
          docs.push(doc.flatContents);
        }
      } else if (doc.type !== "line") {
        docs.push(doc.contents);
      }
    }
  }
}

const line = { type: "line" };
const softline = { type: "line", soft: true };
const hardline = { type: "line", hard: true };
const literalline = { type: "line", hard: true, literal: true };

function isEmpty(n) {
  return typeof n === "string" && n.length === 0;
}

function join(sep, arr) {
  var res = [];

  for (var i = 0; i < arr.length; i++) {
    if (i !== 0) {
      res.push(sep);
    }

    res.push(arr[i]);
  }

  return concat(res);
}

function getFirstString(doc) {
  return iterDoc(doc, (type, doc) => {
    if (type === "string" && doc.trim().length !== 0) {
      return doc;
    }
  });
}

function hasHardLine(doc) {
  // TODO: If we hit a group, check if it's already marked as a
  // multiline group because they should be marked bottom-up.
  return !!iterDoc(doc, (type, doc) => {
    switch (type) {
      case "line":
        if (doc.hard) {
          return true;
        }

        break;
    }
  });
}

const MODE_BREAK = 1;
const MODE_FLAT = 2;

function fits(next, restCommands, width) {
  let restIdx = restCommands.length;
  const cmds = [ next ];
  while (width >= 0) {
    if (cmds.length === 0) {
      if (restIdx === 0) {
        return true;
      } else {
        cmds.push(restCommands[restIdx - 1]);

        restIdx--;

        continue;
      }
    }

    const x = cmds.pop();
    const ind = x[(0)];
    const mode = x[(1)];
    const doc = x[(2)];

    if (typeof doc === "string") {
      width -= doc.length;
    } else {
      switch (doc.type) {
        case "concat":
          for (var i = doc.parts.length - 1; i >= 0; i--) {
            cmds.push([ ind, mode, doc.parts[i] ]);
          }

          break;
        case "indent":
          cmds.push([ ind + doc.n, mode, doc.contents ]);

          break;
        case "group":
          cmds.push([ ind, doc.break ? MODE_BREAK : mode, doc.contents ]);

          break;
        case "if-break":
          if (mode === MODE_BREAK) {
            if (doc.breakContents) {
              cmds.push([ ind, mode, doc.breakContents ]);
            }
          }
          if (mode === MODE_FLAT) {
            if (doc.flatContents) {
              cmds.push([ ind, mode, doc.flatContents ]);
            }
          }

          break;
        case "line":
          switch (mode) {
            // fallthrough
            case MODE_FLAT:
              if (!doc.hard) {
                if (!doc.soft) {
                  width -= 1;
                }

                break;
              }

            case MODE_BREAK:
              return true;
          }
          break;
      }
    }
  }
  return false;
}

function print(w, doc) {
  let pos = 0;
  // cmds is basically a stack. We've turned a recursive call into a
  // while loop which is much faster. The while loop below adds new
  // cmds to the array instead of recursively calling `print`.
  let cmds = [ [ 0, MODE_BREAK, doc ] ];
  let out = [];
  let shouldRemeasure = false;
  while (cmds.length !== 0) {
    const x = cmds.pop();
    const ind = x[(0)];
    const mode = x[(1)];
    const doc = x[(2)];

    if (typeof doc === "string") {
      out.push(doc);

      pos += doc.length;
    } else {
      switch (doc.type) {
        case "concat":
          for (var i = doc.parts.length - 1; i >= 0; i--) {
            cmds.push([ ind, mode, doc.parts[i] ]);
          }

          break;
        case "indent":
          cmds.push([ ind + doc.n, mode, doc.contents ]);

          break;
        case "group":
          switch (mode) {
            // fallthrough
            case MODE_FLAT:
              if (!shouldRemeasure) {
                cmds.push([
                  ind,
                  doc.break ? MODE_BREAK : MODE_FLAT,
                  doc.contents
                ]);

                break;
              }

            case MODE_BREAK:
              shouldRemeasure = false;

              const next = [ ind, MODE_FLAT, doc.contents ];
              let rem = w - pos;

              if (!doc.break && fits(next, cmds, rem)) {
                cmds.push(next);
              } else {
                // Expanded states are a rare case where a document
                // can manually provide multiple representations of
                // itself. It provides an array of documents
                // going from the least expanded (most flattened)
                // representation first to the most expanded. If a
                // group has these, we need to manually go through
                // these states and find the first one that fits.
                if (doc.expandedStates) {
                  const mostExpanded = doc.expandedStates[doc.expandedStates.length -
                    1];

                  if (doc.break) {
                    cmds.push([ ind, MODE_BREAK, mostExpanded ]);

                    break;
                  } else {
                    for (var i = 1; i < doc.expandedStates.length + 1; i++) {
                      if (i >= doc.expandedStates.length) {
                        cmds.push([ ind, MODE_BREAK, mostExpanded ]);

                        break;
                      } else {
                        const state = doc.expandedStates[i];
                        const cmd = [ ind, MODE_FLAT, state ];

                        if (fits(cmd, cmds, rem)) {
                          cmds.push(cmd);

                          break;
                        }
                      }
                    }
                  }
                } else {
                  cmds.push([ ind, MODE_BREAK, doc.contents ]);
                }
              }

              break;
          }
          break;
        case "if-break":
          if (mode === MODE_BREAK) {
            if (doc.breakContents) {
              cmds.push([ ind, mode, doc.breakContents ]);
            }
          }
          if (mode === MODE_FLAT) {
            if (doc.flatContents) {
              cmds.push([ ind, mode, doc.flatContents ]);
            }
          }

          break;
        case "line":
          switch (mode) {
            // fallthrough
            case MODE_FLAT:
              if (!doc.hard) {
                if (!doc.soft) {
                  out.push(" ");

                  pos += 1;
                }

                break;
              } else {
                // This line was forced into the output even if we
                // were in flattened mode, so we need to tell the next
                // group that no matter what, it needs to remeasure
                // because the previous measurement didn't accurately
                // capture the entire expression (this is necessary
                // for nested groups)
                shouldRemeasure = true;
              }

            case MODE_BREAK:
              if (out.length > 0) {
                const lastString = out[out.length - 1];

                if (lastString.match(/^\s*\n\s*$/)) {
                  out[out.length - 1] = "\n";
                }
              }

              if (doc.literal) {
                out.push("\n");

                pos = 0;
              } else {
                out.push("\n" + " ".repeat(ind));

                pos = ind;
              }

              break;
          }
          break;
        default:
      }
    }
  }
  return out.join("");
}

module.exports = {
  fromString,
  concat,
  isEmpty,
  join,
  line,
  softline,
  hardline,
  literalline,
  group,
  multilineGroup,
  conditionalGroup,
  ifBreak,
  hasHardLine,
  indent,
  print,
  getFirstString
};
<|endoftext|>


<commit_before>var config = require('./../config');
var devices = require('./database').devices;
var redis = require('./redis');
var cacheDevice = require('./cacheDevice');

function genError(uuid){
  return {
        error: {
          uuid: uuid,
          message: 'Device not found',
          code: 404
        }
      };
}

function dbGet(uuid, callback){
  devices.findOne({
    uuid: uuid
  }, function(merr, mdata) {
    if(merr || !mdata) {
      callback(genError(uuid));
    }else{
      callback(null, mdata);
    }
  });
}

function getDevice(uuid, callback){
  if(config.redis && config.redis.host){
    redis.get('DEVICE_' + uuid, function(rerr, rdata){
      if(!rdata){
        dbGet(uuid, function(merr, mdata){
          if(!merr){
            cacheDevice(mdata);
          }
          callback(merr, mdata);
          //store to redis in background

        });
      }else{
        callback(null, JSON.parse(rdata));
      }
    });
  }else{
    dbGet(uuid, callback);
  }
}

module.exports = function(uuid, owner, callback) {
  try{
    getDevice(uuid, function(err, devicedata){
      if(err){
        callback(err);
      }else{
        delete devicedata._id;
        delete devicedata.timestamp;
        callback(devicedata);
      }
    });
  }catch(exp){
    console.error(exp);
    callback({error: exp});
  }
};
<commit_msg>remove token<commit_after>var config = require('./../config');
var devices = require('./database').devices;
var redis = require('./redis');
var cacheDevice = require('./cacheDevice');

function genError(uuid){
  return {
    error: {
      uuid: uuid,
      message: 'Device not found',
      code: 404
    }
  };
}

function dbGet(uuid, callback){
  devices.findOne({
    uuid: uuid
  }, function(merr, mdata) {
    if(merr || !mdata) {
      callback(genError(uuid));
    }else{
      delete mdata.token;
      callback(null, mdata);
    }
  });
}

function getDevice(uuid, callback){
  if(config.redis && config.redis.host){
    redis.get('DEVICE_' + uuid, function(rerr, rdata){
      if(!rdata){
        dbGet(uuid, function(merr, mdata){
          if(!merr){
            cacheDevice(mdata);
          }
          callback(merr, mdata);
          //store to redis in background

        });
      }else{
        callback(null, JSON.parse(rdata));
      }
    });
  }else{
    dbGet(uuid, callback);
  }
}

module.exports = function(uuid, owner, callback) {
  try{
    getDevice(uuid, function(err, devicedata){
      if(err){
        callback(err);
      }else{
        delete devicedata._id;
        delete devicedata.timestamp;
        callback(devicedata);
      }
    });
  }catch(exp){
    console.error(exp);
    callback({error: exp});
  }
};
<|endoftext|>


<commit_before>export const SET_COMPLETION = 'SEARCH_SET_COMPLETION'
export const SET_BODY = 'SEARCH_SET_BODY'
export const SET_PAGE = 'SEARCH_SET_PAGE'
export const SET_RESULTS_PER_PAGE = 'SEARCH_SET_RESULTS_PER_PAGE'
export const TRIGGER_SEARCH = 'SEARCH_TRIGGER'

export const setCompletion = (completion, namespace = 'default') => (
  { type: SET_COMPLETION, completion, namespace }
)

export const setBody = (body, namespace = 'default') => (
  { type: SET_BODY, body, namespace }
)

export const setPage = (page, namespace = 'default') => (
  { type: SET_PAGE, page, namespace }
)

export const setResultsPerPage = (resultsPerPage, namespace = 'default') => (
  { type: SET_RESULTS_PER_PAGE, resultsPerPage, namespace }
)

export const triggerSearch = (namespace = 'default') => (
  { type: TRIGGER_SEARCH, namespace }
)
<commit_msg>Allow triggerSearch to work for pipelines<commit_after>export const SET_COMPLETION = 'SEARCH_SET_COMPLETION'
export const SET_BODY = 'SEARCH_SET_BODY'
export const SET_PAGE = 'SEARCH_SET_PAGE'
export const SET_RESULTS_PER_PAGE = 'SEARCH_SET_RESULTS_PER_PAGE'
export const TRIGGER_SEARCH = 'SEARCH_TRIGGER'

export const setCompletion = (completion, namespace = 'default') => (
  { type: SET_COMPLETION, completion, namespace }
)

export const setBody = (body, namespace = 'default') => (
  { type: SET_BODY, body, namespace }
)

export const setPage = (page, namespace = 'default') => (
  { type: SET_PAGE, page, namespace }
)

export const setResultsPerPage = (resultsPerPage, namespace = 'default') => (
  { type: SET_RESULTS_PER_PAGE, resultsPerPage, namespace }
)

export const triggerSearch = (namespace = 'default', pipeline) => (
  { type: TRIGGER_SEARCH, namespace , pipeline }
)
<|endoftext|>


<commit_before>//////////////////////////////
// Walk
//  - A Gulp Plugin
//
// Walks a directory to find all files
//////////////////////////////
'use strict';

//////////////////////////////
// Variables
//////////////////////////////
var through = require('through2'),
    gutil = require('gulp-util'),
    fs = require('fs-extra'),
    path = require('path'),
    PluginError = gutil.PluginError,
    PLUGIN_NAME = 'walk';

var birthTime = function birthTime(a,b) {
  if (a.birthtime < b.birthtime) {
    return -1;
  }
  if (a.birthtime > b.birthtime) {
    return 1;
  }
  return 0;
};

var modTime = function modTime(a,b) {
  if (a.mtime < b.mtime) {
    return -1;
  }
  if (a.mtime > b.mtime) {
    return 1;
  }
  return 0;
}

//////////////////////////////
// Export
//////////////////////////////
module.exports = function (options) {
  options = options || {};

  //////////////////////////////
  // Command line arguments for each option
  //////////////////////////////
  process.argv.forEach(function (val, index, array) {
    if (index >= 3) {
      switch (val) {
        case '--fail': options.failOnError = true; break;
      }
    }
  });

  //////////////////////////////
  // Through Object
  //////////////////////////////
  var compile = through.obj(function (file, encoding, cb) {
    var walk,
        end = [],
        _this = this;
    /////////////////////////////
    // Default plugin issues
    //////////////////////////////
    if (file.isNull()) {
      return cb();
    }
    if (file.isStream()) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
      return cb();
    }

    //////////////////////////////
    // Manipulate Files
    //////////////////////////////
    walk = function(dir, done) {
      var results = [],
          pth,
          pTransform,
          stat;
      fs.readdir(dir, function(err, list) {
        if (err) return done(err);
        var i = 0;
        (function next() {
          var file = list[i++];

          if (!file) return done(null, results);

          file = dir + '/' + file;
          fs.stat(file, function(err, stat) {
            if (stat && stat.isDirectory()) {
              walk(file, function(err, res) {
                results = results.concat(res);
                next();
              });
            } else {
              stat = fs.statSync(file);
              pth = file.split('/');
              if (options.transformURL) {
                if (pth[pth.length - 1] !== 'index.html') {
                  pTransform = pth.pop();
                  pth.push(pTransform.replace(ext, ''));
                  pth.push('index.html');
                }
              }
              pth = pth.join('/');
              stat['path'] = pth;
              end.push(stat);
              stat = {};
              results.push(file);
              next();
            }
          });
        })();
      });
    };

    if (options.dir) {
      walk(options.dir, function (err, results) {
        if (err) {
          _this.emit('error', new PluginError(PLUGIN_NAME, err));
        }

        if (options.sort === 'created') {
          end = end.sort(birthTime);
        }
        else if (options.sort === 'modified') {
          end = end.sort(modTime);
        }

        if (options.reverse) {
          end = end.reverse();
        }

        file.dirmap = end;

        //////////////////////////////
        // Push the file back to the stream!
        //////////////////////////////
        _this.push(file);

        //////////////////////////////
        // Callback to tell us we're done
        //////////////////////////////
        cb();
      });
    }
    else {
      //////////////////////////////
      // Push the file back to the stream!
      //////////////////////////////
      this.push(file);

      //////////////////////////////
      // Callback to tell us we're done
      //////////////////////////////
      cb();
    }
  })

  return compile;
}
<commit_msg>Better Path handling<commit_after>//////////////////////////////
// Walk
//  - A Gulp Plugin
//
// Walks a directory to find all files
//////////////////////////////
'use strict';

//////////////////////////////
// Variables
//////////////////////////////
var through = require('through2'),
    gutil = require('gulp-util'),
    fs = require('fs-extra'),
    path = require('path'),
    PluginError = gutil.PluginError,
    PLUGIN_NAME = 'walk';

var birthTime = function birthTime(a,b) {
  if (a.birthtime < b.birthtime) {
    return -1;
  }
  if (a.birthtime > b.birthtime) {
    return 1;
  }
  return 0;
};

var modTime = function modTime(a,b) {
  if (a.mtime < b.mtime) {
    return -1;
  }
  if (a.mtime > b.mtime) {
    return 1;
  }
  return 0;
}

//////////////////////////////
// Export
//////////////////////////////
module.exports = function (options) {
  options = options || {};

  //////////////////////////////
  // Command line arguments for each option
  //////////////////////////////
  process.argv.forEach(function (val, index, array) {
    if (index >= 3) {
      switch (val) {
        case '--fail': options.failOnError = true; break;
      }
    }
  });

  //////////////////////////////
  // Through Object
  //////////////////////////////
  var compile = through.obj(function (file, encoding, cb) {
    var walk,
        end = [],
        _this = this;
    /////////////////////////////
    // Default plugin issues
    //////////////////////////////
    if (file.isNull()) {
      return cb();
    }
    if (file.isStream()) {
      this.emit('error', new PluginError(PLUGIN_NAME, 'Streams are not supported!'));
      return cb();
    }

    //////////////////////////////
    // Manipulate Files
    //////////////////////////////
    walk = function(dir, done) {
      var results = [],
          pth,
          pTransform,
          ext,
          stat;
      fs.readdir(dir, function(err, list) {
        if (err) return done(err);
        var i = 0;
        (function next() {
          var file = list[i++];

          if (!file) return done(null, results);

          file = dir + '/' + file;
          fs.stat(file, function(err, stat) {
            if (stat && stat.isDirectory()) {
              walk(file, function(err, res) {
                results = results.concat(res);
                next();
              });
            } else {
              stat = fs.statSync(file);
              pth = file.split('/');
              pth.shift();
              if (options.transformURL) {
                if (pth[pth.length - 1] !== 'index.html' && pth[pth.length - 1] !== '404.html') {
                  pTransform = pth.pop();
                  ext = path.extname(file);
                  pth.push(pTransform.replace(ext, ''));
                  pth.push('index.html');
                }
              }
              else {
                pth[pth.length - 1] = pth[pth.length - 1].replace(ext, '.html');
              }
              pth = pth.join('/');
              stat['path'] = pth;
              stat['file'] = file;
              end.push(stat);
              stat = {};
              results.push(file);
              next();
            }
          });
        })();
      });
    };

    if (options.dir) {
      walk(options.dir, function (err, results) {
        if (err) {
          _this.emit('error', new PluginError(PLUGIN_NAME, err));
        }

        if (options.sort === 'created') {
          end = end.sort(birthTime);
        }
        else if (options.sort === 'modified') {
          end = end.sort(modTime);
        }

        if (options.reverse) {
          end = end.reverse();
        }

        file.dirmap = end;

        //////////////////////////////
        // Push the file back to the stream!
        //////////////////////////////
        _this.push(file);

        //////////////////////////////
        // Callback to tell us we're done
        //////////////////////////////
        cb();
      });
    }
    else {
      //////////////////////////////
      // Push the file back to the stream!
      //////////////////////////////
      this.push(file);

      //////////////////////////////
      // Callback to tell us we're done
      //////////////////////////////
      cb();
    }
  })

  return compile;
}
<|endoftext|>


<commit_before>'use strict';

var nullFormCtrl = {
  $addControl: noop,
  $removeControl: noop,
  $setValidity: noop,
  $setDirty: noop,
  $setPristine: noop
};

/**
 * @ngdoc object
 * @name ng.directive:form.FormController
 *
 * @property {boolean} $pristine True if user has not interacted with the form yet.
 * @property {boolean} $dirty True if user has already interacted with the form.
 * @property {boolean} $valid True if all of the containg forms and controls are valid.
 * @property {boolean} $invalid True if at least one containing control or form is invalid.
 *
 * @property {Object} $error Is an object hash, containing references to all invalid controls or
 *  forms, where:
 *
 *  - keys are validation tokens (error names) — such as `REQUIRED`, `URL` or `EMAIL`),
 *  - values are arrays of controls or forms that are invalid with given error.
 *
 * @description
 * `FormController` keeps track of all its controls and nested forms as well as state of them,
 * such as being valid/invalid or dirty/pristine.
 *
 * Each {@link ng.directive:form form} directive creates an instance
 * of `FormController`.
 *
 */
//asks for $scope to fool the BC controller module
FormController.$inject = ['$element', '$attrs', '$scope'];
function FormController(element, attrs) {
  var form = this,
      parentForm = element.parent().controller('form') || nullFormCtrl,
      invalidCount = 0, // used to easily determine if we are valid
      errors = form.$error = {},
      controls = [];

  // init state
  form.$name = attrs.name;
  form.$dirty = false;
  form.$pristine = true;
  form.$valid = true;
  form.$invalid = false;

  parentForm.$addControl(form);

  // Setup initial state of the control
  element.addClass(PRISTINE_CLASS);
  toggleValidCss(true);

  // convenience method for easy toggling of classes
  function toggleValidCss(isValid, validationErrorKey) {
    validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
    element.
      removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
      addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
  }

  form.$addControl = function(control) {
    controls.push(control);

    if (control.$name && !form.hasOwnProperty(control.$name)) {
      form[control.$name] = control;
    }
  };

  form.$removeControl = function(control) {
    if (control.$name && form[control.$name] === control) {
      delete form[control.$name];
    }
    forEach(errors, function(queue, validationToken) {
      form.$setValidity(validationToken, true, control);
    });

    arrayRemove(controls, control);
  };

  form.$setValidity = function(validationToken, isValid, control) {
    var queue = errors[validationToken];

    if (isValid) {
      if (queue) {
        arrayRemove(queue, control);
        if (!queue.length) {
          invalidCount--;
          if (!invalidCount) {
            toggleValidCss(isValid);
            form.$valid = true;
            form.$invalid = false;
          }
          errors[validationToken] = false;
          toggleValidCss(true, validationToken);
          parentForm.$setValidity(validationToken, true, form);
        }
      }

    } else {
      if (!invalidCount) {
        toggleValidCss(isValid);
      }
      if (queue) {
        if (includes(queue, control)) return;
      } else {
        errors[validationToken] = queue = [];
        invalidCount++;
        toggleValidCss(false, validationToken);
        parentForm.$setValidity(validationToken, false, form);
      }
      queue.push(control);

      form.$valid = false;
      form.$invalid = true;
    }
  };

  form.$setDirty = function() {
    element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
    form.$dirty = true;
    form.$pristine = false;
    parentForm.$setDirty();
  };

  /**
   * @ngdoc function
   * @name ng.directive:form.FormController#$setPristine
   * @methodOf ng.directive:form.FormController
   *
   * @description
   * Sets the form to its pristine state.
   *
   * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
   * state (ng-pristine class). This method will also propagate to all the controls contained
   * in this form.
   *
   * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
   * saving or resetting it.
   */
  form.$setPristine = function () {
    element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
    form.$dirty = false;
    form.$pristine = true;
    forEach(controls, function(control) {
      control.$setPristine();
    });
  };
}

/**
 * @ngdoc directive
 * @name ng.directive:ngForm
 * @restrict EAC
 *
 * @description
 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
 * sub-group of controls needs to be determined.
 *
 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
 *                       related scope, under this name.
 *
 */

 /**
 * @ngdoc directive
 * @name ng.directive:form
 * @restrict E
 *
 * @description
 * Directive that instantiates
 * {@link ng.directive:form.FormController FormController}.
 *
 * If `name` attribute is specified, the form controller is published onto the current scope under
 * this name.
 *
 * # Alias: {@link ng.directive:ngForm `ngForm`}
 *
 * In angular forms can be nested. This means that the outer form is valid when all of the child
 * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
 * reason angular provides {@link ng.directive:ngForm `ngForm`} alias
 * which behaves identical to `<form>` but allows form nesting.
 *
 *
 * # CSS classes
 *  - `ng-valid` Is set if the form is valid.
 *  - `ng-invalid` Is set if the form is invalid.
 *  - `ng-pristine` Is set if the form is pristine.
 *  - `ng-dirty` Is set if the form is dirty.
 *
 *
 * # Submitting a form and preventing default action
 *
 * Since the role of forms in client-side Angular applications is different than in classical
 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
 * page reload that sends the data to the server. Instead some javascript logic should be triggered
 * to handle the form submission in application specific way.
 *
 * For this reason, Angular prevents the default action (form submission to the server) unless the
 * `<form>` element has an `action` attribute specified.
 *
 * You can use one of the following two ways to specify what javascript method should be called when
 * a form is submitted:
 *
 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
 * - {@link ng.directive:ngClick ngClick} directive on the first
  *  button or input field of type submit (input[type=submit])
 *
 * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
 * is because of the following form submission rules coming from the html spec:
 *
 * - If a form has only one input field then hitting enter in this field triggers form submit
 * (`ngSubmit`)
 * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
 * doesn't trigger submit
 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
 *
 * @param {string=} name Name of the form. If specified, the form controller will be published into
 *                       related scope, under this name.
 *
 * @example
    <doc:example>
      <doc:source>

       <form name="myForm" ng-controller="Ctrl">
         userType: <input name="input" ng-model="userType" required>
         Required!

         <tt>userType = {{userType}}</tt>

         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt>

         <tt>myForm.input.$error = {{myForm.input.$error}}</tt>

         <tt>myForm.$valid = {{myForm.$valid}}</tt>

         <tt>myForm.$error.REQUIRED = {{!!myForm.$error.REQUIRED}}</tt>

        </form>
      </doc:source>
      <doc:scenario>
        it('should initialize to model', function() {
         expect(binding('userType')).toEqual('guest');
         expect(binding('myForm.input.$valid')).toEqual('true');
        });

        it('should be invalid if empty', function() {
         input('userType').enter('');
         expect(binding('userType')).toEqual('');
         expect(binding('myForm.input.$valid')).toEqual('false');
        });
      </doc:scenario>
    </doc:example>
 */
var formDirectiveFactory = function(isNgForm) {
  return ['$timeout', function($timeout) {
    var formDirective = {
      name: 'form',
      restrict: 'E',
      controller: FormController,
      compile: function() {
        return {
          pre: function(scope, formElement, attr, controller) {
            if (!attr.action) {
              // we can't use jq events because if a form is destroyed during submission the default
              // action is not prevented. see #1238
              //
              // IE 9 is not affected because it doesn't fire a submit event and try to do a full
              // page reload if the form was destroyed by submission of the form via a click handler
              // on a button in the form. Looks like an IE9 specific bug.
              var preventDefaultListener = function(event) {
                event.preventDefault
                  ? event.preventDefault()
                  : event.returnValue = false; // IE
              };

              addEventListenerFn(formElement[0], 'submit', preventDefaultListener);

              // unregister the preventDefault listener so that we don't not leak memory but in a
              // way that will achieve the prevention of the default action.
              formElement.bind('$destroy', function() {
                $timeout(function() {
                  removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
                }, 0, false);
              });
            }

            var parentFormCtrl = formElement.parent().controller('form'),
                alias = attr.name || attr.ngForm;

            if (alias) {
              scope[alias] = controller;
            }
            if (parentFormCtrl) {
              formElement.bind('$destroy', function() {
                parentFormCtrl.$removeControl(controller);
                if (alias) {
                  scope[alias] = undefined;
                }
                extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
              });
            }
          }
        };
      }
    };

    return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective;
  }];
};

var formDirective = formDirectiveFactory();
var ngFormDirective = formDirectiveFactory(true);
<commit_msg>docs(form): minor form doc and example fixes<commit_after>'use strict';

var nullFormCtrl = {
  $addControl: noop,
  $removeControl: noop,
  $setValidity: noop,
  $setDirty: noop,
  $setPristine: noop
};

/**
 * @ngdoc object
 * @name ng.directive:form.FormController
 *
 * @property {boolean} $pristine True if user has not interacted with the form yet.
 * @property {boolean} $dirty True if user has already interacted with the form.
 * @property {boolean} $valid True if all of the containing forms and controls are valid.
 * @property {boolean} $invalid True if at least one containing control or form is invalid.
 *
 * @property {Object} $error Is an object hash, containing references to all invalid controls or
 *  forms, where:
 *
 *  - keys are validation tokens (error names) — such as `required`, `url` or `email`),
 *  - values are arrays of controls or forms that are invalid with given error.
 *
 * @description
 * `FormController` keeps track of all its controls and nested forms as well as state of them,
 * such as being valid/invalid or dirty/pristine.
 *
 * Each {@link ng.directive:form form} directive creates an instance
 * of `FormController`.
 *
 */
//asks for $scope to fool the BC controller module
FormController.$inject = ['$element', '$attrs', '$scope'];
function FormController(element, attrs) {
  var form = this,
      parentForm = element.parent().controller('form') || nullFormCtrl,
      invalidCount = 0, // used to easily determine if we are valid
      errors = form.$error = {},
      controls = [];

  // init state
  form.$name = attrs.name;
  form.$dirty = false;
  form.$pristine = true;
  form.$valid = true;
  form.$invalid = false;

  parentForm.$addControl(form);

  // Setup initial state of the control
  element.addClass(PRISTINE_CLASS);
  toggleValidCss(true);

  // convenience method for easy toggling of classes
  function toggleValidCss(isValid, validationErrorKey) {
    validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
    element.
      removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
      addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
  }

  form.$addControl = function(control) {
    controls.push(control);

    if (control.$name && !form.hasOwnProperty(control.$name)) {
      form[control.$name] = control;
    }
  };

  form.$removeControl = function(control) {
    if (control.$name && form[control.$name] === control) {
      delete form[control.$name];
    }
    forEach(errors, function(queue, validationToken) {
      form.$setValidity(validationToken, true, control);
    });

    arrayRemove(controls, control);
  };

  form.$setValidity = function(validationToken, isValid, control) {
    var queue = errors[validationToken];

    if (isValid) {
      if (queue) {
        arrayRemove(queue, control);
        if (!queue.length) {
          invalidCount--;
          if (!invalidCount) {
            toggleValidCss(isValid);
            form.$valid = true;
            form.$invalid = false;
          }
          errors[validationToken] = false;
          toggleValidCss(true, validationToken);
          parentForm.$setValidity(validationToken, true, form);
        }
      }

    } else {
      if (!invalidCount) {
        toggleValidCss(isValid);
      }
      if (queue) {
        if (includes(queue, control)) return;
      } else {
        errors[validationToken] = queue = [];
        invalidCount++;
        toggleValidCss(false, validationToken);
        parentForm.$setValidity(validationToken, false, form);
      }
      queue.push(control);

      form.$valid = false;
      form.$invalid = true;
    }
  };

  form.$setDirty = function() {
    element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
    form.$dirty = true;
    form.$pristine = false;
    parentForm.$setDirty();
  };

  /**
   * @ngdoc function
   * @name ng.directive:form.FormController#$setPristine
   * @methodOf ng.directive:form.FormController
   *
   * @description
   * Sets the form to its pristine state.
   *
   * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
   * state (ng-pristine class). This method will also propagate to all the controls contained
   * in this form.
   *
   * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
   * saving or resetting it.
   */
  form.$setPristine = function () {
    element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
    form.$dirty = false;
    form.$pristine = true;
    forEach(controls, function(control) {
      control.$setPristine();
    });
  };
}

/**
 * @ngdoc directive
 * @name ng.directive:ngForm
 * @restrict EAC
 *
 * @description
 * Nestable alias of {@link ng.directive:form `form`} directive. HTML
 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
 * sub-group of controls needs to be determined.
 *
 * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into
 *                       related scope, under this name.
 *
 */

 /**
 * @ngdoc directive
 * @name ng.directive:form
 * @restrict E
 *
 * @description
 * Directive that instantiates
 * {@link ng.directive:form.FormController FormController}.
 *
 * If `name` attribute is specified, the form controller is published onto the current scope under
 * this name.
 *
 * # Alias: {@link ng.directive:ngForm `ngForm`}
 *
 * In angular forms can be nested. This means that the outer form is valid when all of the child
 * forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
 * reason angular provides {@link ng.directive:ngForm `ngForm`} alias
 * which behaves identical to `<form>` but allows form nesting.
 *
 *
 * # CSS classes
 *  - `ng-valid` Is set if the form is valid.
 *  - `ng-invalid` Is set if the form is invalid.
 *  - `ng-pristine` Is set if the form is pristine.
 *  - `ng-dirty` Is set if the form is dirty.
 *
 *
 * # Submitting a form and preventing default action
 *
 * Since the role of forms in client-side Angular applications is different than in classical
 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
 * page reload that sends the data to the server. Instead some javascript logic should be triggered
 * to handle the form submission in application specific way.
 *
 * For this reason, Angular prevents the default action (form submission to the server) unless the
 * `<form>` element has an `action` attribute specified.
 *
 * You can use one of the following two ways to specify what javascript method should be called when
 * a form is submitted:
 *
 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
 * - {@link ng.directive:ngClick ngClick} directive on the first
  *  button or input field of type submit (input[type=submit])
 *
 * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
 * is because of the following form submission rules coming from the html spec:
 *
 * - If a form has only one input field then hitting enter in this field triggers form submit
 * (`ngSubmit`)
 * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
 * doesn't trigger submit
 * - if a form has one or more input fields and one or more buttons or input[type=submit] then
 * hitting enter in any of the input fields will trigger the click handler on the *first* button or
 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
 *
 * @param {string=} name Name of the form. If specified, the form controller will be published into
 *                       related scope, under this name.
 *
 * @example
    <doc:example>
      <doc:source>

       <form name="myForm" ng-controller="Ctrl">
         userType: <input name="input" ng-model="userType" required>
         Required!

         <tt>userType = {{userType}}</tt>

         <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt>

         <tt>myForm.input.$error = {{myForm.input.$error}}</tt>

         <tt>myForm.$valid = {{myForm.$valid}}</tt>

         <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt>

        </form>
      </doc:source>
      <doc:scenario>
        it('should initialize to model', function() {
         expect(binding('userType')).toEqual('guest');
         expect(binding('myForm.input.$valid')).toEqual('true');
        });

        it('should be invalid if empty', function() {
         input('userType').enter('');
         expect(binding('userType')).toEqual('');
         expect(binding('myForm.input.$valid')).toEqual('false');
        });
      </doc:scenario>
    </doc:example>
 */
var formDirectiveFactory = function(isNgForm) {
  return ['$timeout', function($timeout) {
    var formDirective = {
      name: 'form',
      restrict: 'E',
      controller: FormController,
      compile: function() {
        return {
          pre: function(scope, formElement, attr, controller) {
            if (!attr.action) {
              // we can't use jq events because if a form is destroyed during submission the default
              // action is not prevented. see #1238
              //
              // IE 9 is not affected because it doesn't fire a submit event and try to do a full
              // page reload if the form was destroyed by submission of the form via a click handler
              // on a button in the form. Looks like an IE9 specific bug.
              var preventDefaultListener = function(event) {
                event.preventDefault
                  ? event.preventDefault()
                  : event.returnValue = false; // IE
              };

              addEventListenerFn(formElement[0], 'submit', preventDefaultListener);

              // unregister the preventDefault listener so that we don't not leak memory but in a
              // way that will achieve the prevention of the default action.
              formElement.bind('$destroy', function() {
                $timeout(function() {
                  removeEventListenerFn(formElement[0], 'submit', preventDefaultListener);
                }, 0, false);
              });
            }

            var parentFormCtrl = formElement.parent().controller('form'),
                alias = attr.name || attr.ngForm;

            if (alias) {
              scope[alias] = controller;
            }
            if (parentFormCtrl) {
              formElement.bind('$destroy', function() {
                parentFormCtrl.$removeControl(controller);
                if (alias) {
                  scope[alias] = undefined;
                }
                extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
              });
            }
          }
        };
      }
    };

    return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective;
  }];
};

var formDirective = formDirectiveFactory();
var ngFormDirective = formDirectiveFactory(true);
<|endoftext|>


<commit_before>(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object') {
    // CMD
    // all dependencies need to passed as parameters manually,
    // will not require here.
    module.exports = factory;
  } else if (typeof define === 'function' && define.amd) {
    // AMD. Register as sk
    // TODO how to define the jquery plugin here?
    define('sk', ['jsface', 'jquery'], factory);
  } else {
    // in browser, global is window.
    // all dependencies were loaded already.
    // bootstrap and jquery's plugin are all attached to jquery,
    // expose $sk and all components to window.
    factory(global, jsface, jQuery);
  }
}(typeof window !== 'undefined' ? window : this, function (window, jsface, jQuery, DO_NOT_EXPOSE_SK_TO_GLOBAL) {
  var _sk = window.$sk;
  var $sk = {};
  window.$sk = $sk;

  $sk.noConflict = function () {
    window.$sk = _sk;
    return $sk;
  };

  // insert all source code here
  // copy from jQuery
  $sk.extend = function () {
    var options, name, src, copy, copyIsArray, clone,
      target = arguments[0] || {},
      i = 1,
      length = arguments.length,
      deep = false;
    // Handle a deep copy situation
    if (typeof target === "boolean") {
      deep = target;
      // Skip the boolean and the target
      target = arguments[i] || {};
      i++;
    }
    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== "object" && !jQuery.isFunction(target)) {
      target = {};
    }
    // Extend jQuery itself if only one argument is passed
    if (i === length) {
      target = this;
      i--;
    }
    for (; i < length; i++) {
      // Only deal with non-null/undefined values
      if (( options = arguments[i] ) != null) {
        // Extend the base object
        for (name in options) {
          src = target[name];
          copy = options[name];
          // Prevent never-ending loop
          if (target === copy) {
            continue;
          }
          // Recurse if we're merging plain objects or arrays
          if (deep && copy && ( jQuery.isPlainObject(copy) ||
            ( copyIsArray = jQuery.isArray(copy) ) )) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = []//src && jQuery.isArray( src ) ? src : []; //sk different with jQuery
            } else {
              clone = src && jQuery.isPlainObject(src) ? src : {};
            }
            // Never move original objects, clone them
            target[name] = extend(deep, clone, copy);
            // Don't bring in undefined values
          } else if (copy !== undefined) {
            target[name] = copy;
          }
        }
      }
    }
    // Return the modified object
    return target;
  };

  // sk body here
  //Always return valid Array, if invalid return empty array
  $sk.a = function (array) {
    return Array.isArray(array) ? array : [];
  };

  //Just true return true, other return false
  $sk.b = function (boolean) {
    return String(boolean) === 'true' && boolean !== 'true' && boolean;
  };

  //Always return valid Date, if invalid return defaultDate or new Date()
  $sk.d = function (date, defaultDate) {
    var rtnDate = defaultDate ? defaultDate : new Date();
    return (date instanceof Date) ? (date.toString() === 'Invalid Date' ? rtnDate : date) : rtnDate;
  };

  //Can be to Number than return value of number, other return 0
  $sk.n = function (number) {
    return isNaN(Number(number)) ? 0 : Number(number);
  };

  //Always return valid Object, if invalid return empty object
  $sk.o = function (object) {
    return jQuery.isPlainObject(object) ? object : {};
  };

  //Return the String of input
  $sk.s = function (string) {
    return String(string);
  };

  // reset to old $sk
  if (typeof DO_NOT_EXPOSE_SK_TO_GLOBAL !== 'undefined' && DO_NOT_EXPOSE_SK_TO_GLOBAL === true) {
    window.$sk = _sk;
  }
  return $sk;
}));
<commit_msg>Array.isArray<commit_after>(function (global, factory) {
  if (typeof module === 'object' && typeof module.exports === 'object') {
    // CMD
    // all dependencies need to passed as parameters manually,
    // will not require here.
    module.exports = factory;
  } else if (typeof define === 'function' && define.amd) {
    // AMD. Register as sk
    // TODO how to define the jquery plugin here?
    define('sk', ['jsface', 'jquery'], factory);
  } else {
    // in browser, global is window.
    // all dependencies were loaded already.
    // bootstrap and jquery's plugin are all attached to jquery,
    // expose $sk and all components to window.
    factory(global, jsface, jQuery);
  }
}(typeof window !== 'undefined' ? window : this, function (window, jsface, jQuery, DO_NOT_EXPOSE_SK_TO_GLOBAL) {
  var _sk = window.$sk;
  var $sk = {};
  window.$sk = $sk;

  $sk.noConflict = function () {
    window.$sk = _sk;
    return $sk;
  };

  // insert all source code here
  // copy from jQuery
  $sk.extend = function () {
    var options, name, src, copy, copyIsArray, clone,
      target = arguments[0] || {},
      i = 1,
      length = arguments.length,
      deep = false;
    // Handle a deep copy situation
    if (typeof target === "boolean") {
      deep = target;
      // Skip the boolean and the target
      target = arguments[i] || {};
      i++;
    }
    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== "object" && !jQuery.isFunction(target)) {
      target = {};
    }
    // Extend jQuery itself if only one argument is passed
    if (i === length) {
      target = this;
      i--;
    }
    for (; i < length; i++) {
      // Only deal with non-null/undefined values
      if (( options = arguments[i] ) != null) {
        // Extend the base object
        for (name in options) {
          src = target[name];
          copy = options[name];
          // Prevent never-ending loop
          if (target === copy) {
            continue;
          }
          // Recurse if we're merging plain objects or arrays
          if (deep && copy && ( jQuery.isPlainObject(copy) ||
            ( copyIsArray = Array.isArray(copy) ) )) {
            if (copyIsArray) {
              copyIsArray = false;
              clone = []//src && jQuery.isArray( src ) ? src : []; //sk different with jQuery
            } else {
              clone = src && jQuery.isPlainObject(src) ? src : {};
            }
            // Never move original objects, clone them
            target[name] = $sk.extend(deep, clone, copy);
            // Don't bring in undefined values
          } else if (copy !== undefined) {
            target[name] = copy;
          }
        }
      }
    }
    // Return the modified object
    return target;
  };

  // sk body here
  //Always return valid Array, if invalid return empty array
  $sk.a = function (array) {
    return Array.isArray(array) ? array : [];
  };

  //Just true return true, other return false
  $sk.b = function (boolean) {
    return String(boolean) === 'true' && boolean !== 'true' && boolean;
  };

  //Always return valid Date, if invalid return defaultDate or new Date()
  $sk.d = function (date, defaultDate) {
    var rtnDate = defaultDate ? defaultDate : new Date();
    return (date instanceof Date) ? (date.toString() === 'Invalid Date' ? rtnDate : date) : rtnDate;
  };

  //Can be to Number than return value of number, other return 0
  $sk.n = function (number) {
    return isNaN(Number(number)) ? 0 : Number(number);
  };

  //Always return valid Object, if invalid return empty object
  $sk.o = function (object) {
    return jQuery.isPlainObject(object) ? object : {};
  };

  //Return the String of input
  $sk.s = function (string) {
    return String(string);
  };

  // reset to old $sk
  if (typeof DO_NOT_EXPOSE_SK_TO_GLOBAL !== 'undefined' && DO_NOT_EXPOSE_SK_TO_GLOBAL === true) {
    window.$sk = _sk;
  }
  return $sk;
}));
<|endoftext|>


<commit_before>/**
 * Dropdown shows a list of options for navigation.
 * @name Dropdown
 * @class Dropdown
 * @augments ch.Widget
 * @requires ch.Positioner
* @requires ch.Collapsible
 * @requires ch.Closable
 * @see ch.Positioner
 * @memberOf ch
 * @param {Object} [options] Object with configuration properties.
 * @param {Boolean} [options.open] Shows the dropdown open when component was loaded. By default, the value is false.
 * @param {Boolean} [options.reposition]
 * @param {String} [options.points] Sets the points where component will be positioned, specified by configuration or centered by default: "cm cm".
 * @param {Boolean} [options.fx] Enable or disable UI effects. By default, the effects are disable.
 * @returns itself
 * @factorized
 * @exampleDescription Create a new dropdown without configuration.
 * @example
 * var widget = $('.example').dropdown();
 * @exampleDescription Create a new dropdown with configuration.
 * @example
 * var widget = $('.example').dropdown({
 *     'open': true,
 *     'icon': false,
 *     'points': 'lt lt',
 *     'fx': true
 * });
 */
(function (window, $, ch) {
	'use strict';

	if (window.ch === undefined) {
		throw new window.Error('Expected ch namespace defined.');
	}

	function Dropdown($el, options) {

		/**
		 * Reference to a internal component instance, saves all the information and configuration properties.
		 * @private
		 * @type {Object}
		 */
		var that = this;

		that.init($el, options);

		/**
		 * Triggers when the component is ready to use (Since 0.8.0).
		 * @fires ch.Dropdown#ready
		 * @since 0.8.0
		 * @exampleDescription Following the first example, using widget as dropdown's instance controller:
		 * @example
		 * widget.on('ready',function () {
		 *	this.show();
		 * });
		 */
		window.setTimeout(function () { that.emit('ready'); }, 50);

	}

	/**
	 * Private
	 */
	var $document = $(window.document),
		$html = $('html'),

		/**
		 * Inheritance
		 */
		parent = ch.util.inherits(Dropdown, ch.Widget);

	/**
	 * Prototype
	 */
	Dropdown.prototype.name = 'dropdown';

	Dropdown.prototype.constructor = Dropdown;

	Dropdown.prototype._defaults = {
		'open': false,
		'fx': false,
		'side': 'bottom',
		'align': 'left',
		'offsetY': -1,
		'closable': true
	};

	Dropdown.prototype.init = function ($el, options) {
		parent.init.call(this, $el, options);

		this.require('Collapsible', 'Closable');

		/**
		 * Private Members
		 */

		/**
		 * Reference to a internal component instance, saves all the information and configuration properties.
		 * @private
		 * @type {Object}
		 */
		var that = this,

			/**
			 * Map that contains the ARIA attributes for the trigger element
			 * @private
			 * @type {Object}
			 */
			triggerAttr = {
				'aria-expanded': that._options.open,
				'aria-controls': 'ch-dropdown-' + that.uid
			},

			/**
			 * Map that contains the ARIA attributes for the container element
			 * @private
			 * @type {Object}
			 */
			containerAttr = {
				'role': 'menu',
				'id': triggerAttr['aria-controls'],
				'aria-hidden': !triggerAttr['aria-expanded']
			};

		/**
		 * Protected Members
		 */

		 /**
		 * The component's trigger.
		 * @protected
		 * @type {Selector}
		 * @ignore
		 */
		that.$trigger = that.$el.children(':first-child')
			.attr(triggerAttr)
			.addClass('ch-dropdown-trigger ch-dropdown-ico')
			.on(ch.events.pointer.TAP + '.dropdown', function (event) {
				ch.util.prevent(event);
				that.show();
			});

		/**
		 * The component's container.
		 * @protected
		 * @type {Selector}
		 * @ignore
		 */
		that.$container = that.$el.children(':last-child')
			.attr(containerAttr)
			.addClass('ch-dropdown-container ch-hide')
			.on(ch.events.pointer.TAP + '.dropdown', function (event) {
				if ((event.target || event.srcElement).tagName === 'A') {
					that.hide();
				}
			});

		/**
		 * Dropdown options.
		 * @protected
		 * @type {Selector}
		 */
		that.$options = this.$container.find('a')
			.attr('role', 'menuitem');

		/**
		 * Default behavior
		 */

		that.$el.addClass('ch-dropdown');

		if (!that.$el.hasClass('ch-dropdown-skin')) {
			that.$trigger.addClass('ch-btn-skin ch-btn-small');
		}

		that._closable();

		ch.util.avoidTextSelection(this.$trigger);

		return that;
	};

	Dropdown.prototype.show = function () {
		var that = this;

		if (that._active) {
			return that.hide();
		}

		// TODO: Move this code outside show method
		if (that.position === undefined) {
			// TODO: this implementation will be re done
			that.position = new ch.Positioner({
				'target': that.$container,
				'reference': that.$trigger,
				'side': that._options.side,
				'align': that._options.align,
				'offsetY': that._options.offsetY,
				'offsetX': that._options.offsetX
			});
		}

		that.position.refresh();

		that._show();

		// Z-index of content and updates aria values
		that.$container.css('z-index', ch.util.zIndex += 1);

		// Z-index of trigger over content (secondary / skin dropdown)
		if (that.$el.hasClass('ch-dropdown-skin')) {
			that.$trigger.css('z-index', ch.util.zIndex += 1);
		}

		// Reset all dropdowns except itself
		$.each(ch.instances.dropdown, function (i, widget) {
			if (widget.uid !== that.uid) {
				widget.hide();
			}
		});

		that.$options[0].focus();

		// Turn on keyboards arrows
		that.arrowsOn();

		return that;
	};

	Dropdown.prototype.hide = function () {
		var that = this;

		if (!that._active) {
			return that;
		}

		that._hide();

		// Turn off keyboards arrows
		that.arrowsOff();

		return that;
	};

	/**
	 * Returns a Boolean if the component's core behavior is active. That means it will return 'true' if the component is on and it will return false otherwise.
	 * @name isActive
	 * @methodOf ch.Dropdown#isActive
	 * @returns {boolean}
	 * @exampleDescription
	 * @example
	 * if (widget.isActive()) {
	 *     fn();
	 * }
	 */
	Dropdown.prototype.isActive = function () {
		return this._active;
	};

	/**
	 * Turns on keyboard arrows
	 * @protected
	 * @Object
	 * @memberOf ch.dropdown#arrowsOn
	 * @name on
	 */
	Dropdown.prototype.arrowsOn = (function () {
		var selected,
			map = {},
			arrow,
			optionsLength,
			selectOption = function (key) {
				var that = this;

				// Sets the arrow that user press
				arrow = key.type;

				// Sets limits behaivor
				if (selected === (arrow === 'down_arrow' ? optionsLength - 1 : 0)) { return; }

				// Unselects current option
				that.$options[selected].blur();

				if (arrow === 'down_arrow') { selected += 1; } else { selected -= 1; }

				// Selects new current option
				that.$options[selected].focus();
			};

		return function () {
			var that = this;

			// Keyboard support initialize
			selected = 0;

			optionsLength = that.$options.length;

			// Item selected by mouseover
			$.each(that.$options, function (i, e) {
				$(e).on('mouseenter.dropdown', function () {
					that.$options[selected = i].focus();
				});
			});

			// Creates keyboard shortcuts map and binding events
			map[ch.events.key.UP_ARROW + '.dropdown ' + ch.events.key.DOWN_ARROW + '.dropdown'] = function (key, event) {

				// Validations
				if (!that._active) { return; }

				// Prevent default behaivor
				ch.util.prevent(event);
				selectOption.call(that, key);
			}

			$document.on(map);

			return that;
		}
	}());

	/**
	 * Turns off keyboard arrows
	 * @protected
	 * @Object
	 * @memberOf ch.dropdown#arrowsOff
	 * @name off
	 */
	Dropdown.prototype.arrowsOff = function () {
		$document.off(ch.events.key.UP_ARROW + '.dropdown ' + ch.events.key.DOWN_ARROW + '.dropdown');

		return this;
	};

	ch.factory(Dropdown);

}(this, this.jQuery, this.ch));<commit_msg>#913 Dropdown: Update closable configuration.<commit_after>/**
 * Dropdown shows a list of options for navigation.
 * @name Dropdown
 * @class Dropdown
 * @augments ch.Widget
 * @requires ch.Positioner
* @requires ch.Collapsible
 * @requires ch.Closable
 * @see ch.Positioner
 * @memberOf ch
 * @param {Object} [options] Object with configuration properties.
 * @param {Boolean} [options.open] Shows the dropdown open when component was loaded. By default, the value is false.
 * @param {Boolean} [options.reposition]
 * @param {String} [options.points] Sets the points where component will be positioned, specified by configuration or centered by default: "cm cm".
 * @param {Boolean} [options.fx] Enable or disable UI effects. By default, the effects are disable.
 * @returns itself
 * @factorized
 * @exampleDescription Create a new dropdown without configuration.
 * @example
 * var widget = $('.example').dropdown();
 * @exampleDescription Create a new dropdown with configuration.
 * @example
 * var widget = $('.example').dropdown({
 *     'open': true,
 *     'icon': false,
 *     'points': 'lt lt',
 *     'fx': true
 * });
 */
(function (window, $, ch) {
	'use strict';

	if (window.ch === undefined) {
		throw new window.Error('Expected ch namespace defined.');
	}

	function Dropdown($el, options) {

		/**
		 * Reference to a internal component instance, saves all the information and configuration properties.
		 * @private
		 * @type {Object}
		 */
		var that = this;

		that.init($el, options);

		/**
		 * Triggers when the component is ready to use (Since 0.8.0).
		 * @fires ch.Dropdown#ready
		 * @since 0.8.0
		 * @exampleDescription Following the first example, using widget as dropdown's instance controller:
		 * @example
		 * widget.on('ready',function () {
		 *	this.show();
		 * });
		 */
		window.setTimeout(function () { that.emit('ready'); }, 50);

	}

	/**
	 * Private
	 */
	var $document = $(window.document),
		$html = $('html'),

		/**
		 * Inheritance
		 */
		parent = ch.util.inherits(Dropdown, ch.Widget);

	/**
	 * Prototype
	 */
	Dropdown.prototype.name = 'dropdown';

	Dropdown.prototype.constructor = Dropdown;

	Dropdown.prototype._defaults = {
		'open': false,
		'fx': false,
		'side': 'bottom',
		'align': 'left',
		'offsetY': -1,
		'closable': 'keys-only'
	};

	Dropdown.prototype.init = function ($el, options) {
		parent.init.call(this, $el, options);

		this.require('Collapsible', 'Closable');

		/**
		 * Private Members
		 */

		/**
		 * Reference to a internal component instance, saves all the information and configuration properties.
		 * @private
		 * @type {Object}
		 */
		var that = this,

			/**
			 * Map that contains the ARIA attributes for the trigger element
			 * @private
			 * @type {Object}
			 */
			triggerAttr = {
				'aria-expanded': that._options.open,
				'aria-controls': 'ch-dropdown-' + that.uid
			},

			/**
			 * Map that contains the ARIA attributes for the container element
			 * @private
			 * @type {Object}
			 */
			containerAttr = {
				'role': 'menu',
				'id': triggerAttr['aria-controls'],
				'aria-hidden': !triggerAttr['aria-expanded']
			};

		/**
		 * Protected Members
		 */

		 /**
		 * The component's trigger.
		 * @protected
		 * @type {Selector}
		 * @ignore
		 */
		that.$trigger = that.$el.children(':first-child')
			.attr(triggerAttr)
			.addClass('ch-dropdown-trigger ch-dropdown-ico')
			.on(ch.events.pointer.TAP + '.dropdown', function (event) {
				ch.util.prevent(event);
				that.show();
			});

		/**
		 * The component's container.
		 * @protected
		 * @type {Selector}
		 * @ignore
		 */
		that.$container = that.$el.children(':last-child')
			.attr(containerAttr)
			.addClass('ch-dropdown-container ch-hide')
			.on(ch.events.pointer.TAP + '.dropdown', function (event) {
				if ((event.target || event.srcElement).tagName === 'A') {
					that.hide();
				}
			});

		/**
		 * Dropdown options.
		 * @protected
		 * @type {Selector}
		 */
		that.$options = this.$container.find('a')
			.attr('role', 'menuitem');

		/**
		 * Default behavior
		 */

		that.$el.addClass('ch-dropdown');

		if (!that.$el.hasClass('ch-dropdown-skin')) {
			that.$trigger.addClass('ch-btn-skin ch-btn-small');
		}

		that._closable();

		ch.util.avoidTextSelection(this.$trigger);

		return that;
	};

	Dropdown.prototype.show = function () {
		var that = this;

		if (that._active) {
			return that.hide();
		}

		// TODO: Move this code outside show method
		if (that.position === undefined) {
			// TODO: this implementation will be re done
			that.position = new ch.Positioner({
				'target': that.$container,
				'reference': that.$trigger,
				'side': that._options.side,
				'align': that._options.align,
				'offsetY': that._options.offsetY,
				'offsetX': that._options.offsetX
			});
		}

		that.position.refresh();

		that._show();

		// Z-index of content and updates aria values
		that.$container.css('z-index', ch.util.zIndex += 1);

		// Z-index of trigger over content (secondary / skin dropdown)
		if (that.$el.hasClass('ch-dropdown-skin')) {
			that.$trigger.css('z-index', ch.util.zIndex += 1);
		}

		// Reset all dropdowns except itself
		$.each(ch.instances.dropdown, function (i, widget) {
			if (widget.uid !== that.uid) {
				widget.hide();
			}
		});

		that.$options[0].focus();

		// Turn on keyboards arrows
		that.arrowsOn();

		return that;
	};

	Dropdown.prototype.hide = function () {
		var that = this;

		if (!that._active) {
			return that;
		}

		that._hide();

		// Turn off keyboards arrows
		that.arrowsOff();

		return that;
	};

	/**
	 * Returns a Boolean if the component's core behavior is active. That means it will return 'true' if the component is on and it will return false otherwise.
	 * @name isActive
	 * @methodOf ch.Dropdown#isActive
	 * @returns {boolean}
	 * @exampleDescription
	 * @example
	 * if (widget.isActive()) {
	 *     fn();
	 * }
	 */
	Dropdown.prototype.isActive = function () {
		return this._active;
	};

	/**
	 * Turns on keyboard arrows
	 * @protected
	 * @Object
	 * @memberOf ch.dropdown#arrowsOn
	 * @name on
	 */
	Dropdown.prototype.arrowsOn = (function () {
		var selected,
			map = {},
			arrow,
			optionsLength,
			selectOption = function (key) {
				var that = this;

				// Sets the arrow that user press
				arrow = key.type;

				// Sets limits behaivor
				if (selected === (arrow === 'down_arrow' ? optionsLength - 1 : 0)) { return; }

				// Unselects current option
				that.$options[selected].blur();

				if (arrow === 'down_arrow') { selected += 1; } else { selected -= 1; }

				// Selects new current option
				that.$options[selected].focus();
			};

		return function () {
			var that = this;

			// Keyboard support initialize
			selected = 0;

			optionsLength = that.$options.length;

			// Item selected by mouseover
			$.each(that.$options, function (i, e) {
				$(e).on('mouseenter.dropdown', function () {
					that.$options[selected = i].focus();
				});
			});

			// Creates keyboard shortcuts map and binding events
			map[ch.events.key.UP_ARROW + '.dropdown ' + ch.events.key.DOWN_ARROW + '.dropdown'] = function (key, event) {

				// Validations
				if (!that._active) { return; }

				// Prevent default behaivor
				ch.util.prevent(event);
				selectOption.call(that, key);
			}

			$document.on(map);

			return that;
		}
	}());

	/**
	 * Turns off keyboard arrows
	 * @protected
	 * @Object
	 * @memberOf ch.dropdown#arrowsOff
	 * @name off
	 */
	Dropdown.prototype.arrowsOff = function () {
		$document.off(ch.events.key.UP_ARROW + '.dropdown ' + ch.events.key.DOWN_ARROW + '.dropdown');

		return this;
	};

	ch.factory(Dropdown);

}(this, this.jQuery, this.ch));<|endoftext|>


<commit_before>var crypto = require('crypto');
var xml2js = require('xml2js');
var ejs = require('ejs');
var BufferHelper = require('bufferhelper');
var Session = require('./session');
var List = require('./list');

/*!
 * 检查签名
 */
var checkSignature = function (query, token) {
  var signature = query.signature;
  var timestamp = query.timestamp;
  var nonce = query.nonce;

  var shasum = crypto.createHash('sha1');
  var arr = [token, timestamp, nonce].sort();
  shasum.update(arr.join(''));

  return shasum.digest('hex') === signature;
};

/*!
 * 响应模版
 */
var tpl = ['<xml>',
    '<ToUserName><![CDATA[<%-toUsername%>]]></ToUserName>',
    '<FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName>',
    '<CreateTime><%=createTime%></CreateTime>',
    '<MsgType><![CDATA[<%=msgType%>]]></MsgType>',
  '<% if (msgType === "news") { %>',
    '<ArticleCount><%=content.length%></ArticleCount>',
    '<Articles>',
    '<% content.forEach(function(item){ %>',
      '<item>',
        '<Title><![CDATA[<%-item.title%>]]></Title>',
        '<Description><![CDATA[<%-item.description%>]]></Description>',
        '<PicUrl><![CDATA[<%-item.picUrl || item.picurl || item.pic %>]]></PicUrl>',
        '<Url><![CDATA[<%-item.url%>]]></Url>',
      '</item>',
    '<% }); %>',
    '</Articles>',
  '<% } else if (msgType === "music") { %>',
    '<Music>',
      '<Title><![CDATA[<%-content.title%>]]></Title>',
      '<Description><![CDATA[<%-content.description%>]]></Description>',
      '<MusicUrl><![CDATA[<%-content.musicUrl || content.url %>]]></MusicUrl>',
      '<HQMusicUrl><![CDATA[<%-content.hqMusicUrl || content.hqUrl %>]]></HQMusicUrl>',
    '</Music>',
  '<% } else if (msgType === "voice") { %>',
    '<Voice>',
      '<MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>',
    '</Voice>',
  '<% } else if (msgType === "image") { %>',
    '<Image>',
      '<MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>',
    '</Image>',
  '<% } else if (msgType === "video") { %>',
    '<Video>',
      '<MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>',
      '<Title><![CDATA[<%-content.title%>]]></Title>',
      '<Description><![CDATA[<%-content.description%>]]></Description>',
    '</Video>',
  '<% } else if (msgType === "transfer_customer_service") { %>',
    '<% if (content && content.kfAccount) { %>',
      '<TransInfo>',
        '<KfAccount><%-content.kfAccount%></KfAccount>',
      '</TransInfo>',
    '<% } %>',
  '<% } else { %>',
    '<Content><![CDATA[<%-content%>]]></Content>',
  '<% } %>',
  '</xml>'].join('');

/*!
 * 编译过后的模版
 */
var compiled = ejs.compile(tpl);

/*!
 * 从微信的提交中提取XML文件
 */
var getMessage = function (stream, callback) {
  var buf = new BufferHelper();
  buf.load(stream, function (err, buf) {
    if (err) {
      return callback(err);
    }
    var xml = buf.toString('utf-8');
    stream.weixin_xml = xml;
    xml2js.parseString(xml, {trim: true}, callback);
  });
};

/*!
 * 检查对象是否为空，对xml2js的workaround
 */
var isEmpty = function (thing) {
  return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
};

/*!
 * 将xml2js解析出来的对象转换成直接可访问的对象
 */
var formatMessage = function (result) {
  var message = {};
  for (var key in result.xml) {
    var val = result.xml[key][0];
    message[key] = (isEmpty(val) ? '' : val).trim();
  }
  return message;
};

/*!
 * 将内容回复给微信的封装方法
 */
var reply = function (content, fromUsername, toUsername) {
  var info = {};
  var type = 'text';
  info.content = content || '';
  if (Array.isArray(content)) {
    type = 'news';
  } else if (typeof content === 'object') {
    if (content.hasOwnProperty('type')) {
      type = content.type;
      info.content = content.content;
    } else {
      type = 'music';
    }
  }
  info.msgType = type;
  info.createTime = new Date().getTime();
  info.toUsername = toUsername;
  info.fromUsername = fromUsername;
  return compiled(info);
};

var reply2CustomerService = function (kfAccount, fromUsername, toUsername) {
  var info = {};
  info.msgType = 'transfer_customer_service';
  info.createTime = new Date().getTime();
  info.toUsername = toUsername;
  info.fromUsername = fromUsername;
  info.content = {};
  if (typeof kfAccount === 'string') info.content.kfAccount = kfAccount;
  return compiled(info);
};

var respond = function (handler) {
  return function (req, res, next) {
    getMessage(req, function (err, result) {
      if (err) {
        err.name = 'BadMessage' + err.name;
        return next(err);
      }
      var message = formatMessage(result);
      var callback = handler.getHandler(message.MsgType);
      req.weixin = message;
      res.reply = function (content) {
        res.writeHead(200);
        if (!content) return res.end('');
        res.end(reply(content, message.ToUserName, message.FromUserName));
      };

      // 响应消息，转到客服模式
      res.transfer2CustomerService = function (kfAccount) {
        res.writeHead(200);
        res.end(reply2CustomerService(kfAccount, message.ToUserName, message.FromUserName));
      };

      var done = function () {
        // 如果session中有_wait标记
        if (message.MsgType === 'text' && req.wxsession && req.wxsession._wait) {
          var list = List.get(req.wxsession._wait);
          var handle = list.get(message.Content);
          var wrapper = function (message) {
            return handler.handle ? function(req, res) {
              res.reply(message);
            } : function (info, req, res) {
              res.reply(message);
            };
          };

          // 如果回复命中规则，则用预置的方法回复
          if (handle) {
            callback = typeof handle === 'string' ? wrapper(handle) : handle;
          }
        }

        // 兼容旧API
        if (handler.handle) {
          callback(req, res, next);
        } else {
          callback(message, req, res, next);
        }
      };

      if (req.sessionStore) {
        var storage = req.sessionStore;
        var _end = res.end;
        var openid = message.FromUserName + ':' + message.ToUserName;
        res.end = function () {
          _end.apply(res, arguments);
          if (req.wxsession) {
            req.wxsession.save();
          }
        };
        // 等待列表
        res.wait = function (name, callback) {
          var list = List.get(name);
          if (list) {
            req.wxsession._wait = name;
            res.reply(list.description);
          } else {
            var err = new Error('Undefined list: ' + name);
            err.name = 'UndefinedListError';
            res.writeHead(500);
            res.end(err.name);
            callback && callback(err);
          }
        };

        // 清除等待列表
        res.nowait = function () {
          delete req.wxsession._wait;
          res.reply.apply(res, arguments);
        };

        storage.get(openid, function (err, session) {
          if (!session) {
            req.wxsession = new Session(openid, req);
            req.wxsession.cookie = req.session.cookie;
          } else {
            req.wxsession = new Session(openid, req, session);
          }
          done();
        });
      } else {
        done();
      }
    });
  };
};

/**
 * 微信自动回复平台的内部的Handler对象
 * @param {String} token 口令
 * @param {Function} handle handle对象
 */
var Handler = function (token, handle) {
  this.token = token;
  this.handlers = {};
  this.handle = handle;
};

/**
 * 设置handler对象
 * 按消息设置handler对象的快捷方式
 *
 * - `text(fn)`
 * - `image(fn)`
 * - `voice(fn)`
 * - `video(fn)`
 * - `location(fn)`
 * - `link(fn)`
 * - `event(fn)`
 * @param {String} type handler处理的消息类型
 * @param {Function} handle handle对象
 */
Handler.prototype.setHandler = function (type, fn) {
  this.handlers[type] = fn;
  return this;
};

['text', 'image', 'voice', 'video', 'location', 'link', 'event'].forEach(function (method) {
  Handler.prototype[method] = function (fn) {
    return this.setHandler(method, fn);
  };
});

/**
 * 根据消息类型取出handler对象
 * @param {String} type 消息类型
 */
Handler.prototype.getHandler = function (type) {
  return this.handle || this.handlers[type] || function (info, req, res, next) {
    next();
  };
};

/**
 * 根据Handler对象生成响应方法，并最终生成中间件函数
 */
Handler.prototype.middlewarify = function () {
  var token = this.token;
  var _respond = respond(this);
  return function (req, res, next) {
    // 动态token，在前置中间件中设置该值req.wechat_token，优先选用
    if (!checkSignature(req.query, req.wechat_token || token)) {
      res.writeHead(401);
      res.end('Invalid signature');
      return;
    }
    var method = req.method;
    if (method === 'GET') {
      res.writeHead(200);
      res.end(req.query.echostr);
    } else if (method === 'POST') {
      _respond(req, res, next);
    } else {
      res.writeHead(501);
      res.end('Not Implemented');
    }
  };
};

/**
 * 根据口令
 *
 * Examples:
 * 使用wechat作为自动回复中间件的三种方式
 * ```
 * wechat(token, function (req, res, next) {});
 *
 * wechat(token, wechat.text(function (message, req, res, next) {
 *   // TODO
 * }).location(function (message, req, res, next) {
 *   // TODO
 * }));
 *
 * wechat(token)
 *   .text(function (message, req, res, next) {
 *     // TODO
 *   }).location(function (message, req, res, next) {
 *    // TODO
 *   }).middleware();
 * ```
 * 静态方法
 *
 * - `text`，处理文字推送的回调函数，接受参数为(text, req, res, next)。
 * - `image`，处理图片推送的回调函数，接受参数为(image, req, res, next)。
 * - `voice`，处理声音推送的回调函数，接受参数为(voice, req, res, next)。
 * - `video`，处理视频推送的回调函数，接受参数为(video, req, res, next)。
 * - `location`，处理位置推送的回调函数，接受参数为(location, req, res, next)。
 * - `link`，处理链接推送的回调函数，接受参数为(link, req, res, next)。
 * - `event`，处理事件推送的回调函数，接受参数为(event, req, res, next)。
 * @param {String} token 在微信平台填写的口令
 * @param {Function} handle 生成的回调函数，参见示例
 */
var middleware = function (token, handle) {
  if (arguments.length === 1) {
    return new Handler(token);
  }

  if (arguments.length === 2) {
    if (handle instanceof Handler) {
      handle.token = token;
      return handle.middlewarify();
    } else {
      return new Handler(token, handle).middlewarify();
    }
  }
};

['text', 'image', 'voice', 'video', 'location', 'link', 'event'].forEach(function (method) {
  middleware[method] = function (fn) {
    return (new Handler())[method](fn);
  };
});

middleware.toXML = compiled;
middleware.reply = reply;
middleware.reply2CustomerService = reply2CustomerService;
middleware.checkSignature = checkSignature;

module.exports = middleware;
<commit_msg>Refine code<commit_after>var crypto = require('crypto');
var xml2js = require('xml2js');
var ejs = require('ejs');
var BufferHelper = require('bufferhelper');
var Session = require('./session');
var List = require('./list');

/*!
 * 检查签名
 */
var checkSignature = function (query, token) {
  var signature = query.signature;
  var timestamp = query.timestamp;
  var nonce = query.nonce;

  var shasum = crypto.createHash('sha1');
  var arr = [token, timestamp, nonce].sort();
  shasum.update(arr.join(''));

  return shasum.digest('hex') === signature;
};

/*!
 * 响应模版
 */
var tpl = ['<xml>',
    '<ToUserName><![CDATA[<%-toUsername%>]]></ToUserName>',
    '<FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName>',
    '<CreateTime><%=createTime%></CreateTime>',
    '<MsgType><![CDATA[<%=msgType%>]]></MsgType>',
  '<% if (msgType === "news") { %>',
    '<ArticleCount><%=content.length%></ArticleCount>',
    '<Articles>',
    '<% content.forEach(function(item){ %>',
      '<item>',
        '<Title><![CDATA[<%-item.title%>]]></Title>',
        '<Description><![CDATA[<%-item.description%>]]></Description>',
        '<PicUrl><![CDATA[<%-item.picUrl || item.picurl || item.pic %>]]></PicUrl>',
        '<Url><![CDATA[<%-item.url%>]]></Url>',
      '</item>',
    '<% }); %>',
    '</Articles>',
  '<% } else if (msgType === "music") { %>',
    '<Music>',
      '<Title><![CDATA[<%-content.title%>]]></Title>',
      '<Description><![CDATA[<%-content.description%>]]></Description>',
      '<MusicUrl><![CDATA[<%-content.musicUrl || content.url %>]]></MusicUrl>',
      '<HQMusicUrl><![CDATA[<%-content.hqMusicUrl || content.hqUrl %>]]></HQMusicUrl>',
    '</Music>',
  '<% } else if (msgType === "voice") { %>',
    '<Voice>',
      '<MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>',
    '</Voice>',
  '<% } else if (msgType === "image") { %>',
    '<Image>',
      '<MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>',
    '</Image>',
  '<% } else if (msgType === "video") { %>',
    '<Video>',
      '<MediaId><![CDATA[<%-content.mediaId%>]]></MediaId>',
      '<Title><![CDATA[<%-content.title%>]]></Title>',
      '<Description><![CDATA[<%-content.description%>]]></Description>',
    '</Video>',
  '<% } else if (msgType === "transfer_customer_service") { %>',
    '<% if (content && content.kfAccount) { %>',
      '<TransInfo>',
        '<KfAccount><%-content.kfAccount%></KfAccount>',
      '</TransInfo>',
    '<% } %>',
  '<% } else { %>',
    '<Content><![CDATA[<%-content%>]]></Content>',
  '<% } %>',
  '</xml>'].join('');

/*!
 * 编译过后的模版
 */
var compiled = ejs.compile(tpl);

/*!
 * 从微信的提交中提取XML文件
 */
var getMessage = function (stream, callback) {
  var buf = new BufferHelper();
  buf.load(stream, function (err, buf) {
    if (err) {
      return callback(err);
    }
    var xml = buf.toString('utf-8');
    stream.weixin_xml = xml;
    xml2js.parseString(xml, {trim: true}, callback);
  });
};

/*!
 * 检查对象是否为空，对xml2js的workaround
 */
var isEmpty = function (thing) {
  return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
};

/*!
 * 将xml2js解析出来的对象转换成直接可访问的对象
 */
var formatMessage = function (result) {
  var message = {};
  for (var key in result.xml) {
    var val = result.xml[key][0];
    message[key] = (isEmpty(val) ? '' : val).trim();
  }
  return message;
};

/*!
 * 将内容回复给微信的封装方法
 */
var reply = function (content, fromUsername, toUsername) {
  var info = {};
  var type = 'text';
  info.content = content || '';
  if (Array.isArray(content)) {
    type = 'news';
  } else if (typeof content === 'object') {
    if (content.hasOwnProperty('type')) {
      type = content.type;
      info.content = content.content;
    } else {
      type = 'music';
    }
  }
  info.msgType = type;
  info.createTime = new Date().getTime();
  info.toUsername = toUsername;
  info.fromUsername = fromUsername;
  return compiled(info);
};

var reply2CustomerService = function (kfAccount, fromUsername, toUsername) {
  var info = {};
  info.msgType = 'transfer_customer_service';
  info.createTime = new Date().getTime();
  info.toUsername = toUsername;
  info.fromUsername = fromUsername;
  info.content = {};
  if (typeof kfAccount === 'string') {
    info.content.kfAccount = kfAccount;
  }
  return compiled(info);
};

var respond = function (handler) {
  return function (req, res, next) {
    getMessage(req, function (err, result) {
      if (err) {
        err.name = 'BadMessage' + err.name;
        return next(err);
      }
      var message = formatMessage(result);
      var callback = handler.getHandler(message.MsgType);
      req.weixin = message;
      res.reply = function (content) {
        res.writeHead(200);
        // 响应空字符串，用于响应慢的情况，避免微信重试
        if (!content) {
          return res.end();
        }
        res.end(reply(content, message.ToUserName, message.FromUserName));
      };

      // 响应消息，转到客服模式
      res.transfer2CustomerService = function (kfAccount) {
        res.writeHead(200);
        res.end(reply2CustomerService(kfAccount, message.ToUserName, message.FromUserName));
      };

      var done = function () {
        // 如果session中有_wait标记
        if (message.MsgType === 'text' && req.wxsession && req.wxsession._wait) {
          var list = List.get(req.wxsession._wait);
          var handle = list.get(message.Content);
          var wrapper = function (message) {
            return handler.handle ? function(req, res) {
              res.reply(message);
            } : function (info, req, res) {
              res.reply(message);
            };
          };

          // 如果回复命中规则，则用预置的方法回复
          if (handle) {
            callback = typeof handle === 'string' ? wrapper(handle) : handle;
          }
        }

        // 兼容旧API
        if (handler.handle) {
          callback(req, res, next);
        } else {
          callback(message, req, res, next);
        }
      };

      if (req.sessionStore) {
        var storage = req.sessionStore;
        var _end = res.end;
        var openid = message.FromUserName + ':' + message.ToUserName;
        res.end = function () {
          _end.apply(res, arguments);
          if (req.wxsession) {
            req.wxsession.save();
          }
        };
        // 等待列表
        res.wait = function (name, callback) {
          var list = List.get(name);
          if (list) {
            req.wxsession._wait = name;
            res.reply(list.description);
          } else {
            var err = new Error('Undefined list: ' + name);
            err.name = 'UndefinedListError';
            res.writeHead(500);
            res.end(err.name);
            callback && callback(err);
          }
        };

        // 清除等待列表
        res.nowait = function () {
          delete req.wxsession._wait;
          res.reply.apply(res, arguments);
        };

        storage.get(openid, function (err, session) {
          if (!session) {
            req.wxsession = new Session(openid, req);
            req.wxsession.cookie = req.session.cookie;
          } else {
            req.wxsession = new Session(openid, req, session);
          }
          done();
        });
      } else {
        done();
      }
    });
  };
};

/**
 * 微信自动回复平台的内部的Handler对象
 * @param {String} token 口令
 * @param {Function} handle handle对象
 */
var Handler = function (token, handle) {
  this.token = token;
  this.handlers = {};
  this.handle = handle;
};

/**
 * 设置handler对象
 * 按消息设置handler对象的快捷方式
 *
 * - `text(fn)`
 * - `image(fn)`
 * - `voice(fn)`
 * - `video(fn)`
 * - `location(fn)`
 * - `link(fn)`
 * - `event(fn)`
 * @param {String} type handler处理的消息类型
 * @param {Function} handle handle对象
 */
Handler.prototype.setHandler = function (type, fn) {
  this.handlers[type] = fn;
  return this;
};

['text', 'image', 'voice', 'video', 'location', 'link', 'event'].forEach(function (method) {
  Handler.prototype[method] = function (fn) {
    return this.setHandler(method, fn);
  };
});

/**
 * 根据消息类型取出handler对象
 * @param {String} type 消息类型
 */
Handler.prototype.getHandler = function (type) {
  return this.handle || this.handlers[type] || function (info, req, res, next) {
    next();
  };
};

/**
 * 根据Handler对象生成响应方法，并最终生成中间件函数
 */
Handler.prototype.middlewarify = function () {
  var token = this.token;
  var _respond = respond(this);
  return function (req, res, next) {
    // 动态token，在前置中间件中设置该值req.wechat_token，优先选用
    if (!checkSignature(req.query, req.wechat_token || token)) {
      res.writeHead(401);
      res.end('Invalid signature');
      return;
    }
    var method = req.method;
    if (method === 'GET') {
      res.writeHead(200);
      res.end(req.query.echostr);
    } else if (method === 'POST') {
      _respond(req, res, next);
    } else {
      res.writeHead(501);
      res.end('Not Implemented');
    }
  };
};

/**
 * 根据口令
 *
 * Examples:
 * 使用wechat作为自动回复中间件的三种方式
 * ```
 * wechat(token, function (req, res, next) {});
 *
 * wechat(token, wechat.text(function (message, req, res, next) {
 *   // TODO
 * }).location(function (message, req, res, next) {
 *   // TODO
 * }));
 *
 * wechat(token)
 *   .text(function (message, req, res, next) {
 *     // TODO
 *   }).location(function (message, req, res, next) {
 *    // TODO
 *   }).middleware();
 * ```
 * 静态方法
 *
 * - `text`，处理文字推送的回调函数，接受参数为(text, req, res, next)。
 * - `image`，处理图片推送的回调函数，接受参数为(image, req, res, next)。
 * - `voice`，处理声音推送的回调函数，接受参数为(voice, req, res, next)。
 * - `video`，处理视频推送的回调函数，接受参数为(video, req, res, next)。
 * - `location`，处理位置推送的回调函数，接受参数为(location, req, res, next)。
 * - `link`，处理链接推送的回调函数，接受参数为(link, req, res, next)。
 * - `event`，处理事件推送的回调函数，接受参数为(event, req, res, next)。
 * @param {String} token 在微信平台填写的口令
 * @param {Function} handle 生成的回调函数，参见示例
 */
var middleware = function (token, handle) {
  if (arguments.length === 1) {
    return new Handler(token);
  }

  if (arguments.length === 2) {
    if (handle instanceof Handler) {
      handle.token = token;
      return handle.middlewarify();
    } else {
      return new Handler(token, handle).middlewarify();
    }
  }
};

['text', 'image', 'voice', 'video', 'location', 'link', 'event'].forEach(function (method) {
  middleware[method] = function (fn) {
    return (new Handler())[method](fn);
  };
});

middleware.toXML = compiled;
middleware.reply = reply;
middleware.reply2CustomerService = reply2CustomerService;
middleware.checkSignature = checkSignature;

module.exports = middleware;
<|endoftext|>