<commit_before>/*globals */
'use strict';

var EventEmitter = require('eventemitter3')
  , collection = require('./collection')
  , Fortress = require('fortress')
  , async = require('./async')
  , val = require('parsifal')
  , sandbox;

/**
 * Representation of a single pagelet.
 *
 * @constructor
 * @param {Pipe} pipe The pipe.
 * @api public
 */
function Pagelet(pipe) {
  EventEmitter.call(this);

  this.orchestrate = pipe.orchestrate;
  this.stream = pipe.stream;
  this.pipe = pipe;

  //
  // Create one single Fortress instance that orchestrates all iframe based client
  // code. This sandbox variable should never be exposed to the outside world in
  // order to prevent leaking.
  //
  this.sandbox = sandbox = sandbox || new Fortress;
}

//
// Inherit from EventEmitter.
//
Pagelet.prototype = new EventEmitter();
Pagelet.prototype.constructor = Pagelet;

/**
 * Configure the Pagelet.
 *
 * @param {String} name The given name of the pagelet.
 * @param {Object} data The data of the pagelet.
 * @api private
 */
Pagelet.prototype.configure = function configure(name, data) {
  var pagelet = this;

  this.placeholders = this.$('data-pagelet', name);

  //
  // Pagelet identification.
  //
  this.id = data.id;
  this.name = name;

  //
  // The pagelet
  //
  if (data.remove) {
    return this.destroy(true);
  }

  //
  // Attach event listeners for FORM posts so we can intercept those.
  //
  this.listen();

  //
  // Create a real-time Substream over which we can communicate over without.
  //
  this.substream = this.stream.substream(this.name);
  this.substream.on('data', function data(packet) {
    pagelet.processor(packet);
  });

  //
  // Register the pagelet with the BigPipe server as an indication that we've
  // been fully loaded and ready for action.
  //
  this.orchestrate.write({ type: 'pagelet', name: name });

  this.css = collection.array(data.css);    // CSS for the Page.
  this.js = collection.array(data.js);      // Dependencies for the page.
  this.run = data.run;                      // Pagelet client code.
  this.rpc = data.rpc;                      // Pagelet RPC methods.
  this.data = data.data;                    // All the template data.
  this.mode = data.mode;                    // Fragment rendering mode.
  this.streaming = !!data.streaming;        // Are we streaming POST/GET.
  this.container = this.sandbox.create();   // Create an application sandbox.
  this.timeout = data.timeout || 25 * 1000; // Resource loading timeout.

  //
  // Generate the RPC methods that we're given by the server. We will make the
  // assumption that:
  //
  // - A callback function is always given as last argument.
  // - The function should return it self in order to chain.
  // - The function given supports and uses error first callback styles.
  // - Does not override the build-in prototypes of the Pagelet.
  //
  collection.each(this.rpc, function rpc(method) {
    var counter = 0;

    //
    // Never override build-in methods as this WILL affect the way a Pagelet is
    // working.
    //
    if (method in Pagelet.prototype) return;

    pagelet[method] = function rpcfactory() {
      var args = Array.prototype.slice.call(arguments, 0)
        , id = method +'#'+ (++counter);

      pagelet.once('rpc::'+ id, args.pop());
      pagelet.substream.write({ method: method, type: 'rpc', args: args, id: id });

      return pagelet;
    };
  });

  //
  // Should be called before we create `rpc` hooks.
  //
  this.broadcast('configured', data);

  async.each(this.css.concat(this.js), function download(asset, next) {
    this.load(document.body, asset, next);
  }, function done(err) {
    if (err) return pagelet.broadcast('error', err);
    pagelet.broadcast('loaded');

    pagelet.render(pagelet.parse());
    pagelet.initialise();
  }, { context: this.pipe, timeout: this.timeout });
};

/**
 * Intercept form posts and stream them over our substream instead to prevent
 * full page reload.
 *
 * @returns {Pagelet}
 * @api private
 */
Pagelet.prototype.listen = function listen() {
  var pagelet = this;

  /**
   * Handles the actual form submission.
   *
   * @param {Event} evt The submit event.
   * @api private
   */
  function submission(evt) {
    evt = evt || window.event;
    var form = evt.target || evt.srcElement;

    //
    // In previous versions we had and `evt.preventDefault()` so we could make
    // changes to the form and re-submit it. But there's a big problem with that
    // and that is that in FireFox it loses the reference to the button that
    // triggered the submit. If causes buttons that had a name and value:
    //
    // ```html
    // <button name="key" value="value" type="submit">submit</button>
    // ```
    //
    // To be missing from the POST or GET. We managed to go around it by not
    // simply preventing the default action. If this still does not not work we
    // need to transform the form URLs once the pagelets are loaded.
    //
    if (
         ('getAttribute' in form && form.getAttribute('data-pagelet-async') === 'false')
      || !pagelet.streaming
    ) {
      var action = form.getAttribute('action');
      return form.setAttribute('action', [
        action,
        ~action.indexOf('?') ? '&' : '?',
        '_pagelet=',
        pagelet.name
      ].join(''));
    }

    //
    // As we're submitting the form over our real-time connection and gather the
    // data our self we can safely prevent default.
    //
    evt.preventDefault();
    pagelet.submit(form);
  }

  collection.each(this.placeholders, function each(root) {
    root.addEventListener('submit', submission, false);
  });

  //
  // When the pagelet is removed we want to remove our listeners again. To
  // prevent memory leaks as well possible duplicate listeners when a pagelet is
  // loaded in the same placeholder (in case of a full reload).
  //
  return this.once('destroy', function destroy() {
    collection.each(pagelet.placeholders, function each(root) {
      root.removeEventListener('submit', submission, false);
    });
  });
};

/**
 * Submit the contents of a <form> to the server.
 *
 * @param {FormElement} form Form that needs to be submitted.
 * @returns {Object} The data that is ported to the server.
 * @api public
 */
Pagelet.prototype.submit = function submit(form) {
  var active = document.activeElement
    , elements = form.elements
    , data = {}
    , element
    , i;

  if (active && active.name) {
    data[active.name] = active.value;
  } else {
    active = false;
  }

  for (i = 0; i < elements.length; i++) {
    element = elements[i];

    //
    // Story time children! Once upon a time there was a developer, this
    // developer created a form with a lot of submit buttons. The developer
    // knew that when a user clicked on one of those buttons the value="" and
    // name="" attributes would get send to the server so he could see which
    // button people had clicked. He implemented this and all was good. Until
    // someone captured the `submit` event in the browser which didn't have
    // a reference to the clicked element. This someone found out that the
    // `document.activeElement` pointed to the last clicked element and used
    // that to restore the same functionality and the day was saved again.
    //
    // There are valuable lessons to be learned here. Submit buttons are the
    // suck. PERIOD.
    //
    if (
         element.name
      && !(element.name in data)
      && element.disabled === false
      && /^(?:input|select|textarea|keygen)/i.test(element.nodeName)
      && !/^(?:submit|button|image|reset|file)$/i.test(element.type)
      && (element.checked || !/^(?:checkbox|radio)$/i.test(element.type))
    ) data[element.name] = val(element);
  }

  //
  // Now that we have a JSON object, we can just send it over our real-time
  // connection and wait for a page refresh.
  //
  this.substream.write({
    type: (form.method || 'GET').toLowerCase(),
    body: data
  });

  return data;
};

/**
 * Get the pagelet contents once again.
 *
 * @returns {Pagelet}
 * @api public
 */
Pagelet.prototype.get = function get() {
  this.substream.write({ type: 'get' });

  return this;
};

/**
 * Process the incoming messages from our SubStream.
 *
 * @param {Object} packet The decoded message.
 * @returns {Boolean}
 * @api private
 */
Pagelet.prototype.processor = function processor(packet) {
  if ('object' !== typeof packet) return false;

  switch (packet.type) {
    case 'rpc':
      this.emit.apply(this, ['rpc::'+ packet.id].concat(packet.args || []));
    break;

    case 'event':
      if (packet.args && packet.args.length) {
        this.emit.apply(this, packet.args);
      }
    break;

    case 'fragment':
      this.render(packet.frag.view);
    break;

    case 'redirect':
      window.location.href = packet.url;
    break;

    default:
    return false;
  }

  return true;
};

/**
 * The pagelet's resource has all been loaded.
 *
 * @api private
 */
Pagelet.prototype.initialise = function initialise() {
  this.broadcast('initialise');

  //
  // Only load the client code in a sandbox when it exists. There no point in
  // spinning up a sandbox if it does nothing
  //
  if (!this.code) return;
  this.sandbox(this.prepare(this.code));
};

/**
 * Broadcast an event that will be emitted on the pagelet and the page.
 *
 * @param {String} event The name of the event we should emit
 * @returns {Pagelet}
 * @api public
 */
Pagelet.prototype.broadcast = function broadcast(event) {
  this.emit.apply(this, arguments);
  this.pipe.emit.apply(this.pipe, [
    this.name +'::'+ event,
    this
  ].concat(Array.prototype.slice.call(arguments, 1)));

  return this;
};

/**
 * Find the element based on the attribute and value.
 *
 * @param {String} attribute The name of the attribute we're searching.
 * @param {String} value The value that the attribute should equal to.
 * @returns {Array} A list of HTML elements that match.
 * @api public
 */
Pagelet.prototype.$ = function $(attribute, value) {
  if (document && 'querySelectorAll' in document) {
    return Array.prototype.slice.call(
        document.querySelectorAll('['+ attribute +'="'+ value +'"]')
      , 0
    );
  }

  //
  // No querySelectorAll support, so we're going to do a full DOM scan.
  //
  var all = document.getElementsByTagName('*')
    , length = all.length
    , results = []
    , i = 0;

  for (; i < length; i++) {
    if (value === all[i].getAttribute(attribute)) {
      results.push(all[i]);
    }
  }

  return results;
};

/**
 * Invoke the correct render method for the pagelet.
 *
 * @param {String} html The HTML that needs to be added in the placeholders.
 * @returns {Boolean} Successfully rendered a pagelet.
 * @api public
 */
Pagelet.prototype.render = function render(html) {
  if (!this.placeholders.length || !html) return false;
  var mode = this.mode in this ? this[this.mode] : this.html;

  collection.each(this.placeholders, function each(root) {
    mode.call(this, root, html);
  }, this);

  this.broadcast('render', html);
  return true;
};

/**
 * Render the fragment as HTML (default).
 *
 * @param {Element} root Container.
 * @param {String} content Fragment content.
 * @api public
 */
Pagelet.prototype.html = function html(root, content) {
  this.createElements(root, content);
};

/**
 * Render the fragment as SVG.
 *
 * @param {Element} root Container.
 * @param {String} content Fragment content.
 * @api public
 */
Pagelet.prototype.svg = function svg(root, content) {
  this.createElements(root, content);
};

/**
 * Get the element NameSpace description based on mode.
 *
 * @param {String} mode Mode the pagelet will be rendered in.
 * @return {String} Element namespace.
 */
Pagelet.prototype.getElementNS = function getElementNS(mode) {
  mode = mode.toLowerCase();

  switch(mode) {
    case 'svg': return 'http://www.w3.org/2000/svg';
    default: return 'http://www.w3.org/1999/xhtml';
  }
};

/**
 * Create elements by namespace and via a document fragment.
 *
 * @param {Element} root Container.
 * @param {String} content Fragment content.
 * @api private
 */
Pagelet.prototype.createElements = function createElements(root, content) {
  var fragment = document.createDocumentFragment()
    , div = document.createElementNS(this.getElementNS(this.mode), 'div')
    , borked = this.pipe.IEV < 7;

  //
  // Clean out old HTML before we append our new HTML or we will get duplicate
  // DOM. Or there might have been a loading placeholder in place that needs
  // to be removed.
  //
  while (root.firstChild) {
    root.removeChild(root.firstChild);
  }

  if (borked) root.appendChild(div);

  div.innerHTML = content;

  while (div.firstChild) {
    fragment.appendChild(div.firstChild);
  }

  root.appendChild(fragment);
  if (borked) root.removeChild(div);
};

