<commit_before>var helper = require('./options-helper');
var isArray = require('./array-helper').isArray;

var currentElement, currentElementName;

function validateOptions(userOptions) {
  var options = helper.copyOptions(userOptions);
  helper.ensureFlagExists('ignoreDeclaration', options);
  helper.ensureFlagExists('ignoreInstruction', options);
  helper.ensureFlagExists('ignoreAttributes', options);
  helper.ensureFlagExists('ignoreText', options);
  helper.ensureFlagExists('ignoreComment', options);
  helper.ensureFlagExists('ignoreCdata', options);
  helper.ensureFlagExists('ignoreDoctype', options);
  helper.ensureFlagExists('compact', options);
  helper.ensureFlagExists('indentText', options);
  helper.ensureFlagExists('indentCdata', options);
  helper.ensureFlagExists('indentAttributes', options);
  helper.ensureFlagExists('indentInstruction', options);
  helper.ensureFlagExists('fullTagEmptyElement', options);
  helper.ensureFlagExists('noQuotesForNativeAttributes', options);
  helper.ensureSpacesExists(options);
  if (typeof options.spaces === 'number') {
    options.spaces = Array(options.spaces + 1).join(' ');
  }
  helper.ensureKeyExists('declaration', options);
  helper.ensureKeyExists('instruction', options);
  helper.ensureKeyExists('attributes', options);
  helper.ensureKeyExists('text', options);
  helper.ensureKeyExists('comment', options);
  helper.ensureKeyExists('cdata', options);
  helper.ensureKeyExists('doctype', options);
  helper.ensureKeyExists('type', options);
  helper.ensureKeyExists('name', options);
  helper.ensureKeyExists('elements', options);
  helper.checkFnExists('doctype', options);
  helper.checkFnExists('instruction', options);
  helper.checkFnExists('cdata', options);
  helper.checkFnExists('comment', options);
  helper.checkFnExists('text', options);
  helper.checkFnExists('instructionName', options);
  helper.checkFnExists('elementName', options);
  helper.checkFnExists('attributeName', options);
  helper.checkFnExists('attributeValue', options);
  helper.checkFnExists('attributes', options);
  helper.checkFnExists('fullTagEmptyElement', options);
  return options;
}

function writeIndentation(options, depth, firstLine) {
  return (!firstLine && options.spaces ? '\n' : '') + Array(depth + 1).join(options.spaces);
}

