<commit_before>import path from "path";
import { createHash } from "crypto";
import { types as t } from "@marko/babel-types";
import { getLoc, getTemplateId } from "@marko/babel-utils";
import traverse, { visitors } from "@babel/traverse";
import { buildLookup } from "../taglib";
import { parseMarko } from "./parser";
import { visitor as migrate } from "./plugins/migrate";
import { visitor as transform } from "./plugins/transform";
import markoModules from "../../modules";
import { MarkoFile } from "./file";

const SOURCE_FILES = new WeakMap();

export default (api, markoOpts) => {
  api.assertVersion(7);
  let translator = markoOpts.translator;

  if (typeof translator === "string") {
    try {
      translator = markoModules.require(translator);
    } catch (err) {
      try {
        translator = markoModules.require(`@marko/translator-${translator}`);
      } catch {
        try {
          translator = markoModules.require(`marko-translator-${translator}`);
        } catch {
          throw err;
        }
      }
    }
  }

  markoOpts.translator = translator;
  markoOpts.output = markoOpts.output || "html";

  if (markoOpts.optimize === undefined) {
    api.cache.using(shouldOptimize);
    markoOpts.optimize = shouldOptimize();
  }

  if (!translator || !translator.translate) {
    throw new Error(
      "@marko/compiler: translator must provide a translate visitor object"
    );
  }

  return {
    name: "marko",
    parserOverride(code, jsParseOptions) {
      const file = getMarkoFile(code, jsParseOptions, markoOpts);
      const finalAst = t.cloneDeep(file.ast);
      SOURCE_FILES.set(finalAst, file);
      return finalAst;
    },
    pre(file) {
      const { ast, metadata } = file;
      const sourceFile = SOURCE_FILES.get(ast);
      metadata.marko = shallowClone(sourceFile.metadata.marko);

      if (markoOpts._translate !== false) {
        const { buildCodeFrameError } = file;
        const { buildError } = file.hub;
        file.buildCodeFrameError = MarkoFile.prototype.buildCodeFrameError;
        file.hub.buildError = file.buildCodeFrameError.bind(file);
        file.markoOpts = markoOpts;
        file.___taglibLookup = sourceFile.___taglibLookup;
        file.___getMarkoFile = getMarkoFile;
        traverse(ast, translator.translate, file.scope, {});
        file.buildCodeFrameError = buildCodeFrameError;
        file.hub.buildError = buildError;
        file.markoOpts = file.___taglibLookup = file.___getMarkoFile = undefined;
      }

      metadata.marko.watchFiles = metadata.marko.watchFiles.filter(unique);
    }
  };
};

export function getMarkoFile(code, jsParseOptions, markoOpts) {
  let compileCache = markoOpts.cache.get(markoOpts.translator);

  if (!compileCache) {
    markoOpts.cache.set(markoOpts.translator, (compileCache = new Map()));
  }

  const filename = jsParseOptions.sourceFileName;
  const id = getTemplateId(markoOpts.optimize, filename);
  const contentHash = createHash("MD5")
    .update(code)
    .digest("hex");
  const cacheKey = createHash("MD5")
    .update(id)
    .update(markoOpts.migrate ? "\0migrate" : "")
    .digest("hex");

  let cached = compileCache.get(cacheKey);

  if (cached) {
    if (cached.contentHash !== contentHash) {
      // File content changed, invalidate the cache.
      cached = undefined;
    } else {
      for (const watchFile of cached.file.metadata.marko.watchFiles) {
        let mtime = Infinity;
        try {
          mtime = markoOpts.fileSystem.statSync(watchFile).mtime;
          // eslint-disable-next-line no-empty
        } catch {}

        if (mtime > cached.time) {
          // Some dependency changed, invalidate the cache.
          cached = undefined;
          break;
        }
      }
    }
  }

  if (cached) {
    return cached.file;
  }

  const taglibLookup = buildLookup(
    path.dirname(filename),
    markoOpts.translator
  );

  const file = new MarkoFile(jsParseOptions, {
    code,
    ast: {
      type: "File",
      program: {
        type: "Program",
        sourceType: "module",
        body: [],
        directives: []
      }
    }
  });

  const meta = (file.metadata.marko = {
    id,
    macros: {},
    deps: [],
    tags: [],
    watchFiles: []
  });

  const rootMigrators = [migrate];
  const rootTransformers = [transform];

  file.markoOpts = markoOpts;
  file.___taglibLookup = taglibLookup;
  file.___getMarkoFile = getMarkoFile;

  file.ast.start = file.ast.program.start = 0;
  file.ast.end = file.ast.program.end = code.length - 1;
  file.ast.loc = file.ast.program.loc = {
    start: { line: 0, column: 0 },
    end: getLoc(file, file.ast.end)
  };

  parseMarko(file);
  file.path.scope.crawl(); // Initialize bindings.

  for (const id in taglibLookup.taglibsById) {
    const { migratorPath } = taglibLookup.taglibsById[id];
    if (migratorPath) {
      const mod = markoModules.require(migratorPath);
      meta.watchFiles.push(migratorPath);
      rootMigrators.push(mod.default || mod);
    }
  }

  for (const { path: transformerPath } of taglibLookup.merged.transformers) {
    const mod = markoModules.require(transformerPath);
    meta.watchFiles.push(transformerPath);
    rootTransformers.push(mod.default || mod);
  }

  traverse(file.ast, mergeVisitors(rootMigrators), file.scope, {});
  traverse(file.ast, mergeVisitors(rootTransformers), file.scope, {});

  for (const taglibId in taglibLookup.taglibsById) {
    const { filePath } = taglibLookup.taglibsById[taglibId];

    if (
      filePath[filePath.length - 5] === "." &&
      filePath.endsWith("marko.json")
    ) {
      meta.watchFiles.push(filePath);
    }
  }

  if (markoOpts.translator.analyze) {
    traverse(file.ast, markoOpts.translator.analyze, file.scope, {});
  }

  compileCache.set(cacheKey, {
    time: Date.now(),
    file,
    contentHash
  });

  return file;
}

function shallowClone(data) {
  const clone = {};

  for (const key in data) {
    const v = data[key];

    if (v !== undefined) {
      if (v === null || typeof v !== "object") {
        clone[key] = v;
      } else {
        const Ctor = v.constructor;

        switch (Ctor) {
          case Array:
            clone[key] = [...v];
            break;
          case Object:
          case null:
            clone[key] = { ...v };
            break;
          case Map:
          case Set:
          case Date:
          case RegExp:
            clone[key] = new Ctor(v);
            break;

          default:
            throw new Error(`Unsupported metadata type of ${Ctor.name}`);
        }
      }
    }
  }

  return clone;
}

function mergeVisitors(all) {
  if (all.length <= 1) {
    return all[0];
  }

  return visitors.merge(all);
}

function unique(item, i, list) {
  return list.indexOf(item) === i;
}

function shouldOptimize() {
  const { NODE_ENV, MARKO_DEBUG } = process.env;
  return (
    (MARKO_DEBUG && MARKO_DEBUG !== "false" && MARKO_DEBUG !== "0") ||
    (NODE_ENV && NODE_ENV !== "development")
  );
}
<commit_msg>fix: issue with path.hub being missing when traversing<commit_after>import path from "path";
import { createHash } from "crypto";
import { types as t } from "@marko/babel-types";
import { getLoc, getTemplateId } from "@marko/babel-utils";
import { visitors } from "@babel/traverse";
import { buildLookup } from "../taglib";
import { parseMarko } from "./parser";
import { visitor as migrate } from "./plugins/migrate";
import { visitor as transform } from "./plugins/transform";
import markoModules from "../../modules";
import { MarkoFile } from "./file";

const SOURCE_FILES = new WeakMap();

export default (api, markoOpts) => {
  api.assertVersion(7);
  let translator = markoOpts.translator;

  if (typeof translator === "string") {
    try {
      translator = markoModules.require(translator);
    } catch (err) {
      try {
        translator = markoModules.require(`@marko/translator-${translator}`);
      } catch {
        try {
          translator = markoModules.require(`marko-translator-${translator}`);
        } catch {
          throw err;
        }
      }
    }
  }

  markoOpts.translator = translator;
  markoOpts.output = markoOpts.output || "html";

  if (markoOpts.optimize === undefined) {
    api.cache.using(shouldOptimize);
    markoOpts.optimize = shouldOptimize();
  }

  if (!translator || !translator.translate) {
    throw new Error(
      "@marko/compiler: translator must provide a translate visitor object"
    );
  }

  return {
    name: "marko",
    parserOverride(code, jsParseOptions) {
      const file = getMarkoFile(code, jsParseOptions, markoOpts);
      const finalAst = t.cloneDeep(file.ast);
      SOURCE_FILES.set(finalAst, file);
      return finalAst;
    },
    pre(file) {
      const { ast, metadata } = file;
      const sourceFile = SOURCE_FILES.get(ast);
      metadata.marko = shallowClone(sourceFile.metadata.marko);

      if (markoOpts._translate !== false) {
        const { buildCodeFrameError } = file;
        const { buildError } = file.hub;
        file.buildCodeFrameError = MarkoFile.prototype.buildCodeFrameError;
        file.hub.buildError = file.buildCodeFrameError.bind(file);
        file.markoOpts = markoOpts;
        file.___taglibLookup = sourceFile.___taglibLookup;
        file.___getMarkoFile = getMarkoFile;
        traverseAll(file, translator.translate);
        file.buildCodeFrameError = buildCodeFrameError;
        file.hub.buildError = buildError;
        file.markoOpts = file.___taglibLookup = file.___getMarkoFile = undefined;
      }

      metadata.marko.watchFiles = metadata.marko.watchFiles.filter(unique);
    }
  };
};