/**
 * Parse the included template from the comment node so it can be injected in to
 * the page as initial rendered view.
 *
 * @returns {String} View.
 * @api private
 */
Pagelet.prototype.parse = function parse() {
  var node = this.$('data-pagelet-fragment', this.name)[0]
    , comment;

  //
  // The firstChild of the fragment should have been a HTML comment, this is to
  // prevent the browser from rendering and parsing the template.
  //
  if (!node.firstChild || node.firstChild.nodeType !== 8) return;

  comment = node.firstChild.nodeValue;

  return comment
    .substring(1, comment.length -1)
    .replace(/\\([\s\S]|$)/g, '$1');
};

/**
 * Destroy the pagelet and clean up all references so it can be re-used again in
 * the future.
 *
 * @TODO unload CSS
 * @TODO unload JavaScript
 *
 * @param {Boolean} remove Remove the placeholder as well.
 * @api public
 */
Pagelet.prototype.destroy = function destroy(remove) {
  var pagelet = this;

  this.broadcast('destroy'); // Execute any extra destroy hooks.

  //
  // Remove all the HTML from the placeholders.
  //
  if (this.placeholders) collection.each(this.placeholders, function remove(root) {
    if (remove && root.parentNode) root.parentNode.removeChild(root);
    else while (root.firstChild) root.removeChild(root.firstChild);
  });

  //
  // Remove the added RPC handlers, make sure we don't delete prototypes.
  //
  if (this.rpc && this.rpc.length) collection.each(this.rpc, function nuke(method) {
    if (method in Pagelet.prototype) return;
    delete pagelet[method];
  });

  //
  // Remove the sandboxing.
  //
  if (this.container) sandbox.kill(this.container.id);
  this.placeholders = this.container = null;

  //
  // Announce the destruction and remove it.
  //
  if (this.substream) this.substream.end({
    type: 'end'
  });

  //
  // Everything has been cleaned up, release it to our Freelist Pagelet pool.
  //
  this.pipe.free(this);

  return this;
};

//
// Expose the module.
//
module.exports = Pagelet;
<commit_msg>[docs] namespace<commit_after>/*globals */
'use strict';

var EventEmitter = require('eventemitter3')
  , collection = require('./collection')
  , Fortress = require('fortress')
  , async = require('./async')
  , val = require('parsifal')
  , sandbox;

/**
 * Representation of a single pagelet.
 *
 * @constructor
 * @param {Pipe} pipe The pipe.
 * @api public
 */
function Pagelet(pipe) {
  EventEmitter.call(this);

  this.orchestrate = pipe.orchestrate;
  this.stream = pipe.stream;
  this.pipe = pipe;

  //
  // Create one single Fortress instance that orchestrates all iframe based client
  // code. This sandbox variable should never be exposed to the outside world in
  // order to prevent leaking.
  //
  this.sandbox = sandbox = sandbox || new Fortress;
}

//
// Inherit from EventEmitter.
//
Pagelet.prototype = new EventEmitter();
Pagelet.prototype.constructor = Pagelet;

/**
 * Configure the Pagelet.
 *
 * @param {String} name The given name of the pagelet.
 * @param {Object} data The data of the pagelet.
 * @api private
 */
Pagelet.prototype.configure = function configure(name, data) {
  var pagelet = this;

  this.placeholders = this.$('data-pagelet', name);

  //
  // Pagelet identification.
  //
  this.id = data.id;
  this.name = name;

  //
  // The pagelet
  //
  if (data.remove) {
    return this.destroy(true);
  }

  //
  // Attach event listeners for FORM posts so we can intercept those.
  //
  this.listen();

  //
  // Create a real-time Substream over which we can communicate over without.
  //
  this.substream = this.stream.substream(this.name);
  this.substream.on('data', function data(packet) {
    pagelet.processor(packet);
  });

  //
  // Register the pagelet with the BigPipe server as an indication that we've
  // been fully loaded and ready for action.
  //
  this.orchestrate.write({ type: 'pagelet', name: name });

  this.css = collection.array(data.css);    // CSS for the Page.
  this.js = collection.array(data.js);      // Dependencies for the page.
  this.run = data.run;                      // Pagelet client code.
  this.rpc = data.rpc;                      // Pagelet RPC methods.
  this.data = data.data;                    // All the template data.
  this.mode = data.mode;                    // Fragment rendering mode.
  this.streaming = !!data.streaming;        // Are we streaming POST/GET.
  this.container = this.sandbox.create();   // Create an application sandbox.
  this.timeout = data.timeout || 25 * 1000; // Resource loading timeout.

  //
  // Generate the RPC methods that we're given by the server. We will make the
  // assumption that:
  //
  // - A callback function is always given as last argument.
  // - The function should return it self in order to chain.
  // - The function given supports and uses error first callback styles.
  // - Does not override the build-in prototypes of the Pagelet.
  //
  collection.each(this.rpc, function rpc(method) {
    var counter = 0;

    //
    // Never override build-in methods as this WILL affect the way a Pagelet is
    // working.
    //
    if (method in Pagelet.prototype) return;

    pagelet[method] = function rpcfactory() {
      var args = Array.prototype.slice.call(arguments, 0)
        , id = method +'#'+ (++counter);

      pagelet.once('rpc::'+ id, args.pop());
      pagelet.substream.write({ method: method, type: 'rpc', args: args, id: id });

      return pagelet;
    };
  });

  //
  // Should be called before we create `rpc` hooks.
  //
  this.broadcast('configured', data);

  async.each(this.css.concat(this.js), function download(asset, next) {
    this.load(document.body, asset, next);
  }, function done(err) {
    if (err) return pagelet.broadcast('error', err);
    pagelet.broadcast('loaded');

    pagelet.render(pagelet.parse());
    pagelet.initialise();
  }, { context: this.pipe, timeout: this.timeout });
};

/**
 * Intercept form posts and stream them over our substream instead to prevent
 * full page reload.
 *
 * @returns {Pagelet}
 * @api private
 */
Pagelet.prototype.listen = function listen() {
  var pagelet = this;

  /**
   * Handles the actual form submission.
   *
   * @param {Event} evt The submit event.
   * @api private
   */
  function submission(evt) {
    evt = evt || window.event;
    var form = evt.target || evt.srcElement;

    //
    // In previous versions we had and `evt.preventDefault()` so we could make
    // changes to the form and re-submit it. But there's a big problem with that
    // and that is that in FireFox it loses the reference to the button that
    // triggered the submit. If causes buttons that had a name and value:
    //
    // ```html
    // <button name="key" value="value" type="submit">submit</button>
    // ```
    //
    // To be missing from the POST or GET. We managed to go around it by not
    // simply preventing the default action. If this still does not not work we
    // need to transform the form URLs once the pagelets are loaded.
    //
    if (
         ('getAttribute' in form && form.getAttribute('data-pagelet-async') === 'false')
      || !pagelet.streaming
    ) {
      var action = form.getAttribute('action');
      return form.setAttribute('action', [
        action,
        ~action.indexOf('?') ? '&' : '?',
        '_pagelet=',
        pagelet.name
      ].join(''));
    }

    //
    // As we're submitting the form over our real-time connection and gather the
    // data our self we can safely prevent default.
    //
    evt.preventDefault();
    pagelet.submit(form);
  }

  collection.each(this.placeholders, function each(root) {
    root.addEventListener('submit', submission, false);
  });

  //
  // When the pagelet is removed we want to remove our listeners again. To
  // prevent memory leaks as well possible duplicate listeners when a pagelet is
  // loaded in the same placeholder (in case of a full reload).
  //
  return this.once('destroy', function destroy() {
    collection.each(pagelet.placeholders, function each(root) {
      root.removeEventListener('submit', submission, false);
    });
  });
};

/**
 * Submit the contents of a <form> to the server.
 *
 * @param {FormElement} form Form that needs to be submitted.
 * @returns {Object} The data that is ported to the server.
 * @api public
 */
Pagelet.prototype.submit = function submit(form) {
  var active = document.activeElement
    , elements = form.elements
    , data = {}
    , element
    , i;

  if (active && active.name) {
    data[active.name] = active.value;
  } else {
    active = false;
  }

  for (i = 0; i < elements.length; i++) {
    element = elements[i];

    //
    // Story time children! Once upon a time there was a developer, this
    // developer created a form with a lot of submit buttons. The developer
    // knew that when a user clicked on one of those buttons the value="" and
    // name="" attributes would get send to the server so he could see which
    // button people had clicked. He implemented this and all was good. Until
    // someone captured the `submit` event in the browser which didn't have
    // a reference to the clicked element. This someone found out that the
    // `document.activeElement` pointed to the last clicked element and used
    // that to restore the same functionality and the day was saved again.
    //
    // There are valuable lessons to be learned here. Submit buttons are the
    // suck. PERIOD.
    //
    if (
         element.name
      && !(element.name in data)
      && element.disabled === false
      && /^(?:input|select|textarea|keygen)/i.test(element.nodeName)
      && !/^(?:submit|button|image|reset|file)$/i.test(element.type)
      && (element.checked || !/^(?:checkbox|radio)$/i.test(element.type))
    ) data[element.name] = val(element);
  }

  //
  // Now that we have a JSON object, we can just send it over our real-time
  // connection and wait for a page refresh.
  //
  this.substream.write({
    type: (form.method || 'GET').toLowerCase(),
    body: data
  });

  return data;
};

/**
 * Get the pagelet contents once again.
 *
 * @returns {Pagelet}
 * @api public
 */
Pagelet.prototype.get = function get() {
  this.substream.write({ type: 'get' });

  return this;
};

/**
 * Process the incoming messages from our SubStream.
 *
 * @param {Object} packet The decoded message.
 * @returns {Boolean}
 * @api private
 */
Pagelet.prototype.processor = function processor(packet) {
  if ('object' !== typeof packet) return false;

  switch (packet.type) {
    case 'rpc':
      this.emit.apply(this, ['rpc::'+ packet.id].concat(packet.args || []));
    break;

    case 'event':
      if (packet.args && packet.args.length) {
        this.emit.apply(this, packet.args);
      }
    break;

    case 'fragment':
      this.render(packet.frag.view);
    break;

    case 'redirect':
      window.location.href = packet.url;
    break;

    default:
    return false;
  }

  return true;
};

/**
 * The pagelet's resource has all been loaded.
 *
 * @api private
 */
Pagelet.prototype.initialise = function initialise() {
  this.broadcast('initialise');

  //
  // Only load the client code in a sandbox when it exists. There no point in
  // spinning up a sandbox if it does nothing
  //
  if (!this.code) return;
  this.sandbox(this.prepare(this.code));
};

/**
 * Broadcast an event that will be emitted on the pagelet and the page.
 *
 * @param {String} event The name of the event we should emit
 * @returns {Pagelet}
 * @api public
 */
Pagelet.prototype.broadcast = function broadcast(event) {
  this.emit.apply(this, arguments);
  this.pipe.emit.apply(this.pipe, [
    this.name +'::'+ event,
    this
  ].concat(Array.prototype.slice.call(arguments, 1)));

  return this;
};

/**
 * Find the element based on the attribute and value.
 *
 * @param {String} attribute The name of the attribute we're searching.
 * @param {String} value The value that the attribute should equal to.
 * @returns {Array} A list of HTML elements that match.
 * @api public
 */