function writeAttributes(attributes, options, depth) {
  if (options.ignoreAttributes) {
    return '';
  }
  if ('attributesFn' in options) {
    attributes = options.attributesFn(attributes, currentElementName, currentElement);
  }
  var key, attr, attrName, quote, result = '';
  for (key in attributes) {
    if (attributes.hasOwnProperty(key)) {
      quote = options.noQuotesForNativeAttributes && typeof attributes[key] !== 'string' ? '' : '"';
      attr = '' + attributes[key]; // ensure number and boolean are converted to String
      attr = attr.replace(/"/g, '"');
      attrName = 'attributeNameFn' in options ? options.attributeNameFn(key, attr, currentElementName, currentElement) : key;
      result += (options.spaces && options.indentAttributes? writeIndentation(options, depth+1, false) : ' ');
      result += attrName + '=' + quote + ('attributeValueFn' in options ? options.attributeValueFn(attr, key, currentElementName, currentElement) : attr) + quote;
    }
  }
  if (attributes && Object.keys(attributes).length && options.spaces && options.indentAttributes) {
    result += writeIndentation(options, depth, false);
  }
  return result;
}

function writeDeclaration(declaration, options, depth) {
  currentElement = declaration;
  currentElementName = 'xml';
  return options.ignoreDeclaration ? '' :  '<?' + 'xml' + writeAttributes(declaration[options.attributesKey], options, depth) + '?>';
}

function writeInstruction(instruction, options, depth) {
  if (options.ignoreInstruction) {
    return '';
  }
  var key;
  for (key in instruction) {
    if (instruction.hasOwnProperty(key)) {
      break;
    }
  }
  var instructionName = 'instructionNameFn' in options ? options.instructionNameFn(key, instruction[key], currentElementName, currentElement) : key;
  if (typeof instruction[key] === 'object') {
    currentElement = instruction;
    currentElementName = instructionName;
    return '<?' + instructionName + writeAttributes(instruction[key][options.attributesKey], options, depth) + '?>';
  } else {
    var instructionValue = instruction[key] ? instruction[key] : '';
    if ('instructionFn' in options) instructionValue = options.instructionFn(instructionValue, key, currentElementName, currentElement);
    return '<?' + instructionName + (instructionValue ? ' ' + instructionValue : '') + '?>';
  }
}

function writeComment(comment, options) {
  return options.ignoreComment ? '' : '<!--' + ('commentFn' in options ? options.commentFn(comment, currentElementName, currentElement) : comment) + '-->';
}

function writeCdata(cdata, options) {
  return options.ignoreCdata ? '' : '<![CDATA[' + ('cdataFn' in options ? options.cdataFn(cdata, currentElementName, currentElement) : cdata) + ']]>';
}

function writeDoctype(doctype, options) {
  return options.ignoreDoctype ? '' : '<!DOCTYPE ' + ('doctypeFn' in options ? options.doctypeFn(doctype, currentElementName, currentElement) : doctype) + '>';
}

function writeText(text, options) {
  if (options.ignoreText) return '';
  text = '' + text; // ensure Number and Boolean are converted to String
  text = text.replace(/&/g, '&'); // desanitize to avoid double sanitization
  text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
  return 'textFn' in options ? options.textFn(text, currentElementName, currentElement) : text;
}

function hasContent(element, options) {
  var i;
  if (element.elements && element.elements.length) {
    for (i = 0; i < element.elements.length; ++i) {
      switch (element.elements[i][options.typeKey]) {
      case 'text':
        if (options.indentText) {
          return true;
        }
        break; // skip to next key
      case 'cdata':
        if (options.indentCdata) {
          return true;
        }
        break; // skip to next key
      case 'instruction':
        if (options.indentInstruction) {
          return true;
        }
        break; // skip to next key
      case 'doctype':
      case 'comment':
      case 'element':
        return true;
      default:
        return true;
      }
    }
  }
  return false;
}

function writeElement(element, options, depth) {
  currentElement = element;
  currentElementName = element.name;
  var xml = '', elementName = 'elementNameFn' in options ? options.elementNameFn(element.name, element) : element.name;
  xml += '<' + elementName;
  if (element[options.attributesKey]) {
    xml += writeAttributes(element[options.attributesKey], options, depth);
  }
  var withClosingTag = element[options.elementsKey] && element[options.elementsKey].length || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve';
  if (!withClosingTag) {
    if ('fullTagEmptyElementFn' in options) {
      withClosingTag = options.fullTagEmptyElementFn(element.name, element);
    } else {
      withClosingTag = options.fullTagEmptyElement;
    }
  }
  if (withClosingTag) {
    xml += '>';
    if (element[options.elementsKey] && element[options.elementsKey].length) {
      xml += writeElements(element[options.elementsKey], options, depth + 1);
      currentElement = element;
      currentElementName = element.name;
    }
    xml += options.spaces && hasContent(element, options) ? '\n' + Array(depth + 1).join(options.spaces) : '';
    xml += '</' + elementName + '>';
  } else {
    xml += '/>';
  }
  return xml;
}

function writeElements(elements, options, depth, firstLine) {
  return elements.reduce(function (xml, element) {
    var indent = writeIndentation(options, depth, firstLine && !xml);
    switch (element.type) {
    case 'element': return xml + indent + writeElement(element, options, depth);
    case 'comment': return xml + indent + writeComment(element[options.commentKey], options);
    case 'doctype': return xml + indent + writeDoctype(element[options.doctypeKey], options);
    case 'cdata': return xml + (options.indentCdata ? indent : '') + writeCdata(element[options.cdataKey], options);
    case 'text': return xml + (options.indentText ? indent : '') + writeText(element[options.textKey], options);
    case 'instruction':
      var instruction = {};
      instruction[element[options.nameKey]] = element[options.attributesKey] ? element : element[options.instructionKey];
      return xml + (options.indentInstruction ? indent : '') + writeInstruction(instruction, options, depth);
    }
  }, '');
}

function hasContentCompact(element, options, anyContent) {
  var key;
  for (key in element) {
    if (element.hasOwnProperty(key)) {
      switch (key) {
      case options.parentKey:
      case options.attributesKey:
        break; // skip to next key
      case options.textKey:
        if (options.indentText || anyContent) {
          return true;
        }
        break; // skip to next key
      case options.cdataKey:
        if (options.indentCdata || anyContent) {
          return true;
        }
        break; // skip to next key
      case options.instructionKey:
        if (options.indentInstruction || anyContent) {
          return true;
        }
        break; // skip to next key
      case options.doctypeKey:
      case options.commentKey:
        return true;
      default:
        return true;
      }
    }
  }
  return false;
}

function writeElementCompact(element, name, options, depth, indent) {
  currentElement = element;
  currentElementName = name;
  var elementName = 'elementNameFn' in options ? options.elementNameFn(name, element) : name;
  if (typeof element === 'undefined' || element === null) {
    return 'fullTagEmptyElementFn' in options && options.fullTagEmptyElementFn(name, element) || options.fullTagEmptyElement ? '<' + elementName + '></' + elementName + '>' : '<' + elementName + '/>';
  }
  var xml = '';
  if (name) {
    xml += '<' + elementName;
    if (typeof element !== 'object') {
      xml += '>' + writeText(element,options) + '</' + elementName + '>';
      return xml;
    }
    if (element[options.attributesKey]) {
      xml += writeAttributes(element[options.attributesKey], options, depth);
    }
    var withClosingTag = hasContentCompact(element, options, true) || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve';
    if (!withClosingTag) {
      if ('fullTagEmptyElementFn' in options) {
        withClosingTag = options.fullTagEmptyElementFn(name, element);
      } else {
        withClosingTag = options.fullTagEmptyElement;
      }
    }
    if (withClosingTag) {
      xml += '>';
    } else {
      xml += '/>';
      return xml;
    }
  }
  xml += writeElementsCompact(element, options, depth + 1, false);
  currentElement = element;
  currentElementName = name;
  if (name) {
    xml += (indent ? writeIndentation(options, depth, false) : '') + '</' + elementName + '>';
  }
  return xml;
}

function writeElementsCompact(element, options, depth, firstLine) {
  var i, key, nodes, xml = '';
  for (key in element) {
    if (element.hasOwnProperty(key)) {
      nodes = isArray(element[key]) ? element[key] : [element[key]];
      for (i = 0; i < nodes.length; ++i) {
        switch (key) {
        case options.declarationKey: xml += writeDeclaration(nodes[i], options, depth); break;
        case options.instructionKey: xml += (options.indentInstruction ? writeIndentation(options, depth, firstLine) : '') + writeInstruction(nodes[i], options, depth); break;
        case options.attributesKey: case options.parentKey: break; // skip
        case options.textKey: xml += (options.indentText ? writeIndentation(options, depth, firstLine) : '') + writeText(nodes[i], options); break;
        case options.cdataKey: xml += (options.indentCdata ? writeIndentation(options, depth, firstLine) : '') + writeCdata(nodes[i], options); break;
        case options.doctypeKey: xml += writeIndentation(options, depth, firstLine) + writeDoctype(nodes[i], options); break;
        case options.commentKey: xml += writeIndentation(options, depth, firstLine) + writeComment(nodes[i], options); break;
        default: xml += writeIndentation(options, depth, firstLine) + writeElementCompact(nodes[i], key, options, depth, hasContentCompact(nodes[i], options));
        }
        firstLine = firstLine && !xml;
      }
    }
  }
  return xml;
}

module.exports = function (js, options) {
  options = validateOptions(options);
  var xml = '';
  currentElement = js;
  currentElementName = '_root_';
  if (options.compact) {
    xml = writeElementsCompact(js, options, 0, true);
  } else {
    if (js[options.declarationKey]) {
      xml += writeDeclaration(js[options.declarationKey], options, 0);
    }
    if (js[options.elementsKey] && js[options.elementsKey].length) {
      xml += writeElements(js[options.elementsKey], options, 0, !xml);
    }
  }
  return xml;
};
<commit_msg>Change string concat to array join<commit_after>var helper = require('./options-helper');
var isArray = require('./array-helper').isArray;

var currentElement, currentElementName;

function validateOptions(userOptions) {
  var options = helper.copyOptions(userOptions);
  helper.ensureFlagExists('ignoreDeclaration', options);
  helper.ensureFlagExists('ignoreInstruction', options);
  helper.ensureFlagExists('ignoreAttributes', options);
  helper.ensureFlagExists('ignoreText', options);
  helper.ensureFlagExists('ignoreComment', options);
  helper.ensureFlagExists('ignoreCdata', options);
  helper.ensureFlagExists('ignoreDoctype', options);
  helper.ensureFlagExists('compact', options);
  helper.ensureFlagExists('indentText', options);
  helper.ensureFlagExists('indentCdata', options);
  helper.ensureFlagExists('indentAttributes', options);
  helper.ensureFlagExists('indentInstruction', options);
  helper.ensureFlagExists('fullTagEmptyElement', options);
  helper.ensureFlagExists('noQuotesForNativeAttributes', options);
  helper.ensureSpacesExists(options);
  if (typeof options.spaces === 'number') {
    options.spaces = Array(options.spaces + 1).join(' ');
  }
  helper.ensureKeyExists('declaration', options);
  helper.ensureKeyExists('instruction', options);
  helper.ensureKeyExists('attributes', options);
  helper.ensureKeyExists('text', options);
  helper.ensureKeyExists('comment', options);
  helper.ensureKeyExists('cdata', options);
  helper.ensureKeyExists('doctype', options);
  helper.ensureKeyExists('type', options);
  helper.ensureKeyExists('name', options);
  helper.ensureKeyExists('elements', options);
  helper.checkFnExists('doctype', options);
  helper.checkFnExists('instruction', options);
  helper.checkFnExists('cdata', options);
  helper.checkFnExists('comment', options);
  helper.checkFnExists('text', options);
  helper.checkFnExists('instructionName', options);
  helper.checkFnExists('elementName', options);
  helper.checkFnExists('attributeName', options);
  helper.checkFnExists('attributeValue', options);
  helper.checkFnExists('attributes', options);
  helper.checkFnExists('fullTagEmptyElement', options);
  return options;
}

function writeIndentation(options, depth, firstLine) {
  return (!firstLine && options.spaces ? '\n' : '') + Array(depth + 1).join(options.spaces);
}

function writeAttributes(attributes, options, depth) {
  if (options.ignoreAttributes) {
    return '';
  }
  if ('attributesFn' in options) {
    attributes = options.attributesFn(attributes, currentElementName, currentElement);
  }
  var key, attr, attrName, quote, result = [];
  for (key in attributes) {
    if (attributes.hasOwnProperty(key)) {
      quote = options.noQuotesForNativeAttributes && typeof attributes[key] !== 'string' ? '' : '"';
      attr = '' + attributes[key]; // ensure number and boolean are converted to String
      attr = attr.replace(/"/g, '"');
      attrName = 'attributeNameFn' in options ? options.attributeNameFn(key, attr, currentElementName, currentElement) : key;
      result.push((options.spaces && options.indentAttributes? writeIndentation(options, depth+1, false) : ' '));
      result.push(attrName + '=' + quote + ('attributeValueFn' in options ? options.attributeValueFn(attr, key, currentElementName, currentElement) : attr) + quote);
    }
  }
  if (attributes && Object.keys(attributes).length && options.spaces && options.indentAttributes) {
    result.push(writeIndentation(options, depth, false));
  }
  return result.join('');
}

function writeDeclaration(declaration, options, depth) {
  currentElement = declaration;
  currentElementName = 'xml';
  return options.ignoreDeclaration ? '' :  '<?' + 'xml' + writeAttributes(declaration[options.attributesKey], options, depth) + '?>';
}

function writeInstruction(instruction, options, depth) {
  if (options.ignoreInstruction) {
    return '';
  }
  var key;
  for (key in instruction) {
    if (instruction.hasOwnProperty(key)) {
      break;
    }
  }
  var instructionName = 'instructionNameFn' in options ? options.instructionNameFn(key, instruction[key], currentElementName, currentElement) : key;
  if (typeof instruction[key] === 'object') {
    currentElement = instruction;
    currentElementName = instructionName;
    return '<?' + instructionName + writeAttributes(instruction[key][options.attributesKey], options, depth) + '?>';
  } else {
    var instructionValue = instruction[key] ? instruction[key] : '';
    if ('instructionFn' in options) instructionValue = options.instructionFn(instructionValue, key, currentElementName, currentElement);
    return '<?' + instructionName + (instructionValue ? ' ' + instructionValue : '') + '?>';
  }
}

function writeComment(comment, options) {
  return options.ignoreComment ? '' : '<!--' + ('commentFn' in options ? options.commentFn(comment, currentElementName, currentElement) : comment) + '-->';
}

function writeCdata(cdata, options) {
  return options.ignoreCdata ? '' : '<![CDATA[' + ('cdataFn' in options ? options.cdataFn(cdata, currentElementName, currentElement) : cdata) + ']]>';
}

function writeDoctype(doctype, options) {
  return options.ignoreDoctype ? '' : '<!DOCTYPE ' + ('doctypeFn' in options ? options.doctypeFn(doctype, currentElementName, currentElement) : doctype) + '>';
}

function writeText(text, options) {
  if (options.ignoreText) return '';
  text = '' + text; // ensure Number and Boolean are converted to String
  text = text.replace(/&/g, '&'); // desanitize to avoid double sanitization
  text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
  return 'textFn' in options ? options.textFn(text, currentElementName, currentElement) : text;
}

function hasContent(element, options) {
  var i;
  if (element.elements && element.elements.length) {
    for (i = 0; i < element.elements.length; ++i) {
      switch (element.elements[i][options.typeKey]) {
      case 'text':
        if (options.indentText) {
          return true;
        }
        break; // skip to next key
      case 'cdata':
        if (options.indentCdata) {
          return true;
        }
        break; // skip to next key
      case 'instruction':
        if (options.indentInstruction) {
          return true;
        }
        break; // skip to next key
      case 'doctype':
      case 'comment':
      case 'element':
        return true;
      default:
        return true;
      }
    }
  }
  return false;
}

function writeElement(element, options, depth) {
  currentElement = element;
  currentElementName = element.name;
  var xml = [], elementName = 'elementNameFn' in options ? options.elementNameFn(element.name, element) : element.name;
  xml.push('<' + elementName);
  if (element[options.attributesKey]) {
    xml.push(writeAttributes(element[options.attributesKey], options, depth));
  }
  var withClosingTag = element[options.elementsKey] && element[options.elementsKey].length || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve';
  if (!withClosingTag) {
    if ('fullTagEmptyElementFn' in options) {
      withClosingTag = options.fullTagEmptyElementFn(element.name, element);
    } else {
      withClosingTag = options.fullTagEmptyElement;
    }
  }
  if (withClosingTag) {
    xml.push('>');
    if (element[options.elementsKey] && element[options.elementsKey].length) {
      xml.push(writeElements(element[options.elementsKey], options, depth + 1));
      currentElement = element;
      currentElementName = element.name;
    }
    xml.push(options.spaces && hasContent(element, options) ? '\n' + Array(depth + 1).join(options.spaces) : '');
    xml.push('</' + elementName + '>');
  } else {
    xml.push('/>');
  }
  return xml.join('');
}

function writeElements(elements, options, depth, firstLine) {
  return elements.reduce(function (xml, element) {
    var indent = writeIndentation(options, depth, firstLine && !xml);
    switch (element.type) {
    case 'element': return xml + indent + writeElement(element, options, depth);
    case 'comment': return xml + indent + writeComment(element[options.commentKey], options);
    case 'doctype': return xml + indent + writeDoctype(element[options.doctypeKey], options);
    case 'cdata': return xml + (options.indentCdata ? indent : '') + writeCdata(element[options.cdataKey], options);
    case 'text': return xml + (options.indentText ? indent : '') + writeText(element[options.textKey], options);
    case 'instruction':
      var instruction = {};
      instruction[element[options.nameKey]] = element[options.attributesKey] ? element : element[options.instructionKey];
      return xml + (options.indentInstruction ? indent : '') + writeInstruction(instruction, options, depth);
    }
  }, '');
}

function hasContentCompact(element, options, anyContent) {
  var key;
  for (key in element) {
    if (element.hasOwnProperty(key)) {
      switch (key) {
      case options.parentKey:
      case options.attributesKey:
        break; // skip to next key
      case options.textKey:
        if (options.indentText || anyContent) {
          return true;
        }
        break; // skip to next key
      case options.cdataKey:
        if (options.indentCdata || anyContent) {
          return true;
        }
        break; // skip to next key
      case options.instructionKey:
        if (options.indentInstruction || anyContent) {
          return true;
        }
        break; // skip to next key
      case options.doctypeKey:
      case options.commentKey:
        return true;
      default:
        return true;
      }
    }
  }
  return false;
}

function writeElementCompact(element, name, options, depth, indent) {
  currentElement = element;
  currentElementName = name;
  var elementName = 'elementNameFn' in options ? options.elementNameFn(name, element) : name;
  if (typeof element === 'undefined' || element === null) {
    return 'fullTagEmptyElementFn' in options && options.fullTagEmptyElementFn(name, element) || options.fullTagEmptyElement ? '<' + elementName + '></' + elementName + '>' : '<' + elementName + '/>';
  }
  var xml = [];
  if (name) {
    xml.push('<' + elementName);
    if (typeof element !== 'object') {
      xml.push('>' + writeText(element,options) + '</' + elementName + '>');
      return xml.join('');
    }
    if (element[options.attributesKey]) {
      xml.push(writeAttributes(element[options.attributesKey], options, depth));
    }
    var withClosingTag = hasContentCompact(element, options, true) || element[options.attributesKey] && element[options.attributesKey]['xml:space'] === 'preserve';
    if (!withClosingTag) {
      if ('fullTagEmptyElementFn' in options) {
        withClosingTag = options.fullTagEmptyElementFn(name, element);
      } else {
        withClosingTag = options.fullTagEmptyElement;
      }
    }
    if (withClosingTag) {
      xml.push('>');
    } else {
      xml.push('/>');
      return xml.join('');
    }
  }
  xml.push(writeElementsCompact(element, options, depth + 1, false));
  currentElement = element;
  currentElementName = name;
  if (name) {
    xml.push((indent ? writeIndentation(options, depth, false) : '') + '</' + elementName + '>');
  }
  return xml.join('');
}

function writeElementsCompact(element, options, depth, firstLine) {
  var i, key, nodes, xml = [];
  for (key in element) {
    if (element.hasOwnProperty(key)) {
      nodes = isArray(element[key]) ? element[key] : [element[key]];
      for (i = 0; i < nodes.length; ++i) {
        switch (key) {
        case options.declarationKey: xml.push(writeDeclaration(nodes[i], options, depth)); break;
        case options.instructionKey: xml.push((options.indentInstruction ? writeIndentation(options, depth, firstLine) : '') + writeInstruction(nodes[i], options, depth)); break;
        case options.attributesKey: case options.parentKey: break; // skip
        case options.textKey: xml.push((options.indentText ? writeIndentation(options, depth, firstLine) : '') + writeText(nodes[i], options)); break;
        case options.cdataKey: xml.push((options.indentCdata ? writeIndentation(options, depth, firstLine) : '') + writeCdata(nodes[i], options)); break;
        case options.doctypeKey: xml.push(writeIndentation(options, depth, firstLine) + writeDoctype(nodes[i], options)); break;
        case options.commentKey: xml.push(writeIndentation(options, depth, firstLine) + writeComment(nodes[i], options)); break;
        default: xml.push(writeIndentation(options, depth, firstLine) + writeElementCompact(nodes[i], key, options, depth, hasContentCompact(nodes[i], options)));
        }
        firstLine = firstLine && !xml.length;
      }
    }
  }
  return xml.join('');
}

module.exports = function (js, options) {
  options = validateOptions(options);
  var xml = [];
  currentElement = js;
  currentElementName = '_root_';
  if (options.compact) {
    xml.push(writeElementsCompact(js, options, 0, true));
  } else {
    if (js[options.declarationKey]) {
      xml.push(writeDeclaration(js[options.declarationKey], options, 0));
    }
    if (js[options.elementsKey] && js[options.elementsKey].length) {
      xml.push(writeElements(js[options.elementsKey], options, 0, !xml.length));
    }
  }
  return xml.join('');
};
<|endoftext|>


<commit_before>var _ = require('underscore');

var Layout = function () {};

Layout.prototype.layout = function(images, options) {
    options = options || {};

    var self = this,
        hpadding,
        vpadding,
        hmargin,
        vmargin,
        root;

    hpadding = options.hpadding || 0;
    vpadding = options.vpadding || 0;
    hmargin = options.hmargin || 0;
    vmargin = options.vmargin || 0;

    _.each(images, function (image) {
        image.w = image.width + hpadding * 2 + hmargin * 2;
        image.h = image.height + vpadding * 2 + vmargin * 2;
    });

    // sort by size
    images.sort(function(a, b) {
        var diff;
        diff = self.compare(Math.max(b.w, b.h), Math.max(a.w, a.h));
        if (diff === 0) {
            diff = self.compare(Math.min(b.w, b.h), Math.min(a.w, a.h));
        }
        if (diff === 0) {
            diff = self.compare(b.h, a.h);
        }
        if (diff === 0) {
            diff = self.compare(b.w, a.w);
        }
        return diff;
    });

    // root node
    root = {
        x: 0,
        y: 0,
        w: images[0].w,
        h: images[0].h
    };

    var layout = function layout(image) {
        var node;
        node = self.findNode(root, image.w, image.h);

        if (node) {
            self.placeImage(image, node, hpadding, vpadding, hmargin, vmargin);
            self.splitNode(node, image.w, image.h);
            return;
        }

        root = self.grow(root, image.w, image.h);
        layout(image);
        return;

    };
    _.each(images, layout);

    return {
        width: root.w,
        height: root.h
    };
};

Layout.prototype.compare = function(a, b) {
    if (a > b) {
        return 1;
    }
    if (b > a) {
        return -1;
    }
    return 0;
};

Layout.prototype.placeImage = function(image, node, hpadding, vpadding, hmargin, vmargin) {
    image.cssx = node.x + hmargin;
    image.cssy = node.y + vmargin;
    image.cssw = image.width + (2 * hpadding);
    image.cssh = image.height + (2 * vpadding);
    image.x = image.cssx + hpadding;
    image.y = image.cssy + vpadding;
    return;
};

Layout.prototype.findNode = function(root, w, h) {
    if (root.used) {
        return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    }
    if ((w <= root.w) && (h <= root.h)) {
        return root;
    }
    return;
};

Layout.prototype.splitNode = function(node, w, h) {
    node.used = true;
    node.down = {
        x: node.x,
        y: node.y + h,
        w: node.w,
        h: node.h - h
    };
    node.right = {
        x: node.x + w,
        y: node.y,
        w: node.w - w,
        h: h
    };
    return node.right;
};

Layout.prototype.grow = function(root, w, h) {
    var canGrowDown,
        canGrowRight,
        shouldGrowDown,
        shouldGrowRight;

    canGrowDown = w <= root.w;
    canGrowRight = h <= root.h;
    shouldGrowRight = canGrowRight && (root.h >= (root.w + w));
    shouldGrowDown = canGrowDown && (root.w >= (root.h + h));

    if (shouldGrowRight) {
        return this.growRight(root, w, h);
    } else if (shouldGrowDown) {
        return this.growDown(root, w, h);
    } else if (canGrowRight) {
        return this.growRight(root, w, h);
    } else if (canGrowDown) {
        return this.growDown(root, w, h);
    } else {
        throw "Can't fit " + w + "x" + h + " block into root " + root.w + "x" + root.h + " - this should not happen if images are pre-sorted correctly";
    }
};

Layout.prototype.growRight = function(root, w, h) {
    return {
        used: true,
        x: 0,
        y: 0,
        w: root.w + w,
        h: root.h,
        down: root,
        right: {
            x: root.w,
            y: 0,
            w: w,
            h: root.h
        }
    };
};

Layout.prototype.growDown = function(root, w, h) {
    return {
        used: true,
        x: 0,
        y: 0,
        w: root.w,
        h: root.h + h,
        down: {
            x: 0,
            y: root.h,
            w: root.w,
            h: h
        },
        right: root
    };
};

module.exports = new Layout();<commit_msg>Fix: calcuration of padding and margin<commit_after>var _ = require('underscore');

var Layout = function () {};

Layout.prototype.layout = function(images, options) {
    options = options || {};

    var self = this,
        hpadding,
        vpadding,
        hmargin,
        vmargin,
        root;

    hpadding = options.hpadding || 0;
    vpadding = options.vpadding || 0;
    hmargin = options.hmargin || 0;
    vmargin = options.vmargin || 0;

    _.each(images, function (image) {
        image.w = image.width + hpadding * 2 + hmargin * 2;
        image.h = image.height + vpadding * 2 + vmargin * 2;
    });

    // sort by size
    images.sort(function(a, b) {
        var diff;
        diff = self.compare(Math.max(b.w, b.h), Math.max(a.w, a.h));
        if (diff === 0) {
            diff = self.compare(Math.min(b.w, b.h), Math.min(a.w, a.h));
        }
        if (diff === 0) {
            diff = self.compare(b.h, a.h);
        }
        if (diff === 0) {
            diff = self.compare(b.w, a.w);
        }
        return diff;
    });

    // root node
    root = {
        x: 0,
        y: 0,
        w: images[0].w,
        h: images[0].h
    };

    var layout = function layout(image) {
        var node;
        node = self.findNode(root, image.w, image.h);

        if (node) {
            self.placeImage(image, node, hpadding, vpadding, hmargin, vmargin);
            self.splitNode(node, image.w, image.h);
            return;
        }

        root = self.grow(root, image.w, image.h);
        layout(image);
        return;

    };
    _.each(images, layout);

    return {
        width: root.w,
        height: root.h
    };
};

Layout.prototype.compare = function(a, b) {
    if (a > b) {
        return 1;
    }
    if (b > a) {
        return -1;
    }
    return 0;
};

Layout.prototype.placeImage = function(image, node, hpadding, vpadding, hmargin, vmargin) {
    image.cssx = node.x + hmargin;
    image.cssy = node.y + vmargin;
    image.cssw = image.w - (2 * hpadding) - (2 * hmargin);
    image.cssh = image.h - (2 * vpadding) - (2 * vmargin);
    image.x = image.cssx + hpadding;
    image.y = image.cssy + vpadding;
    return;
};

Layout.prototype.findNode = function(root, w, h) {
    if (root.used) {
        return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
    }
    if ((w <= root.w) && (h <= root.h)) {
        return root;
    }
    return;
};

Layout.prototype.splitNode = function(node, w, h) {
    node.used = true;
    node.down = {
        x: node.x,
        y: node.y + h,
        w: node.w,
        h: node.h - h
    };
    node.right = {
        x: node.x + w,
        y: node.y,
        w: node.w - w,
        h: h
    };
    return node.right;
};

Layout.prototype.grow = function(root, w, h) {
    var canGrowDown,
        canGrowRight,
        shouldGrowDown,
        shouldGrowRight;

    canGrowDown = w <= root.w;
    canGrowRight = h <= root.h;
    shouldGrowRight = canGrowRight && (root.h >= (root.w + w));
    shouldGrowDown = canGrowDown && (root.w >= (root.h + h));

    if (shouldGrowRight) {
        return this.growRight(root, w, h);
    } else if (shouldGrowDown) {
        return this.growDown(root, w, h);
    } else if (canGrowRight) {
        return this.growRight(root, w, h);
    } else if (canGrowDown) {
        return this.growDown(root, w, h);
    } else {
        throw "Can't fit " + w + "x" + h + " block into root " + root.w + "x" + root.h + " - this should not happen if images are pre-sorted correctly";
    }
};

Layout.prototype.growRight = function(root, w, h) {
    return {
        used: true,
        x: 0,
        y: 0,
        w: root.w + w,
        h: root.h,
        down: root,
        right: {
            x: root.w,
            y: 0,
            w: w,
            h: root.h
        }
    };
};

Layout.prototype.growDown = function(root, w, h) {
    return {
        used: true,
        x: 0,
        y: 0,
        w: root.w,
        h: root.h + h,
        down: {
            x: 0,
            y: root.h,
            w: root.w,
            h: h
        },
        right: root
    };
};

module.exports = new Layout();
<|endoftext|>


<commit_before>/*
 * Stage.js
 * Copyright (c) 2015 Ali Shakiba, Piqnt LLC
 * Available under the MIT license
 * @license
 */

var Class = require('./core');
require('./pin');
require('./render');

var create = require('./util/create');

Class.row = function(align) {
  return Class.create().row(align).label('Row');
};

Class.prototype.row = function(align) {
  this.sequence('row', align);
  return this;
};

Class.column = function(align) {
  return Class.create().column(align).label('Row');
};

Class.prototype.column = function(align) {
  this.sequence('column', align);
  return this;
};

Class.sequence = function(type, align) {
  return Class.create().sequence(type, align).label('Sequence');
};

Class.prototype.sequence = function(type, align) {

  this._padding = this._padding || 0;
  this._spacing = this._spacing || 0;

  this.untick(this._layoutTiker);
  this.tick(this._layoutTiker = function() {
    if (this._mo_seq == this._ts_touch) {
      return;
    }
    this._mo_seq = this._ts_touch;

    var alignChildren = (this._mo_seqAlign != this._ts_children);
    this._mo_seqAlign = this._ts_children;

    var width = 0, height = 0;

    var child, next = this.first(true);
    var first = true;
    while (child = next) {
      next = child.next(true);

      child._pin.relativeMatrix();
      var w = child.pin('boxWidth');
      var h = child.pin('boxHeight');

      if (type == 'column') {
        !first && (height += this._spacing);
        child.pin('offsetY') != height && child.pin('offsetY', height);
        width = Math.max(width, w);
        height = height + h;
        alignChildren && child.pin('alignX', align);

      } else if (type == 'row') {
        !first && (width += this._spacing);
        child.pin('offsetX') != width && child.pin('offsetX', width);
        width = width + w;
        height = Math.max(height, h);
        alignChildren && child.pin('alignY', align);
      }
      first = false;
    }
    width += 2 * this._padding;
    height += 2 * this._padding;
    this.pin('width') != width && this.pin('width', width);
    this.pin('height') != height && this.pin('height', height);
  });
  return this;
};

Class.box = function() {
  return Class.create().box().label('Box');
};

Class.prototype.box = function() {
  this._padding = this._padding || 0;

  this.untick(this._layoutTiker);
  this.tick(this._layoutTiker = function() {
    if (this._mo_box == this._ts_touch) {
      return;
    }
    this._mo_box = this._ts_touch;

    var width = 0, height = 0;
    var child, next = this.first(true);
    while (child = next) {
      next = child.next(true);
      child._pin.relativeMatrix();
      var w = child.pin('boxWidth');
      var h = child.pin('boxHeight');
      width = Math.max(width, w);
      height = Math.max(height, h);
    }
    width += 2 * this._padding;
    height += 2 * this._padding;
    this.pin('width') != width && this.pin('width', width);
    this.pin('height') != height && this.pin('height', height);
  });
  return this;
};

// TODO: move padding to pin
Class.prototype.padding = function(pad) {
  this._padding = pad;
  return this;
};

Class.prototype.spacing = function(space) {
  this._spacing = space;
  return this;
};
<commit_msg>+layer<commit_after>/*
 * Stage.js
 * Copyright (c) 2015 Ali Shakiba, Piqnt LLC
 * Available under the MIT license
 * @license
 */

var Class = require('./core');
require('./pin');
require('./render');

var create = require('./util/create');

Class.row = function(align) {
  return Class.create().row(align).label('Row');
};

Class.prototype.row = function(align) {
  this.sequence('row', align);
  return this;
};

Class.column = function(align) {
  return Class.create().column(align).label('Row');
};

Class.prototype.column = function(align) {
  this.sequence('column', align);
  return this;
};

Class.sequence = function(type, align) {
  return Class.create().sequence(type, align).label('Sequence');
};

Class.prototype.sequence = function(type, align) {

  this._padding = this._padding || 0;
  this._spacing = this._spacing || 0;

  this.untick(this._layoutTiker);
  this.tick(this._layoutTiker = function() {
    if (this._mo_seq == this._ts_touch) {
      return;
    }
    this._mo_seq = this._ts_touch;

    var alignChildren = (this._mo_seqAlign != this._ts_children);
    this._mo_seqAlign = this._ts_children;

    var width = 0, height = 0;

    var child, next = this.first(true);
    var first = true;
    while (child = next) {
      next = child.next(true);

      child._pin.relativeMatrix();
      var w = child.pin('boxWidth');
      var h = child.pin('boxHeight');

      if (type == 'column') {
        !first && (height += this._spacing);
        child.pin('offsetY') != height && child.pin('offsetY', height);
        width = Math.max(width, w);
        height = height + h;
        alignChildren && child.pin('alignX', align);

      } else if (type == 'row') {
        !first && (width += this._spacing);
        child.pin('offsetX') != width && child.pin('offsetX', width);
        width = width + w;
        height = Math.max(height, h);
        alignChildren && child.pin('alignY', align);
      }
      first = false;
    }
    width += 2 * this._padding;
    height += 2 * this._padding;
    this.pin('width') != width && this.pin('width', width);
    this.pin('height') != height && this.pin('height', height);
  });
  return this;
};

Class.box = function() {
  return Class.create().box().label('Box');
};

Class.prototype.box = function() {
  this._padding = this._padding || 0;

  this.untick(this._layoutTiker);
  this.tick(this._layoutTiker = function() {
    if (this._mo_box == this._ts_touch) {
      return;
    }
    this._mo_box = this._ts_touch;

    var width = 0, height = 0;
    var child, next = this.first(true);
    while (child = next) {
      next = child.next(true);
      child._pin.relativeMatrix();
      var w = child.pin('boxWidth');
      var h = child.pin('boxHeight');
      width = Math.max(width, w);
      height = Math.max(height, h);
    }
    width += 2 * this._padding;
    height += 2 * this._padding;
    this.pin('width') != width && this.pin('width', width);
    this.pin('height') != height && this.pin('height', height);
  });
  return this;
};

Class.layer = function() {
  return Class.create().layer().label('Layer');
};

Class.prototype.layer = function() {

  this.untick(this._layoutTiker);
  this.tick(this._layoutTiker = function() {
    var parent = this.parent();
    if (parent) {
      var width = parent.pin('width');
      if (this.pin('width') != width) {
        this.pin('width', width);
      }
      var height = parent.pin('height');
      if (this.pin('height') != height) {
        this.pin('height', height);
      }
    }
  }, true);
  return this;
};

// TODO: move padding to pin
Class.prototype.padding = function(pad) {
  this._padding = pad;
  return this;
};

Class.prototype.spacing = function(space) {
  this._spacing = space;
  return this;
};
<|endoftext|>


<commit_before>var W = require('WINSTON'),
    UTIL = require('util'),
    BEMUTIL = require('./util');

init();

for(var i in W) exports[i] = W[i];

Object.keys(W.levels).forEach(function (level) {
  exports['f' + level] = function (msg) {
      var m = UTIL.format.apply(this, arguments);
      W[level](m);
  };
});

exports.setLevel = function(level) {
    return init(level);
}

function init(level) {
    W.cli();
    W.remove(W.transports.Console);
    W.add(W.transports.Console,
        {
            timestamp: function() {
                var d = new Date();

                return UTIL.format(
                    '%s:%s:%s.%s',
                    BEMUTIL.pad(d.getHours(), 2, '0'),
                    BEMUTIL.pad(d.getMinutes(), 2, '0'),
                    BEMUTIL.pad(d.getSeconds(), 2, '0'),
                    BEMUTIL.pad(d.getMilliseconds(), 3, '0'));
            },
            colorize: true,
            level: level
        });

    W.setLevels({
            silly: 0,
            debug: 1,
            verbose: 2,
            info: 3,
            warn: 4,
            error: 5
        });
}
<commit_msg>- fixed issue with logging levels - added helpers to trace methods execution<commit_after>var W = require('WINSTON'),
    UTIL = require('util'),
    BEMUTIL = require('./util');

for(var i in W) exports[i] = W[i];

init();

exports.setLevel = function(level) {
    return init(level);
}

/**
 * Adds tracing before and after executing specified function or specified object's methods
 *
 * @param {hash} options The object with properties:
 * object - pass object the methods of which you want to trace
 * func - pass function you want to trace (the method will return wrapper function in this case)
 * object of func must be specified
 * objectName - this value is used in the traces to identify what object method is being executed (optional)
 * whiteList - in case object was specified only methods in the whiteList will be wrapped (optional)
 * blackList - in case object was specified the methods in blackList hash won't be wrapped (optional)
 * @return {function} The wrapped function in case options.func was specified or nothing otherwise
 */
exports.trace = function(options) {

    return attach(
        options,
        function(f) {
            var err = new Error;
            Error.captureStackTrace(err, arguments.callee);
            exports.fsilly('>>> %s.%s', f.objectName || '?', f.functionName || '?');
        },

        function(f) {
            exports.fsilly('<<< %s.%s', f.objectName || '?', f.functionName || '?');
        }
    );
}

/**
 * @type {Function}
 * The same as trace but uses specified callbacks to do tracing
 *
 * @param {hash} options
 * @param {function} callbackBefore (optional)
 * @param {function} callbackAfter (optional)
 * @return {function} The wrapped function in case options.func was specified or nothing otherwise
 */
var attach = exports.attach = function(options, callbackBefore, callbackAfter) {
    if (options.object) {
        var c = options.object.prototype;
        if (!c) return;

        Object.keys(c)
            .filter(function(prop) {

                if (typeof c[prop] === 'function') {
                    if (options.whiteList) return options.whiteList[prop];

                    if (options.blackList) return !options.blackList[prop];

                    return true;
                }

                return false;
            })
            .map(function(f) {
                c[f] = wrapFunction(
                    {
                        objectName: options.objectName,
                        functionName: f,
                        func: c[f],
                        object: options.object
                    },
                    callbackBefore,
                    callbackAfter);
            });

    } else if (options.func) {
        return wrapFunction(options.func, callbackBefore, callbackAfter);
    }
}

function wrapFunction(options, callbackBefore, callbackAfter) {
    var f = (typeof options === 'function')? otions: options.func;

    return function() {
        callbackBefore && callbackBefore(options);
        var res = f.apply(this, arguments);
        callbackAfter && callbackAfter(options);

        return res;
    }
}

function init(level) {
    W.remove(W.transports.Console);
    W.add(W.transports.Console,
        {
            timestamp: function() {
                var d = new Date();

                return UTIL.format(
                    '%s:%s:%s.%s',
                    BEMUTIL.pad(d.getHours(), 2, '0'),
                    BEMUTIL.pad(d.getMinutes(), 2, '0'),
                    BEMUTIL.pad(d.getSeconds(), 2, '0'),
                    BEMUTIL.pad(d.getMilliseconds(), 3, '0'));
            },
            colorize: true,
            level: level
        });

    W.setLevels({
            silly: 0,
            debug: 1,
            verbose: 2,
            info: 3,
            warn: 4,
            error: 5
        });

    Object.keys(W.levels).forEach(function (level) {
        exports[level] = W[level];
        exports['f' + level] = function (msg) {
            var m = UTIL.format.apply(this, arguments);
            W[level](m);
        };
    });
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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
 *
 *     http://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.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'IDBInternalException',
  extends: 'foam.dao.InternalException',

  // TODO: Which errors are internal (system problems) vs. external
  // (i.e. invalid data for clone, but you can try again with different data)
  properties: [
    'id',
    'error',
    {
      name: 'message',
      expression: function(id, error) {
        return "IndexedDB Error for " + id +
          ( error ? ": " + error.toString() : "" );
      }
    }
  ]
});

/*
TODO:
-verify that multi part keys work properly
*/
foam.CLASS({
  package: 'foam.dao',
  name: 'IDBDAO',
  extends: 'foam.dao.AbstractDAO',

  requires: [
    'foam.dao.FlowControl',
    'foam.dao.ArraySink',
    'foam.dao.IDBInternalException',
    'foam.mlang.predicate.True',
    'foam.mlang.predicate.Eq',
  ],

  imports: [
    'async',
  ],

  constants: {
    /** Global cache of the current transaction reference. Only element 0 is used. */
    __TXN__: [],
  },

  properties: [
    {
      name:  'of',
      required: true
    },
    {
      name:  'name',
      label: 'Store Name',
      expression: function() {
        return this.of.id;
      }
    },
    {
      name: 'indicies',
      factory: function() { return []; }
    },
    {
      /** The promise that holds the open DB. Call this.withDB.then(function(db) { ... }); */
      name: 'withDB',
      factory: function() {
        var self = this;
        return new Promise(function(resolve, reject) {
          var indexedDB = window.indexedDB ||
            window.webkitIndexedDB         ||
            window.mozIndexedDB;

          var request = indexedDB.open("FOAM:" + self.name, 1);

          request.onupgradeneeded = function(e) {
            var store = e.target.result.createObjectStore(self.name);
            for ( var i = 0; i < self.indicies.length; i++ ) {
              store.createIndex(self.indicies[i][0], self.indicies[i][0], { unique: self.indicies[i][1] });
            }
          }

          request.onsuccess = function(e) {
            resolve(e.target.result);
          }

          request.onerror = function (e) {
            reject(self.IDBInternalException.create({ id: 'open', error: e }));
          };
        });
      }
    },
  ],

  methods: [

    function deserialize(json) {
      // TODO: Better serialization, error handling
      return foam.json.parse(foam.json.parseString(json));
    },

    function serialize(obj) {
      // TODO: Better serialization, error handling
      return foam.json.stringify(obj);
    },

    function withStore(mode, fn) {
      if ( mode !== 'readwrite' ) return this.withStore_(mode, fn);

      var self = this;

      if ( ! this.q_ ) {
        var q = [fn];
        this.q_ = q;
        this.async(function() {
          self.withStore_(mode, function(store) {
            // console.log('q length: ', q.length);
            if ( self.q_ == q ) self.q_ = undefined;
            for ( var i = 0 ; i < q.length ; i++ ) q[i](store);
          });
        })();
      } else {
        this.q_.push(fn);
        // Diminishing returns after 10000 per batch
        if ( this.q_.length == 10000 ) this.q_ = undefined;
      }
    },

    function withStore_(mode, fn) {
      var self = this;
      if ( self.__TXN__[0] ) {
        try {
          fn.call(self, self.__TXN__[0]);
          return;
        } catch (x) {
          self.__TXN__[0] = undefined;
        }
      }
      self.withDB.then(function (db) {
        var tx = db.transaction([self.name], mode);
        var os = tx.objectStore(self.name);
        self.__TXN__[0] = os;
        fn.call(self, os);
      });
    },

    function put(value) {
      var self = this;
      return new Promise(function(resolve, reject) {
        self.withStore("readwrite", function(store) {
          var request = store.put(self.serialize(value), value.id);
          request.transaction.addEventListener(
            'complete',
            function(e) {
              self.pub('on','put', value);
              resolve(value);
            });
          request.transaction.addEventListener(
            'error',
            function(e) {
              reject(self.IDBInternalException.create({ id: value.id, error: e }));
            });
        });
      });
    },

    function find(key) {
      var self = this;

      return new Promise(function(resolve, reject) {
        self.withStore("readonly", function(store) {
          var request = store.get(key);
          request.transaction.addEventListener(
            'complete',
            function() {
              if (!request.result) {
                reject(self.ObjectNotFoundException.create({ id: key }));
                return;
              }
              var result = self.deserialize(request.result);
              resolve(result);
            });
          request.onerror = function(e) {
            reject(self.IDBInternalException.create({ id: key, error: e }));
          };
        });
      });
    },

    function remove(obj) {
      var self = this;
      var key = obj.id != undefined ? obj.id : obj;
      return new Promise(function(resolve, reject) {
        self.withStore("readwrite", function(store) {
          var getRequest = store.get(key);
          getRequest.onsuccess = function(e) {
            if (!getRequest.result) {
              // not found? as good as removed!
              resolve();
              return;
            }
            var data = self.deserialize(getRequest.result);
            var delRequest = store.delete(key);
            delRequest.transaction.addEventListener('complete', function(e) {
              self.pub('on','remove', data);
              resolve();
            });

            delRequest.onerror = function(e) {
              reject(self.IDBInternalException.create({ id: key, error: e }));
            };
          };
          getRequest.onerror = function(e) {
            reject(self.IDBInternalException.create({ id: key, error: e }));
          };
        });
      });
    },

    function removeAll(skip, limit, order, predicate) {
      var query = predicate || this.True.create();

      var self = this;

      // If the caller doesn't care to see the objects as they get removed,
      // then just nuke them in one go.
      if ( ! predicate && ! self.hasListeners('on', 'remove') ) {
        return new Promise(function(resolve, reject) {
          self.withStore('readwrite', function(store) {
            var req = store.clear();
            req.onsuccess = function() {
              resolve();
            };
            req.onerror = function(e) {
              reject(self.IDBInternalException.create({ id: 'remove_all', error: e }));
            };
          });
        });
      } else {
        // send items to the sink and remove one by one
        return new Promise(function(resolve, reject) {
          self.withStore('readwrite', function(store) {
            var request = store.openCursor();
            request.onsuccess = function(e) {
              var cursor = e.target.result;
              if (cursor) {
                var value = self.deserialize(cursor.value);
                if (query.f(value)) {
                  var deleteReq = cursor.delete();
                  deleteReq.transaction.addEventListener(
                    'complete',
                    function() {
                      self.pub('on','remove', value);
                    });
                  deleteReq.onerror = function(e) {
                  };
                }
                cursor.continue();
              }
            };
            request.transaction.oncomplete = function() {
              resolve();
            };
            request.onerror = function(e) {
              reject(self.IDBInternalException.create({ id: 'remove_all', error: e }));
            };
          });
        });
      }
    },

    function select(sink, skip, limit, order, predicate) {
      var resultSink = sink || this.ArraySink.create();
      sink = this.decorateSink_(resultSink, skip, limit, order, predicate);

      var fc = this.FlowControl.create();
      var self = this;

      return new Promise(function(resolve, reject) {
        self.withStore("readonly", function(store) {
          var useIndex = predicate &&
            this.Eq.isInstance(predicate) &&
            store.indexNames.contains(predicate.arg1.name);

          var request = useIndex ?
            store.index(predicate.arg1.name).openCursor(IDBKeyRange.only(predicate.arg2.f())) :
            store.openCursor() ;

          request.onsuccess = function(e) {
            var cursor = e.target.result;
            if ( fc.errorEvt ) {
              sink.error && sink.error(fc.errorEvt);
              reject(fc.errorEvt);
              return;
            }

            if ( ! cursor || fc.stopped ) {
              sink.eof && sink.eof();
              resolve(resultSink);
              return;
            }

            var value = self.deserialize(cursor.value);
            sink.put(value, null, fc);
            cursor.continue();
          };
          request.onerror = function(e) {
            sink.error && sink.error(e);
            reject(self.IDBInternalException.create({ id: 'select', error: e }));
          };
        });
      });
    },

    function addIndex(prop) {
      this.indicies.push([prop.name, false]);
      return this;
    }
  ]
});
<commit_msg>IDBDAO.js formatting.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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
 *
 *     http://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.
 */

foam.CLASS({
  package: 'foam.dao',
  name: 'IDBInternalException',
  extends: 'foam.dao.InternalException',

  // TODO: Which errors are internal (system problems) vs. external
  // (i.e. invalid data for clone, but you can try again with different data)
  properties: [
    'id',
    'error',
    {
      name: 'message',
      expression: function(id, error) {
        return "IndexedDB Error for " + id +
          ( error ? ": " + error.toString() : "" );
      }
    }
  ]
});

/*
TODO:
-verify that multi part keys work properly
*/
foam.CLASS({
  package: 'foam.dao',
  name: 'IDBDAO',
  extends: 'foam.dao.AbstractDAO',

  requires: [
    'foam.dao.FlowControl',
    'foam.dao.ArraySink',
    'foam.dao.IDBInternalException',
    'foam.mlang.predicate.True',
    'foam.mlang.predicate.Eq'
  ],

  imports: [
    'async'
  ],

  constants: {
    /** Global cache of the current transaction reference. Only element 0 is used. */
    __TXN__: []
  },

  properties: [
    {
      name:  'of',
      required: true
    },
    {
      name:  'name',
      label: 'Store Name',
      expression: function() {
        return this.of.id;
      }
    },
    {
      name: 'indicies',
      factory: function() { return []; }
    },
    {
      /** The promise that holds the open DB. Call this.withDB.then(function(db) { ... }); */
      name: 'withDB',
      factory: function() {
        var self = this;
        return new Promise(function(resolve, reject) {
          var indexedDB = window.indexedDB ||
            window.webkitIndexedDB         ||
            window.mozIndexedDB;

          var request = indexedDB.open("FOAM:" + self.name, 1);

          request.onupgradeneeded = function(e) {
            var store = e.target.result.createObjectStore(self.name);
            for ( var i = 0; i < self.indicies.length; i++ ) {
              store.createIndex(
                  self.indicies[i][0],
                  self.indicies[i][0],
                  { unique: self.indicies[i][1] });
            }
          }

          request.onsuccess = function(e) {
            resolve(e.target.result);
          }

          request.onerror = function (e) {
            reject(self.IDBInternalException.create({ id: 'open', error: e }));
          };
        });
      }
    },
  ],

  methods: [
    function deserialize(json) {
      // TODO: Better serialization, error handling
      return foam.json.parse(foam.json.parseString(json));
    },

    function serialize(obj) {
      // TODO: Better serialization, error handling
      return foam.json.stringify(obj);
    },

    function withStore(mode, fn) {
      if ( mode !== 'readwrite' ) return this.withStore_(mode, fn);

      var self = this;

      if ( ! this.q_ ) {
        var q = [fn];
        this.q_ = q;
        this.async(function() {
          self.withStore_(mode, function(store) {
            // console.log('q length: ', q.length);
            if ( self.q_ == q ) self.q_ = undefined;
            for ( var i = 0 ; i < q.length ; i++ ) q[i](store);
          });
        })();
      } else {
        this.q_.push(fn);
        // Diminishing returns after 10000 per batch
        if ( this.q_.length == 10000 ) this.q_ = undefined;
      }
    },

    function withStore_(mode, fn) {
      var self = this;
      if ( self.__TXN__[0] ) {
        try {
          fn.call(self, self.__TXN__[0]);
          return;
        } catch (x) {
          self.__TXN__[0] = undefined;
        }
      }
      self.withDB.then(function (db) {
        var tx = db.transaction([self.name], mode);
        var os = tx.objectStore(self.name);
        self.__TXN__[0] = os;
        fn.call(self, os);
      });
    },

    function put(value) {
      var self = this;
      return new Promise(function(resolve, reject) {
        self.withStore("readwrite", function(store) {
          var request = store.put(self.serialize(value), value.id);
          request.transaction.addEventListener(
            'complete',
            function(e) {
              self.pub('on','put', value);
              resolve(value);
            });
          request.transaction.addEventListener(
            'error',
            function(e) {
              reject(self.IDBInternalException.create({ id: value.id, error: e }));
            });
        });
      });
    },

    function find(key) {
      var self = this;

      return new Promise(function(resolve, reject) {
        self.withStore("readonly", function(store) {
          var request = store.get(key);
          request.transaction.addEventListener(
            'complete',
            function() {
              if (!request.result) {
                reject(self.ObjectNotFoundException.create({ id: key }));
                return;
              }
              var result = self.deserialize(request.result);
              resolve(result);
            });
          request.onerror = function(e) {
            reject(self.IDBInternalException.create({ id: key, error: e }));
          };
        });
      });
    },

    function remove(obj) {
      var self = this;
      var key = obj.id != undefined ? obj.id : obj;
      return new Promise(function(resolve, reject) {
        self.withStore("readwrite", function(store) {
          var getRequest = store.get(key);
          getRequest.onsuccess = function(e) {
            if (!getRequest.result) {
              // not found? as good as removed!
              resolve();
              return;
            }
            var data = self.deserialize(getRequest.result);
            var delRequest = store.delete(key);
            delRequest.transaction.addEventListener('complete', function(e) {
              self.pub('on','remove', data);
              resolve();
            });

            delRequest.onerror = function(e) {
              reject(self.IDBInternalException.create({ id: key, error: e }));
            };
          };
          getRequest.onerror = function(e) {
            reject(self.IDBInternalException.create({ id: key, error: e }));
          };
        });
      });
    },

    function removeAll(skip, limit, order, predicate) {
      var query = predicate || this.True.create();

      var self = this;

      // If the caller doesn't care to see the objects as they get removed,
      // then just nuke them in one go.
      if ( ! predicate && ! self.hasListeners('on', 'remove') ) {
        return new Promise(function(resolve, reject) {
          self.withStore('readwrite', function(store) {
            var req = store.clear();
            req.onsuccess = function() {
              resolve();
            };
            req.onerror = function(e) {
              reject(self.IDBInternalException.create({ id: 'remove_all', error: e }));
            };
          });
        });
      } else {
        // send items to the sink and remove one by one
        return new Promise(function(resolve, reject) {
          self.withStore('readwrite', function(store) {
            var request = store.openCursor();
            request.onsuccess = function(e) {
              var cursor = e.target.result;
              if (cursor) {
                var value = self.deserialize(cursor.value);
                if (query.f(value)) {
                  var deleteReq = cursor.delete();
                  deleteReq.transaction.addEventListener(
                    'complete',
                    function() {
                      self.pub('on','remove', value);
                    });
                  deleteReq.onerror = function(e) {
                  };
                }
                cursor.continue();
              }
            };
            request.transaction.oncomplete = function() {
              resolve();
            };
            request.onerror = function(e) {
              reject(self.IDBInternalException.create({ id: 'remove_all', error: e }));
            };
          });
        });
      }
    },

    function select(sink, skip, limit, order, predicate) {
      var resultSink = sink || this.ArraySink.create();
      sink = this.decorateSink_(resultSink, skip, limit, order, predicate);

      var fc = this.FlowControl.create();
      var self = this;

      return new Promise(function(resolve, reject) {
        self.withStore("readonly", function(store) {
          var useIndex = predicate &&
            this.Eq.isInstance(predicate) &&
            store.indexNames.contains(predicate.arg1.name);

          var request = useIndex ?
            store.index(predicate.arg1.name).openCursor(IDBKeyRange.only(predicate.arg2.f())) :
            store.openCursor() ;

          request.onsuccess = function(e) {
            var cursor = e.target.result;
            if ( fc.errorEvt ) {
              sink.error && sink.error(fc.errorEvt);
              reject(fc.errorEvt);
              return;
            }

            if ( ! cursor || fc.stopped ) {
              sink.eof && sink.eof();
              resolve(resultSink);
              return;
            }

            var value = self.deserialize(cursor.value);
            sink.put(value, null, fc);
            cursor.continue();
          };
          request.onerror = function(e) {
            sink.error && sink.error(e);
            reject(self.IDBInternalException.create({ id: 'select', error: e }));
          };
        });
      });
    },

    function addIndex(prop) {
      this.indicies.push([prop.name, false]);
      return this;
    }
  ]
});
<|endoftext|>


<commit_before>/***
*   Copyright 2014 Sean Wragg <seanwragg@gmail.com>
*
*   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
*
*	   http://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.
*/

var Database = require('./Database.js'),
	Q = require('q');

var Player = {
	locate: function(player) {
		var deferred = Q.defer();

		if (!player.name || !player.lid) {
			deferred.reject({
				status: 400
			});

			return deferred.promise;
		}

		var query = Database.format(
			'SELECT pid, uid FROM wol_players WHERE name = ? AND lid = ?', [player.name, player.lid]
		);

		Database.query(query, function(err, result) {
			if (result.length < 1) {
				if (!!player.create) {
					var ctime = Math.floor(new Date().getTime() / 1000);
					var insert = {
						name: player.name,
						lid: player.lid,
						ctime: ctime,
						mtime: ctime
					};

					if (player.uid) {
						insert.uid = player.uid;
					}

					Database.insert('wol_players', insert, function(pid) {
						player.pid = pid;
						deferred.resolve(player);
					});
				} else {
					deferred.reject({
						status: 404
					});
				}
			} else {
				player.pid = result[0].pid;
				player.uid = result[0].uid;
				deferred.resolve(player);
			}
		});

		return deferred.promise;
	}
};

module.exports = Player;<commit_msg>adding flag to determine whether the player already existed<commit_after>/***
*   Copyright 2014 Sean Wragg <seanwragg@gmail.com>
*
*   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
*
*	   http://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.
*/

var Database = require('./Database.js'),
	Q = require('q');

var Player = {
	locate: function(player) {
		var deferred = Q.defer();

		if (!player.name || !player.lid) {
			deferred.reject({
				status: 400
			});

			return deferred.promise;
		}

		var query = Database.format(
			'SELECT pid, uid FROM wol_players WHERE name = ? AND lid = ?', [player.name, player.lid]
		);

		Database.query(query, function(err, result) {
			if (result.length < 1) {
				if (!!player.create) {
					var ctime = Math.floor(new Date().getTime() / 1000);
					var insert = {
						name: player.name,
						lid: player.lid,
						ctime: ctime,
						mtime: ctime
					};

					if (player.uid) {
						insert.uid = player.uid;
					}

					Database.insert('wol_players', insert, function(pid) {
						player.pid = pid;
						deferred.resolve(player);
					});
				} else {
					deferred.reject({
						status: 404
					});
				}
			} else {
				player.pid = result[0].pid;
				player.uid = result[0].uid;
				player.exists = true;
				deferred.resolve(player);
			}
		});

		return deferred.promise;
	}
};

module.exports = Player;<|endoftext|>


<commit_before>/**
 * Client wraps the "websockets/ws" library providing JSON RPC 2.0 support on top.
 * @module Client
 */

"use strict"

import assertArgs from "assert-args"
import EventEmitter from "events"
import WebSocket from "ws"

export default class Client extends EventEmitter
{
    /**
     * Instantiate a Client client.
     * @constructor
     * @param {String} address - url to a websocket server
     * @param {Object} options - ws options object with reconnect parameters
     * @return {Client}
     */
    constructor(address = "ws://localhost:8080/rpc/1.0",
    {
        autoconnect = true,
        reconnect = true,
        reconnect_interval = 1000,
        max_reconnects = 5
    } = {})
    {
        super()

        this.queue = {}
        this.rpc_id = 0

        this.autoconnect = autoconnect
        this.ready = false
        this.reconnect = reconnect
        this.reconnect_interval = reconnect_interval
        this.max_reconnects = max_reconnects
        this.current_reconnects = 0

        if (this.autoconnect)
            this._connect(address, arguments[1])
    }

    /**
     * Calls a registered RPC method on server.
     * @method
     * @param {String} method - RPC method name
     * @param {Object|Array} params - optional method parameters
     * @return {Promise}
     */
    call(method, params)
    {
        assertArgs(arguments, {
            "method": "string",
            "[params]": ["object", Array]
        })

        return new Promise((resolve, reject) =>
        {
            if (!this.ready)
                return reject(new Error("socket not ready"))

            const rpc_id = ++this.rpc_id

            const message = {
                jsonrpc: "2.0",
                method: method,
                params: params || null,
                id: rpc_id
            }

            this.socket.send(JSON.stringify(message), (error) =>
            {
                if (error)
                    return reject(error)

                this.queue[rpc_id] = [resolve, reject]
            })
        })
    }

    /**
     * Sends a JSON-RPC 2.0 notification to server.
     * @method
     * @param {String} method - RPC method name
     * @param {Object} params - optional method parameters
     * @return {Promise}
     */
    notify(method, params)
    {
        assertArgs(arguments, {
            "method": "string",
            "[params]": ["object", Array]
        })

        return new Promise((resolve, reject) =>
        {
            if (!this.ready)
                return reject(new Error("socket not ready"))

            const message = {
                jsonrpc: "2.0",
                method: method,
                params: params || null
            }

            this.socket.send(JSON.stringify(message), (error) =>
            {
                if (error)
                    return reject(error)

                resolve()
            })
        })
    }

    /**
     * Subscribes for a defined event.
     * @method
     * @param {String} event - event name
     * @return {Undefined}
     * @throws {Error}
     */
    async subscribe(event)
    {
        assertArgs(arguments, {
            event: "string"
        })

        const result = await this.call("rpc.on", [event])

        if (result[event] !== "ok")
            throw new Error("Failed subscribing to an event with: " + result[event])
    }

    /**
     * Unsubscribes for a defined event.
     * @method
     * @param {String} event - event name
     * @return {Undefined}
     * @throws {Error}
     */
    async unsubscribe(event)
    {
        assertArgs(arguments, {
            event: "string"
        })

        const result = await this.call("rpc.off", [event])

        if (result[event] !== "ok")
            throw new Error("Failed unsubscribing from an event with: " + result)
    }

    /**
     * Closes a WebSocket connection gracefully.
     * @method
     * @param {Number} code - socket close code
     * @param {String} data - optional data to be sent before closing
     * @return {Undefined}
     */
    close(code, data)
    {
        this.socket.close(code || 1000, data)
    }

    /**
     * Connection/Message handler.
     * @method
     * @private
     * @param {String} address - WebSocket API address
     * @param {Object} options - ws options object
     * @return {Undefined}
     */
    _connect(address, options)
    {
        this.socket = new WebSocket(address, options)

        this.socket.on("open", () =>
        {
            this.ready = true
            this.emit("open")
            this.current_reconnects = 0
        })

        this.socket.on("message", (message) =>
        {
            try { message = JSON.parse(message) }

            catch (error) { return }

            // check if any listeners are attached and forward event
            if (message.notification && this.listeners(message.notification).length)
            {
                if (!message.params.length)
                    return this.emit(message.notification)

                const args = [message.notification]

                // using for-loop instead of unshift/spread because performance is better
                for (let i = 0 ;i < message.params.length ;i++)
                    args.push(message.params[i])

                return this.emit.apply(this, args)
            }

            if (!this.queue[message.id])
                return

            if (message.error)
                this.queue[message.id][1](message.error)
            else
                this.queue[message.id][0](message.result)

            this.queue[message.id] = null
        })

        this.socket.on("error", (error) => this.emit("error", error))

        this.socket.on("close", (code, message) =>
        {
            this.ready = false
            this.emit("close", code, message)

            if (code === 1000)
                return

            this.current_reconnects++

            if (this.reconnect && (this.max_reconnects > this.current_reconnects) ||
                    this.max_reconnects === 0)
                setTimeout(() => this._connect(address, options), this.reconnect_interval)
        })
    }
}
<commit_msg>minor style fix<commit_after>/**
 * Client wraps the "websockets/ws" library providing JSON RPC 2.0 support on top.
 * @module Client
 */

"use strict"

import assertArgs from "assert-args"
import EventEmitter from "events"
import WebSocket from "ws"

export default class Client extends EventEmitter
{
    /**
     * Instantiate a Client client.
     * @constructor
     * @param {String} address - url to a websocket server
     * @param {Object} options - ws options object with reconnect parameters
     * @return {Client}
     */
    constructor(address = "ws://localhost:8080/rpc/1.0", {
        autoconnect = true,
        reconnect = true,
        reconnect_interval = 1000,
        max_reconnects = 5
    } = {})
    {
        super()

        this.queue = {}
        this.rpc_id = 0

        this.autoconnect = autoconnect
        this.ready = false
        this.reconnect = reconnect
        this.reconnect_interval = reconnect_interval
        this.max_reconnects = max_reconnects
        this.current_reconnects = 0

        if (this.autoconnect)
            this._connect(address, arguments[1])
    }

    /**
     * Calls a registered RPC method on server.
     * @method
     * @param {String} method - RPC method name
     * @param {Object|Array} params - optional method parameters
     * @return {Promise}
     */
    call(method, params)
    {
        assertArgs(arguments, {
            "method": "string",
            "[params]": ["object", Array]
        })

        return new Promise((resolve, reject) =>
        {
            if (!this.ready)
                return reject(new Error("socket not ready"))

            const rpc_id = ++this.rpc_id

            const message = {
                jsonrpc: "2.0",
                method: method,
                params: params || null,
                id: rpc_id
            }

            this.socket.send(JSON.stringify(message), (error) =>
            {
                if (error)
                    return reject(error)

                this.queue[rpc_id] = [resolve, reject]
            })
        })
    }

    /**
     * Sends a JSON-RPC 2.0 notification to server.
     * @method
     * @param {String} method - RPC method name
     * @param {Object} params - optional method parameters
     * @return {Promise}
     */
    notify(method, params)
    {
        assertArgs(arguments, {
            "method": "string",
            "[params]": ["object", Array]
        })

        return new Promise((resolve, reject) =>
        {
            if (!this.ready)
                return reject(new Error("socket not ready"))

            const message = {
                jsonrpc: "2.0",
                method: method,
                params: params || null
            }

            this.socket.send(JSON.stringify(message), (error) =>
            {
                if (error)
                    return reject(error)

                resolve()
            })
        })
    }

    /**
     * Subscribes for a defined event.
     * @method
     * @param {String} event - event name
     * @return {Undefined}
     * @throws {Error}
     */
    async subscribe(event)
    {
        assertArgs(arguments, {
            event: "string"
        })

        const result = await this.call("rpc.on", [event])

        if (result[event] !== "ok")
            throw new Error("Failed subscribing to an event with: " + result[event])
    }

    /**
     * Unsubscribes for a defined event.
     * @method
     * @param {String} event - event name
     * @return {Undefined}
     * @throws {Error}
     */
    async unsubscribe(event)
    {
        assertArgs(arguments, {
            event: "string"
        })

        const result = await this.call("rpc.off", [event])

        if (result[event] !== "ok")
            throw new Error("Failed unsubscribing from an event with: " + result)
    }

    /**
     * Closes a WebSocket connection gracefully.
     * @method
     * @param {Number} code - socket close code
     * @param {String} data - optional data to be sent before closing
     * @return {Undefined}
     */
    close(code, data)
    {
        this.socket.close(code || 1000, data)
    }

    /**
     * Connection/Message handler.
     * @method
     * @private
     * @param {String} address - WebSocket API address
     * @param {Object} options - ws options object
     * @return {Undefined}
     */
    _connect(address, options)
    {
        this.socket = new WebSocket(address, options)

        this.socket.on("open", () =>
        {
            this.ready = true
            this.emit("open")
            this.current_reconnects = 0
        })

        this.socket.on("message", (message) =>
        {
            try { message = JSON.parse(message) }

            catch (error) { return }

            // check if any listeners are attached and forward event
            if (message.notification && this.listeners(message.notification).length)
            {
                if (!message.params.length)
                    return this.emit(message.notification)

                const args = [message.notification]

                // using for-loop instead of unshift/spread because performance is better
                for (let i = 0 ;i < message.params.length ;i++)
                    args.push(message.params[i])

                return this.emit.apply(this, args)
            }

            if (!this.queue[message.id])
                return

            if (message.error)
                this.queue[message.id][1](message.error)
            else
                this.queue[message.id][0](message.result)

            this.queue[message.id] = null
        })

        this.socket.on("error", (error) => this.emit("error", error))

        this.socket.on("close", (code, message) =>
        {
            this.ready = false
            this.emit("close", code, message)

            if (code === 1000)
                return

            this.current_reconnects++

            if (this.reconnect && (this.max_reconnects > this.current_reconnects) ||
                    this.max_reconnects === 0)
                setTimeout(() => this._connect(address, options), this.reconnect_interval)
        })
    }
}
<|endoftext|>


<commit_before>var _ = require('underscore'),
    combine = require('./jsCombine'),
    config = require('./config'),
    fs = require('fs'),
    fu = require('./fileUtil'),
    plugin = require('./plugin'),
    watch = require('./watcher.js'),
    growl = require('growl');

function logError(err) {
    // TODO : Move this to the executable
    console.error(err.stack || err.message);
    growl.notify(err.message, { title: 'Lumbar error' });
}

var configCache;
function loadConfig(path) {
  try {
    configCache = {};
    config.load(path);
  } catch (err) {
    // Do not die a horrible death if this is just a syntax error
    logError(err);
  }
}

exports.plugin = plugin.plugin;
exports.init = function(configFile, options) {
    var outdir = options.outdir,
        minimize = options.minimize,
        plugins = plugin.create(options);

    function initPlatforms() {
        config.platformList().forEach(function(platform) {
            if (!platform) {
                return;
            }
            try {
                console.log("mkdir:\t\033[90mmaking directory\033[0m " + outdir + '/' + platform);
                fs.mkdirSync(outdir + '/' + platform, 0755);
            } catch (err) {
                if (err.code !== 'EEXIST') {
                    throw err;
                }
            }
        });
    }

    function buildPlatform(context, callback) {
      context.combined = config.combineModules(context.package);

      var modules = config.moduleList(context.package);
      modules.forEach(function(module) {
        context = _.clone(context);
        context.module = module;
        buildModule(context, callback)
      });

      if (context.combined) {
        combineResources(context, context.package, function(err, data) {
          if (data) {
            data.package = context.package;
          }
          callback(err, data);
        });
      }
    }
    function buildModule(context, callback) {
      context.platformPath = (context.platform ? context.platform + '/' : '');
      context.module = config.module(context.module);
      context.options = options;
      context.config = config;
      context.configCache = configCache;
      context.fileCache = context.fileCache || {};
      context.moduleCache = {};

      var fileList = config.fileList(plugins, context);

      var resources = fileList.map(function(resource) {
        context.resource = resource;
        return plugins.resource(context);
      }).filter(function(resource) { return resource; });

      context.moduleResources = resources;
      plugins.module(context);

      if (!context.combined) {
        context.resources = context.moduleResources;
        combineResources(context, context.module.name, function(err, data) {
          if (data) {
            data.package = context.package;
            data.module = context.module.name;
          }
          callback(err, data);
        });
      } else {
        context.resources = context.resources || [];
        context.resources.push.apply(context.resources, context.moduleResources);
      }
    }
    function combineResources(context, name, callback) {
      var fileName = outdir + '/' + context.platformPath + name + '.js';

      context.fileName = fileName;
      plugins.file(context);

      combine.combine(context.resources, fileName, options.minimize, function(err, data) {
        if (err) {
          fs.unlink(fileName);
        }

        context.fileCache = undefined;

        if (data) {
          data.platform = context.platform;
        }
        callback(err, data)
      });
    }

    loadConfig(configFile);
    initPlatforms();

    return {
        use: function(plugin) {
          plugins.use(plugin);
        },
        build: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList();
            packages.forEach(function(package) {
              var platforms = config.platformList(package);
              platforms.forEach(function(platform) {
                buildPlatform({package: package, platform: platform}, callback);
              });
            });
        },
        watch: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList(),
                self = this;

            // Watch for changes in the config file
            // WARN : This isn't really safe for multiple executions, this is a concious design decision
            watch.watchFile(configFile, [], function() {
                loadConfig(configFile);

                // Cleanup what we can, breaking things along the way
                watch.unwatchAll();
                fu.resetCache();

                // Give it a chance to cleanup before start recreating. Attempting to avoid the too many files error.
                setTimeout(function() {
                  initPlatforms();
                  self.watch(packageName, callback);
                }, 1000);
            });

            // Watch the individual components
            packages.forEach(function(name) {
                self.build(name, function(err, status) {
                    if (err) {
                        logError(err);
                        return;
                    }

                    callback(err, status);

                    var context = {package: status.package, platform: status.platform, module: status.module};
                    watch.watchFile("foo" + status.fileName + "foo", status.input, function() {
                      if (context.module) {
                        buildModule(_.clone(context), callback);
                      } else {
                        buildPlatform(_.clone(context), callback);
                      }
                    });
                });
            });
        },
        unwatch: function() {
          watch.unwatchAll();
        }
    };
};
<commit_msg>Fix fileCache lifetime<commit_after>var _ = require('underscore'),
    combine = require('./jsCombine'),
    config = require('./config'),
    fs = require('fs'),
    fu = require('./fileUtil'),
    plugin = require('./plugin'),
    watch = require('./watcher.js'),
    growl = require('growl');