export function getMarkoFile(code, jsParseOptions, markoOpts) {
  const { translator } = markoOpts;
  let compileCache = markoOpts.cache.get(translator);

  if (!compileCache) {
    markoOpts.cache.set(translator, (compileCache = new Map()));
  }

  const filename = jsParseOptions.sourceFileName;
  const id = getTemplateId(markoOpts.optimize, filename);
  const contentHash = createHash("MD5")
    .update(code)
    .digest("hex");
  const cacheKey = createHash("MD5")
    .update(id)
    .update(markoOpts.migrate ? "\0migrate" : "")
    .digest("hex");

  let cached = compileCache.get(cacheKey);

  if (cached) {
    if (cached.contentHash !== contentHash) {
      // File content changed, invalidate the cache.
      cached = undefined;
    } else {
      for (const watchFile of cached.file.metadata.marko.watchFiles) {
        let mtime = Infinity;
        try {
          mtime = markoOpts.fileSystem.statSync(watchFile).mtime;
          // eslint-disable-next-line no-empty
        } catch {}

        if (mtime > cached.time) {
          // Some dependency changed, invalidate the cache.
          cached = undefined;
          break;
        }
      }
    }
  }

  if (cached) {
    return cached.file;
  }

  const taglibLookup = buildLookup(path.dirname(filename), translator);

  const file = new MarkoFile(jsParseOptions, {
    code,
    ast: {
      type: "File",
      program: {
        type: "Program",
        sourceType: "module",
        body: [],
        directives: []
      }
    }
  });

  const meta = (file.metadata.marko = {
    id,
    macros: {},
    deps: [],
    tags: [],
    watchFiles: []
  });

  const rootMigrators = [migrate];
  const rootTransformers = [transform];

  file.markoOpts = markoOpts;
  file.___taglibLookup = taglibLookup;
  file.___getMarkoFile = getMarkoFile;

  file.ast.start = file.ast.program.start = 0;
  file.ast.end = file.ast.program.end = code.length - 1;
  file.ast.loc = file.ast.program.loc = {
    start: { line: 0, column: 0 },
    end: getLoc(file, file.ast.end)
  };

  parseMarko(file);
  file.path.scope.crawl(); // Initialize bindings.

  for (const id in taglibLookup.taglibsById) {
    const { migratorPath } = taglibLookup.taglibsById[id];
    if (migratorPath) {
      const mod = markoModules.require(migratorPath);
      meta.watchFiles.push(migratorPath);
      rootMigrators.push(mod.default || mod);
    }
  }

  for (const { path: transformerPath } of taglibLookup.merged.transformers) {
    const mod = markoModules.require(transformerPath);
    meta.watchFiles.push(transformerPath);
    rootTransformers.push(mod.default || mod);
  }

  traverseAll(file, rootMigrators);
  traverseAll(file, rootTransformers);

  for (const taglibId in taglibLookup.taglibsById) {
    const { filePath } = taglibLookup.taglibsById[taglibId];

    if (
      filePath[filePath.length - 5] === "." &&
      filePath.endsWith("marko.json")
    ) {
      meta.watchFiles.push(filePath);
    }
  }

  if (translator.analyze) {
    traverseAll(file, translator.analyze);
  }

  compileCache.set(cacheKey, {
    time: Date.now(),
    file,
    contentHash
  });

  return file;
}

function shallowClone(data) {
  const clone = {};

  for (const key in data) {
    const v = data[key];

    if (v !== undefined) {
      if (v === null || typeof v !== "object") {
        clone[key] = v;
      } else {
        const Ctor = v.constructor;

        switch (Ctor) {
          case Array:
            clone[key] = [...v];
            break;
          case Object:
          case null:
            clone[key] = { ...v };
            break;
          case Map:
          case Set:
          case Date:
          case RegExp:
            clone[key] = new Ctor(v);
            break;

          default:
            throw new Error(`Unsupported metadata type of ${Ctor.name}`);
        }
      }
    }
  }

  return clone;
}

function mergeVisitors(all) {
  if (Array.isArray(all)) {
    if (all.length === 1) {
      all = all[0];
    } else {
      return visitors.merge(all);
    }
  }

  return visitors.explode(all);
}

function traverseAll(file, visitors) {
  const program = file.path;
  const { Program, ...mergedVisitors } = mergeVisitors(visitors);
  // Traverse only walks into children by default
  // This manually traverses into the Program node as well.
  if (!(Program && Program.enter && program._call(Program.enter))) {
    program.traverse(mergedVisitors);

    if (Program && Program.exit) {
      program._call(Program.exit);
    }
  }
}

function unique(item, i, list) {
  return list.indexOf(item) === i;
}

function shouldOptimize() {
  const { NODE_ENV, MARKO_DEBUG } = process.env;
  return (
    (MARKO_DEBUG && MARKO_DEBUG !== "false" && MARKO_DEBUG !== "0") ||
    (NODE_ENV && NODE_ENV !== "development")
  );
}
<|endoftext|>


<commit_before>// Uses AMD or browser globals to create a module.

// Grabbed from https://github.com/umdjs/umd/blob/master/amdWeb.js.
// Check out https://github.com/umdjs/umd for more patterns.

// Defines a module "cart".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.

// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing `this` as the first arg to
// the top function.