Pagelet.prototype.$ = function $(attribute, value) {
  if (document && 'querySelectorAll' in document) {
    return Array.prototype.slice.call(
        document.querySelectorAll('['+ attribute +'="'+ value +'"]')
      , 0
    );
  }

  //
  // No querySelectorAll support, so we're going to do a full DOM scan.
  //
  var all = document.getElementsByTagName('*')
    , length = all.length
    , results = []
    , i = 0;

  for (; i < length; i++) {
    if (value === all[i].getAttribute(attribute)) {
      results.push(all[i]);
    }
  }

  return results;
};

/**
 * Invoke the correct render method for the pagelet.
 *
 * @param {String} html The HTML that needs to be added in the placeholders.
 * @returns {Boolean} Successfully rendered a pagelet.
 * @api public
 */
Pagelet.prototype.render = function render(html) {
  if (!this.placeholders.length || !html) return false;
  var mode = this.mode in this ? this[this.mode] : this.html;

  collection.each(this.placeholders, function each(root) {
    mode.call(this, root, html);
  }, this);

  this.broadcast('render', html);
  return true;
};

/**
 * Render the fragment as HTML (default).
 *
 * @param {Element} root Container.
 * @param {String} content Fragment content.
 * @api public
 */
Pagelet.prototype.html = function html(root, content) {
  this.createElements(root, content);
};

/**
 * Render the fragment as SVG.
 *
 * @param {Element} root Container.
 * @param {String} content Fragment content.
 * @api public
 */
Pagelet.prototype.svg = function svg(root, content) {
  this.createElements(root, content);
};

/**
 * Get the element namespaceURI description based on mode.
 *
 * @param {String} mode Mode the pagelet will be rendered in.
 * @return {String} Element namespace.
 */
Pagelet.prototype.getElementNS = function getElementNS(mode) {
  mode = mode.toLowerCase();

  switch(mode) {
    case 'svg': return 'http://www.w3.org/2000/svg';
    default: return 'http://www.w3.org/1999/xhtml';
  }
};

/**
 * Create elements by namespace and via a document fragment.
 *
 * @param {Element} root Container.
 * @param {String} content Fragment content.
 * @api private
 */
Pagelet.prototype.createElements = function createElements(root, content) {
  var fragment = document.createDocumentFragment()
    , div = document.createElementNS(this.getElementNS(this.mode), 'div')
    , borked = this.pipe.IEV < 7;

  //
  // Clean out old HTML before we append our new HTML or we will get duplicate
  // DOM. Or there might have been a loading placeholder in place that needs
  // to be removed.
  //
  while (root.firstChild) {
    root.removeChild(root.firstChild);
  }

  if (borked) root.appendChild(div);

  div.innerHTML = content;

  while (div.firstChild) {
    fragment.appendChild(div.firstChild);
  }

  root.appendChild(fragment);
  if (borked) root.removeChild(div);
};

/**
 * Parse the included template from the comment node so it can be injected in to
 * the page as initial rendered view.
 *
 * @returns {String} View.
 * @api private
 */
Pagelet.prototype.parse = function parse() {
  var node = this.$('data-pagelet-fragment', this.name)[0]
    , comment;

  //
  // The firstChild of the fragment should have been a HTML comment, this is to
  // prevent the browser from rendering and parsing the template.
  //
  if (!node.firstChild || node.firstChild.nodeType !== 8) return;

  comment = node.firstChild.nodeValue;

  return comment
    .substring(1, comment.length -1)
    .replace(/\\([\s\S]|$)/g, '$1');
};

/**
 * Destroy the pagelet and clean up all references so it can be re-used again in
 * the future.
 *
 * @TODO unload CSS
 * @TODO unload JavaScript
 *
 * @param {Boolean} remove Remove the placeholder as well.
 * @api public
 */
Pagelet.prototype.destroy = function destroy(remove) {
  var pagelet = this;

  this.broadcast('destroy'); // Execute any extra destroy hooks.

  //
  // Remove all the HTML from the placeholders.
  //
  if (this.placeholders) collection.each(this.placeholders, function remove(root) {
    if (remove && root.parentNode) root.parentNode.removeChild(root);
    else while (root.firstChild) root.removeChild(root.firstChild);
  });

  //
  // Remove the added RPC handlers, make sure we don't delete prototypes.
  //
  if (this.rpc && this.rpc.length) collection.each(this.rpc, function nuke(method) {
    if (method in Pagelet.prototype) return;
    delete pagelet[method];
  });

  //
  // Remove the sandboxing.
  //
  if (this.container) sandbox.kill(this.container.id);
  this.placeholders = this.container = null;

  //
  // Announce the destruction and remove it.
  //
  if (this.substream) this.substream.end({
    type: 'end'
  });

  //
  // Everything has been cleaned up, release it to our Freelist Pagelet pool.
  //
  this.pipe.free(this);

  return this;
};

//
// Expose the module.
//
module.exports = Pagelet;
<|endoftext|>


<commit_before>import fs from "fs"
import pify from "pify"
import minimatch from "minimatch"

export const withoutTrailingSlash = path =>
  path === `/` ? path : path.replace(/\/$/, ``)

export const writeFile = pify(fs.writeFile)
export const renameFile = pify(fs.rename)