function logError(err) {
    // TODO : Move this to the executable
    console.error(err.stack || err.message);
    growl.notify(err.message, { title: 'Lumbar error' });
}

var configCache;
function loadConfig(path) {
  try {
    configCache = {};
    config.load(path);
  } catch (err) {
    // Do not die a horrible death if this is just a syntax error
    logError(err);
  }
}

exports.plugin = plugin.plugin;
exports.init = function(configFile, options) {
    var outdir = options.outdir,
        minimize = options.minimize,
        plugins = plugin.create(options);

    function initPlatforms() {
        config.platformList().forEach(function(platform) {
            if (!platform) {
                return;
            }
            try {
                console.log("mkdir:\t\033[90mmaking directory\033[0m " + outdir + '/' + platform);
                fs.mkdirSync(outdir + '/' + platform, 0755);
            } catch (err) {
                if (err.code !== 'EEXIST') {
                    throw err;
                }
            }
        });
    }

    function buildPlatform(context, callback) {
      context.combined = config.combineModules(context.package);
      context.fileCache = context.combined ? {} : undefined;

      var modules = config.moduleList(context.package);
      modules.forEach(function(module) {
        context = _.clone(context);
        context.module = module;
        buildModule(context, callback)
      });

      if (context.combined) {
        combineResources(context, context.package, function(err, data) {
          if (data) {
            data.package = context.package;
          }
          callback(err, data);
        });
      }
    }
    function buildModule(context, callback) {
      context.platformPath = (context.platform ? context.platform + '/' : '');
      context.module = config.module(context.module);
      context.options = options;
      context.config = config;
      context.configCache = configCache;
      context.fileCache = context.combined ? context.fileCache : {};
      context.moduleCache = {};

      var fileList = config.fileList(plugins, context);

      var resources = fileList.map(function(resource) {
        context.resource = resource;
        return plugins.resource(context);
      }).filter(function(resource) { return resource; });

      context.moduleResources = resources;
      plugins.module(context);

      if (!context.combined) {
        context.resources = context.moduleResources;
        combineResources(context, context.module.name, function(err, data) {
          if (data) {
            data.package = context.package;
            data.module = context.module.name;
          }
          callback(err, data);
        });
      } else {
        context.resources = context.resources || [];
        context.resources.push.apply(context.resources, context.moduleResources);
      }
    }
    function combineResources(context, name, callback) {
      var fileName = outdir + '/' + context.platformPath + name + '.js';

      context.fileName = fileName;
      plugins.file(context);

      combine.combine(context.resources, fileName, options.minimize, function(err, data) {
        if (err) {
          fs.unlink(fileName);
        }

        context.fileCache = undefined;

        if (data) {
          data.platform = context.platform;
        }
        callback(err, data)
      });
    }

    loadConfig(configFile);
    initPlatforms();

    return {
        use: function(plugin) {
          plugins.use(plugin);
        },
        build: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList();
            packages.forEach(function(package) {
              var platforms = config.platformList(package);
              platforms.forEach(function(platform) {
                buildPlatform({package: package, platform: platform}, callback);
              });
            });
        },
        watch: function(packageName, callback) {
            var packages = packageName ? [packageName] : config.packageList(),
                self = this;

            // Watch for changes in the config file
            // WARN : This isn't really safe for multiple executions, this is a concious design decision
            watch.watchFile(configFile, [], function() {
                loadConfig(configFile);

                // Cleanup what we can, breaking things along the way
                watch.unwatchAll();
                fu.resetCache();

                // Give it a chance to cleanup before start recreating. Attempting to avoid the too many files error.
                setTimeout(function() {
                  initPlatforms();
                  self.watch(packageName, callback);
                }, 1000);
            });

            // Watch the individual components
            packages.forEach(function(name) {
                self.build(name, function(err, status) {
                    if (err) {
                        logError(err);
                        return;
                    }

                    callback(err, status);

                    var context = {package: status.package, platform: status.platform, module: status.module};
                    watch.watchFile("foo" + status.fileName + "foo", status.input, function() {
                      if (context.module) {
                        buildModule(_.clone(context), callback);
                      } else {
                        buildPlatform(_.clone(context), callback);
                      }
                    });
                });
            });
        },
        unwatch: function() {
          watch.unwatchAll();
        }
    };
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Csernik Flaviu Andrei
 *
 * See the file LICENSE.txt for copying permission.
 *
 */