(function (root, factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    define(['quickcart'], function (quickcart) {
      return factory(quickcart);
    });
  }
  else if (typeof module !== 'undefined' && module.exports && require) {
    module.exports = factory(require('../node_modules/quickcart/quickcart.js'));
  }
  else {
    root.QuickCart = factory(root.Cart);
  }
}(this, function (Cart) {
  'use strict';

  var DATA_ATTRIBUTE_PREFIX = 'data-quickcart-'
    , DATA_ATTRIBUTE_PREFIX_ITEM = DATA_ATTRIBUTE_PREFIX + 'item-'
    , DATA_ATTRIBUTE_PREFIX_ITEM_PROPERTY = DATA_ATTRIBUTE_PREFIX_ITEM + 'property-'
    , DEFAULT_TEMPLATES = {}
    , __uid = 0;

  function lazyCompileTemplate(type, source) {
    var compiled = null;
    DEFAULT_TEMPLATES[type] = {
      render: function() {
        compiled = compiled || Hogan.compile(source.join('\n'));
        return compiled.render.apply(compiled, arguments);
      }
    };
  }

  lazyCompileTemplate('item-row', [
    '
',
      '',
        '{{properties.name}}',
        '
',
        '<button ' + DATA_ATTRIBUTE_PREFIX + 'action="remove">Remove</button>',
      '	',
      '',
        '{{localizedPrice}}',
      '	',
      '',
        '<input type="number" value="{{quantity}}" ' + DATA_ATTRIBUTE_PREFIX + 'action="update" ' + DATA_ATTRIBUTE_PREFIX + 'commit="change" ' + DATA_ATTRIBUTE_PREFIX + 'target="quantity" data-quickcart-propertyvalue="javascript:parseInt(getValue(this), 10)">',
      '	',
      '',
        '{{localizedSubtotal}}',
      '	',
    '
',
  ]);

  function QuickCartView(doc, options) {
    options = options || {};
    this.doc = doc;
    this.localize = options.localizeAmount;
    this.handlers = options.handlers;
    this.renderer = options.renderer;
    this.objectReferences = {};
  }

  QuickCartView.prototype.getValue = function(element, ignorePropertyValue) {
    if (!ignorePropertyValue && element.getAttribute(DATA_ATTRIBUTE_PREFIX + 'propertyvalue')) {
      var dataValue = element.getAttribute(DATA_ATTRIBUTE_PREFIX + 'propertyvalue')
        , isJsThis = 0 === dataValue.indexOf('this.')
        , isJsProto = 0 === dataValue.indexOf('javascript:');
      if (isJsThis || isJsProto) {
        if (isJsProto) {
          dataValue = dataValue.substr('javascript:'.length);
        }
        return (function(getValue) {
          console.log('datavalue', element, dataValue);
          return eval(dataValue);
        }).call(element, function(element) {
          return QuickCartView.prototype.getValue(element, true);
        });
      }
      else {
        return dataValue;
      }
    }
    else if ('value' in element) {
      return element.value
    }
    else if ('selectedIndex' in element) {
      return element.options[element.selectedIndex].value;
    }
    else {
      return element.innerHTML;
    }
  };

  QuickCartView.prototype.getProperties = function(element, allProperties) {
    var attrs = {}
      , itemProperties = {}
      , prefix = allProperties ? DATA_ATTRIBUTE_PREFIX : DATA_ATTRIBUTE_PREFIX_ITEM;

    if (element.hasAttributes()) {
      for (var i=0; i < element.attributes.length; i++) {
        var attr = element.attributes[i]
          , k = attr.name
          , v = attr.value[0] === '$' ? this.getValue(this.doc.querySelector(attr.value.substr(1))) : attr.value;
        if (0 === k.indexOf(DATA_ATTRIBUTE_PREFIX_ITEM_PROPERTY)) {
          itemProperties[k.substr(DATA_ATTRIBUTE_PREFIX_ITEM_PROPERTY.length)] = v;
        }

        if (0 === k.indexOf(prefix)) {
          attrs[k.substr(prefix.length)] = v;
        }
      }
    }

    return {
        id: attrs.id
      , product: attrs.product
      , price: parseInt(attrs.price, 10)
      , quantity: parseInt(attrs.quantity, 10)
      , properties: itemProperties
    };
  };

  QuickCartView.prototype.setProperties = function(element, properties) {
    for (var k in properties) {
      element.setAttribute(DATA_ATTRIBUTE_PREFIX_ITEM + k, properties[k]);
    }
  };

  QuickCartView.prototype.getItem = function(element) {
    console.log('getItem', element);
    if (element) {
      while (!element.getAttribute(DATA_ATTRIBUTE_PREFIX_ITEM + 'id')) {
        element = element.parentNode;
        if (element === this.doc) {
          console.log('getItem found document. aborting.');
          return null;
        }
        console.log('\t-> parent', element);
      }
      return this.objectReferences[element.getAttribute(DATA_ATTRIBUTE_PREFIX_ITEM + 'id')];
    }
    console.log('getItem failed');
    return null;
  };

  QuickCartView.prototype.init = function() {
    var _this = this;
    var handler = function() {
      this.handleEvent = function(event) {
        var cartAction = event.target.getAttribute('data-quickcart-action')
          , cartCommit = event.target.getAttribute('data-quickcart-commit');
        if (cartAction && (!cartCommit || cartCommit.trim().toLowerCase().split(/\s*\,\s*/g).indexOf(event.type) > -1)) {
          var cartTarget = event.target.getAttribute('data-quickcart-target');

          var properties = _this.getItem(event.target) || _this.getProperties(event.target);
          console.log('view:event', event.type, properties, event);

          if (cartTarget) {
            var val = _this.getValue(event.target);
            eval('properties.' + cartTarget + ' = val');
          }
          _this.handlers[cartAction](event.target, properties);
        }
      };
      _this.doc.addEventListener('click', this, false);
      _this.doc.addEventListener('change', this, false);
    };
    new handler();
  };

  QuickCartView.prototype.getContext = function(item) {
    var context = Object.create(item);
    context.localizedSubtotal = this.localize(context.subtotal);
    context.localizedPrice = this.localize(context.price);
    return context;
  };

  QuickCartView.prototype.add = function(item) {
    var listings = this.doc.querySelectorAll('.quickcart-items');
    var rendered = this.renderer('item-row', this.getContext(item));
    this.objectReferences[item.id] = item;
    for (var i=0; i < listings.length; i++) {
      var listing = listings[i];
      listing.innerHTML += rendered;
    }
  };

  QuickCartView.prototype.remove = function(item) {
    var items = this.doc.querySelectorAll('.quickcart-items [' + DATA_ATTRIBUTE_PREFIX_ITEM + 'id="' + item.id + '"]');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.parentNode.removeChild(element);
    }
    delete this.objectReferences[item.id];
  };

  QuickCartView.prototype.update = function(item) {
    var items = this.doc.querySelectorAll('.quickcart-items [' + DATA_ATTRIBUTE_PREFIX_ITEM + 'id="' + item.id + '"]');
    var rendered = this.renderer('item-row', this.getContext(item));
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.outerHTML = rendered;
    }
  };

  QuickCartView.prototype.count = function(count) {
    var items = this.doc.querySelectorAll('.quickcart-count');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.innerHTML = count;
    }
  };

  QuickCartView.prototype.quantity = function(quantity) {
    var items = this.doc.querySelectorAll('.quickcart-quantity');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.innerHTML = quantity;
    }
  };

  QuickCartView.prototype.total = function(total) {
    var items = this.doc.querySelectorAll('.quickcart-total');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.innerHTML = this.localize(total);
    }
  };

  var _uid = 0;
  function QuickCart(doc, options) {
    options = options || {};
    options.model = options.cart || {};
    var _this = this;
    this.options = options;
    this.renderer = this.options.renderer || function(type, context) {
      return DEFAULT_TEMPLATES[type].render(context);
    };
    this.doc = doc;
    this.view = null;
    this.model = new Cart(options.model);
    this.storage = options.storage || null;

    // TODO: implement something that polls storage for changes and syncs so that multiple tabs work as expected
  }

  QuickCart.prototype.add = function(product, quantity) {
    if (typeof product === 'string') {
      product = {
          product: product
        , quantity: quantity || 1
      };
    }
    product.id = QuickCart.generateItemUID();
    this.model.add(product);
    return product.id;
  };

  QuickCart.prototype.update = function(cartId, properties) {
    var item = this.model.find(cartId);
    for (var k in properties) {
      item[k] = properties[k];
    }
  };

  QuickCart.prototype.init = function() {
    this._initView();
    this._initModel();
  };

  QuickCart.prototype._initView = function() {
    var _this = this;
    this.view = new QuickCartView(this.doc, {
      localizeAmount: function(amt) {
        return '$' + (amt / 100).toFixed(2);
      },
      handlers: {
        add: function(element, properties) {
          if (properties.product && properties.price) {
            var cartId = properties.id || QuickCart.generateItemUID();
            properties.id = cartId;
            _this.model.add(properties);
          }
          else {
            console.warn('Product being added from view is incomplete', element, properties);
          }
        },
        remove: function(element, properties) {
          if (properties.id) {
            _this.model.remove(properties.id);
          }
          else {
            console.warn('Product being removed from view is incomplete', element, properties);
          }
        },
        update: function(element, properties) {
          if (properties.id) {
            var item = _this.model.find(properties.id);
            ['product','price','signature','quantity'].forEach(function(element) {
              if (item[element] !== properties[element]) {
                item[element] = properties[element];
              }
            });
            for (var propertyName in properties.properties) {
              item.property(propertyName, properties.properties[propertyName]);
            }
            _this.update(item);
          }
          else {
            console.warn('Product being updated from view is incomplete', element, properties);
          }
        },
        empty: function(element, properties) {
          _this.model.clear();
        },
      },
      renderer: this.renderer
    });
    this.view.init();
    this.view.count(0);
    this.view.total(0);
  };

  QuickCart.prototype._initModel = function() {
    var _this = this;

    this.model.on('item:add', function(item) {
      _this.view.add(item);
    });

    this.model.on('item:remove', function(item) {
      _this.view.remove(item);
    });

    this.model.on('item:change', function(item) {
      _this.view.update(item);
    });

    this.model.on('count', function() {
      _this.view.count(_this.model.count);
      _this.save();
    });

    this.model.on('quantity', function() {
      _this.view.quantity(_this.model.quantity);
      _this.save();
    });

    this.model.on('total', function() {
      _this.view.total(_this.model.total);
      _this.save();
    });

    var savedState = null;
    if (this.storage) {
      savedState = this.storage.load();
    }
    if (savedState) {
      if (savedState.items) {
        savedState.items.forEach(function(item) {
          item.id = QuickCart.generateItemUID();
        });
        this.model.add(savedState.items);
      }
      if (savedState.meta) {
        for (var k in savedState.meta) {
          this.model.options.meta[k] = savedState.meta[k];
        }
      }
    }
  };

  QuickCart.prototype.empty = function() {
    this.model.clear();
  };

  QuickCart.prototype.save = function() {
    if (this.storage) {
      this.storage.save(this.model);
    }
  };

  QuickCart.prototype.purchase = function(callback) {
    var _this = this;
    // TODO: import purchase code from cart model
    throw new Error('Not implemented');
  };

  QuickCart.generateItemUID = function() {
    __uid++;
    return 'quickcartitem' + __uid;
  };

  QuickCart.browserStorage = function(type, scope) {
    scope = scope || window;
    var engine = scope[type + 'Storage']
      , storageKey = 'quickcart';
    if (!engine) {
      throw new Error('Invalid browser storage engine: ' + type);
    }
    return {
      save: function(data) {
        var json = JSON.stringify(data, null, 2);
        console.debug('<- Saving state', json);
        engine.setItem(storageKey, json);
      },
      load: function() {
        var json = engine.getItem(storageKey)
          , parsed = null;
        console.debug('-> Loading state', json);
        if (json) {
          parsed = JSON.parse(json);
          for (var i=0; i < parsed.items.length; i++) {
            if (typeof parsed.items[i].price === 'string') { // expects price handlers to be in the provided scope
              parsed.items[i].price = scope[parsed.items[i].price];
            }
          }
        }
        return parsed;
      }
    };
  };

  return QuickCart;
}));
<commit_msg>remove unnecessary and expose internal<commit_after>// Uses AMD or browser globals to create a module.

// Grabbed from https://github.com/umdjs/umd/blob/master/amdWeb.js.
// Check out https://github.com/umdjs/umd for more patterns.

// Defines a module "cart".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.

// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing `this` as the first arg to
// the top function.