export const runQuery = (handler, query, excludes, pathPrefix) =>
  handler(query).then(r => {
    if (r.errors) {
      throw new Error(r.errors.join(`, `))
    }

    // Removing excluded paths
    r.data.allSitePage.edges = r.data.allSitePage.edges.filter(
      page =>
        !excludes.some(excludedRoute =>
          minimatch(withoutTrailingSlash(page.node.path), excludedRoute)
        )
    )

    // Add path prefix
    r.data.allSitePage.edges = r.data.allSitePage.edges.map(page => {
      page.node.path = (pathPrefix + page.node.path).replace(/^\/\//g, `/`)
      return page
    })

    // siteUrl Validation
    if (
      !r.data.site.siteMetadata.siteUrl ||
      r.data.site.siteMetadata.siteUrl == null ||
      r.data.site.siteMetadata.siteUrl.trim().length == 0
    ) {
      throw new Error(
        `SiteMetaData 'siteUrl' property is required and cannot be left empty. Check out the documentation to see a working example: https://www.gatsbyjs.org/packages/gatsby-plugin-sitemap/#how-to-use`
      )
    }

    // remove trailing slash of siteUrl
    r.data.site.siteMetadata.siteUrl = withoutTrailingSlash(
      r.data.site.siteMetadata.siteUrl
    )

    return r.data
  })

export const defaultOptions = {
  query: `
    {
      site {
        siteMetadata {
          siteUrl
        }
      }

      allSitePage {
        edges {
          node {
            path
          }
        }
      }
  }`,
  output: `/sitemap.xml`,
  exclude: [
    `/dev-404-page`,
    `/404`,
    `/404.html`,
    `/offline-plugin-app-shell-fallback`,
  ],
  createLinkInHead: true,
  serialize: ({ site, allSitePage }) =>
    allSitePage.edges.map(edge => {
      return {
        url: site.siteMetadata.siteUrl + edge.node.path,
        changefreq: `daily`,
        priority: 0.7,
      }
    }),
}
<commit_msg>fix(gatsby-plugin-sitemap): Removed redundant check for siteUrl (#17683)<commit_after>import fs from "fs"
import pify from "pify"
import minimatch from "minimatch"

export const withoutTrailingSlash = path =>
  path === `/` ? path : path.replace(/\/$/, ``)

export const writeFile = pify(fs.writeFile)
export const renameFile = pify(fs.rename)

export const runQuery = (handler, query, excludes, pathPrefix) =>
  handler(query).then(r => {
    if (r.errors) {
      throw new Error(r.errors.join(`, `))
    }

    // Removing excluded paths
    r.data.allSitePage.edges = r.data.allSitePage.edges.filter(
      page =>
        !excludes.some(excludedRoute =>
          minimatch(withoutTrailingSlash(page.node.path), excludedRoute)
        )
    )

    // Add path prefix
    r.data.allSitePage.edges = r.data.allSitePage.edges.map(page => {
      page.node.path = (pathPrefix + page.node.path).replace(/^\/\//g, `/`)
      return page
    })

    // siteUrl Validation
    if (
      !r.data.site.siteMetadata.siteUrl ||
      r.data.site.siteMetadata.siteUrl.trim().length == 0
    ) {
      throw new Error(
        `SiteMetaData 'siteUrl' property is required and cannot be left empty. Check out the documentation to see a working example: https://www.gatsbyjs.org/packages/gatsby-plugin-sitemap/#how-to-use`
      )
    }

    // remove trailing slash of siteUrl
    r.data.site.siteMetadata.siteUrl = withoutTrailingSlash(
      r.data.site.siteMetadata.siteUrl
    )

    return r.data
  })

export const defaultOptions = {
  query: `
    {
      site {
        siteMetadata {
          siteUrl
        }
      }

      allSitePage {
        edges {
          node {
            path
          }
        }
      }
  }`,
  output: `/sitemap.xml`,
  exclude: [
    `/dev-404-page`,
    `/404`,
    `/404.html`,
    `/offline-plugin-app-shell-fallback`,
  ],
  createLinkInHead: true,
  serialize: ({ site, allSitePage }) =>
    allSitePage.edges.map(edge => {
      return {
        url: site.siteMetadata.siteUrl + edge.node.path,
        changefreq: `daily`,
        priority: 0.7,
      }
    }),
}
<|endoftext|>


<commit_before>// @flow
const {
  GraphQLInputObjectType,
  GraphQLList,
  GraphQLEnumType,
  GraphQLNonNull,
} = require(`graphql`)
const _ = require(`lodash`)
const createKey = require(`./create-key`)

// builds an input field for sorting, given an array of names to sort on
module.exports = function createSortField(
  typeName: string,
  fieldNames: string[]
) {
  const enumValues = {}
  fieldNames.forEach(field => {
    enumValues[createKey(field)] = { value: field }
  })

  const SortByType = new GraphQLEnumType({
    name: `${typeName}SortByFieldsEnum`,
    values: enumValues,
  })

  return {
    type: new GraphQLInputObjectType({
      name: _.camelCase(`${typeName} sort`),
      fields: {
        fields: {
          name: _.camelCase(`${typeName} sortFields`),
          type: new GraphQLNonNull(new GraphQLList(SortByType)),
        },
        order: {
          name: _.camelCase(`${typeName} sortOrder`),
          defaultValue: `asc`,
          type: new GraphQLEnumType({
            name: _.camelCase(`${typeName} sortOrderValues`),
            values: {
              ASC: { value: `asc` },
              DESC: { value: `desc` },
            },
          }),
        },
      },
    }),
  }
}
<commit_msg>fix running queries without explicit order (#7050)<commit_after>// @flow
const {
  GraphQLInputObjectType,
  GraphQLList,
  GraphQLEnumType,
  GraphQLNonNull,
} = require(`graphql`)
const _ = require(`lodash`)
const createKey = require(`./create-key`)

// builds an input field for sorting, given an array of names to sort on
module.exports = function createSortField(
  typeName: string,
  fieldNames: string[]
) {
  const enumValues = {}
  fieldNames.forEach(field => {
    enumValues[createKey(field)] = { value: field }
  })

  const SortByType = new GraphQLEnumType({
    name: `${typeName}SortByFieldsEnum`,
    values: enumValues,
  })

  return {
    type: new GraphQLInputObjectType({
      name: _.camelCase(`${typeName} sort`),
      fields: {
        fields: {
          name: _.camelCase(`${typeName} sortFields`),
          type: new GraphQLNonNull(new GraphQLList(SortByType)),
        },
        order: {
          name: _.camelCase(`${typeName} sortOrder`),
          defaultValue: `ASC`,
          type: new GraphQLEnumType({
            name: _.camelCase(`${typeName} sortOrderValues`),
            values: {
              ASC: { value: `asc` },
              DESC: { value: `desc` },
            },
          }),
        },
      },
    }),
  }
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

describe('DeviceManagerSmokeTest', () => {
  if (!process.env.GCLOUD_PROJECT) {
    throw new Error("Usage: GCLOUD_PROJECT=<project_id> node #{$0}");
  }
  var projectId = process.env.GCLOUD_PROJECT;

  it('successfully makes a call to the service using promises', done => {
    const iot = require('../src');

    var client = new iot.v1.DeviceManagerClient({
      // optional auth parameters.
    });

    // Iterate over all elements.
    var formattedParent = client.locationPath(projectId, "us-central1");

    client.listDeviceRegistries({parent: formattedParent})
      .then(responses => {
        var resources = responses[0];
        for (let i = 0; i < resources.length; i += 1) {
          console.log(resources[i]);
        }
      })
      .then(done)
      .catch(done);
  });

  it('successfully makes a call to the service using callbacks', done => {
    const iot = require('../src');

    var client = new iot.v1.DeviceManagerClient({
      // optional auth parameters.
    });

    // Or obtain the paged response.
    var formattedParent = client.locationPath(projectId, "us-central1");

    var options = {autoPaginate: false};
    var callback = responses => {
      // The actual resources in a response.
      var resources = responses[0];
      // The next request if the response shows that there are more responses.
      var nextRequest = responses[1];
      // The actual response object, if necessary.
      // var rawResponse = responses[2];
      for (let i = 0; i < resources.length; i += 1) {
        console.log(resources[i]);
      }
      if (nextRequest) {
        // Fetch the next page.
        return client.listDeviceRegistries(nextRequest, options).then(callback);
      }
    }
    client.listDeviceRegistries({parent: formattedParent}, options)
      .then(callback)
      .then(done)
      .catch(done);
  });

  it('successfully makes a call to the service using streaming', done => {
    const iot = require('../src');

    var client = new iot.v1.DeviceManagerClient({
      // optional auth parameters.
    });

    var formattedParent = client.locationPath(projectId, "us-central1");
    client.listDeviceRegistriesStream({parent: formattedParent})
      .on('data', element => {
        console.log(element);
      })
      .on('error', done)
      .on('end', done);
  });
});
<commit_msg>quiet some linting issues on sample<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

/* global describe, it */

describe('DeviceManagerSmokeTest', () => {
  if (!process.env.GCLOUD_PROJECT) {
    throw new Error('Usage: GCLOUD_PROJECT=<project_id> node #{$0}');
  }
  var projectId = process.env.GCLOUD_PROJECT;

  it('successfully makes a call to the service using promises', done => {
    const iot = require('../src');

    var client = new iot.v1.DeviceManagerClient({
      // optional auth parameters.
    });

    // Iterate over all elements.
    var formattedParent = client.locationPath(projectId, 'us-central1');

    client
      .listDeviceRegistries({parent: formattedParent})
      .then(responses => {
        var resources = responses[0];
        for (let i = 0; i < resources.length; i += 1) {
          console.log(resources[i]);
        }
      })
      .then(done)
      .catch(done);
  });

  it('successfully makes a call to the service using callbacks', done => {
    const iot = require('../src');

    var client = new iot.v1.DeviceManagerClient({
      // optional auth parameters.
    });

    // Or obtain the paged response.
    var formattedParent = client.locationPath(projectId, 'us-central1');

    var options = {autoPaginate: false};
    var callback = responses => {
      // The actual resources in a response.
      var resources = responses[0];
      // The next request if the response shows that there are more responses.
      var nextRequest = responses[1];
      // The actual response object, if necessary.
      // var rawResponse = responses[2];
      for (let i = 0; i < resources.length; i += 1) {
        console.log(resources[i]);
      }
      if (nextRequest) {
        // Fetch the next page.
        return client.listDeviceRegistries(nextRequest, options).then(callback);
      }
    };
    client
      .listDeviceRegistries({parent: formattedParent}, options)
      .then(callback)
      .then(done)
      .catch(done);
  });

  it('successfully makes a call to the service using streaming', done => {
    const iot = require('../src');

    var client = new iot.v1.DeviceManagerClient({
      // optional auth parameters.
    });

    var formattedParent = client.locationPath(projectId, 'us-central1');
    client
      .listDeviceRegistriesStream({parent: formattedParent})
      .on('data', element => {
        console.log(element);
      })
      .on('error', done)
      .on('end', done);
  });
});
<|endoftext|>


<commit_before>const debug = require('debug')('streamroller:RollingFileWriteStream');
const _ = require('lodash');
const fs = require('fs-extra');
const zlib = require('zlib');
const moment = require('moment');
const path = require('path');
const {Writable} = require('stream');

const FILENAME_SEP = '.';
const ZIP_EXT = '.gz';

/**
 * RollingFileWriteStream is mainly used when writing to a file rolling by date or size.
 * RollingFileWriteStream inhebites from stream.Writable
 */
class RollingFileWriteStream extends Writable {
  /**
   * Create a RollingFileWriteStream
   * @constructor
   * @param {string} filePath - The file path to write.
   * @param {object} options - The extra options
   * @param {number} options.numToKeep - The max numbers of files to keep.
   * @param {number} options.intervalDays - The interval days between the two rolling.
   *                                        This can be 0 to disable date-rolling. The default is 1.
   * @param {number} options.daysToKeep - The max days of files to keep. This will be ignored if interval days is 0.
   * @param {number} options.maxSize - The maxSize one file can reach. Unit is Byte.
   *                                   This should be more than 1024. The default is Number.MAX_SAFE_INTEGER.
   * @param {string} options.mode - The mode of the files. The default is '0644'. Refer to stream.writable for more.
   * @param {string} options.flags - The default is 'a'. Refer to stream.flags for more.
   * @param {boolean} options.compress - Whether to compress backup files.
   * @param {boolean} options.keepFileExt - Whether to keep the file extension.
   * @param {string} options.datePattern - The date string pattern in the file name.
   *                                       Refer to moment.js for valid strings. The default is 'YYYY-MM-DD'.
   *                                       This is used to adapt the previous version and will be removed soon
   * @param {boolean} options.alwaysIncludePattern - Whether to add date to the name of the first file.
   *                                                 This is used to adapt the previous version and will be removed soon
   */
  constructor(filePath, options) {
    debug(`creating RollingFileWriteStream. path=${filePath}`);
    super(options);
    if (!filePath) {
      throw new Error('fileName is required.');
    }
    this.fileObject = path.parse(filePath);
    this.options = this._parseOption(options);
    this._initState();
  }

  path() {
    return this.currentFileStream ? this.currentFileStream.path : undefined;
  }

  _parseOption(rawOptions) {
    const defautOptions = {
      intervalDays: 1,
      maxSize: Number.MAX_SAFE_INTEGER,
      datePattern: 'YYYY-MM-DD', // it's better that this cannot be customized.
      encoding: 'utf8',
      mode: parseInt('0644', 8),
      flags: 'a',
      compress: false,
      keepFileExt: false,
      alwaysIncludePattern: false,
      adaptOldDateRolling: false
    };
    const options = _.assign({}, defautOptions, rawOptions);
    if (options.intervalDays < 0) {
      // it can be 0 to adapt the old `RollingFileStream` for internal usage.
      // But this must be used only for this purpose and will be removed in the future.
      // So this error text is > 0 instead of >= 0.
      throw new Error(`options.intervalDays (${options.intervalDays}) should be > 0.`);
    }
    // The min maxSize should better be 1024 * 10
    // But test cases use very small number. So it's 0 here to make test cases pass.
    // Test cases will be recoded in the future.
    if (options.maxSize <= 0) {
      throw new Error(`options.maxSize (${options.maxSize}) should be >= 10 * 1024 (Byte)`);
    }
    if (options.numToKeep && options.numToKeep < 1) {
      throw new Error(`options.numToKeep (${options.numToKeep}) should be > 0`);
    }
    if (options.daysToKeep && options.daysToKeep < 1) {
      throw new Error(`options.daysToKeep (${options.daysToKeep}) should be > 0`);
    }
    debug(`creating stream with option=${JSON.stringify(options)}`);
    return options;
  }

  _initState() {
    fs.ensureDirSync(this.fileObject.dir);
    const existingFileDetails = this._getExistingFiles();
    const now = moment();
    const newFileName = this._formatFileName({isHotFile: true});
    if (existingFileDetails.length > 0
      && _.find(existingFileDetails, f => f.fileName === newFileName)) {
      const hotFileState = fs.statSync(path.format({
        dir: this.fileObject.dir,
        base: newFileName
      }));
      const hotFileDate = moment(hotFileState.birthtimeMs);
      const oldestFileDetail = existingFileDetails[0];
      this.state = {
        currentDate: hotFileDate,
        currentIndex: oldestFileDetail.index,
        currentSize: hotFileState.size
      };
      debug(`using the existing hot file. name=${newFileName}, state=${JSON.stringify(this.state)}`);
      this._renewWriteStream(newFileName, true);
    } else {
      this.state = {
        currentDate: now,
        currentIndex: 0,
        currentSize: 0
      };
      debug(`create new file with no hot file. name=${newFileName}, state=${JSON.stringify(this.state)}`);
      this._renewWriteStream(newFileName);
    }
    this.currentFileStream.write('', 'utf8', () => this._clean(existingFileDetails)); // to ensure file
    return;
  }

  _write(chunk, encoding, callback) {
    try {
      if (this._dateRollingEnabled() && (this.state.currentDate || moment()).clone()
        .add(this.options.intervalDays - 1, 'days')
        .isBefore(moment(), 'day')
      ) {
        this._roll({isNextPeriod: true});
      }
      if (this.state.currentSize >= this.options.maxSize) {
        this._roll({isNextPeriod: false});
      }
      this.state.currentSize += chunk.length;
      debug(`writing chunk. file=${this.currentFileStream.path} state=${JSON.stringify(this.state)} chunk=${chunk}`);
      this.currentFileStream.write(chunk, encoding, callback);
    } catch (e) {
      return callback(e);
    }
  }

  _writev(chunks, callback) {
    try {
      if (this._dateRollingEnabled() && (this.state.currentDate || moment()).clone()
        .add(this.options.intervalDays - 1, 'days')
        .isBefore(moment(), 'day')
      ) {
        this._roll({isNextPeriod: true});
      }
      const chunksForOldFile = [];
      for (let i = 0; i < chunks.length; i++) {
        const c = chunks[i];
        if (this.state.currentSize + c.chunk.length < this.options.maxSize) {
          this.state.currentSize += c.chunk.length;
          chunksForOldFile.push(c);
        }
      }
      let error;
      debug(`writing chunks. file=${this.currentFileStream.path} `
         + `state=${JSON.stringify(this.state)} chunks=${chunksForOldFile}`);
      this.currentFileStream.writev(chunksForOldFile, e => error = e);
      if (error) {
        return callback(error);
      }
      const chunksForNewFile = _.slice(chunks, chunksForOldFile.length);
      if (chunksForNewFile.length > 0) {
        this._roll({isNextPeriod: false});
        this.state.currentSize = _.sumBy(chunksForNewFile, c => c.chunk.length);
        debug(`writing chunks. file=${this.currentFileStream.path} `
          + `state=${JSON.stringify(this.state)} chunks=${chunksForNewFile}`);
        this.currentFileStream.writev(chunksForNewFile, callback);
      } else {
        callback();
      }
    } catch (e) {
      return callback(e);
    }
  }

  end(callback) {
    super.end();
    if (this.currentFileStream) {
      debug(`ending the stream. filename=${this.currentFileStream.path}`);
      this.currentFileStream.end(callback);
    } else {
      callback();
    }
  }

  _final(callback) {
    if (this.currentFileStream) {
      debug(`finalizing the stream. filename=${this.currentFileStream.path}`);
      this.currentFileStream.end(callback);
    } else {
      callback();
    }
  }

  _destroy(err, callback) {
    if (this.currentFileStream) {
      debug(`destroying the stream. filename=${this.currentFileStream.path}`);
      this.currentFileStream.destroy(err, callback);
    } else {
      callback();
    }
  }

  // Sorted from the oldest to the latest
  _getExistingFiles(rawDir) {
    const dir = rawDir || this.fileObject.dir;
    const existingFileDetails = _.compact(
      _.map(fs.readdirSync(dir), n => {
        const parseResult = this._parseFileName(n);
        if (!parseResult) {
          return;
        }
        if (parseResult.index < 0) {
          return;
        }
        return _.assign({fileName: n}, parseResult);
      })
    );
    return _.sortBy(
      existingFileDetails,
      n => (n.date ? n.date.valueOf() : moment().valueOf()) - n.index
    );
  }

  // need file name instead of file abs path.
  _parseFileName(fileName) {
    if (!fileName) {
      return;
    }
    let isCompressed = false;
    if (fileName.endsWith(ZIP_EXT)) {
      fileName = fileName.slice(0, -1 * ZIP_EXT.length);
      isCompressed = true;
    }
    let metaStr;
    if (this.options.keepFileExt) {
      const prefix = this.fileObject.name + FILENAME_SEP;
      const suffix = this.fileObject.ext;
      if (!fileName.startsWith(prefix) || !fileName.endsWith(suffix)) {
        return;
      }
      metaStr = fileName.slice(prefix.length, suffix ? -1 * suffix.length : undefined);
    } else {
      const prefix = this.fileObject.base;
      if (!fileName.startsWith(prefix)) {
        return;
      }
      metaStr = fileName.slice(prefix.length + 1);
    }
    if (!metaStr) {
      return {
        index: 0,
        isCompressed
      };
    }
    if (this._dateRollingEnabled()) {
      const items = _.split(metaStr, FILENAME_SEP);
      if (items.length >= 2) {
        const indexStr = items[items.length - 1];
        if (indexStr !== undefined && indexStr.match(/^\d+$/)) {
          const dateStr = metaStr.slice(0, -1 * (indexStr.length + 1));
          const date = moment(dateStr, this.options.datePattern);
          if (date.isValid()) {
            return {
              index: parseInt(indexStr, 10),
              date,
              isCompressed
            };
          }
        }
      } else {
        const date = moment(metaStr, this.options.datePattern);
        if (date.isValid()) {
          return {
            index: 0,
            date,
            isCompressed
          };
        }
      }
    } else {
      if (metaStr.match(/^\d+$/)) {
        return {
          index: parseInt(metaStr, 10),
          isCompressed
        };
      }
    }
    return;
  }

  // moment.date
  // return file name instead of file abs path.
  _formatFileName({date, index, isHotFile}) {
    // Not support hours, minutes, seconds on the filename
    const dateOpt = date || _.get(this, 'state.currentDate') || moment();
    const dateStr = dateOpt.clone().startOf('day').format(this.options.datePattern);
    const indexOpt = index || _.get(this, 'state.currentIndex');
    const oriFileName = this.fileObject.base;
    if (isHotFile) {
      if (this.options.alwaysIncludePattern && this._dateRollingEnabled()) {
        return this.options.keepFileExt
          ? _.join([this.fileObject.name, dateStr], FILENAME_SEP) + this.fileObject.ext
          : _.join([oriFileName, dateStr], FILENAME_SEP);
      }
      return oriFileName;
    }
    let fileNameExtraItems = [];
    if (this._dateRollingEnabled()) {
      fileNameExtraItems.push(dateStr);
    }
    if (indexOpt) {
      fileNameExtraItems.push(indexOpt);
    }
    let fileName;
    if (this.options.keepFileExt) {
      fileNameExtraItems = _.concat([this.fileObject.name], fileNameExtraItems);
      const baseFileName = _.join(fileNameExtraItems, FILENAME_SEP);
      fileName = baseFileName + this.fileObject.ext;
    } else {
      fileNameExtraItems = _.concat([oriFileName], fileNameExtraItems);
      fileName = _.join(fileNameExtraItems, FILENAME_SEP);
    }
    if (this.options.compress) {
      fileName += ZIP_EXT;
    }
    return fileName;
  }

  _dateRollingEnabled() {
    return this.options.intervalDays > 0;
  }

  _roll({isNextPeriod}) {
    const currentFilePath = this.currentFileStream.path;
    this.currentFileStream.end('', this.options.encoding, e => {
      if (e !== undefined) {
        console.log('Closing file failed.');
        throw e;
      }
    });

    for (let i = _.min([this.state.currentIndex, this.options.numToKeep - 1]); i >= 0; i--) {
      const sourceFilePath = i === 0
        ? currentFilePath
        : path.format({
          dir: this.fileObject.dir,
          base: this._formatFileName({date: this.state.currentDate, index: i})
        });
      const targetFilePath = this.options.adaptOldDateRolling && i == 0 && isNextPeriod
        ? path.format({
          dir: this.fileObject.dir,
          base: this._formatFileName({date: this.state.currentDate})
        })
        : path.format({
          dir: this.fileObject.dir,
          base: this._formatFileName({date: this.state.currentDate, index: i + 1})
        });
      this._moveFile(sourceFilePath, targetFilePath, this.options.compress && i === 0);
    }
    if (isNextPeriod) {
      debug(`rolling for next peried. state=${JSON.stringify(this.state)}`);
      this.state.currentSize = 0;
      this.state.currentIndex = 0;
      this.state.currentDate = moment();
    } else {
      debug(`rolling during the same peried. state=${JSON.stringify(this.state)}`);
      this.state.currentSize = 0;
      this.state.currentIndex += 1;
    }
    this._renewWriteStream(this._formatFileName({isHotFile: true}));
    this.currentFileStream.write('', 'utf8', () => this._clean()); // to touch file
  }

  _renewWriteStream(fileName, exists = false) {
    fs.ensureDirSync(this.fileObject.dir);
    const filePath = path.format({dir: this.fileObject.dir, base: fileName});
    const ops = _.pick(this.options, ['flags', 'encoding', 'mode']);
    if (exists) {
      ops.flags = 'a';
    }
    this.currentFileStream = fs.createWriteStream(filePath, ops);
  }

  _moveFile(sourceFilePath, targetFilePath, needCompress) {
    if (sourceFilePath === targetFilePath) {
      return;
    }
    if (!fs.existsSync(sourceFilePath)) {
      debug(`source file path does not exist. not moving. sourceFilePath=${sourceFilePath}`);
      return;
    }
    try {
      if (needCompress) {
        debug(`moving file from ${sourceFilePath} to ${targetFilePath} with compress`);
        const content = fs.readFileSync(sourceFilePath);
        const gzipedContent = zlib.gzipSync(content);
        fs.writeFileSync(targetFilePath, gzipedContent);
        fs.unlinkSync(sourceFilePath);
      } else {
        debug(`moving file from ${sourceFilePath} to ${targetFilePath} without compress`);
        if (fs.existsSync(targetFilePath)) {
          debug(`deleting file. path=${targetFilePath}`);
          fs.unlinkSync(targetFilePath);
        }
        fs.renameSync(sourceFilePath, targetFilePath);
      }
    } catch (e) {
      console.error(`Moving file failed. sourceFilePath=${sourceFilePath},
        targetFilePath=${targetFilePath}, error=${e}`);
    }
  }

  _clean(givenExistingFileDetails) {
    debug('cleaning dir.');
    let existingFileDetails = givenExistingFileDetails;
    if (!existingFileDetails) {
      existingFileDetails = this._getExistingFiles();
    }
    if (existingFileDetails.length === 0) {
      return false;
    }
    if (this._dateRollingEnabled() && this.options.daysToKeep) {
      const outOfDateFileDetails = _.filter(
        existingFileDetails,
        f => (f.date || moment()).clone().add(this.options.daysToKeep + 1, 'days').isBefore(moment(), 'day')
      );
      this._deleteFiles(outOfDateFileDetails.map(f => f.fileName));
      existingFileDetails = _.slice(existingFileDetails, outOfDateFileDetails.length);
    }
    if (this.options.numToKeep && existingFileDetails.length > this.options.numToKeep) {
      const fileNamesToRemove = _.slice(
        existingFileDetails.map(f => f.fileName),
        0,
        existingFileDetails.length - this.options.numToKeep - 1,
      );
      this._deleteFiles(fileNamesToRemove);
      if (this.state.currentIndex > this.options.numToKeep - 1) {
        this.state.currentIndex = this.options.numToKeep - 1;
      }
    }
  }

  _deleteFiles(fileNames) {
    if (fileNames) {
      fileNames.forEach(n => {
        try {
          const filePath = path.format({dir: this.fileObject.dir, base: n});
          debug(`deleting file. path=${filePath}`);
          fs.unlinkSync(filePath);
        } catch (e) {
          console.error(`remove file failed. error=${e}`);
        }
      });
    }
  }
}

module.exports = RollingFileWriteStream;
<commit_msg>fix: removed _writev<commit_after>const debug = require('debug')('streamroller:RollingFileWriteStream');
const _ = require('lodash');
const fs = require('fs-extra');
const zlib = require('zlib');
const moment = require('moment');
const path = require('path');
const {Writable} = require('stream');

const FILENAME_SEP = '.';
const ZIP_EXT = '.gz';

/**
 * RollingFileWriteStream is mainly used when writing to a file rolling by date or size.
 * RollingFileWriteStream inhebites from stream.Writable
 */
class RollingFileWriteStream extends Writable {
  /**
   * Create a RollingFileWriteStream
   * @constructor
   * @param {string} filePath - The file path to write.
   * @param {object} options - The extra options
   * @param {number} options.numToKeep - The max numbers of files to keep.
   * @param {number} options.intervalDays - The interval days between the two rolling.
   *                                        This can be 0 to disable date-rolling. The default is 1.
   * @param {number} options.daysToKeep - The max days of files to keep. This will be ignored if interval days is 0.
   * @param {number} options.maxSize - The maxSize one file can reach. Unit is Byte.
   *                                   This should be more than 1024. The default is Number.MAX_SAFE_INTEGER.
   * @param {string} options.mode - The mode of the files. The default is '0644'. Refer to stream.writable for more.
   * @param {string} options.flags - The default is 'a'. Refer to stream.flags for more.
   * @param {boolean} options.compress - Whether to compress backup files.
   * @param {boolean} options.keepFileExt - Whether to keep the file extension.
   * @param {string} options.datePattern - The date string pattern in the file name.
   *                                       Refer to moment.js for valid strings. The default is 'YYYY-MM-DD'.
   *                                       This is used to adapt the previous version and will be removed soon
   * @param {boolean} options.alwaysIncludePattern - Whether to add date to the name of the first file.
   *                                                 This is used to adapt the previous version and will be removed soon
   */
  constructor(filePath, options) {
    debug(`creating RollingFileWriteStream. path=${filePath}`);
    super(options);
    if (!filePath) {
      throw new Error('fileName is required.');
    }
    this.fileObject = path.parse(filePath);
    this.options = this._parseOption(options);
    this._initState();
  }

  path() {
    return this.currentFileStream ? this.currentFileStream.path : undefined;
  }

  _parseOption(rawOptions) {
    const defautOptions = {
      intervalDays: 1,
      maxSize: Number.MAX_SAFE_INTEGER,
      datePattern: 'YYYY-MM-DD', // it's better that this cannot be customized.
      encoding: 'utf8',
      mode: parseInt('0644', 8),
      flags: 'a',
      compress: false,
      keepFileExt: false,
      alwaysIncludePattern: false,
      adaptOldDateRolling: false
    };
    const options = _.assign({}, defautOptions, rawOptions);
    if (options.intervalDays < 0) {
      // it can be 0 to adapt the old `RollingFileStream` for internal usage.
      // But this must be used only for this purpose and will be removed in the future.
      // So this error text is > 0 instead of >= 0.
      throw new Error(`options.intervalDays (${options.intervalDays}) should be > 0.`);
    }
    // The min maxSize should better be 1024 * 10
    // But test cases use very small number. So it's 0 here to make test cases pass.
    // Test cases will be recoded in the future.
    if (options.maxSize <= 0) {
      throw new Error(`options.maxSize (${options.maxSize}) should be >= 10 * 1024 (Byte)`);
    }
    if (options.numToKeep && options.numToKeep < 1) {
      throw new Error(`options.numToKeep (${options.numToKeep}) should be > 0`);
    }
    if (options.daysToKeep && options.daysToKeep < 1) {
      throw new Error(`options.daysToKeep (${options.daysToKeep}) should be > 0`);
    }
    debug(`creating stream with option=${JSON.stringify(options)}`);
    return options;
  }

  _initState() {
    fs.ensureDirSync(this.fileObject.dir);
    const existingFileDetails = this._getExistingFiles();
    const now = moment();
    const newFileName = this._formatFileName({isHotFile: true});
    if (existingFileDetails.length > 0
      && _.find(existingFileDetails, f => f.fileName === newFileName)) {
      const hotFileState = fs.statSync(path.format({
        dir: this.fileObject.dir,
        base: newFileName
      }));
      const hotFileDate = moment(hotFileState.birthtimeMs);
      const oldestFileDetail = existingFileDetails[0];
      this.state = {
        currentDate: hotFileDate,
        currentIndex: oldestFileDetail.index,
        currentSize: hotFileState.size
      };
      debug(`using the existing hot file. name=${newFileName}, state=${JSON.stringify(this.state)}`);
      this._renewWriteStream(newFileName, true);
    } else {
      this.state = {
        currentDate: now,
        currentIndex: 0,
        currentSize: 0
      };
      debug(`create new file with no hot file. name=${newFileName}, state=${JSON.stringify(this.state)}`);
      this._renewWriteStream(newFileName);
    }
    this.currentFileStream.write('', 'utf8', () => this._clean(existingFileDetails)); // to ensure file
    return;
  }

  _write(chunk, encoding, callback) {
    try {
      if (this._dateRollingEnabled() && (this.state.currentDate || moment()).clone()
        .add(this.options.intervalDays - 1, 'days')
        .isBefore(moment(), 'day')
      ) {
        this._roll({isNextPeriod: true});
      }
      if (this.state.currentSize >= this.options.maxSize) {
        this._roll({isNextPeriod: false});
      }
      this.state.currentSize += chunk.length;
      debug(`writing chunk. file=${this.currentFileStream.path} state=${JSON.stringify(this.state)} chunk=${chunk}`);
      this.currentFileStream.write(chunk, encoding, callback);
    } catch (e) {
      return callback(e);
    }
  }

  end(callback) {
    super.end();
    if (this.currentFileStream) {
      debug(`ending the stream. filename=${this.currentFileStream.path}`);
      this.currentFileStream.end(callback);
    } else {
      callback();
    }
  }

  _final(callback) {
    if (this.currentFileStream) {
      debug(`finalizing the stream. filename=${this.currentFileStream.path}`);
      this.currentFileStream.end(callback);
    } else {
      callback();
    }
  }

  _destroy(err, callback) {
    if (this.currentFileStream) {
      debug(`destroying the stream. filename=${this.currentFileStream.path}`);
      this.currentFileStream.destroy(err, callback);
    } else {
      callback();
    }
  }

  // Sorted from the oldest to the latest
  _getExistingFiles(rawDir) {
    const dir = rawDir || this.fileObject.dir;
    const existingFileDetails = _.compact(
      _.map(fs.readdirSync(dir), n => {
        const parseResult = this._parseFileName(n);
        if (!parseResult) {
          return;
        }
        if (parseResult.index < 0) {
          return;
        }
        return _.assign({fileName: n}, parseResult);
      })
    );
    return _.sortBy(
      existingFileDetails,
      n => (n.date ? n.date.valueOf() : moment().valueOf()) - n.index
    );
  }

  // need file name instead of file abs path.
  _parseFileName(fileName) {
    if (!fileName) {
      return;
    }
    let isCompressed = false;
    if (fileName.endsWith(ZIP_EXT)) {
      fileName = fileName.slice(0, -1 * ZIP_EXT.length);
      isCompressed = true;
    }
    let metaStr;
    if (this.options.keepFileExt) {
      const prefix = this.fileObject.name + FILENAME_SEP;
      const suffix = this.fileObject.ext;
      if (!fileName.startsWith(prefix) || !fileName.endsWith(suffix)) {
        return;
      }
      metaStr = fileName.slice(prefix.length, suffix ? -1 * suffix.length : undefined);
    } else {
      const prefix = this.fileObject.base;
      if (!fileName.startsWith(prefix)) {
        return;
      }
      metaStr = fileName.slice(prefix.length + 1);
    }
    if (!metaStr) {
      return {
        index: 0,
        isCompressed
      };
    }
    if (this._dateRollingEnabled()) {
      const items = _.split(metaStr, FILENAME_SEP);
      if (items.length >= 2) {
        const indexStr = items[items.length - 1];
        if (indexStr !== undefined && indexStr.match(/^\d+$/)) {
          const dateStr = metaStr.slice(0, -1 * (indexStr.length + 1));
          const date = moment(dateStr, this.options.datePattern);
          if (date.isValid()) {
            return {
              index: parseInt(indexStr, 10),
              date,
              isCompressed
            };
          }
        }
      } else {
        const date = moment(metaStr, this.options.datePattern);
        if (date.isValid()) {
          return {
            index: 0,
            date,
            isCompressed
          };
        }
      }
    } else {
      if (metaStr.match(/^\d+$/)) {
        return {
          index: parseInt(metaStr, 10),
          isCompressed
        };
      }
    }
    return;
  }

  // moment.date
  // return file name instead of file abs path.
  _formatFileName({date, index, isHotFile}) {
    // Not support hours, minutes, seconds on the filename
    const dateOpt = date || _.get(this, 'state.currentDate') || moment();
    const dateStr = dateOpt.clone().startOf('day').format(this.options.datePattern);
    const indexOpt = index || _.get(this, 'state.currentIndex');
    const oriFileName = this.fileObject.base;
    if (isHotFile) {
      if (this.options.alwaysIncludePattern && this._dateRollingEnabled()) {
        return this.options.keepFileExt
          ? _.join([this.fileObject.name, dateStr], FILENAME_SEP) + this.fileObject.ext
          : _.join([oriFileName, dateStr], FILENAME_SEP);
      }
      return oriFileName;
    }
    let fileNameExtraItems = [];
    if (this._dateRollingEnabled()) {
      fileNameExtraItems.push(dateStr);
    }
    if (indexOpt) {
      fileNameExtraItems.push(indexOpt);
    }
    let fileName;
    if (this.options.keepFileExt) {
      fileNameExtraItems = _.concat([this.fileObject.name], fileNameExtraItems);
      const baseFileName = _.join(fileNameExtraItems, FILENAME_SEP);
      fileName = baseFileName + this.fileObject.ext;
    } else {
      fileNameExtraItems = _.concat([oriFileName], fileNameExtraItems);
      fileName = _.join(fileNameExtraItems, FILENAME_SEP);
    }
    if (this.options.compress) {
      fileName += ZIP_EXT;
    }
    return fileName;
  }

  _dateRollingEnabled() {
    return this.options.intervalDays > 0;
  }

  _roll({isNextPeriod}) {
    const currentFilePath = this.currentFileStream.path;
    this.currentFileStream.end('', this.options.encoding, e => {
      if (e !== undefined) {
        console.log('Closing file failed.');
        throw e;
      }
    });

    for (let i = _.min([this.state.currentIndex, this.options.numToKeep - 1]); i >= 0; i--) {
      const sourceFilePath = i === 0
        ? currentFilePath
        : path.format({
          dir: this.fileObject.dir,
          base: this._formatFileName({date: this.state.currentDate, index: i})
        });
      const targetFilePath = this.options.adaptOldDateRolling && i == 0 && isNextPeriod
        ? path.format({
          dir: this.fileObject.dir,
          base: this._formatFileName({date: this.state.currentDate})
        })
        : path.format({
          dir: this.fileObject.dir,
          base: this._formatFileName({date: this.state.currentDate, index: i + 1})
        });
      this._moveFile(sourceFilePath, targetFilePath, this.options.compress && i === 0);
    }
    if (isNextPeriod) {
      debug(`rolling for next peried. state=${JSON.stringify(this.state)}`);
      this.state.currentSize = 0;
      this.state.currentIndex = 0;
      this.state.currentDate = moment();
    } else {
      debug(`rolling during the same peried. state=${JSON.stringify(this.state)}`);
      this.state.currentSize = 0;
      this.state.currentIndex += 1;
    }
    this._renewWriteStream(this._formatFileName({isHotFile: true}));
    this.currentFileStream.write('', 'utf8', () => this._clean()); // to touch file
  }

  _renewWriteStream(fileName, exists = false) {
    fs.ensureDirSync(this.fileObject.dir);
    const filePath = path.format({dir: this.fileObject.dir, base: fileName});
    const ops = _.pick(this.options, ['flags', 'encoding', 'mode']);
    if (exists) {
      ops.flags = 'a';
    }
    this.currentFileStream = fs.createWriteStream(filePath, ops);
  }

  _moveFile(sourceFilePath, targetFilePath, needCompress) {
    if (sourceFilePath === targetFilePath) {
      return;
    }
    if (!fs.existsSync(sourceFilePath)) {
      debug(`source file path does not exist. not moving. sourceFilePath=${sourceFilePath}`);
      return;
    }
    try {
      if (needCompress) {
        debug(`moving file from ${sourceFilePath} to ${targetFilePath} with compress`);
        const content = fs.readFileSync(sourceFilePath);
        const gzipedContent = zlib.gzipSync(content);
        fs.writeFileSync(targetFilePath, gzipedContent);
        fs.unlinkSync(sourceFilePath);
      } else {
        debug(`moving file from ${sourceFilePath} to ${targetFilePath} without compress`);
        if (fs.existsSync(targetFilePath)) {
          debug(`deleting file. path=${targetFilePath}`);
          fs.unlinkSync(targetFilePath);
        }
        fs.renameSync(sourceFilePath, targetFilePath);
      }
    } catch (e) {
      console.error(`Moving file failed. sourceFilePath=${sourceFilePath},
        targetFilePath=${targetFilePath}, error=${e}`);
    }
  }

  _clean(givenExistingFileDetails) {
    debug('cleaning dir.');
    let existingFileDetails = givenExistingFileDetails;
    if (!existingFileDetails) {
      existingFileDetails = this._getExistingFiles();
    }
    if (existingFileDetails.length === 0) {
      return false;
    }
    if (this._dateRollingEnabled() && this.options.daysToKeep) {
      const outOfDateFileDetails = _.filter(
        existingFileDetails,
        f => (f.date || moment()).clone().add(this.options.daysToKeep + 1, 'days').isBefore(moment(), 'day')
      );
      this._deleteFiles(outOfDateFileDetails.map(f => f.fileName));
      existingFileDetails = _.slice(existingFileDetails, outOfDateFileDetails.length);
    }
    if (this.options.numToKeep && existingFileDetails.length > this.options.numToKeep) {
      const fileNamesToRemove = _.slice(
        existingFileDetails.map(f => f.fileName),
        0,
        existingFileDetails.length - this.options.numToKeep - 1,
      );
      this._deleteFiles(fileNamesToRemove);
      if (this.state.currentIndex > this.options.numToKeep - 1) {
        this.state.currentIndex = this.options.numToKeep - 1;
      }
    }
  }

  _deleteFiles(fileNames) {
    if (fileNames) {
      fileNames.forEach(n => {
        try {
          const filePath = path.format({dir: this.fileObject.dir, base: n});
          debug(`deleting file. path=${filePath}`);
          fs.unlinkSync(filePath);
        } catch (e) {
          console.error(`remove file failed. error=${e}`);
        }
      });
    }
  }
}

module.exports = RollingFileWriteStream;
<|endoftext|>


<commit_before>( function() {

	var _oldConsole = console.log;

	var supportedTypes = [];

	function addSupportedType( type, format ) {

		supportedTypes.push( {
			type: type,
			format: format
		} );

	}

	addSupportedType( THREE.Matrix4, function( v ) {

		var e = v.elements;

		return [
			[ e[ 0 ], e[ 1 ], e[ 2 ], e[ 3 ] ],
			[ e[ 4 ], e[ 5 ], e[ 6 ], e[ 7 ] ],
			[ e[ 8 ], e[ 9 ], e[ 10 ], e[ 11 ] ],
			[ e[ 12 ], e[ 13 ], e[ 14 ], e[ 15 ] ]
		];

	} );

	addSupportedType( THREE.Matrix3, function( v ) {

		var e = v.elements;

		return [
			[ e[ 0 ], e[ 1 ], e[ 2 ] ],
			[ e[ 3 ], e[ 4 ], e[ 5 ] ],
			[ e[ 6 ], e[ 7 ], e[ 8 ] ]
		];

	} );

	addSupportedType( THREE.Vector2, function( v ) {

		return {
			Vector2: { x: v.x, y: v.y }
		};

	} );

	addSupportedType( THREE.Vector3, function( v ) {

		return {
			Vector3: { x: v.x, y: v.y, z: v.z }
		};

	} );

	addSupportedType( THREE.Vector4, function( v ) {

		return {
			Vector4: { x: v.x, y: v.y, z: v.z, w: v.w }
		};

	} );

	addSupportedType( THREE.Euler, function( v ) {

		return {
			Vector4: { x: v.x, y: v.y, z: v.z, order: v.order }
		};

	} );

	addSupportedType( THREE.Ray, function( v ) {

		return {
			'Ray origin': { x: v.origin.x, y: v.origin.y, z: v.origin.z },
			'Ray direction': { x: v.direction.x, y: v.direction.y, z: v.direction.z }
		};

	} );

	console.log = function() {

		var special = false;

		[].forEach.call( arguments, function( a ) {
			supportedTypes.forEach( function( t ) {
				if( a instanceof t.type ) special = true;;
			} );
		} );

		if( special ) {

			[].forEach.call( arguments, function( a ) {

				_oldConsole.apply( console, [ a ] );

				supportedTypes.forEach( function( t ) {

					if( a instanceof t.type ) {

						console.table( t.format( a ) );

					}

				} );

			} );

		} else {

			_oldConsole.apply( console, arguments );

		}

	}

} )();<commit_msg>Changed matrices output [https://github.com/spite/THREE.AugmentedConsole.js/issues/1]<commit_after>( function() {

	var _oldConsole = console.log;

	var supportedTypes = [];

	function addSupportedType( type, format ) {

		supportedTypes.push( {
			type: type,
			format: format
		} );

	}

	addSupportedType( THREE.Matrix4, function( v ) {

		var e = v.elements;

		return [
			[ e[ 0 ], e[ 4 ], e[ 8 ], e[ 12 ] ],
			[ e[ 1 ], e[ 5 ], e[ 9 ], e[ 13 ] ],
			[ e[ 2 ], e[ 6 ], e[ 10 ], e[ 14 ] ],
			[ e[ 3 ], e[ 7 ], e[ 11 ], e[ 15 ] ]
		];

	} );

	addSupportedType( THREE.Matrix3, function( v ) {

		var e = v.elements;

		return [
			[ e[ 0 ], e[ 3 ], e[ 6 ] ],
			[ e[ 1 ], e[ 4 ], e[ 7 ] ],
			[ e[ 2 ], e[ 5 ], e[ 8 ] ]
		];

	} );

	addSupportedType( THREE.Vector2, function( v ) {

		return {
			Vector2: { x: v.x, y: v.y }
		};

	} );

	addSupportedType( THREE.Vector3, function( v ) {

		return {
			Vector3: { x: v.x, y: v.y, z: v.z }
		};

	} );

	addSupportedType( THREE.Vector4, function( v ) {

		return {
			Vector4: { x: v.x, y: v.y, z: v.z, w: v.w }
		};

	} );

	addSupportedType( THREE.Euler, function( v ) {

		return {
			Vector4: { x: v.x, y: v.y, z: v.z, order: v.order }
		};

	} );

	addSupportedType( THREE.Ray, function( v ) {

		return {
			'Ray origin': { x: v.origin.x, y: v.origin.y, z: v.origin.z },
			'Ray direction': { x: v.direction.x, y: v.direction.y, z: v.direction.z }
		};

	} );

	function stacktrace() {
		var err = new Error();
		return err.stack;
	}

	console.log = function() {

		//console.trace ();

		var special = false;

		[].forEach.call( arguments, function( a ) {
			supportedTypes.forEach( function( t ) {
				if( a instanceof t.type ) special = true;;
			} );
		} );

		if( special ) {

			[].forEach.call( arguments, function( a ) {

				_oldConsole.apply( console, [ a ] );

				supportedTypes.forEach( function( t ) {

					if( a instanceof t.type ) {

						console.table( t.format( a ) );

					}

				} );

			} );

		} else {

			_oldConsole.apply( console, arguments );

		}

	}

} )();<|endoftext|>


<commit_before>'use strict';

angular.module('mapProjectApp.widgets.feedback', [ 'adf.provider' ]).config(
  function(dashboardProvider) {
    dashboardProvider.widget('feedback', {
      title : 'Feedback',
      description : 'Displays a list of feedback notes',
      controller : 'feedbackCtrl',
      templateUrl : 'js/widgets/feedback/feedback.html',
      edit : {}
    });
  })
  .controller(
    'feedbackCtrl',
    function($scope, $rootScope, $http, $location, $uibModal, $sce, localStorageService) {
      $scope.currentUser = null;
      $scope.currentRole = null;
      $scope.focusProject = null;
      $scope.feedbackConversations = null;

      $scope.feedbackTypes = [ 'All Feedback', 'Feedback', 'Group Feedback', 'Error Feedback',
        'Discrepancy Review Feedback' ];
      $scope.reviewedTypes = [ 'All', 'Viewed', 'Unviewed' ];
      $scope.resolvedTypes = [ 'All', 'Active', 'Resolved' ];
      $scope.ownedByList = [ 'All', 'Owned By Me', 'Not Owned By Me' ];

      // initialize as empty to indicate still initializing database connection
      $scope.currentUser = localStorageService.get('currentUser');
      $scope.currentUserToken = localStorageService.get('userToken');
      $scope.currentRole = localStorageService.get('currentRole');
      $scope.focusProject = localStorageService.get('focusProject');

      // table sort fields
      $scope.tableFields = [ {
        id : 0,
        title : 'id',
        sortDir : 'asc',
        sortOn : false
      } ];

      $scope.mapUserViewed == null;
      $scope.searchPerformed = false; // initialize variable to track whether
      // search was performed
      $scope.feedbackType = 'All Feedback';
      $scope.resolvedType = 'All';
      $scope.reviewedType = 'All';
      $scope.ownedByMe = 'All';
      $scope.recordIdOwnerMap = new Array();

      // pagination variables
      $scope.recordsPerPage = 10;
      $scope.recordPage = 1;

      // watch for project change
      $scope.$on('localStorageModule.notification.setFocusProject', function(event, parameters) {
        $scope.focusProject = parameters.focusProject;
      });

      // on publish, update feedbacks to display newly resolved
      $scope.$on('feedbackWidget.notification.retrieveFeedback', function(event, parameters) {
          $scope.retrieveFeedback(1, $scope.feedbackType, $scope.reviewedType,
                  $scope.resolvedType, $scope.ownedByMe, $scope.query);
      });

      // on any change of focusProject, retrieve new available work
      $scope
        .$watch([ 'focusProject', 'currentUser', 'currentUserToken', 'currentRole' ],
          function() {

            if ($scope.focusProject != null && $scope.currentUser != null
              && $scope.currentUserToken != null && $scope.currentRole != null) {
              $http.defaults.headers.common.Authorization = $scope.currentUserToken;
              $scope.mapUsers = $scope.focusProject.mapSpecialist
                .concat($scope.focusProject.mapLead);
              $scope.retrieveFeedback(1, $scope.feedbackType, $scope.reviewedType,
                $scope.resolvedType, $scope.ownedByMe, $scope.query);
            }
          });

      $scope.retrieveFeedback = function(ppage, feedbackType, reviewedType, resolvedType, ownedByMe, pquery) {

    	console.log("ppage", ppage,
    			"feedbackType", feedbackType,
    			"reviewedType", reviewedType,
    			"resolvedType", resolvedType,
    			"ownedByMe", ownedByMe,
    			"pquery", pquery);

        var query = pquery;
        var page = ppage;

        $scope.feedbackType = feedbackType;
        $scope.resolvedType = resolvedType;
        $scope.reviewedType = reviewedType;
        $scope.ownedByMe = ownedByMe;

        // add a check to ensure page is not null
        if (page == null)
          page = 1;

        // construct full query based on all parameters
        if (query == null || query == 'undefined' || query == '')
          query = 'mapProjectId:' + $scope.focusProject.id;
        else
          query = query + ' AND mapProjectId:' + $scope.focusProject.id;

        if (feedbackType == 'Feedback')
          query = query
            + ' AND title:Feedback NOT title:Discrepancy NOT title:Error NOT title:Group';
        else if (feedbackType != null && feedbackType != 'undefined' && feedbackType != ''
          && feedbackType != 'All Feedback')
          query = query + ' AND title:\"' + feedbackType + '\"';
        if (reviewedType != null && reviewedType != 'undefined' && reviewedType != ''
          && reviewedType != 'All')
          query = query + ' AND viewed:' + (reviewedType == 'Viewed' ? 'true' : 'false');
        if (resolvedType != null && resolvedType != 'undefined' && resolvedType != ''
          && resolvedType != 'All')
          query = query + ' AND resolved:' + (resolvedType == 'Active' ? 'false' : 'true');
        //owned by Me
        if (ownedByMe != null && ownedByMe != 'undefined' && ownedByMe != ''
            && ownedByMe != 'All')
            query = query + ' AND ownedByMe:' + (ownedByMe == 'Owned By Me' ? 'true' : 'false');

        // construct a paging/filtering/sorting object
        var pfsParameterObj = {
          'startIndex' : (page - 1) * $scope.recordsPerPage,
          'maxResults' : $scope.recordsPerPage,
          'sortField' : 'lastModified'
        };

        console.log("pfsParameterObj", pfsParameterObj);

        $rootScope.glassPane++;

        $http(
          {
            url : root_workflow + 'conversation/project/id/' + $scope.focusProject.id + '/'
              + $scope.currentUser.userName + '?query=' + encodeURIComponent(query),
            dataType : 'json',
            data : pfsParameterObj,
            method : 'POST',
            headers : {
              'Content-Type' : 'application/json'
            }
          }).success(function(data) {
          $rootScope.glassPane--;

          // set pagination variables
          $scope.nRecords = data.totalCount;
          $scope.numRecordPages = Math.ceil(data.totalCount / $scope.recordsPerPage);

          $scope.feedbackConversations = data.feedbackConversation;

        }).error(function(data, status, headers, config) {
          $rootScope.glassPane--;
          $rootScope.handleHttpError(data, status, headers, config);
        });

      };

      // if any of the feedbacks are not yet viewed, return false indicating
      // that conversation is not yet viewed
      $scope.isFeedbackViewed = function(conversation) {

        for (var i = 0; i < conversation.feedback.length; i++) {
          var alreadyViewedBy = conversation.feedback[i].viewedBy;
          var found = false;
          if (conversation.feedback[i].sender.userName === $scope.currentUser.userName)
      	  	found = true;
          else {
          for (var j = 0; j < alreadyViewedBy.length; j++) {
        	  if (alreadyViewedBy[j].userName == $scope.currentUser.userName)
              	found = true;
          	}
          }
          if (found == false)
            return false;
        }
        return true;
      };

      $scope.markActive = function(conversation) {
        conversation.resolved = false;
        updateFeedbackConversation(conversation);
      };

      $scope.markFeedbackResolved = function(conversation) {
        conversation.resolved = true;
        updateFeedbackConversation(conversation);
      };

      $scope.markFeedbackViewed = function(conversation) {
        for (var i = 0; i < conversation.feedback.length; i++) {
          var alreadyViewedBy = conversation.feedback[i].viewedBy;
          alreadyViewedBy.push($scope.currentUser);
          conversation.feedback[i].viewedBy = alreadyViewedBy;
        }
        updateFeedbackConversation(conversation);
      };

      function updateFeedbackConversation(conversation) {
        $rootScope.glassPane++;

        $http({
          url : root_workflow + 'conversation/update',
          dataType : 'json',
          data : conversation,
          method : 'POST',
          headers : {
            'Content-Type' : 'application/json'
          }
        }).success(function(data) {
          $rootScope.glassPane--;
        }).error(function(data, status, headers, config) {
          $rootScope.glassPane--;
          $scope.recordError = 'Error updating feedback conversation.';
          $rootScope.handleHttpError(data, status, headers, config);
        });
      }

      $scope.goFeedbackConversations = function(id) {
        var path = '/conversation/recordId/' + id;
        // redirect page
        $location.path(path);
      };

      // function to return trusted html code (for advice content)
      $scope.to_trusted = function(html_code) {
        return $sce.trustAsHtml(html_code);
      };

      // function to clear input box and return to initial view
      $scope.resetSearch = function() {
        $scope.query = null;
        $scope.retrieveFeedback(1, 'All Feedback', 'All', 'All', 'All', '');
      };
    });
<commit_msg>Feedback can be marked as unviewed even by the author.<commit_after>'use strict';

angular.module('mapProjectApp.widgets.feedback', [ 'adf.provider' ]).config(
  function(dashboardProvider) {
    dashboardProvider.widget('feedback', {
      title : 'Feedback',
      description : 'Displays a list of feedback notes',
      controller : 'feedbackCtrl',
      templateUrl : 'js/widgets/feedback/feedback.html',
      edit : {}
    });
  })
  .controller(
    'feedbackCtrl',
    function($scope, $rootScope, $http, $location, $uibModal, $sce, localStorageService) {
      $scope.currentUser = null;
      $scope.currentRole = null;
      $scope.focusProject = null;
      $scope.feedbackConversations = null;

      $scope.feedbackTypes = [ 'All Feedback', 'Feedback', 'Group Feedback', 'Error Feedback',
        'Discrepancy Review Feedback' ];
      $scope.reviewedTypes = [ 'All', 'Viewed', 'Unviewed' ];
      $scope.resolvedTypes = [ 'All', 'Active', 'Resolved' ];
      $scope.ownedByList = [ 'All', 'Owned By Me', 'Not Owned By Me' ];

      // initialize as empty to indicate still initializing database connection
      $scope.currentUser = localStorageService.get('currentUser');
      $scope.currentUserToken = localStorageService.get('userToken');
      $scope.currentRole = localStorageService.get('currentRole');
      $scope.focusProject = localStorageService.get('focusProject');

      // table sort fields
      $scope.tableFields = [ {
        id : 0,
        title : 'id',
        sortDir : 'asc',
        sortOn : false
      } ];

      $scope.mapUserViewed == null;
      $scope.searchPerformed = false; // initialize variable to track whether
      // search was performed
      $scope.feedbackType = 'All Feedback';
      $scope.resolvedType = 'All';
      $scope.reviewedType = 'All';
      $scope.ownedByMe = 'All';
      $scope.recordIdOwnerMap = new Array();

      // pagination variables
      $scope.recordsPerPage = 10;
      $scope.recordPage = 1;

      // watch for project change
      $scope.$on('localStorageModule.notification.setFocusProject', function(event, parameters) {
        $scope.focusProject = parameters.focusProject;
      });

      // on publish, update feedbacks to display newly resolved
      $scope.$on('feedbackWidget.notification.retrieveFeedback', function(event, parameters) {
          $scope.retrieveFeedback(1, $scope.feedbackType, $scope.reviewedType,
                  $scope.resolvedType, $scope.ownedByMe, $scope.query);
      });

      // on any change of focusProject, retrieve new available work
      $scope
        .$watch([ 'focusProject', 'currentUser', 'currentUserToken', 'currentRole' ],
          function() {

            if ($scope.focusProject != null && $scope.currentUser != null
              && $scope.currentUserToken != null && $scope.currentRole != null) {
              $http.defaults.headers.common.Authorization = $scope.currentUserToken;
              $scope.mapUsers = $scope.focusProject.mapSpecialist
                .concat($scope.focusProject.mapLead);
              $scope.retrieveFeedback(1, $scope.feedbackType, $scope.reviewedType,
                $scope.resolvedType, $scope.ownedByMe, $scope.query);
            }
          });

      $scope.retrieveFeedback = function(ppage, feedbackType, reviewedType, resolvedType, ownedByMe, pquery) {

    	console.log("ppage", ppage,
    			"feedbackType", feedbackType,
    			"reviewedType", reviewedType,
    			"resolvedType", resolvedType,
    			"ownedByMe", ownedByMe,
    			"pquery", pquery);

        var query = pquery;
        var page = ppage;

        $scope.feedbackType = feedbackType;
        $scope.resolvedType = resolvedType;
        $scope.reviewedType = reviewedType;
        $scope.ownedByMe = ownedByMe;

        // add a check to ensure page is not null
        if (page == null)
          page = 1;

        // construct full query based on all parameters
        if (query == null || query == 'undefined' || query == '')
          query = 'mapProjectId:' + $scope.focusProject.id;
        else
          query = query + ' AND mapProjectId:' + $scope.focusProject.id;

        if (feedbackType == 'Feedback')
          query = query
            + ' AND title:Feedback NOT title:Discrepancy NOT title:Error NOT title:Group';
        else if (feedbackType != null && feedbackType != 'undefined' && feedbackType != ''
          && feedbackType != 'All Feedback')
          query = query + ' AND title:\"' + feedbackType + '\"';
        if (reviewedType != null && reviewedType != 'undefined' && reviewedType != ''
          && reviewedType != 'All')
          query = query + ' AND viewed:' + (reviewedType == 'Viewed' ? 'true' : 'false');
        if (resolvedType != null && resolvedType != 'undefined' && resolvedType != ''
          && resolvedType != 'All')
          query = query + ' AND resolved:' + (resolvedType == 'Active' ? 'false' : 'true');
        //owned by Me
        if (ownedByMe != null && ownedByMe != 'undefined' && ownedByMe != ''
            && ownedByMe != 'All')
            query = query + ' AND ownedByMe:' + (ownedByMe == 'Owned By Me' ? 'true' : 'false');

        // construct a paging/filtering/sorting object
        var pfsParameterObj = {
          'startIndex' : (page - 1) * $scope.recordsPerPage,
          'maxResults' : $scope.recordsPerPage,
          'sortField' : 'lastModified'
        };

        console.log("pfsParameterObj", pfsParameterObj);

        $rootScope.glassPane++;

        $http(
          {
            url : root_workflow + 'conversation/project/id/' + $scope.focusProject.id + '/'
              + $scope.currentUser.userName + '?query=' + encodeURIComponent(query),
            dataType : 'json',
            data : pfsParameterObj,
            method : 'POST',
            headers : {
              'Content-Type' : 'application/json'
            }
          }).success(function(data) {
          $rootScope.glassPane--;

          // set pagination variables
          $scope.nRecords = data.totalCount;
          $scope.numRecordPages = Math.ceil(data.totalCount / $scope.recordsPerPage);

          $scope.feedbackConversations = data.feedbackConversation;

        }).error(function(data, status, headers, config) {
          $rootScope.glassPane--;
          $rootScope.handleHttpError(data, status, headers, config);
        });

      };

      // if any of the feedbacks are not yet viewed, return false indicating
      // that conversation is not yet viewed
      $scope.isFeedbackViewed = function(conversation) {

        for (var i = 0; i < conversation.feedback.length; i++) {
          var alreadyViewedBy = conversation.feedback[i].viewedBy;
          var found = false;

          for (var j = 0; j < alreadyViewedBy.length; j++) {
        	  if (alreadyViewedBy[j].userName == $scope.currentUser.userName) {
              	found = true;
        	  }
          }
          if (found == false) {
            return false;
          }
        }
        return true;
      };

      $scope.markActive = function(conversation) {
        conversation.resolved = false;
        updateFeedbackConversation(conversation);
      };

      $scope.markFeedbackResolved = function(conversation) {
        conversation.resolved = true;
        updateFeedbackConversation(conversation);
      };

      $scope.markFeedbackViewed = function(conversation) {
        for (var i = 0; i < conversation.feedback.length; i++) {
          var alreadyViewedBy = conversation.feedback[i].viewedBy;
          alreadyViewedBy.push($scope.currentUser);
          conversation.feedback[i].viewedBy = alreadyViewedBy;
        }
        updateFeedbackConversation(conversation);
      };

      function updateFeedbackConversation(conversation) {
        $rootScope.glassPane++;

        $http({
          url : root_workflow + 'conversation/update',
          dataType : 'json',
          data : conversation,
          method : 'POST',
          headers : {
            'Content-Type' : 'application/json'
          }
        }).success(function(data) {
          $rootScope.glassPane--;
        }).error(function(data, status, headers, config) {
          $rootScope.glassPane--;
          $scope.recordError = 'Error updating feedback conversation.';
          $rootScope.handleHttpError(data, status, headers, config);
        });
      }

      $scope.goFeedbackConversations = function(id) {
        var path = '/conversation/recordId/' + id;
        // redirect page
        $location.path(path);
      };

      // function to return trusted html code (for advice content)
      $scope.to_trusted = function(html_code) {
        return $sce.trustAsHtml(html_code);
      };

      // function to clear input box and return to initial view
      $scope.resetSearch = function() {
        $scope.query = null;
        $scope.retrieveFeedback(1, 'All Feedback', 'All', 'All', 'All', '');
      };
    });
<|endoftext|>


<commit_before>'use strict';

var implementation = require('./implementation');

var supportsDescriptors = require('define-properties').supportsDescriptors;
var $gOPD = Object.getOwnPropertyDescriptor;
var $TypeError = TypeError;

module.exports = function getPolyfill() {
	if (!supportsDescriptors) {
		throw new $TypeError('RegExp.prototype.flags requires a true ES5 environment that supports property descriptors');
	}
	if ((/a/mig).flags === 'gim') {
		var descriptor = $gOPD(RegExp.prototype, 'flags');
		if (descriptor && typeof descriptor.get === 'function' && typeof (/a/).dotAll === 'boolean') {
			return descriptor.get;
		}
	}
	return implementation;
};
<commit_msg>[Fix] `polyfill`: do not throw in a descriptorless environment<commit_after>'use strict';

var implementation = require('./implementation');

var supportsDescriptors = require('define-properties').supportsDescriptors;
var $gOPD = Object.getOwnPropertyDescriptor;

module.exports = function getPolyfill() {
	if (supportsDescriptors && (/a/mig).flags === 'gim') {
		var descriptor = $gOPD(RegExp.prototype, 'flags');
		if (descriptor && typeof descriptor.get === 'function' && typeof (/a/).dotAll === 'boolean') {
			return descriptor.get;
		}
	}
	return implementation;
};
<|endoftext|>