"use strict";

window.AudioContext = window.AudioContext || window.webkitAudioContext;

function PropertyNotInitialized(obj, propName) {
  this.property = propName;
  this.obj      = obj;
}

PropertyNotInitialized.prototype.toString = function () {
  return this.obj + " : " + this.property + " not initialized.";
};

function isInteger(n) {
   return typeof n === "number" && Math.floor(n) == n;
}

function checkNat(callerName, n) {

  if (!isInteger) {
    throw new TypeError("downsampleFactor accepts an integer but" +
                        "given type is " + typeof value);
  }

  if (value <= 0) {
    throw new RangeError("downsampleFactor must be positive." +
                         "given number is not: " + value);
  }

}
<commit_msg>Simplify 'checkNat'.<commit_after>/*
 * Copyright (c) 2013 Csernik Flaviu Andrei
 *
 * See the file LICENSE.txt for copying permission.
 *
 */

"use strict";

window.AudioContext = window.AudioContext || window.webkitAudioContext;

function PropertyNotInitialized(obj, propName) {
  this.property = propName;
  this.obj      = obj;
}

PropertyNotInitialized.prototype.toString = function () {
  return this.obj + " : " + this.property + " not initialized.";
};

function isInteger(n) {
   return typeof n === "number" && Math.floor(n) == n;
}

function checkNat(callerName, n) {

  if (!isInteger || value < 1) {
    throw new TypeError("downsampleFactor must be a natural number." +
                         "given value is not: " + value);
  }

}
<|endoftext|>