(function (root, factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    define(['quickcart'], function (quickcart) {
      return factory(quickcart);
    });
  }
  else if (typeof module !== 'undefined' && module.exports && require) {
    module.exports = factory(require('../node_modules/quickcart/quickcart.js'));
  }
  else {
    root.QuickCart = factory(root.Cart);
  }
}(this, function (Cart) {
  'use strict';

  var DATA_ATTRIBUTE_PREFIX = 'data-quickcart-'
    , DATA_ATTRIBUTE_PREFIX_ITEM = DATA_ATTRIBUTE_PREFIX + 'item-'
    , DATA_ATTRIBUTE_PREFIX_ITEM_PROPERTY = DATA_ATTRIBUTE_PREFIX_ITEM + 'property-'
    , DEFAULT_TEMPLATES = {}
    , __uid = 0;

  function lazyCompileTemplate(type, source) {
    var compiled = null;
    DEFAULT_TEMPLATES[type] = {
      render: function() {
        compiled = compiled || Hogan.compile(source.join('\n'));
        return compiled.render.apply(compiled, arguments);
      }
    };
  }

  lazyCompileTemplate('item-row', [
    '
',
      '',
        '{{properties.name}}',
        '
',
        '<button ' + DATA_ATTRIBUTE_PREFIX + 'action="remove">Remove</button>',
      '	',
      '',
        '{{localizedPrice}}',
      '	',
      '',
        '<input type="number" value="{{quantity}}" ' + DATA_ATTRIBUTE_PREFIX + 'action="update" ' + DATA_ATTRIBUTE_PREFIX + 'commit="change" ' + DATA_ATTRIBUTE_PREFIX + 'target="quantity" data-quickcart-propertyvalue="javascript:parseInt(getValue(this), 10)">',
      '	',
      '',
        '{{localizedSubtotal}}',
      '	',
    '
',
  ]);

  function QuickCartView(doc, options) {
    options = options || {};
    this.doc = doc;
    this.localize = options.localizeAmount;
    this.handlers = options.handlers;
    this.renderer = options.renderer;
    this.objectReferences = {};
  }

  QuickCartView.prototype.getValue = function(element, ignorePropertyValue) {
    if (!ignorePropertyValue && element.getAttribute(DATA_ATTRIBUTE_PREFIX + 'propertyvalue')) {
      var dataValue = element.getAttribute(DATA_ATTRIBUTE_PREFIX + 'propertyvalue')
        , isJsThis = 0 === dataValue.indexOf('this.')
        , isJsProto = 0 === dataValue.indexOf('javascript:');
      if (isJsThis || isJsProto) {
        if (isJsProto) {
          dataValue = dataValue.substr('javascript:'.length);
        }
        return (function(getValue) {
          console.log('datavalue', element, dataValue);
          return eval(dataValue);
        }).call(element, function(element) {
          return QuickCartView.prototype.getValue(element, true);
        });
      }
      else {
        return dataValue;
      }
    }
    else if ('value' in element) {
      return element.value
    }
    else if ('selectedIndex' in element) {
      return element.options[element.selectedIndex].value;
    }
    else {
      return element.innerHTML;
    }
  };

  QuickCartView.prototype.getProperties = function(element, allProperties) {
    var attrs = {}
      , itemProperties = {}
      , prefix = allProperties ? DATA_ATTRIBUTE_PREFIX : DATA_ATTRIBUTE_PREFIX_ITEM;

    if (element.hasAttributes()) {
      for (var i=0; i < element.attributes.length; i++) {
        var attr = element.attributes[i]
          , k = attr.name
          , v = attr.value[0] === '$' ? this.getValue(this.doc.querySelector(attr.value.substr(1))) : attr.value;
        if (0 === k.indexOf(DATA_ATTRIBUTE_PREFIX_ITEM_PROPERTY)) {
          itemProperties[k.substr(DATA_ATTRIBUTE_PREFIX_ITEM_PROPERTY.length)] = v;
        }

        if (0 === k.indexOf(prefix)) {
          attrs[k.substr(prefix.length)] = v;
        }
      }
    }

    return {
        id: attrs.id
      , product: attrs.product
      , price: parseInt(attrs.price, 10)
      , quantity: parseInt(attrs.quantity, 10)
      , properties: itemProperties
    };
  };

  QuickCartView.prototype.setProperties = function(element, properties) {
    for (var k in properties) {
      element.setAttribute(DATA_ATTRIBUTE_PREFIX_ITEM + k, properties[k]);
    }
  };

  QuickCartView.prototype.getItem = function(element) {
    console.log('getItem', element);
    if (element) {
      while (!element.getAttribute(DATA_ATTRIBUTE_PREFIX_ITEM + 'id')) {
        element = element.parentNode;
        if (element === this.doc) {
          console.log('getItem found document. aborting.');
          return null;
        }
        console.log('\t-> parent', element);
      }
      return this.objectReferences[element.getAttribute(DATA_ATTRIBUTE_PREFIX_ITEM + 'id')];
    }
    console.log('getItem failed');
    return null;
  };

  QuickCartView.prototype.init = function() {
    var _this = this;
    var handler = function() {
      this.handleEvent = function(event) {
        var cartAction = event.target.getAttribute('data-quickcart-action')
          , cartCommit = event.target.getAttribute('data-quickcart-commit');
        if (cartAction && (!cartCommit || cartCommit.trim().toLowerCase().split(/\s*\,\s*/g).indexOf(event.type) > -1)) {
          var cartTarget = event.target.getAttribute('data-quickcart-target');

          var properties = _this.getItem(event.target) || _this.getProperties(event.target);
          console.log('view:event', event.type, properties, event);

          if (cartTarget) {
            var val = _this.getValue(event.target);
            eval('properties.' + cartTarget + ' = val');
          }
          _this.handlers[cartAction](event.target, properties);
        }
      };
      _this.doc.addEventListener('click', this, false);
      _this.doc.addEventListener('change', this, false);
    };
    new handler();
  };

  QuickCartView.prototype.getContext = function(item) {
    var context = Object.create(item);
    context.localizedSubtotal = this.localize(context.subtotal);
    context.localizedPrice = this.localize(context.price);
    return context;
  };

  QuickCartView.prototype.add = function(item) {
    var listings = this.doc.querySelectorAll('.quickcart-items');
    var rendered = this.renderer('item-row', this.getContext(item));
    this.objectReferences[item.id] = item;
    for (var i=0; i < listings.length; i++) {
      var listing = listings[i];
      listing.innerHTML += rendered;
    }
  };

  QuickCartView.prototype.remove = function(item) {
    var items = this.doc.querySelectorAll('.quickcart-items [' + DATA_ATTRIBUTE_PREFIX_ITEM + 'id="' + item.id + '"]');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.parentNode.removeChild(element);
    }
    delete this.objectReferences[item.id];
  };

  QuickCartView.prototype.update = function(item) {
    var items = this.doc.querySelectorAll('.quickcart-items [' + DATA_ATTRIBUTE_PREFIX_ITEM + 'id="' + item.id + '"]');
    var rendered = this.renderer('item-row', this.getContext(item));
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.outerHTML = rendered;
    }
  };

  QuickCartView.prototype.count = function(count) {
    var items = this.doc.querySelectorAll('.quickcart-count');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.innerHTML = count;
    }
  };

  QuickCartView.prototype.quantity = function(quantity) {
    var items = this.doc.querySelectorAll('.quickcart-quantity');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.innerHTML = quantity;
    }
  };

  QuickCartView.prototype.total = function(total) {
    var items = this.doc.querySelectorAll('.quickcart-total');
    for (var i=0; i < items.length; i++) {
      var element = items[i];
      element.innerHTML = this.localize(total);
    }
  };

  var _uid = 0;
  function QuickCart(doc, options) {
    options = options || {};
    options.model = options.cart || {};
    var _this = this;
    this.options = options;
    this.renderer = this.options.renderer || function(type, context) {
      return DEFAULT_TEMPLATES[type].render(context);
    };
    this.doc = doc;
    this.view = null;
    this.model = new Cart(options.model);
    this.storage = options.storage || null;

    // TODO: implement something that polls storage for changes and syncs so that multiple tabs work as expected
  }

  QuickCart.prototype.add = function(product, quantity) {
    if (typeof product === 'string') {
      product = {
          product: product
        , quantity: quantity || 1
      };
    }
    product.id = QuickCart.generateItemUID();
    this.model.add(product);
    return product.id;
  };

  QuickCart.prototype.init = function() {
    this._initView();
    this._initModel();
  };

  QuickCart.prototype._initView = function() {
    var _this = this;
    this.view = new QuickCartView(this.doc, {
      localizeAmount: function(amt) {
        return '$' + (amt / 100).toFixed(2);
      },
      handlers: {
        add: function(element, properties) {
          if (properties.product && properties.price) {
            var cartId = properties.id || QuickCart.generateItemUID();
            properties.id = cartId;
            _this.model.add(properties);
          }
          else {
            console.warn('Product being added from view is incomplete', element, properties);
          }
        },
        remove: function(element, properties) {
          if (properties.id) {
            _this.model.remove(properties.id);
          }
          else {
            console.warn('Product being removed from view is incomplete', element, properties);
          }
        },
        update: function(element, properties) {
          if (properties.id) {
            var item = _this.model.find(properties.id);
            ['product','price','signature','quantity'].forEach(function(element) {
              if (item[element] !== properties[element]) {
                item[element] = properties[element];
              }
            });
            for (var propertyName in properties.properties) {
              item.property(propertyName, properties.properties[propertyName]);
            }
          }
          else {
            console.warn('Product being updated from view is incomplete', element, properties);
          }
        },
        empty: function(element, properties) {
          _this.model.clear();
        },
      },
      renderer: this.renderer
    });
    this.view.init();
    this.view.count(0);
    this.view.total(0);
  };

  QuickCart.prototype._initModel = function() {
    var _this = this;

    this.model.on('item:add', function(item) {
      _this.view.add(item);
    });

    this.model.on('item:remove', function(item) {
      _this.view.remove(item);
    });

    this.model.on('item:change', function(item) {
      _this.view.update(item);
    });

    this.model.on('count', function() {
      _this.view.count(_this.model.count);
      _this.save();
    });

    this.model.on('quantity', function() {
      _this.view.quantity(_this.model.quantity);
      _this.save();
    });

    this.model.on('total', function() {
      _this.view.total(_this.model.total);
      _this.save();
    });

    var savedState = null;
    if (this.storage) {
      savedState = this.storage.load();
    }
    if (savedState) {
      if (savedState.items) {
        savedState.items.forEach(function(item) {
          item.id = QuickCart.generateItemUID();
        });
        this.model.add(savedState.items);
      }
      if (savedState.meta) {
        for (var k in savedState.meta) {
          this.model.options.meta[k] = savedState.meta[k];
        }
      }
    }
  };

  QuickCart.prototype.empty = function() {
    this.model.clear();
  };

  QuickCart.prototype.save = function() {
    if (this.storage) {
      this.storage.save(this.model);
    }
  };

  QuickCart.prototype.purchase = function(callback) {
    var _this = this;
    // TODO: import purchase code from cart model
    throw new Error('Not implemented');
  };

  QuickCart.generateItemUID = function() {
    __uid++;
    return 'quickcartitem' + __uid;
  };

  QuickCart.browserStorage = function(type, scope) {
    scope = scope || window;
    var engine = scope[type + 'Storage']
      , storageKey = 'quickcart';
    if (!engine) {
      throw new Error('Invalid browser storage engine: ' + type);
    }
    return {
      save: function(data) {
        var json = JSON.stringify(data, null, 2);
        console.debug('<- Saving state', json);
        engine.setItem(storageKey, json);
      },
      load: function() {
        var json = engine.getItem(storageKey)
          , parsed = null;
        console.debug('-> Loading state', json);
        if (json) {
          parsed = JSON.parse(json);
          for (var i=0; i < parsed.items.length; i++) {
            if (typeof parsed.items[i].price === 'string') { // expects price handlers to be in the provided scope
              parsed.items[i].price = scope[parsed.items[i].price];
            }
          }
        }
        return parsed;
      }
    };
  };

  // expose QuickCartView
  QuickCart.QuickCartView = QuickCartView;

  return QuickCart;
}));
<|endoftext|>


<commit_before>class LocalStorageAdapter {
    get(_storageName) {
        return new Promise(_resolve => _resolve(
            JSON.parse(window.localStorage.getItem(_storageName))
        ));
    }

    set(_storageName, _data) {
        return new Promise(_resolve => _resolve(
            window.localStorage.setItem(_storageName, JSON.stringify(_data))
        ));
    }
}

class BrowserStorageAdapter {
    constructor() {
        /**
         * Extension API model is currently being standardized to browser.xxx,
         * and some browsers are defining their own namespaces in the meantime
         * (for example, Edge is using msBrowser), see:
         *
         * {@link https://gist.github.com/superKalo/7cee019a3a6a3f0e231b6b4840b5d701}
         */
        this.browser = window.msBrowser || window.browser || window.chrome;
    }

    get(_storageName) {
        return new Promise(_resolve =>
            this.browser.storage.local.get(_storageName, _response =>
                _resolve(_response[_storageName])
            )
        );
    }

    set(_storageName, _data) {
        return new Promise(_resolve =>
            this.browser.storage.local.set({
                [_storageName]: _data
            }, _resolve)
        );
    }
}

class LocalVariableAdapter {
    constructor(_this) {
        this.context = _this;
    }

    get(_storageName) {
        return new Promise(_resolve => _resolve(this.context.data));
    }

    set(_storageName, _data) {
        return new Promise(_resolve => _resolve(this.context.data = _data));
    }
}

class SuperRepo {

    constructor(_config) {
        this.config = _config;

        /**
         * Helper variables to hold the currently pending Promise (this.promise)
         * and to determine if a Promise is currently pending or not.
         *
         * {@link https://stackoverflow.com/a/36294256/1333836}
         */
        this.promise = null;
        this.isPromisePending = false;

        switch(this.config.storage) {
            case 'LOCAL_VARIABLE': {
                // Introduce this local variable to hold the repository data
                this.data = null;

                this.storage = new LocalVariableAdapter(this);
                break;
            }
            case 'BROWSER_STORAGE': {
                this.storage = new BrowserStorageAdapter();
                break;
            }
            case 'LOCAL_STORAGE':
            default: {
                this.storage = new LocalStorageAdapter();
                break;
            }
        }
    }

    /**
     * Applies the data model to the rough response data, or with other words:
     * sets custom attribute names for each response item.
     *
     * @param  {Object} or {Array} _response - server response
     * @return {Object} or {Array}
     */
    _normalizeData(_response) {
        const { dataModel } = this.config;

        const isMissing =
            typeof dataModel === 'undefined' || dataModel === null;

        if (isMissing) {
            return _response;
        }

        const isArray = Array.isArray(dataModel);

        if (isArray) {
            return _response.map( item => {
                const obj = {};

                Object.keys(dataModel[0]).forEach(
                    key => obj[key] = item[dataModel[0][key]]
                );

                return obj;
            });
        }

        // In all other cases - it should be an Array of Objects.
        const obj = {};

        Object.keys(dataModel).forEach(
            key => obj[key] = _response[dataModel[key]]
        );

        return obj;
    }

    /**
     * After the data model is applied via ._normalizeData(),
     * mapping the response gives an option for further processing the data.
     *
     * @param  {Object} or {Array} _response - server response
     * @return {Object} or {Array}
     */
    _mapData(_response) {
        const { mapData } = this.config;

        return typeof mapData === 'function' ? mapData(_response) : _response;
    }

    /**
     * Checks if repository the data is up to date or not.
     *
     * @return {Boolean}
     */
    _isDataUpToDate(_localStore) {
        const isDataMissing =
            _localStore === null || // Local Storage
            typeof _localStore === 'undefined' || // Browser Storage
            Object.keys(_localStore.data).length === 0;

        if (isDataMissing) {
            return false;
        }

        if (_localStore.isInvalid) {
            return false;
        }

        const { lastFetched } = _localStore;

        const isLimitExceeded =
            (new Date().valueOf() - lastFetched) > this.config.cacheLimit;

        return ! isLimitExceeded;
    }

    /**
     * Stores data via the storage method configured.
     *
     * @param  {Array} or {Object} _data - the repository data
     * @return {Void}
     */
    _storeData(_data) {
        this.storage.set(this.config.name, {
            lastFetched: new Date().valueOf(),
            isInvalid: false,
            data: _data
        });
    }

    /**
     * Invalidates data by setting turning on the `isInvalid` flag.
     * It doesn't delete the data from the storage.
     * However, the very next time when the .getData() method is invoked,
     * it will directly call the server to get fresh data.
     *
     * @return {Promise}
     */
    invalidateData() {
        return this.storage.get(this.config.name).then(_data =>
            this.storage.set(this.config.name, Object.assign({}, _data, {
                isInvalid: true
            }))
        );
    }

    /**
     * Deletes the data from the storage.
     * Therefore, the very next time when the .getData() method is invoked,
     * it will directly call the server to get fresh data.
     *
     * @return {Promise}
     */
    clearData() {
        return this.storage.set(this.config.name, null);
    }

    /**
     * Gets data from the server (if it’s missing or outdated on our side)
     * or otherwise - gets it from the cache.
     *
     * @return {Promise}
     */
    getData(){
        const { config } = this;

        // If there is a Promise pending, wait for it, do not fire another one!
        if (this.isPromisePending) {
            return this.promise;
        }

        /**
         * The only way to detect if a Promise is pending or not,
         * is to attach a flag like so. It doesn't look sexy, but it works.
         * {@link: https://stackoverflow.com/a/36294256/1333836}
         */
        this.isPromisePending = true;

        return this.promise = new Promise(_resolve => {

            this.storage.get(config.name).then(_localData => {
                if (this._isDataUpToDate(_localData)) {
                    this.promise = null;
                    this.isPromisePending = false;

                    _resolve(_localData.data);
                } else {
                    config.request()
                        .then(this._normalizeData.bind(this))
                        .then(this._mapData.bind(this))
                        .then(_response => {
                            this._storeData(_response);

                            this.promise = null;
                            this.isPromisePending = false;

                            return _response;
                        })
                        .then(_resolve);
                }
            });

        });
    }

    _initSyncInterval(_interval) {
        this.syncInterval = setInterval(
            () => this.getData(), _interval
        );
    }

    initSyncer() {
        this.storage.get(this.config.name).then(_localData => {
            if (this._isDataUpToDate(_localData)) {
                const { lastFetched } = _localData;
                const diff = new Date().valueOf() - lastFetched;

                this.syncInterval = this._initSyncInterval(diff);

                setTimeout( () => {
                    this.syncInterval = this._initSyncInterval(this.config.cacheLimit);
                }, diff);
            } else {
                this.getData().then(r => {
                    this.syncInterval = this._initSyncInterval(this.config.cacheLimit)

                    return r;
                });
            }
        });
    }

    destroySyncer() {
        this.syncInterval = null;
    }
};

/**
 * Uses Node, AMD or browser globals to create a module. This example creates
 * a global even when AMD is used. This is useful if you have some scripts
 * that are loaded by an AMD loader, but they still want access to globals.
 *
 * {@link https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js}
 */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], function () {
            return (root.returnExportsGlobal = factory());
        });
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals
        root.returnExportsGlobal = factory();
    }
}(this, function () {
    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return SuperRepo;
}));
<commit_msg>Add: documentation to the rest of the available methods<commit_after>class LocalStorageAdapter {
    get(_storageName) {
        return new Promise(_resolve => _resolve(
            JSON.parse(window.localStorage.getItem(_storageName))
        ));
    }

    set(_storageName, _data) {
        return new Promise(_resolve => _resolve(
            window.localStorage.setItem(_storageName, JSON.stringify(_data))
        ));
    }
}

class BrowserStorageAdapter {
    constructor() {
        /**
         * Extension API model is currently being standardized to browser.xxx,
         * and some browsers are defining their own namespaces in the meantime
         * (for example, Edge is using msBrowser), see:
         *
         * {@link https://gist.github.com/superKalo/7cee019a3a6a3f0e231b6b4840b5d701}
         */
        this.browser = window.msBrowser || window.browser || window.chrome;
    }

    get(_storageName) {
        return new Promise(_resolve =>
            this.browser.storage.local.get(_storageName, _response =>
                _resolve(_response[_storageName])
            )
        );
    }

    set(_storageName, _data) {
        return new Promise(_resolve =>
            this.browser.storage.local.set({
                [_storageName]: _data
            }, _resolve)
        );
    }
}

class LocalVariableAdapter {
    constructor(_this) {
        this.context = _this;
    }

    get(_storageName) {
        return new Promise(_resolve => _resolve(this.context.data));
    }

    set(_storageName, _data) {
        return new Promise(_resolve => _resolve(this.context.data = _data));
    }
}

class SuperRepo {

    constructor(_config) {
        this.config = _config;

        /**
         * Helper variables to hold the currently pending Promise (this.promise)
         * and to determine if a Promise is currently pending or not.
         *
         * {@link https://stackoverflow.com/a/36294256/1333836}
         */
        this.promise = null;
        this.isPromisePending = false;

        switch(this.config.storage) {
            case 'LOCAL_VARIABLE': {
                // Introduce this local variable to hold the repository data
                this.data = null;

                this.storage = new LocalVariableAdapter(this);
                break;
            }
            case 'BROWSER_STORAGE': {
                this.storage = new BrowserStorageAdapter();
                break;
            }
            case 'LOCAL_STORAGE':
            default: {
                this.storage = new LocalStorageAdapter();
                break;
            }
        }
    }

    /**
     * Applies the data model to the rough response data, or with other words:
     * sets custom attribute names for each response item.
     *
     * @param  {Object} or {Array} _response - server response
     * @return {Object} or {Array}
     */
    _normalizeData(_response) {
        const { dataModel } = this.config;

        const isMissing =
            typeof dataModel === 'undefined' || dataModel === null;

        if (isMissing) {
            return _response;
        }

        const isArray = Array.isArray(dataModel);

        if (isArray) {
            return _response.map( item => {
                const obj = {};

                Object.keys(dataModel[0]).forEach(
                    key => obj[key] = item[dataModel[0][key]]
                );

                return obj;
            });
        }

        // In all other cases - it should be an Array of Objects.
        const obj = {};

        Object.keys(dataModel).forEach(
            key => obj[key] = _response[dataModel[key]]
        );

        return obj;
    }

    /**
     * After the data model is applied via ._normalizeData(),
     * mapping the response gives an option for further processing the data.
     *
     * @param  {Object} or {Array} _response - server response
     * @return {Object} or {Array}
     */
    _mapData(_response) {
        const { mapData } = this.config;

        return typeof mapData === 'function' ? mapData(_response) : _response;
    }

    /**
     * Checks if repository the data is up to date or not.
     *
     * @return {Boolean}
     */
    _isDataUpToDate(_localStore) {
        const isDataMissing =
            _localStore === null || // Local Storage
            typeof _localStore === 'undefined' || // Browser Storage
            Object.keys(_localStore.data).length === 0;

        if (isDataMissing) {
            return false;
        }

        if (_localStore.isInvalid) {
            return false;
        }

        const { lastFetched } = _localStore;

        const isLimitExceeded =
            (new Date().valueOf() - lastFetched) > this.config.cacheLimit;

        return ! isLimitExceeded;
    }

    /**
     * Stores data via the storage method configured.
     *
     * @param  {Array} or {Object} _data - the repository data
     * @return {Void}
     */
    _storeData(_data) {
        this.storage.set(this.config.name, {
            lastFetched: new Date().valueOf(),
            isInvalid: false,
            data: _data
        });
    }

    /**
     * Invalidates data by setting turning on the `isInvalid` flag.
     * It doesn't delete the data from the storage.
     * However, the very next time when the .getData() method is invoked,
     * it will directly call the server to get fresh data.
     *
     * @return {Promise}
     */
    invalidateData() {
        return this.storage.get(this.config.name).then(_data =>
            this.storage.set(this.config.name, Object.assign({}, _data, {
                isInvalid: true
            }))
        );
    }

    /**
     * Deletes the data from the storage.
     * Therefore, the very next time when the .getData() method is invoked,
     * it will directly call the server to get fresh data.
     *
     * @return {Promise}
     */
    clearData() {
        return this.storage.set(this.config.name, null);
    }

    /**
     * Gets data from the server (if it’s missing or outdated on our side)
     * or otherwise - gets it from the cache.
     *
     * @return {Promise}
     */
    getData(){
        const { config } = this;

        // If there is a Promise pending, wait for it, do not fire another one!
        if (this.isPromisePending) {
            return this.promise;
        }

        /**
         * The only way to detect if a Promise is pending or not,
         * is to attach a flag like so. It doesn't look sexy, but it works.
         * {@link: https://stackoverflow.com/a/36294256/1333836}
         */
        this.isPromisePending = true;

        return this.promise = new Promise(_resolve => {

            this.storage.get(config.name).then(_localData => {
                if (this._isDataUpToDate(_localData)) {
                    this.promise = null;
                    this.isPromisePending = false;

                    _resolve(_localData.data);
                } else {
                    config.request()
                        .then(this._normalizeData.bind(this))
                        .then(this._mapData.bind(this))
                        .then(_response => {
                            this._storeData(_response);

                            this.promise = null;
                            this.isPromisePending = false;

                            return _response;
                        })
                        .then(_resolve);
                }
            });

        });
    }

    /**
     * Helper method that initiates an interval.
     *
     * @param  {Number} _interval - the interval, in milliseconds
     * @return {Void}
     */
    _initSyncInterval(_interval) {
        this.syncInterval = setInterval(
            () => this.getData(), _interval
        );
    }

    /**
     * Initiates a setInterval, which will countdown to the point
     * when the data is out of date (based on the `cacheLimit` value)
     * and will trigger a server request to get fresh data.
     *
     * @return {Void}
     */
    initSyncer() {
        this.storage.get(this.config.name).then(_localData => {
            if (this._isDataUpToDate(_localData)) {
                const { lastFetched } = _localData;
                const diff = new Date().valueOf() - lastFetched;

                this.syncInterval = this._initSyncInterval(diff);

                setTimeout( () => {
                    this.syncInterval = this._initSyncInterval(this.config.cacheLimit);
                }, diff);
            } else {
                this.getData().then(r => {
                    this.syncInterval = this._initSyncInterval(this.config.cacheLimit)

                    return r;
                });
            }
        });
    }

    /**
     * Destroys the setInterval, initiated by the .initSyncer() method.
     *
     * @return {Void}
     */
    destroySyncer() {
        this.syncInterval = null;
    }
};

/**
 * Uses Node, AMD or browser globals to create a module. This example creates
 * a global even when AMD is used. This is useful if you have some scripts
 * that are loaded by an AMD loader, but they still want access to globals.
 *
 * {@link https://github.com/umdjs/umd/blob/master/templates/returnExportsGlobal.js}
 */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], function () {
            return (root.returnExportsGlobal = factory());
        });
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals
        root.returnExportsGlobal = factory();
    }
}(this, function () {
    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return SuperRepo;
}));
<|endoftext|>


<commit_before>const postcss = require('postcss');
const valueParser = require('postcss-value-parser');

/*
 * Constants (parser usage)
 */

const SINGLE_QUOTE = '\''.charCodeAt(0);
const DOUBLE_QUOTE = '"'.charCodeAt(0);
const BACKSLASH    = '\\'.charCodeAt(0);
const NEWLINE      = '\n'.charCodeAt(0);
const SPACE        =  ' '.charCodeAt(0);
const FEED         = '\f'.charCodeAt(0);
const TAB          = '\t'.charCodeAt(0);
const CR           = '\r'.charCodeAt(0);

const WORD_END = /[ \n\t\r\f'"\\]/g;

/*
 * Constants (node type strings)
 */

const C_STRING = 'string';
const C_ESCAPED_SINGLE_QUOTE = 'escapedSingleQuote';
const C_ESCAPED_DOUBLE_QUOTE = 'escapedDoubleQuote';
const C_SINGLE_QUOTE = 'singleQuote';
const C_DOUBLE_QUOTE = 'doubleQuote';
const C_NEWLINE = 'newline';
const C_SINGLE = 'single';

/*
 * Literals
 */

const L_SINGLE_QUOTE = `'`;
const L_DOUBLE_QUOTE = `"`;
const L_NEWLINE = `\\\n`;

/*
 * Parser nodes
 */

const T_ESCAPED_SINGLE_QUOTE = {type: C_ESCAPED_SINGLE_QUOTE, value: `\\'`};
const T_ESCAPED_DOUBLE_QUOTE = {type: C_ESCAPED_DOUBLE_QUOTE, value: `\\"`};
const T_SINGLE_QUOTE = {type: C_SINGLE_QUOTE, value: L_SINGLE_QUOTE};
const T_DOUBLE_QUOTE = {type: C_DOUBLE_QUOTE, value: L_DOUBLE_QUOTE};
const T_NEWLINE = {type: C_NEWLINE, value: L_NEWLINE};

function stringify (ast) {
    return ast.nodes.reduce((str, {value}) => {
        // Collapse multiple line strings automatically
        if (value === L_NEWLINE) {
            return str;
        }
        return str + value;
    }, '');
}

function parse (str) {
    let code, next, value;
    let pos = 0;
    let len = str.length;

    const ast = {
        nodes: [],
        types: {
            escapedSingleQuote: 0,
            escapedDoubleQuote: 0,
            singleQuote: 0,
            doubleQuote: 0,
        },
        quotes: false,
    };

    while (pos < len) {
        code = str.charCodeAt(pos);
        switch (code) {
        case SPACE:
        case TAB:
        case CR:
        case FEED:
            next = pos;
            do {
                next += 1;
                code = str.charCodeAt(next);
            } while (code === SPACE   ||
                     code === NEWLINE ||
                     code === TAB     ||
                     code === CR      ||
                     code === FEED);

            ast.nodes.push({
                type: 'space',
                value: str.slice(pos, next),
            });
            pos = next - 1;
            break;
        case SINGLE_QUOTE:
            ast.nodes.push(T_SINGLE_QUOTE);
            ast.types[C_SINGLE_QUOTE]++;
            ast.quotes = true;
            break;
        case DOUBLE_QUOTE:
            ast.nodes.push(T_DOUBLE_QUOTE);
            ast.types[C_DOUBLE_QUOTE]++;
            ast.quotes = true;
            break;
        case BACKSLASH:
            next = pos + 1;
            if (str.charCodeAt(next) === SINGLE_QUOTE) {
                ast.nodes.push(T_ESCAPED_SINGLE_QUOTE);
                ast.types[C_ESCAPED_SINGLE_QUOTE]++;
                ast.quotes = true;
                pos = next;
                break;
            } else if (str.charCodeAt(next) === DOUBLE_QUOTE) {
                ast.nodes.push(T_ESCAPED_DOUBLE_QUOTE);
                ast.types[C_ESCAPED_DOUBLE_QUOTE]++;
                ast.quotes = true;
                pos = next;
                break;
            } else if (str.charCodeAt(next) === NEWLINE) {
                ast.nodes.push(T_NEWLINE);
                pos = next;
                break;
            }
            /*
             * We need to fall through here to handle the token as
             * a whole word. The missing 'break' is intentional.
             */
        default:
            WORD_END.lastIndex = pos + 1;
            WORD_END.test(str);

            if (WORD_END.lastIndex === 0) {
                next = len - 1;
            } else {
                next = WORD_END.lastIndex - 2;
            }

            value = str.slice(pos, next + 1);

            ast.nodes.push({
                type: C_STRING,
                value,
            });

            pos = next;
        }
        pos ++;
    }

    return ast;
}

function changeWrappingQuotes (node, ast) {
    const {types} = ast;
    if (types[C_SINGLE_QUOTE] || types[C_DOUBLE_QUOTE]) {
        return;
    }

    if (
        node.quote === L_SINGLE_QUOTE &&
        types[C_ESCAPED_SINGLE_QUOTE] > 0 &&
        !types[C_ESCAPED_DOUBLE_QUOTE]
    ) {
        node.quote = L_DOUBLE_QUOTE;
    }

    if (
        node.quote === L_DOUBLE_QUOTE &&
        types[C_ESCAPED_DOUBLE_QUOTE] > 0 &&
        !types[C_ESCAPED_SINGLE_QUOTE]
    ) {
        node.quote = L_SINGLE_QUOTE;
    }

    ast.nodes = ast.nodes.reduce((newAst, child) => {
        if (child.type === C_ESCAPED_DOUBLE_QUOTE && node.quote === L_SINGLE_QUOTE) {
            return [...newAst, T_DOUBLE_QUOTE];
        }
        if (child.type === C_ESCAPED_SINGLE_QUOTE && node.quote === L_DOUBLE_QUOTE) {
            return [...newAst, T_SINGLE_QUOTE];
        }
        return [...newAst, child];
    }, []);
}

function normalize (value, preferredQuote) {
    if (!value || !value.length) {
        return value;
    }
    return valueParser(value).walk(child => {
        if (child.type !== C_STRING) {
            return;
        }
        const ast = parse(child.value);
        if (ast.quotes) {
            changeWrappingQuotes(child, ast);
        } else if (preferredQuote === C_SINGLE) {
            child.quote = L_SINGLE_QUOTE;
        } else {
            child.quote = L_DOUBLE_QUOTE;
        }
        child.value = stringify(ast);
    }).toString();
}

export default postcss.plugin('cssnano-normalize-string', opts => {
    const {preferredQuote} = Object.assign({}, {
        preferredQuote: 'double',
    }, opts);

    return css => {
        css.walk(node => {
            if (node.type === 'rule') {
                node.selector = normalize(node.selector, preferredQuote);
            }
            if (node.type === 'decl') {
                node.value = normalize(node.value, preferredQuote);
            }
            if (node.type === 'atrule') {
                node.params = normalize(node.params, preferredQuote);
            }
        });
    };
});
<commit_msg>Simplify node type checking in normalizeString.<commit_after>import postcss from 'postcss';
import valueParser from 'postcss-value-parser';
import has from 'has';

/*
 * Constants (parser usage)
 */

const SINGLE_QUOTE = '\''.charCodeAt(0);
const DOUBLE_QUOTE = '"'.charCodeAt(0);
const BACKSLASH    = '\\'.charCodeAt(0);
const NEWLINE      = '\n'.charCodeAt(0);
const SPACE        =  ' '.charCodeAt(0);
const FEED         = '\f'.charCodeAt(0);
const TAB          = '\t'.charCodeAt(0);
const CR           = '\r'.charCodeAt(0);

const WORD_END = /[ \n\t\r\f'"\\]/g;

/*
 * Constants (node type strings)
 */

const C_STRING = 'string';
const C_ESCAPED_SINGLE_QUOTE = 'escapedSingleQuote';
const C_ESCAPED_DOUBLE_QUOTE = 'escapedDoubleQuote';
const C_SINGLE_QUOTE = 'singleQuote';
const C_DOUBLE_QUOTE = 'doubleQuote';
const C_NEWLINE = 'newline';
const C_SINGLE = 'single';

/*
 * Literals
 */

const L_SINGLE_QUOTE = `'`;
const L_DOUBLE_QUOTE = `"`;
const L_NEWLINE = `\\\n`;

/*
 * Parser nodes
 */

const T_ESCAPED_SINGLE_QUOTE = {type: C_ESCAPED_SINGLE_QUOTE, value: `\\'`};
const T_ESCAPED_DOUBLE_QUOTE = {type: C_ESCAPED_DOUBLE_QUOTE, value: `\\"`};
const T_SINGLE_QUOTE = {type: C_SINGLE_QUOTE, value: L_SINGLE_QUOTE};
const T_DOUBLE_QUOTE = {type: C_DOUBLE_QUOTE, value: L_DOUBLE_QUOTE};
const T_NEWLINE = {type: C_NEWLINE, value: L_NEWLINE};

function stringify (ast) {
    return ast.nodes.reduce((str, {value}) => {
        // Collapse multiple line strings automatically
        if (value === L_NEWLINE) {
            return str;
        }
        return str + value;
    }, '');
}

function parse (str) {
    let code, next, value;
    let pos = 0;
    let len = str.length;

    const ast = {
        nodes: [],
        types: {
            escapedSingleQuote: 0,
            escapedDoubleQuote: 0,
            singleQuote: 0,
            doubleQuote: 0,
        },
        quotes: false,
    };

    while (pos < len) {
        code = str.charCodeAt(pos);
        switch (code) {
        case SPACE:
        case TAB:
        case CR:
        case FEED:
            next = pos;
            do {
                next += 1;
                code = str.charCodeAt(next);
            } while (code === SPACE   ||
                     code === NEWLINE ||
                     code === TAB     ||
                     code === CR      ||
                     code === FEED);

            ast.nodes.push({
                type: 'space',
                value: str.slice(pos, next),
            });
            pos = next - 1;
            break;
        case SINGLE_QUOTE:
            ast.nodes.push(T_SINGLE_QUOTE);
            ast.types[C_SINGLE_QUOTE]++;
            ast.quotes = true;
            break;
        case DOUBLE_QUOTE:
            ast.nodes.push(T_DOUBLE_QUOTE);
            ast.types[C_DOUBLE_QUOTE]++;
            ast.quotes = true;
            break;
        case BACKSLASH:
            next = pos + 1;
            if (str.charCodeAt(next) === SINGLE_QUOTE) {
                ast.nodes.push(T_ESCAPED_SINGLE_QUOTE);
                ast.types[C_ESCAPED_SINGLE_QUOTE]++;
                ast.quotes = true;
                pos = next;
                break;
            } else if (str.charCodeAt(next) === DOUBLE_QUOTE) {
                ast.nodes.push(T_ESCAPED_DOUBLE_QUOTE);
                ast.types[C_ESCAPED_DOUBLE_QUOTE]++;
                ast.quotes = true;
                pos = next;
                break;
            } else if (str.charCodeAt(next) === NEWLINE) {
                ast.nodes.push(T_NEWLINE);
                pos = next;
                break;
            }
            /*
             * We need to fall through here to handle the token as
             * a whole word. The missing 'break' is intentional.
             */
        default:
            WORD_END.lastIndex = pos + 1;
            WORD_END.test(str);

            if (WORD_END.lastIndex === 0) {
                next = len - 1;
            } else {
                next = WORD_END.lastIndex - 2;
            }

            value = str.slice(pos, next + 1);

            ast.nodes.push({
                type: C_STRING,
                value,
            });

            pos = next;
        }
        pos ++;
    }

    return ast;
}

function changeWrappingQuotes (node, ast) {
    const {types} = ast;
    if (types[C_SINGLE_QUOTE] || types[C_DOUBLE_QUOTE]) {
        return;
    }

    if (
        node.quote === L_SINGLE_QUOTE &&
        types[C_ESCAPED_SINGLE_QUOTE] > 0 &&
        !types[C_ESCAPED_DOUBLE_QUOTE]
    ) {
        node.quote = L_DOUBLE_QUOTE;
    }

    if (
        node.quote === L_DOUBLE_QUOTE &&
        types[C_ESCAPED_DOUBLE_QUOTE] > 0 &&
        !types[C_ESCAPED_SINGLE_QUOTE]
    ) {
        node.quote = L_SINGLE_QUOTE;
    }

    ast.nodes = ast.nodes.reduce((newAst, child) => {
        if (child.type === C_ESCAPED_DOUBLE_QUOTE && node.quote === L_SINGLE_QUOTE) {
            return [...newAst, T_DOUBLE_QUOTE];
        }
        if (child.type === C_ESCAPED_SINGLE_QUOTE && node.quote === L_DOUBLE_QUOTE) {
            return [...newAst, T_SINGLE_QUOTE];
        }
        return [...newAst, child];
    }, []);
}

function normalize (value, preferredQuote) {
    if (!value || !value.length) {
        return value;
    }
    return valueParser(value).walk(child => {
        if (child.type !== C_STRING) {
            return;
        }
        const ast = parse(child.value);
        if (ast.quotes) {
            changeWrappingQuotes(child, ast);
        } else if (preferredQuote === C_SINGLE) {
            child.quote = L_SINGLE_QUOTE;
        } else {
            child.quote = L_DOUBLE_QUOTE;
        }
        child.value = stringify(ast);
    }).toString();
}

const params = {
    rule: 'selector',
    decl: 'value',
    atrule: 'params',
};

export default postcss.plugin('cssnano-normalize-string', opts => {
    const {preferredQuote} = Object.assign({}, {
        preferredQuote: 'double',
    }, opts);

    return css => {
        css.walk(node => {
            const {type} = node;
            if (has(params, type)) {
                const param = params[type];
                node[param] = normalize(node[param], preferredQuote);
            }
        });
    };
});
<|endoftext|>


<commit_before>'use strict';

var moment = require('moment-timezone');
var request = require('request');
var clc = require('cli-color');
var config = require('../config');

function getBranchName() {
  if (process.env.NODE_ENV === 'production') {
    return 'master'
  } else {
    return 'staging'
  }
}

function getFilename(type) {
  var prefix = '';

  if (type === 'events') {
    prefix = 'events';
  } else {
    prefix = 'repos';
  }

  return prefix + '_archive_' + moment().format('YYYY_MM_DD_HHmmss') + '.json';

}

function getCommitMessage(type) {
  return 'data: ' + type + ' archive on ' + moment().format('DD MMM YYYY h:mm a');
}

function storeToArchives(type, callback) {
  var url = config.apiUrl + type;

  request(url, function(err, msg, response) {
    if (err) {
      console.error(clc.red('Error: Reading We Build API '));
      console.log(err);
      console.log(msg);
      callback(err);
    }

    var filename = getFilename(type);
    var uri = 'https://api.github.com/repos/' + config.archives.githubRepoFolder + type + '/v1/' + filename;
    var token = new Buffer(process.env.BOT_TOKEN.toString()).toString('base64');
    var content = new Buffer(response).toString('base64');
    var body = {
      'message': getCommitMessage(type),
      'committer': {
        'name': config.archives.committer.name,
        'email': config.archives.committer.email
      },
      'content': content,
      'branch': getBranchName(type)
    };

    request({
      method: 'PUT',
      uri: uri,
      headers: {
        'Accept': 'application/vnd.github.v3+json',
        'Content-Type': 'application/json',
        'User-Agent': 'We Build ' + config.symbol + ' Archives',
        'Authorization': 'Basic ' + token
      },
      body: JSON.stringify(body)
    },
    function(error, response) {
      if (error) {
        callback(error, null);
      } else if (response.statusCode !== 201 && response.statusCode !== 200) {
        callback(new Error(), JSON.parse(response.body).message);
      } else {
        var reply = 'Uploaded ' + filename + ' to Github ' + config.archivesRepoFolder + type + ' in branch ' + getBranchName(type);
        callback(null, reply);
      }
    })
  });

}

function update() {
  function createCallbackHandler(type) {
    return function(error, reply) {
      if (error) {
        console.error(clc.red('Error: Cannot push to We Build Archives for ' + type + ': ' + error + '. Reply: ' + reply));
      } else {
        console.log(reply);
      }
    }
  }

  storeToArchives('repos', createCallbackHandler('repos'));
  setTimeout(function() {
    storeToArchives('events', createCallbackHandler('events'))
  }, 5000)

}

exports.getBranchName = getBranchName;
exports.getFilename = getFilename;
exports.getCommitMessage = getCommitMessage;
exports.storeToArchives = storeToArchives;
exports.update = update;
<commit_msg>fix: fixed archive comment<commit_after>'use strict';

var moment = require('moment-timezone');
var request = require('request');
var clc = require('cli-color');
var config = require('../config');

function getBranchName() {
  if (process.env.NODE_ENV === 'production') {
    return 'master'
  } else {
    return 'staging'
  }
}

function getFilename(type) {
  var prefix = '';

  if (type === 'events') {
    prefix = 'events';
  } else {
    prefix = 'repos';
  }

  return prefix + '_archive_' + moment().format('YYYY_MM_DD_HHmmss') + '.json';

}

function getCommitMessage(type) {
  return 'data: ' + type + ' archive on ' + moment().format('DD MMM YYYY h:mm a');
}

function storeToArchives(type, callback) {
  var url = config.apiUrl + type;

  request(url, function(err, msg, response) {
    if (err) {
      console.error(clc.red('Error: Reading We Build API '));
      console.log(err);
      console.log(msg);
      callback(err);
    }

    var filename = getFilename(type);
    var uri = 'https://api.github.com/repos/' + config.archives.githubRepoFolder + type + '/v1/' + filename;
    var token = new Buffer(process.env.BOT_TOKEN.toString()).toString('base64');
    var content = new Buffer(response).toString('base64');
    var body = {
      'message': getCommitMessage(type),
      'committer': {
        'name': config.archives.committer.name,
        'email': config.archives.committer.email
      },
      'content': content,
      'branch': getBranchName(type)
    };

    request({
      method: 'PUT',
      uri: uri,
      headers: {
        'Accept': 'application/vnd.github.v3+json',
        'Content-Type': 'application/json',
        'User-Agent': 'We Build ' + config.symbol + ' Archives',
        'Authorization': 'Basic ' + token
      },
      body: JSON.stringify(body)
    },
    function(error, response) {
      if (error) {
        callback(error, null);
      } else if (response.statusCode !== 201 && response.statusCode !== 200) {
        callback(new Error(), JSON.parse(response.body).message);
      } else {
        var reply = 'Uploaded ' + filename + ' to Github ' + config.archives.githubRepoFolder + type + ' in branch ' + getBranchName(type);
        callback(null, reply);
      }
    })
  });

}

function update() {
  function createCallbackHandler(type) {
    return function(error, reply) {
      if (error) {
        console.error(clc.red('Error: Cannot push to We Build Archives for ' + type + ': ' + error + '. Reply: ' + reply));
      } else {
        console.log(reply);
      }
    }
  }

  storeToArchives('repos', createCallbackHandler('repos'));
  setTimeout(function() {
    storeToArchives('events', createCallbackHandler('events'))
  }, 5000)

}

exports.getBranchName = getBranchName;
exports.getFilename = getFilename;
exports.getCommitMessage = getCommitMessage;
exports.storeToArchives = storeToArchives;
exports.update = update;
<|endoftext|>


<commit_before>import {
  pick,
  isEmpty,
  isArray,
  isEqual,
  keys,
  get,
} from 'lodash';

import { functionName } from '@graphite/utils';

const graphQl = function(target) {
  const nameClass = functionName(target);
  const typesAttr = get(target.prototype, 'Types', '');
  target.prototype.nameClass = nameClass;

  target.prototype.Types = `
    interface node {
      id: ID!
    }

    type ${nameClass} implements node {
      id: ID!,
      ${typesAttr}
    }

    type Errors {
      key: String,
      message: String!,
    }

    ${get(target.prototype, 'oTypes', '')}
  `;

  target.prototype.Resolvers = Object.assign({}, target.prototype.Resolvers);

  if (target.prototype.hasOne) {
    const hasOne = {};
    hasOne[nameClass] = target.prototype.hasOne;
    Object.assign(target.prototype.Resolvers, hasOne);
  }

  if (target.prototype.hasMany) {
    const hasMany = {};
    hasMany[nameClass] = target.prototype.hasMany;
    Object.assign(target.prototype.Resolvers, hasMany);
  }

  if (target.prototype.create || target.prototype.update || target.prototype.remove) {
    const responseTypes = {};
    responseTypes[`response${nameClass}`] = {};
    responseTypes[`response${nameClass}`][nameClass.toLowerCase()] = function(model = {}) {
      return isEmpty(pick(model, Object.keys(get(target.prototype, 'schema', {})))) ? null : model;
    };
    responseTypes[`response${nameClass}`].errors = function(errors) {
      const requiredKeys = ['key', 'message'];
      if (isArray(errors) && isEqual(keys(errors[0]), requiredKeys)) {
        return errors;
      }
      return null;
    };
    Object.assign(target.prototype.Resolvers, responseTypes);

    target.prototype.Types += `
      type response${nameClass} {
        ${nameClass.toLowerCase()}: ${nameClass},
        errors: [Errors],
      }
    `;
  }

  if (target.prototype.create) {
    target.prototype.Types += `
      input create${nameClass} {
        ${target.prototype.createTypes}
      }
    `;
    target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}
                                 ${target.prototype.create(nameClass)}`;
  }

  if (target.prototype.update) {
    target.prototype.Types += `
      input update${nameClass} {
        ${target.prototype.updateTypes}
      }
    `;
    target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}
                                 ${target.prototype.update(nameClass)}`;
  }

  if (target.prototype.remove) {
    target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}
                                 ${target.prototype.remove(nameClass)}`;
  }

  target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}`;
  target.prototype.Types = `${get(target.prototype, 'Types', '')}`;
  target.prototype.Query = `${get(target.prototype, 'Query', '')}`;
};

export default graphQl;
<commit_msg>remove error type and interface node<commit_after>import {
  pick,
  isEmpty,
  isArray,
  isEqual,
  keys,
  get,
} from 'lodash';

import { functionName } from '@graphite/utils';

const graphQl = function(target) {
  const nameClass = functionName(target);
  const typesAttr = get(target.prototype, 'Types', '');
  target.prototype.nameClass = nameClass;

  target.prototype.Types = `
    type ${nameClass} implements node {
      id: ID!,
      ${typesAttr}
    }

    ${get(target.prototype, 'oTypes', '')}
  `;

  target.prototype.Resolvers = Object.assign({}, target.prototype.Resolvers);

  if (target.prototype.hasOne) {
    const hasOne = {};
    hasOne[nameClass] = target.prototype.hasOne;
    Object.assign(target.prototype.Resolvers, hasOne);
  }

  if (target.prototype.hasMany) {
    const hasMany = {};
    hasMany[nameClass] = target.prototype.hasMany;
    Object.assign(target.prototype.Resolvers, hasMany);
  }

  if (target.prototype.create || target.prototype.update || target.prototype.remove) {
    const responseTypes = {};
    responseTypes[`response${nameClass}`] = {};
    responseTypes[`response${nameClass}`][nameClass.toLowerCase()] = function(model = {}) {
      return isEmpty(pick(model, Object.keys(get(target.prototype, 'schema', {})))) ? null : model;
    };
    responseTypes[`response${nameClass}`].errors = function(errors) {
      const requiredKeys = ['key', 'message'];
      if (isArray(errors) && isEqual(keys(errors[0]), requiredKeys)) {
        return errors;
      }
      return null;
    };
    Object.assign(target.prototype.Resolvers, responseTypes);

    target.prototype.Types += `
      type response${nameClass} {
        ${nameClass.toLowerCase()}: ${nameClass},
        errors: [Errors],
      }
    `;
  }

  if (target.prototype.create) {
    target.prototype.Types += `
      input create${nameClass} {
        ${target.prototype.createTypes}
      }
    `;
    target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}
                                 ${target.prototype.create(nameClass)}`;
  }

  if (target.prototype.update) {
    target.prototype.Types += `
      input update${nameClass} {
        ${target.prototype.updateTypes}
      }
    `;
    target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}
                                 ${target.prototype.update(nameClass)}`;
  }

  if (target.prototype.remove) {
    target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}
                                 ${target.prototype.remove(nameClass)}`;
  }

  target.prototype.Mutation = `${get(target.prototype, 'Mutation', '')}`;
  target.prototype.Types = `${get(target.prototype, 'Types', '')}`;
  target.prototype.Query = `${get(target.prototype, 'Query', '')}`;
};

export default graphQl;
<|endoftext|>