<commit_before>// XXX docs

// Things we explicitly do NOT support:
//    - heterogenous arrays

var currentArgumentChecker = new Meteor.EnvironmentVariable;

/**
 * @summary Check that a value matches a [pattern](#matchpatterns).
 * If the value does not match the pattern, throw a `Match.Error`.
 *
 * Particularly useful to assert that arguments to a function have the right
 * types and structure.
 * @locus Anywhere
 * @param {Any} value The value to check
 * @param {MatchPattern} pattern The pattern to match
 * `value` against
 */
check = function (value, pattern) {
  // Record that check got called, if somebody cared.
  //
  // We use getOrNullIfOutsideFiber so that it's OK to call check()
  // from non-Fiber server contexts; the downside is that if you forget to
  // bindEnvironment on some random callback in your method/publisher,
  // it might not find the argumentChecker and you'll get an error about
  // not checking an argument that it looks like you're checking (instead
  // of just getting a "Node code must run in a Fiber" error).
  var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber();
  if (argChecker)
    argChecker.checking(value);
  try {
    checkSubtree(value, pattern);
  } catch (err) {
    if ((err instanceof Match.Error) && err.path)
      err.message += " in field " + err.path;
    throw err;
  }
};

/**
 * @namespace Match
 * @summary The namespace for all Match types and methods.
 */
Match = {
  Optional: function (pattern) {
    return new Optional(pattern);
  },
  OneOf: function (/*arguments*/) {
    return new OneOf(_.toArray(arguments));
  },
  Any: ['__any__'],
  Where: function (condition) {
    return new Where(condition);
  },
  ObjectIncluding: function (pattern) {
    return new ObjectIncluding(pattern);
  },
  ObjectWithValues: function (pattern) {
    return new ObjectWithValues(pattern);
  },
  // Matches only signed 32-bit integers
  Integer: ['__integer__'],

  // XXX matchers should know how to describe themselves for errors
  Error: Meteor.makeErrorType("Match.Error", function (msg) {
    this.message = "Match error: " + msg;
    // The path of the value that failed to match. Initially empty, this gets
    // populated by catching and rethrowing the exception as it goes back up the
    // stack.
    // E.g.: "vals[3].entity.created"
    this.path = "";
    // If this gets sent over DDP, don't give full internal details but at least
    // provide something better than 500 Internal server error.
    this.sanitizedError = new Meteor.Error(400, "Match failed");
  }),

  // Tests to see if value matches pattern. Unlike check, it merely returns true
  // or false (unless an error other than Match.Error was thrown). It does not
  // interact with _failIfArgumentsAreNotAllChecked.
  // XXX maybe also implement a Match.match which returns more information about
  //     failures but without using exception handling or doing what check()
  //     does with _failIfArgumentsAreNotAllChecked and Meteor.Error conversion

  /**
   * @summary Returns true if the value matches the pattern.
   * @locus Anywhere
   * @param {Any} value The value to check
   * @param {MatchPattern} pattern The pattern to match `value` against
   */
  test: function (value, pattern) {
    try {
      checkSubtree(value, pattern);
      return true;
    } catch (e) {
      if (e instanceof Match.Error)
        return false;
      // Rethrow other errors.
      throw e;
    }
  },

  // Runs `f.apply(context, args)`. If check() is not called on every element of
  // `args` (either directly or in the first level of an array), throws an error
  // (using `description` in the message).
  //
  _failIfArgumentsAreNotAllChecked: function (f, context, args, description) {
    var argChecker = new ArgumentChecker(args, description);
    var result = currentArgumentChecker.withValue(argChecker, function () {
      return f.apply(context, args);
    });
    // If f didn't itself throw, make sure it checked all of its arguments.
    argChecker.throwUnlessAllArgumentsHaveBeenChecked();
    return result;
  }
};

var Optional = function (pattern) {
  this.pattern = pattern;
};

var OneOf = function (choices) {
  if (_.isEmpty(choices))
    throw new Error("Must provide at least one choice to Match.OneOf");
  this.choices = choices;
};

var Where = function (condition) {
  this.condition = condition;
};

var ObjectIncluding = function (pattern) {
  this.pattern = pattern;
};

var ObjectWithValues = function (pattern) {
  this.pattern = pattern;
};

var typeofChecks = [
  [String, "string"],
  [Number, "number"],
  [Boolean, "boolean"],
  // While we don't allow undefined in EJSON, this is good for optional
  // arguments with OneOf.
  [undefined, "undefined"]
];

var checkSubtree = function (value, pattern) {
  // Match anything!
  if (pattern === Match.Any)
    return;

  // Basic atomic types.
  // Do not match boxed objects (e.g. String, Boolean)
  for (var i = 0; i < typeofChecks.length; ++i) {
    if (pattern === typeofChecks[i][0]) {
      if (typeof value === typeofChecks[i][1])
        return;
      throw new Match.Error("Expected " + typeofChecks[i][1] + ", got " +
                            typeof value);
    }
  }
  if (pattern === null) {
    if (value === null)
      return;
    throw new Match.Error("Expected null, got " + EJSON.stringify(value));
  }

  // Strings, numbers, and booleans match literally. Goes well with Match.OneOf.
  if (typeof pattern === "string" || typeof pattern === "number" || typeof pattern === "boolean") {
    if (value === pattern)
      return;
    throw new Match.Error("Expected " + pattern + ", got " +
                          EJSON.stringify(value));
  }

  // Match.Integer is special type encoded with array
  if (pattern === Match.Integer) {
    // There is no consistent and reliable way to check if variable is a 64-bit
    // integer. One of the popular solutions is to get reminder of division by 1
    // but this method fails on really large floats with big precision.
    // E.g.: 1.348192308491824e+23 % 1 === 0 in V8
    // Bitwise operators work consistantly but always cast variable to 32-bit
    // signed integer according to JavaScript specs.
    if (typeof value === "number" && (value | 0) === value)
      return
    throw new Match.Error("Expected Integer, got "
                + (value instanceof Object ? EJSON.stringify(value) : value));
  }

  // "Object" is shorthand for Match.ObjectIncluding({});
  if (pattern === Object)
    pattern = Match.ObjectIncluding({});

  // Array (checked AFTER Any, which is implemented as an Array).
  if (pattern instanceof Array) {
    if (pattern.length !== 1)
      throw Error("Bad pattern: arrays must have one type element" +
                  EJSON.stringify(pattern));
    if (!_.isArray(value) && !_.isArguments(value)) {
      throw new Match.Error("Expected array, got " + EJSON.stringify(value));
    }

    _.each(value, function (valueElement, index) {
      try {
        checkSubtree(valueElement, pattern[0]);
      } catch (err) {
        if (err instanceof Match.Error) {
          err.path = _prependPath(index, err.path);
        }
        throw err;
      }
    });
    return;
  }

  // Arbitrary validation checks. The condition can return false or throw a
  // Match.Error (ie, it can internally use check()) to fail.
  if (pattern instanceof Where) {
    if (pattern.condition(value))
      return;
    // XXX this error is terrible
    throw new Match.Error("Failed Match.Where validation");
  }

  if (pattern instanceof Optional)
    pattern = Match.OneOf(undefined, pattern.pattern);

  if (pattern instanceof OneOf) {
    for (var i = 0; i < pattern.choices.length; ++i) {
      try {
        checkSubtree(value, pattern.choices[i]);
        // No error? Yay, return.
        return;
      } catch (err) {
        // Other errors should be thrown. Match errors just mean try another
        // choice.
        if (!(err instanceof Match.Error))
          throw err;
      }
    }
    // XXX this error is terrible
    throw new Match.Error("Failed Match.OneOf or Match.Optional validation");
  }

  // A function that isn't something we special-case is assumed to be a
  // constructor.
  if (pattern instanceof Function) {
    if (value instanceof pattern)
      return;
    throw new Match.Error("Expected " + (pattern.name ||
                                         "particular constructor"));
  }

  var unknownKeysAllowed = false;
  var unknownKeyPattern;
  if (pattern instanceof ObjectIncluding) {
    unknownKeysAllowed = true;
    pattern = pattern.pattern;
  }
  if (pattern instanceof ObjectWithValues) {
    unknownKeysAllowed = true;
    unknownKeyPattern = [pattern.pattern];
    pattern = {};  // no required keys
  }

  if (typeof pattern !== "object")
    throw Error("Bad pattern: unknown pattern type");

  // An object, with required and optional keys. Note that this does NOT do
  // structural matches against objects of special types that happen to match
  // the pattern: this really needs to be a plain old {Object}!
  if (typeof value !== 'object')
    throw new Match.Error("Expected object, got " + typeof value);
  if (value === null)
    throw new Match.Error("Expected object, got null");
  if (value.constructor !== Object)
    throw new Match.Error("Expected plain object");

  var requiredPatterns = {};
  var optionalPatterns = {};
  _.each(pattern, function (subPattern, key) {
    if (subPattern instanceof Optional)
      optionalPatterns[key] = subPattern.pattern;
    else
      requiredPatterns[key] = subPattern;
  });

  _.each(value, function (subValue, key) {
    try {
      if (_.has(requiredPatterns, key)) {
        checkSubtree(subValue, requiredPatterns[key]);
        delete requiredPatterns[key];
      } else if (_.has(optionalPatterns, key)) {
        checkSubtree(subValue, optionalPatterns[key]);
      } else {
        if (!unknownKeysAllowed)
          throw new Match.Error("Unknown key");
        if (unknownKeyPattern) {
          checkSubtree(subValue, unknownKeyPattern[0]);
        }
      }
    } catch (err) {
      if (err instanceof Match.Error)
        err.path = _prependPath(key, err.path);
      throw err;
    }
  });

  _.each(requiredPatterns, function (subPattern, key) {
    throw new Match.Error("Missing key '" + key + "'");
  });
};

var ArgumentChecker = function (args, description) {
  var self = this;
  // Make a SHALLOW copy of the arguments. (We'll be doing identity checks
  // against its contents.)
  self.args = _.clone(args);
  // Since the common case will be to check arguments in order, and we splice
  // out arguments when we check them, make it so we splice out from the end
  // rather than the beginning.
  self.args.reverse();
  self.description = description;
};

_.extend(ArgumentChecker.prototype, {
  checking: function (value) {
    var self = this;
    if (self._checkingOneValue(value))
      return;
    // Allow check(arguments, [String]) or check(arguments.slice(1), [String])
    // or check([foo, bar], [String]) to count... but only if value wasn't
    // itself an argument.
    if (_.isArray(value) || _.isArguments(value)) {
      _.each(value, _.bind(self._checkingOneValue, self));
    }
  },
  _checkingOneValue: function (value) {
    var self = this;
    for (var i = 0; i < self.args.length; ++i) {
      // Is this value one of the arguments? (This can have a false positive if
      // the argument is an interned primitive, but it's still a good enough
      // check.)
      // (NaN is not === to itself, so we have to check specially.)
      if (value === self.args[i] || (_.isNaN(value) && _.isNaN(self.args[i]))) {
        self.args.splice(i, 1);
        return true;
      }
    }
    return false;
  },
  throwUnlessAllArgumentsHaveBeenChecked: function () {
    var self = this;
    if (!_.isEmpty(self.args))
      throw new Error("Did not check() all arguments during " +
                      self.description);
  }
});

var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case",
  "else", "enum", "eval", "false", "null", "this", "true", "void", "with",
  "break", "catch", "class", "const", "super", "throw", "while", "yield",
  "delete", "export", "import", "public", "return", "static", "switch",
  "typeof", "default", "extends", "finally", "package", "private", "continue",
  "debugger", "function", "arguments", "interface", "protected", "implements",
  "instanceof"];

// Assumes the base of path is already escaped properly
// returns key + base
var _prependPath = function (key, base) {
  if ((typeof key) === "number" || key.match(/^[0-9]+$/))
    key = "[" + key + "]";
  else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _.contains(_jsKeywords, key))
    key = JSON.stringify([key]);

  if (base && base[0] !== "[")
    return key + '.' + base;
  return key + base;
};

<commit_msg>Return instead of throw internally in the check package<commit_after>// XXX docs

// Things we explicitly do NOT support:
//    - heterogenous arrays

var currentArgumentChecker = new Meteor.EnvironmentVariable;

/**
 * @summary Check that a value matches a [pattern](#matchpatterns).
 * If the value does not match the pattern, throw a `Match.Error`.
 *
 * Particularly useful to assert that arguments to a function have the right
 * types and structure.
 * @locus Anywhere
 * @param {Any} value The value to check
 * @param {MatchPattern} pattern The pattern to match
 * `value` against
 */
check = function (value, pattern) {
  // Record that check got called, if somebody cared.
  //
  // We use getOrNullIfOutsideFiber so that it's OK to call check()
  // from non-Fiber server contexts; the downside is that if you forget to
  // bindEnvironment on some random callback in your method/publisher,
  // it might not find the argumentChecker and you'll get an error about
  // not checking an argument that it looks like you're checking (instead
  // of just getting a "Node code must run in a Fiber" error).
  var argChecker = currentArgumentChecker.getOrNullIfOutsideFiber();
  if (argChecker)
    argChecker.checking(value);
  var result = testSubtree(value, pattern);
  if (result) {
    var err = new Match.Error(result.message);
    if (result.path) {
      err.message += " in field " + result.path;
      err.path = result.path;
    }
    throw err;
  }
};

/**
 * @namespace Match
 * @summary The namespace for all Match types and methods.
 */
Match = {
  Optional: function (pattern) {
    return new Optional(pattern);
  },
  OneOf: function (/*arguments*/) {
    return new OneOf(_.toArray(arguments));
  },
  Any: ['__any__'],
  Where: function (condition) {
    return new Where(condition);
  },
  ObjectIncluding: function (pattern) {
    return new ObjectIncluding(pattern);
  },
  ObjectWithValues: function (pattern) {
    return new ObjectWithValues(pattern);
  },
  // Matches only signed 32-bit integers
  Integer: ['__integer__'],

  // XXX matchers should know how to describe themselves for errors
  Error: Meteor.makeErrorType("Match.Error", function (msg) {
    this.message = "Match error: " + msg;
    // The path of the value that failed to match. Initially empty, this gets
    // populated by catching and rethrowing the exception as it goes back up the
    // stack.
    // E.g.: "vals[3].entity.created"
    this.path = "";
    // If this gets sent over DDP, don't give full internal details but at least
    // provide something better than 500 Internal server error.
    this.sanitizedError = new Meteor.Error(400, "Match failed");
  }),

  // Tests to see if value matches pattern. Unlike check, it merely returns true
  // or false (unless an error other than Match.Error was thrown). It does not
  // interact with _failIfArgumentsAreNotAllChecked.
  // XXX maybe also implement a Match.match which returns more information about
  //     failures but without using exception handling or doing what check()
  //     does with _failIfArgumentsAreNotAllChecked and Meteor.Error conversion

  /**
   * @summary Returns true if the value matches the pattern.
   * @locus Anywhere
   * @param {Any} value The value to check
   * @param {MatchPattern} pattern The pattern to match `value` against
   */
  test: function (value, pattern) {
    return !testSubtree(value, pattern);
  },

  // Runs `f.apply(context, args)`. If check() is not called on every element of
  // `args` (either directly or in the first level of an array), throws an error
  // (using `description` in the message).
  //
  _failIfArgumentsAreNotAllChecked: function (f, context, args, description) {
    var argChecker = new ArgumentChecker(args, description);
    var result = currentArgumentChecker.withValue(argChecker, function () {
      return f.apply(context, args);
    });
    // If f didn't itself throw, make sure it checked all of its arguments.
    argChecker.throwUnlessAllArgumentsHaveBeenChecked();
    return result;
  }
};

var Optional = function (pattern) {
  this.pattern = pattern;
};

var OneOf = function (choices) {
  if (_.isEmpty(choices))
    throw new Error("Must provide at least one choice to Match.OneOf");
  this.choices = choices;
};

var Where = function (condition) {
  this.condition = condition;
};

var ObjectIncluding = function (pattern) {
  this.pattern = pattern;
};

var ObjectWithValues = function (pattern) {
  this.pattern = pattern;
};

var typeofChecks = [
  [String, "string"],
  [Number, "number"],
  [Boolean, "boolean"],
  // While we don't allow undefined in EJSON, this is good for optional
  // arguments with OneOf.
  [undefined, "undefined"]
];

// Return `false` if it matches. Otherwise, return an object with a `message` and a `path` field.
var testSubtree = function (value, pattern) {
  // Match anything!
  if (pattern === Match.Any)
    return false;

  // Basic atomic types.
  // Do not match boxed objects (e.g. String, Boolean)
  for (var i = 0; i < typeofChecks.length; ++i) {
    if (pattern === typeofChecks[i][0]) {
      if (typeof value === typeofChecks[i][1])
        return false;
      return {
        message: "Expected " + typeofChecks[i][1] + ", got " + typeof value,
        path: ""
      };
    }
  }
  if (pattern === null) {
    if (value === null)
      return false;
    return {
      message: "Expected null, got " + EJSON.stringify(value),
      path: ""
    };
  }

  // Strings, numbers, and booleans match literally. Goes well with Match.OneOf.
  if (typeof pattern === "string" || typeof pattern === "number" || typeof pattern === "boolean") {
    if (value === pattern)
      return false;
    return {
      message: "Expected " + pattern + ", got " + EJSON.stringify(value),
      path: ""
    };
  }

  // Match.Integer is special type encoded with array
  if (pattern === Match.Integer) {
    // There is no consistent and reliable way to check if variable is a 64-bit
    // integer. One of the popular solutions is to get reminder of division by 1
    // but this method fails on really large floats with big precision.
    // E.g.: 1.348192308491824e+23 % 1 === 0 in V8
    // Bitwise operators work consistantly but always cast variable to 32-bit
    // signed integer according to JavaScript specs.
    if (typeof value === "number" && (value | 0) === value)
      return false;
    return {
      message: "Expected Integer, got " + (value instanceof Object ? EJSON.stringify(value) : value),
      path: ""
    };
  }

  // "Object" is shorthand for Match.ObjectIncluding({});
  if (pattern === Object)
    pattern = Match.ObjectIncluding({});

  // Array (checked AFTER Any, which is implemented as an Array).
  if (pattern instanceof Array) {
    if (pattern.length !== 1) {
      return {
        message: "Bad pattern: arrays must have one type element" + EJSON.stringify(pattern),
        path: ""
      };
    }
    if (!_.isArray(value) && !_.isArguments(value)) {
      return {
        message: "Expected array, got " + EJSON.stringify(value),
        path: ""
      };
    }

    for (var i = 0, length = value.length; i < length; i++) {
      var result = testSubtree(value[i], pattern[0]);
      if (result) {
        result.path = _prependPath(i, result.path);
        return result;
      }
    }
    return false;
  }

  // Arbitrary validation checks. The condition can return false or throw a
  // Match.Error (ie, it can internally use check()) to fail.
  if (pattern instanceof Where) {
    var result;
    try {
      result = pattern.condition(value);
    } catch (err) {
      if (!(err instanceof Match.Error))
        throw err;
      return {
        message: err.message,
        path: err.path
      };
    }
    if (pattern.condition(value))
      return false;
    // XXX this error is terrible
    return {
      message: "Failed Match.Where validation",
      path: ""
    };
  }

  if (pattern instanceof Optional)
    pattern = Match.OneOf(undefined, pattern.pattern);

  if (pattern instanceof OneOf) {
    for (var i = 0; i < pattern.choices.length; ++i) {
      var result = testSubtree(value, pattern.choices[i]);
      if (!result) {
        // No error? Yay, return.
        return false;
      }
      // Match errors just mean try another choice.
    }
    // XXX this error is terrible
    return {
      message: "Failed Match.OneOf or Match.Optional validation",
      path: ""
    };
  }

  // A function that isn't something we special-case is assumed to be a
  // constructor.
  if (pattern instanceof Function) {
    if (value instanceof pattern)
      return false;
    return {
      message: "Expected " + (pattern.name ||"particular constructor"),
      path: ""
    };
  }

  var unknownKeysAllowed = false;
  var unknownKeyPattern;
  if (pattern instanceof ObjectIncluding) {
    unknownKeysAllowed = true;
    pattern = pattern.pattern;
  }
  if (pattern instanceof ObjectWithValues) {
    unknownKeysAllowed = true;
    unknownKeyPattern = [pattern.pattern];
    pattern = {};  // no required keys
  }

  if (typeof pattern !== "object") {
    return {
      message: "Bad pattern: unknown pattern type",
      path: ""
    };
  }

  // An object, with required and optional keys. Note that this does NOT do
  // structural matches against objects of special types that happen to match
  // the pattern: this really needs to be a plain old {Object}!
  if (typeof value !== 'object') {
    return {
      message: "Expected object, got " + typeof value,
      path: ""
    };
  }
  if (value === null) {
    return {
      message: "Expected object, got null",
      path: ""
    };
  }
  if (value.constructor !== Object) {
    return {
      message: "Expected plain object",
      path: ""
    };
  }

  var requiredPatterns = {};
  var optionalPatterns = {};
  _.each(pattern, function (subPattern, key) {
    if (subPattern instanceof Optional)
      optionalPatterns[key] = subPattern.pattern;
    else
      requiredPatterns[key] = subPattern;
  });

  for (var keys = _.keys(value), i = 0, length = keys.length; i < length; i++) {
    var key = keys[i];
    var subValue = value[key];
    if (_.has(requiredPatterns, key)) {
      var result = testSubtree(subValue, requiredPatterns[key]);
      if (result) {
        result.path = _prependPath(key, result.path);
        return result;
      }
      delete requiredPatterns[key];
    } else if (_.has(optionalPatterns, key)) {
      var result = testSubtree(subValue, optionalPatterns[key]);
      if (result) {
        result.path = _prependPath(key, result.path);
        return result;
      }
    } else {
      if (!unknownKeysAllowed) {
        return {
          message: "Unknown key",
          path: key
        };
      }
      if (unknownKeyPattern) {
        var result = testSubtree(subValue, unknownKeyPattern[0]);
        if (result) {
          result.path = _prependPath(key, result.path);
          return result;
        }
      }
    }
  }

  var keys = _.keys(requiredPatterns);
  if (keys.length) {
    return {
      message: "Missing key '" + keys[0] + "'",
      path: ""
    };
  }
};

var ArgumentChecker = function (args, description) {
  var self = this;
  // Make a SHALLOW copy of the arguments. (We'll be doing identity checks
  // against its contents.)
  self.args = _.clone(args);
  // Since the common case will be to check arguments in order, and we splice
  // out arguments when we check them, make it so we splice out from the end
  // rather than the beginning.
  self.args.reverse();
  self.description = description;
};

_.extend(ArgumentChecker.prototype, {
  checking: function (value) {
    var self = this;
    if (self._checkingOneValue(value))
      return;
    // Allow check(arguments, [String]) or check(arguments.slice(1), [String])
    // or check([foo, bar], [String]) to count... but only if value wasn't
    // itself an argument.
    if (_.isArray(value) || _.isArguments(value)) {
      _.each(value, _.bind(self._checkingOneValue, self));
    }
  },
  _checkingOneValue: function (value) {
    var self = this;
    for (var i = 0; i < self.args.length; ++i) {
      // Is this value one of the arguments? (This can have a false positive if
      // the argument is an interned primitive, but it's still a good enough
      // check.)
      // (NaN is not === to itself, so we have to check specially.)
      if (value === self.args[i] || (_.isNaN(value) && _.isNaN(self.args[i]))) {
        self.args.splice(i, 1);
        return true;
      }
    }
    return false;
  },
  throwUnlessAllArgumentsHaveBeenChecked: function () {
    var self = this;
    if (!_.isEmpty(self.args))
      throw new Error("Did not check() all arguments during " +
                      self.description);
  }
});

var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case",
  "else", "enum", "eval", "false", "null", "this", "true", "void", "with",
  "break", "catch", "class", "const", "super", "throw", "while", "yield",
  "delete", "export", "import", "public", "return", "static", "switch",
  "typeof", "default", "extends", "finally", "package", "private", "continue",
  "debugger", "function", "arguments", "interface", "protected", "implements",
  "instanceof"];

// Assumes the base of path is already escaped properly
// returns key + base
var _prependPath = function (key, base) {
  if ((typeof key) === "number" || key.match(/^[0-9]+$/))
    key = "[" + key + "]";
  else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _.contains(_jsKeywords, key))
    key = JSON.stringify([key]);

  if (base && base[0] !== "[")
    return key + '.' + base;
  return key + base;
};

<|endoftext|>


<commit_before>const path = require('path');

module.exports = {
  lang: [
    'en', // default language used
    'ja'
  ],
  // Environmental variable / preset to use
  env: 'pl',
  buildDir: '../../www/pattern-lab/build',
  wwwDir: '../../www/',
  startPath: 'pattern-lab/index.html',
  plConfigFile: './config/config.yml',
  verbosity: 1,
  schemaErrorReporting: 'console',
  webpackDevServer: true,
  extraTwigNamespaces: {
    'bolt': {
      recursive: true,
    },
    'pl': {
      recursive: true,
      paths: [
        './src',
        /* Example of including additional component paths to include in the main @bolt namespace */
        // path.relative(process.cwd(), path.dirname(require.resolve('@bolt/components-sticky/package.json'))),
      ],
    },
    /* Example of including a new component namesapce config / overriding an existing config */
    // 'bolt-components-sticky': {
    //   recursive: true,
    //   paths: [
    //     path.relative(process.cwd(), path.dirname(require.resolve('@bolt/components-sticky/package.json'))),
    //   ],
    // },
    'bolt-assets': {
      recursive: true,
      paths: ['../../www/pattern-lab/build'],
    },
    utils: {
      recursive: true,
      paths: ['src/_includes'],
    },
  },
  images: {
    sets: [
      {
        base: './src/assets/images',
        glob: '**',
        dist: '../../www/pattern-lab/images/',
      },
    ],
  },

  // Currently only supports a 'scss' key with an array of Sass partials to pull in.
  globalData: {
    'scss': [
      // './src/test-overrides.scss' // example of including an additional Sass partial to set / redefine additional default values, globally.
    ],
    'js': [
      // './src/global-data.js', // example of including a JS files with a default export to globally include extra data to all Bolt JS components
    ]
  },

  components: {
    global: [
      '@bolt/core',
      '@bolt/global',
      '@bolt/components-placeholder',
      '@bolt/components-action-blocks',
      '@bolt/components-dropdown',
      '@bolt/components-background',
      '@bolt/components-background-shapes',
      '@bolt/components-band',
      '@bolt/components-block-list',
      '@bolt/components-blockquote',
      '@bolt/components-breadcrumb',
      '@bolt/components-button',
      '@bolt/components-button-group',
      '@bolt/components-card',
      '@bolt/components-chip',
      '@bolt/components-chip-list',
      '@bolt/components-code-snippet',
      '@bolt/components-copy-to-clipboard',
      '@bolt/components-device-viewer',
      '@bolt/components-figure',
      '@bolt/components-form',
      '@bolt/components-headline',
      '@bolt/components-icon',
      '@bolt/components-icons',
      '@bolt/components-image',
      '@bolt/components-link',
      '@bolt/components-nav-indicator',
      '@bolt/components-nav-priority',
      '@bolt/components-navbar',
      '@bolt/components-navlink',
      '@bolt/components-logo',
      '@bolt/components-ordered-list',
      '@bolt/components-page-footer',
      '@bolt/components-page-header',
      '@bolt/components-pagination',
      '@bolt/components-share',
      '@bolt/components-site',
      '@bolt/components-smooth-scroll',
      '@bolt/components-sticky',
      '@bolt/components-teaser',
      '@bolt/components-text',
      '@bolt/components-tooltip',
      '@bolt/components-unordered-list',
      '@bolt/components-video',
    ],
    individual: [
      {
        name: 'pl',
        scss: './src/styles/pl.scss',
        js: './src/scripts/pl.js',
      },
      '@bolt/components-critical-fonts',
    ],
  },
};
<commit_msg>tests: Re-enable louder schema validation<commit_after>const path = require('path');

module.exports = {
  lang: [
    'en', // default language used
    'ja'
  ],
  // Environmental variable / preset to use
  env: 'pl',
  buildDir: '../../www/pattern-lab/build',
  wwwDir: '../../www/',
  startPath: 'pattern-lab/index.html',
  plConfigFile: './config/config.yml',
  verbosity: 1,
  schemaErrorReporting: 'cli',
  webpackDevServer: true,
  extraTwigNamespaces: {
    'bolt': {
      recursive: true,
    },
    'pl': {
      recursive: true,
      paths: [
        './src',
        /* Example of including additional component paths to include in the main @bolt namespace */
        // path.relative(process.cwd(), path.dirname(require.resolve('@bolt/components-sticky/package.json'))),
      ],
    },
    /* Example of including a new component namesapce config / overriding an existing config */
    // 'bolt-components-sticky': {
    //   recursive: true,
    //   paths: [
    //     path.relative(process.cwd(), path.dirname(require.resolve('@bolt/components-sticky/package.json'))),
    //   ],
    // },
    'bolt-assets': {
      recursive: true,
      paths: ['../../www/pattern-lab/build'],
    },
    utils: {
      recursive: true,
      paths: ['src/_includes'],
    },
  },
  images: {
    sets: [
      {
        base: './src/assets/images',
        glob: '**',
        dist: '../../www/pattern-lab/images/',
      },
    ],
  },

  // Currently only supports a 'scss' key with an array of Sass partials to pull in.
  globalData: {
    'scss': [
      // './src/test-overrides.scss' // example of including an additional Sass partial to set / redefine additional default values, globally.
    ],
    'js': [
      // './src/global-data.js', // example of including a JS files with a default export to globally include extra data to all Bolt JS components
    ]
  },

  components: {
    global: [
      '@bolt/core',
      '@bolt/global',
      '@bolt/components-placeholder',
      '@bolt/components-action-blocks',
      '@bolt/components-dropdown',
      '@bolt/components-background',
      '@bolt/components-background-shapes',
      '@bolt/components-band',
      '@bolt/components-block-list',
      '@bolt/components-blockquote',
      '@bolt/components-breadcrumb',
      '@bolt/components-button',
      '@bolt/components-button-group',
      '@bolt/components-card',
      '@bolt/components-chip',
      '@bolt/components-chip-list',
      '@bolt/components-code-snippet',
      '@bolt/components-copy-to-clipboard',
      '@bolt/components-device-viewer',
      '@bolt/components-figure',
      '@bolt/components-form',
      '@bolt/components-headline',
      '@bolt/components-icon',
      '@bolt/components-icons',
      '@bolt/components-image',
      '@bolt/components-link',
      '@bolt/components-nav-indicator',
      '@bolt/components-nav-priority',
      '@bolt/components-navbar',
      '@bolt/components-navlink',
      '@bolt/components-logo',
      '@bolt/components-ordered-list',
      '@bolt/components-page-footer',
      '@bolt/components-page-header',
      '@bolt/components-pagination',
      '@bolt/components-share',
      '@bolt/components-site',
      '@bolt/components-smooth-scroll',
      '@bolt/components-sticky',
      '@bolt/components-teaser',
      '@bolt/components-text',
      '@bolt/components-tooltip',
      '@bolt/components-unordered-list',
      '@bolt/components-video',
    ],
    individual: [
      {
        name: 'pl',
        scss: './src/styles/pl.scss',
        js: './src/scripts/pl.js',
      },
      '@bolt/components-critical-fonts',
    ],
  },
};
<|endoftext|>


<commit_before>/*

  Since this is the first Indexer, this seems like a reasonable place
  to document the Indexer protocol:

  - an indexer gets instantiated by the Hub for each configured data
    source.

  - the Indexer's constructor arguments come from the data-source's
    `params` attribute. That is, a data source like a Git repository
    is configured by creating a record of type "data-sources", with a
    source-type attribute that points at a module like this one
    ("@cardstack/git") and a params attribute that contains the
    source-type-specific configuration (in this case, `repo`,
    `basePath`, and `branchPrefix`).

  - the indexer must implement `branches`, which returns a list of
    branch names. In the case of Git, branch literally means
    branch. But other indexers can also make use of the concept of
    branches to support multiple environments (like produciton vs
    staging databases). It's up to the indexer to discover branches
    because they may be as dynamic as desired.

  - branch names may span across data sources. For example, both Git
    and Postgres may have "proudction" branches, and both may define
    both schema and content.

  - an indexer must implement beginUpdate(branch), which returns an
    updater. The updater represents a running update action, and it's
    appropriate that it can be more stateful than an indexer.

  - an updater must implement schema(), which returns the list of
    schema records the data source would like to define. It's not
    mandatory for every data source to define schema -- it's OK to
    only contain content and rely on schema that's stored in a
    different data source. schema() is a separate method because we
    need to know the schema up front, before we can index the rest of
    the content.

  - an updater must implement updateContent(meta, hints, ops), which
    generates the actual update operations to add and remove records
    from the search index.

      - `meta` can store whatever you want, it's your place to keep
        track of how far your indexer has progressed. The value that
        you return from updateContent() will be passed into the next
        call to updateContent() as `meta`.

      - `hints` can contain a list of `{ branch, id, type }`
        references. This is intended as an optimization hint when we
        know that certain resources are the ones that likely need to
        be indexed right away. Indexers are responsible for
        discovering and indexing arbitrary upstream changes regardless
        of this hint, but the hint can make it easier to keep the
        search index nearly real-time fresh.

      - `ops` has two methods you can call: `save(type, id, doc)` and
        `delete(type, id)`. Both are async and you should await them
        (you don't need to do batching, the Hub manages that for you,
        but the methods are async because you can't necessarily
        predict when it will decide to flush).

    `updateContent` should call `ops.save` and/or `ops.delete` as many
    times as necessary, then return a new `meta` state.

  - the Hub ensures that only one indexer per data source is
    instantiated and running at a given time. You don't need to
    implement locking in the indexer.

  - multiple updaters may run in parallel (one for each branch
    name). The Hub guarantees it will call `beginUpdate` for every
    branch before it calls `updateContent` on any of them -- this
    allows you to implement your own coordination if there are
    inter-branch ordering concerns.

*/

const {
  Repository,
  Reference,
  Branch,
  Commit
} = require('nodegit');

const { safeEntryByName } = require('./mutable-tree');

const logger = require('heimdalljs-logger');

module.exports = class Indexer {
  constructor({ repo, basePath, branchPrefix }) {
    this.repoPath = repo;
    this.branchPrefix = branchPrefix || "";
    this.basePath = basePath ? basePath.split('/') : [];
    this.repo = null;
    this.log = logger('git-indexer');
  }

  async _ensureRepo() {
    if (!this.repo) {
      this.repo = await Repository.open(this.repoPath);
    }
  }

  async branches() {
    await this._ensureRepo();
    // nodegit docs show a Branch.iteratorNew method that would be
    // more appropriate than this, but as far as I can tell it is not
    // fully implemented

    let pattern = new RegExp(`^refs\/heads\/${this.branchPrefix}(.*)`);
    return (await Reference.list(this.repo)).map(entry => {
      let m = pattern.exec(entry);
      if (m) {
        return m[1];
      }
    }).filter(Boolean);
  }

  async beginUpdate(branch) {
    await this._ensureRepo();
    return new GitUpdater(this.repo, this.branchPrefix + branch, this.log, this.repoPath, this.basePath);
  }
};

class GitUpdater {
  constructor(repo, branch, log, repoPath, basePath) {
    this.repo = repo;
    this.basePath = basePath;
    this.branch = branch;
    this.commit = null;
    this.commitId = null;
    this.rootTree = null;
    this.log = log;
  }

  async schema() {
    let models = [];
    let ops = new Gather(models);
    await this._loadCommit();
    await this._indexTree(ops, null, this.rootTree, {
      only: this.basePath.concat(['schema'])
    });
    return models;
  }

  async updateContent(meta, hints, ops) {
    await this._loadCommit();
    let originalTree;
    if (meta && meta.commit) {
      try {
        let oldCommit = await Commit.lookup(this.repo, meta.commit);
        originalTree = await oldCommit.getTree();
      } catch (err) {
        this.log.warn(`Unable to load previously indexed commit ${meta.commit} due to ${err}. We will recover by reindexing all content.`);
      }
    }
    await this._indexTree(ops, originalTree, this.rootTree, {
      only: this.basePath.concat([['schema', 'contents']])
    });
    return {
      commit: this.commitId
    };
  }

  async _loadCommit() {
    if (!this.commit) {
      this.commit = await this._commitAtBranch(this.branch);
      this.commitId = this.commit.id().tostrS();
    }
    if (!this.rootTree) {
      this.rootTree = await this.commit.getTree();
    }
  }

  async _commitAtBranch(branchName) {
    let branch = await Branch.lookup(this.repo, branchName, Branch.BRANCH.LOCAL);
    return Commit.lookup(this.repo, branch.target());
  }

  async _indexTree(ops, oldTree, newTree, filter) {
    let seen = new Map();
    if (newTree) {
      for (let newEntry of newTree.entries()) {
        let name = newEntry.name();
        if (!filterAllows(filter, name)) {
          continue;
        }
        seen.set(name, true);
        await this._indexEntry(ops, name, oldTree, newEntry, filter);
      }
    }
    if (oldTree) {
      for (let oldEntry of oldTree.entries()) {
        let name = oldEntry.name();
        if (!filterAllows(filter, name)) {
          continue;
        }
        if (!seen.get(name)) {
          await this._deleteEntry(ops, oldEntry, filter);
        }
      }
    }
  }

  async _indexEntry(ops, name, oldTree, newEntry, filter) {
    let oldEntry;
    if (oldTree) {
      oldEntry = safeEntryByName(oldTree, name);
      if (oldEntry && oldEntry.id().equal(newEntry.id())) {
        // We can prune whole subtrees when we find an identical
        // entry. Which is kinda the point of Git's data
        // structure in the first place.
        return;
      }
    }
    if (newEntry.isTree()) {
      await this._indexTree(
        ops,
        oldEntry && oldEntry.isTree() ? (await oldEntry.getTree()) : null,
        await newEntry.getTree(),
        nextFilter(filter)
      );
    } else if (/\.json$/i.test(newEntry.path())) {
      let { type, id } = identify(newEntry);
      let contents = (await newEntry.getBlob()).content().toString('utf8');
      let doc;
      try {
        doc = JSON.parse(contents);
      } catch (err) {
        this.log.warn("Ignoring record with invalid json at %s", newEntry.path());
        return;
      }
      if (!doc.meta) {
        doc.meta = {};
      }

      // A note on the cardstack meta versioning protocol:
      //
      // meta.version is a point-in-time version indicator. If you
      // change and then undo, you should end up at a different
      // version than where you started. In the git data-source,
      // meta.version is the ID of a commit.
      //
      // meta.hash is a content hash. If you change and then undo, you
      // should end up back at the original meta.hash. In this git
      // data-source, this is ID of a blob.
      doc.meta.version = this.commitId;
      doc.meta.hash = newEntry.id().tostrS();
      await ops.save(type, id, doc);
    }
  }

  async _deleteEntry(ops, oldEntry, filter) {
    if (oldEntry.isTree()) {
      await this._indexTree(ops, await oldEntry.getTree(), nextFilter(filter));
    } else {
      let { type, id } = identify(oldEntry);
      await ops.delete(type, id);
    }
  }
}

function identify(entry) {
  let parts = entry.path().split('/');
  let type = parts[parts.length - 2] || 'tops';
  let filename = parts[parts.length - 1];
  let id = filename.replace(/\.json$/, '');
  return { type, id };
}

class Gather {
  constructor(models) {
    this.models = models;
  }
  save(type, id, document) {
    document.type = type;
    document.id = id;
    this.models.push(document);
  }
}

function filterAllows(filter, name) {
  return !filter || !filter.only || filter.only.length === 0 ||
    (Array.isArray(filter.only[0]) && filter.only[0].includes(name)) ||
    name === filter.only[0];
}

function nextFilter(filter) {
  if (!filter || !filter.only || filter.only.length < 2) {
    return null;
  }
  return { only: filter.only.slice(1) };
}
<commit_msg>Create git repo if it doesn't exist<commit_after>/*

  Since this is the first Indexer, this seems like a reasonable place
  to document the Indexer protocol:

  - an indexer gets instantiated by the Hub for each configured data
    source.

  - the Indexer's constructor arguments come from the data-source's
    `params` attribute. That is, a data source like a Git repository
    is configured by creating a record of type "data-sources", with a
    source-type attribute that points at a module like this one
    ("@cardstack/git") and a params attribute that contains the
    source-type-specific configuration (in this case, `repo`,
    `basePath`, and `branchPrefix`).

  - the indexer must implement `branches`, which returns a list of
    branch names. In the case of Git, branch literally means
    branch. But other indexers can also make use of the concept of
    branches to support multiple environments (like produciton vs
    staging databases). It's up to the indexer to discover branches
    because they may be as dynamic as desired.

  - branch names may span across data sources. For example, both Git
    and Postgres may have "proudction" branches, and both may define
    both schema and content.

  - an indexer must implement beginUpdate(branch), which returns an
    updater. The updater represents a running update action, and it's
    appropriate that it can be more stateful than an indexer.

  - an updater must implement schema(), which returns the list of
    schema records the data source would like to define. It's not
    mandatory for every data source to define schema -- it's OK to
    only contain content and rely on schema that's stored in a
    different data source. schema() is a separate method because we
    need to know the schema up front, before we can index the rest of
    the content.

  - an updater must implement updateContent(meta, hints, ops), which
    generates the actual update operations to add and remove records
    from the search index.

      - `meta` can store whatever you want, it's your place to keep
        track of how far your indexer has progressed. The value that
        you return from updateContent() will be passed into the next
        call to updateContent() as `meta`.

      - `hints` can contain a list of `{ branch, id, type }`
        references. This is intended as an optimization hint when we
        know that certain resources are the ones that likely need to
        be indexed right away. Indexers are responsible for
        discovering and indexing arbitrary upstream changes regardless
        of this hint, but the hint can make it easier to keep the
        search index nearly real-time fresh.

      - `ops` has two methods you can call: `save(type, id, doc)` and
        `delete(type, id)`. Both are async and you should await them
        (you don't need to do batching, the Hub manages that for you,
        but the methods are async because you can't necessarily
        predict when it will decide to flush).

    `updateContent` should call `ops.save` and/or `ops.delete` as many
    times as necessary, then return a new `meta` state.

  - the Hub ensures that only one indexer per data source is
    instantiated and running at a given time. You don't need to
    implement locking in the indexer.

  - multiple updaters may run in parallel (one for each branch
    name). The Hub guarantees it will call `beginUpdate` for every
    branch before it calls `updateContent` on any of them -- this
    allows you to implement your own coordination if there are
    inter-branch ordering concerns.

*/

const {
  Repository,
  Reference,
  Branch,
  Commit
} = require('nodegit');

const Change = require("./change");

const { safeEntryByName } = require('./mutable-tree');

const logger = require('heimdalljs-logger');

module.exports = class Indexer {
  constructor({ repo, basePath, branchPrefix }) {
    this.repoPath = repo;
    this.branchPrefix = branchPrefix || "";
    this.basePath = basePath ? basePath.split('/') : [];
    this.repo = null;
    this.log = logger('git-indexer');
  }

  async _ensureRepo() {
    if (!this.repo) {
      try {
        this.repo = await Repository.open(this.repoPath);
      } catch (e) {
        if (/Failed to resolve path/.test(e.message)) {
          let change = await Change.createInitial(this.repoPath, 'master');
          this.repo = change.repo;

          await change.finalize({
            message: 'First commit',
            authorName: 'Cardstack Hub',
            authorEmail: 'hub@cardstack.com',
          });
        } else {
          throw e;
        }
      }
    }
  }

  async branches() {
    await this._ensureRepo();
    // nodegit docs show a Branch.iteratorNew method that would be
    // more appropriate than this, but as far as I can tell it is not
    // fully implemented

    let pattern = new RegExp(`^refs\/heads\/${this.branchPrefix}(.*)`);
    return (await Reference.list(this.repo)).map(entry => {
      let m = pattern.exec(entry);
      if (m) {
        return m[1];
      }
    }).filter(Boolean);
  }

  async beginUpdate(branch) {
    await this._ensureRepo();
    return new GitUpdater(this.repo, this.branchPrefix + branch, this.log, this.repoPath, this.basePath);
  }
};

class GitUpdater {
  constructor(repo, branch, log, repoPath, basePath) {
    this.repo = repo;
    this.basePath = basePath;
    this.branch = branch;
    this.commit = null;
    this.commitId = null;
    this.rootTree = null;
    this.log = log;
  }

  async schema() {
    let models = [];
    let ops = new Gather(models);
    await this._loadCommit();
    await this._indexTree(ops, null, this.rootTree, {
      only: this.basePath.concat(['schema'])
    });
    return models;
  }

  async updateContent(meta, hints, ops) {
    await this._loadCommit();
    let originalTree;
    if (meta && meta.commit) {
      try {
        let oldCommit = await Commit.lookup(this.repo, meta.commit);
        originalTree = await oldCommit.getTree();
      } catch (err) {
        this.log.warn(`Unable to load previously indexed commit ${meta.commit} due to ${err}. We will recover by reindexing all content.`);
      }
    }
    await this._indexTree(ops, originalTree, this.rootTree, {
      only: this.basePath.concat([['schema', 'contents']])
    });
    return {
      commit: this.commitId
    };
  }

  async _loadCommit() {
    if (!this.commit) {
      this.commit = await this._commitAtBranch(this.branch);
      this.commitId = this.commit.id().tostrS();
    }
    if (!this.rootTree) {
      this.rootTree = await this.commit.getTree();
    }
  }

  async _commitAtBranch(branchName) {
    let branch = await Branch.lookup(this.repo, branchName, Branch.BRANCH.LOCAL);
    return Commit.lookup(this.repo, branch.target());
  }

  async _indexTree(ops, oldTree, newTree, filter) {
    let seen = new Map();
    if (newTree) {
      for (let newEntry of newTree.entries()) {
        let name = newEntry.name();
        if (!filterAllows(filter, name)) {
          continue;
        }
        seen.set(name, true);
        await this._indexEntry(ops, name, oldTree, newEntry, filter);
      }
    }
    if (oldTree) {
      for (let oldEntry of oldTree.entries()) {
        let name = oldEntry.name();
        if (!filterAllows(filter, name)) {
          continue;
        }
        if (!seen.get(name)) {
          await this._deleteEntry(ops, oldEntry, filter);
        }
      }
    }
  }

  async _indexEntry(ops, name, oldTree, newEntry, filter) {
    let oldEntry;
    if (oldTree) {
      oldEntry = safeEntryByName(oldTree, name);
      if (oldEntry && oldEntry.id().equal(newEntry.id())) {
        // We can prune whole subtrees when we find an identical
        // entry. Which is kinda the point of Git's data
        // structure in the first place.
        return;
      }
    }
    if (newEntry.isTree()) {
      await this._indexTree(
        ops,
        oldEntry && oldEntry.isTree() ? (await oldEntry.getTree()) : null,
        await newEntry.getTree(),
        nextFilter(filter)
      );
    } else if (/\.json$/i.test(newEntry.path())) {
      let { type, id } = identify(newEntry);
      let contents = (await newEntry.getBlob()).content().toString('utf8');
      let doc;
      try {
        doc = JSON.parse(contents);
      } catch (err) {
        this.log.warn("Ignoring record with invalid json at %s", newEntry.path());
        return;
      }
      if (!doc.meta) {
        doc.meta = {};
      }

      // A note on the cardstack meta versioning protocol:
      //
      // meta.version is a point-in-time version indicator. If you
      // change and then undo, you should end up at a different
      // version than where you started. In the git data-source,
      // meta.version is the ID of a commit.
      //
      // meta.hash is a content hash. If you change and then undo, you
      // should end up back at the original meta.hash. In this git
      // data-source, this is ID of a blob.
      doc.meta.version = this.commitId;
      doc.meta.hash = newEntry.id().tostrS();
      await ops.save(type, id, doc);
    }
  }

  async _deleteEntry(ops, oldEntry, filter) {
    if (oldEntry.isTree()) {
      await this._indexTree(ops, await oldEntry.getTree(), nextFilter(filter));
    } else {
      let { type, id } = identify(oldEntry);
      await ops.delete(type, id);
    }
  }
}

function identify(entry) {
  let parts = entry.path().split('/');
  let type = parts[parts.length - 2] || 'tops';
  let filename = parts[parts.length - 1];
  let id = filename.replace(/\.json$/, '');
  return { type, id };
}

class Gather {
  constructor(models) {
    this.models = models;
  }
  save(type, id, document) {
    document.type = type;
    document.id = id;
    this.models.push(document);
  }
}

function filterAllows(filter, name) {
  return !filter || !filter.only || filter.only.length === 0 ||
    (Array.isArray(filter.only[0]) && filter.only[0].includes(name)) ||
    name === filter.only[0];
}

function nextFilter(filter) {
  if (!filter || !filter.only || filter.only.length < 2) {
    return null;
  }
  return { only: filter.only.slice(1) };
}
<|endoftext|>


<commit_before>/**
 * ownCloud
 *
 * @author John Molakvoæ
 * @copyright 2016-2018 John Molakvoæ <skjnldsv@protonmail.com>
 * @author Morris Jobke
 * @copyright 2013 Morris Jobke <morris.jobke@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * Adds a background color to the element called on and adds the first character
 * of the passed in string. This string is also the seed for the generation of
 * the background color.
 *
 * You have following HTML:
 *
 *

 *
 * And call this from Javascript:
 *
 * $('#albumart').imageplaceholder('The Album Title');
 *
 * Which will result in:
 *
 *
T

 *
 * You may also call it like this, to have a different background, than the seed:
 *
 * $('#albumart').imageplaceholder('The Album Title', 'Album Title');
 *
 * Resulting in:
 *
 *
A

 *
 */

 /*
 * Alternatively, you can use the prototype function to convert your string to rgb colors:
 *
 * "a6741a86aded5611a8e46ce16f2ad646".toRgb()
 *
 * Will return the rgb parameters within the following object:
 *
 * Color {r: 208, g: 158, b: 109}
 *
 */

(function ($) {

	String.prototype.toRgb = function() {
		var hash = this.toLowerCase().replace(/[^0-9a-f]+/g, '');

		// Already a md5 hash?
		if( !hash.match(/^[0-9a-f]{32}$/g) ) {
			hash = md5(hash);
		}

		function Color(r,g,b) {
			this.r = r;
			this.g = g;
			this.b = b;
		}

		function stepCalc(steps, ends) {
			var step = new Array(3);
			step[0] = (ends[1].r - ends[0].r) / steps;
			step[1] = (ends[1].g - ends[0].g) / steps;
			step[2] = (ends[1].b - ends[0].b) / steps;
			return step;
		}

		function mixPalette(steps, color1, color2) {
			var count = steps + 1;
			var palette = new Array();
			palette.push(color1);
			var step = stepCalc(steps, [color1, color2])
			for (i = 1; i < steps; i++) {
				var r = parseInt(color1.r + (step[0] * i));
				var g = parseInt(color1.g + (step[1] * i));
				var b = parseInt(color1.b + (step[2] * i));
					palette.push(new Color(r,g,b));
			}
			return palette;
		}

		var red = new Color(182, 70, 157);
		var yellow = new Color(221, 203, 85);
		var blue = new Color(0, 130, 201); // Nextcloud blue
		// Number of steps to go from a color to another
		// 3 colors * 6 will result in 18 generated colors
		var steps = 6;

		var palette1 = mixPalette(steps, red, yellow);
		var palette2 = mixPalette(steps, yellow, blue);
		var palette3 = mixPalette(steps, blue, red);

		var finalPalette = palette1.concat(palette2).concat(palette3);

		// Convert a string to an integer evenly
		function hashToInt(hash, maximum) {
			var final = 0;
			var result = Array();

			// Splitting evenly the string
			for (var i in hash) {
				// chars in md5 goes up to f, hex:16
				result.push(parseInt(hash.charAt(i), 16) % 16);
			}
			// Adds up all results
			for (var i in result) {
				final += result[i];
			}
			// chars in md5 goes up to f, hex:16
			return parseInt(final % maximum);
		}

		return finalPalette[hashToInt(hash, steps * 3 )];
	};

	$.fn.imageplaceholder = function(seed, text, size) {
		text = text || seed;

		// Compute the hash
		var rgb = seed.toRgb();
		this.css('background-color', 'rgb('+rgb.r+', '+rgb.g+', '+rgb.b+')');

		// Placeholders are square
		var height = this.height() || size || 32;
		this.height(height);
		this.width(height);

		// CSS rules
		this.css('color', '#fff');
		this.css('font-weight', 'normal');
		this.css('text-align', 'center');

		// calculate the height
		this.css('line-height', height + 'px');
		this.css('font-size', (height * 0.55) + 'px');

		if(seed !== null && seed.length) {
			this.html(text[0].toUpperCase());
		}
	};

	$.fn.clearimageplaceholder = function() {
		this.css('background-color', '');
		this.css('color', '');
		this.css('font-weight', '');
		this.css('text-align', '');
		this.css('line-height', '');
		this.css('font-size', '');
		this.html('');
		this.removeClass('icon-loading');
	};
}(jQuery));
<commit_msg>Fix js tests with placeholder<commit_after>/**
 * ownCloud
 *
 * @author John Molakvoæ
 * @copyright 2016-2018 John Molakvoæ <skjnldsv@protonmail.com>
 * @author Morris Jobke
 * @copyright 2013 Morris Jobke <morris.jobke@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * Adds a background color to the element called on and adds the first character
 * of the passed in string. This string is also the seed for the generation of
 * the background color.
 *
 * You have following HTML:
 *
 *

 *
 * And call this from Javascript:
 *
 * $('#albumart').imageplaceholder('The Album Title');
 *
 * Which will result in:
 *
 *
T

 *
 * You may also call it like this, to have a different background, than the seed:
 *
 * $('#albumart').imageplaceholder('The Album Title', 'Album Title');
 *
 * Resulting in:
 *
 *
A

 *
 */

 /*
 * Alternatively, you can use the prototype function to convert your string to rgb colors:
 *
 * "a6741a86aded5611a8e46ce16f2ad646".toRgb()
 *
 * Will return the rgb parameters within the following object:
 *
 * Color {r: 208, g: 158, b: 109}
 *
 */

(function ($) {

	String.prototype.toRgb = function() {
		var hash = this.toLowerCase().replace(/[^0-9a-f]+/g, '');

		// Already a md5 hash?
		if( !hash.match(/^[0-9a-f]{32}$/g) ) {
			hash = md5(hash);
		}

		function Color(r,g,b) {
			this.r = r;
			this.g = g;
			this.b = b;
		}

		function stepCalc(steps, ends) {
			var step = new Array(3);
			step[0] = (ends[1].r - ends[0].r) / steps;
			step[1] = (ends[1].g - ends[0].g) / steps;
			step[2] = (ends[1].b - ends[0].b) / steps;
			return step;
		}

		function mixPalette(steps, color1, color2) {
			var count = steps + 1;
			var palette = new Array();
			palette.push(color1);
			var step = stepCalc(steps, [color1, color2])
			for (i = 1; i < steps; i++) {
				var r = parseInt(color1.r + (step[0] * i));
				var g = parseInt(color1.g + (step[1] * i));
				var b = parseInt(color1.b + (step[2] * i));
					palette.push(new Color(r,g,b));
			}
			return palette;
		}

		var red = new Color(182, 70, 157);
		var yellow = new Color(221, 203, 85);
		var blue = new Color(0, 130, 201); // Nextcloud blue
		// Number of steps to go from a color to another
		// 3 colors * 6 will result in 18 generated colors
		var steps = 6;

		var palette1 = mixPalette(steps, red, yellow);
		var palette2 = mixPalette(steps, yellow, blue);
		var palette3 = mixPalette(steps, blue, red);

		var finalPalette = palette1.concat(palette2).concat(palette3);

		// Convert a string to an integer evenly
		function hashToInt(hash, maximum) {
			var finalInt = 0;
			var result = Array();

			// Splitting evenly the string
			for (var i in hash) {
				// chars in md5 goes up to f, hex:16
				result.push(parseInt(hash.charAt(i), 16) % 16);
			}
			// Adds up all results
			for (var j in result) {
				finalInt += result[j];
			}
			// chars in md5 goes up to f, hex:16
			// make sure we're always using int in our operation
			return parseInt(parseInt(finalInt) % maximum);
		}
		return finalPalette[hashToInt(hash, steps * 3 )];
	};

	$.fn.imageplaceholder = function(seed, text, size) {
		text = text || seed;

		// Compute the hash
		var rgb = seed.toRgb();
		this.css('background-color', 'rgb('+rgb.r+', '+rgb.g+', '+rgb.b+')');

		// Placeholders are square
		var height = this.height() || size || 32;
		this.height(height);
		this.width(height);

		// CSS rules
		this.css('color', '#fff');
		this.css('font-weight', 'normal');
		this.css('text-align', 'center');

		// calculate the height
		this.css('line-height', height + 'px');
		this.css('font-size', (height * 0.55) + 'px');

		if(seed !== null && seed.length) {
			this.html(text[0].toUpperCase());
		}
	};

	$.fn.clearimageplaceholder = function() {
		this.css('background-color', '');
		this.css('color', '');
		this.css('font-weight', '');
		this.css('text-align', '');
		this.css('line-height', '');
		this.css('font-size', '');
		this.html('');
		this.removeClass('icon-loading');
	};
}(jQuery));
<|endoftext|>


<commit_before>import React from 'react';
import { props, t } from '../utils';
import FlexView from '../flex/FlexView';

/**
 * ### Creates a full-page dimmed background for its children nodes
 */
@props({
  /**
   * children nodes/elements
   */
  children: t.ReactNode,
  /**
   * background-color
   */
  color: t.maybe(t.String),
  /**
   * opacity
   */
  alpha: t.maybe(t.Number),
  /**
   * z-index (BackgroundDimmer has `position: fixed`)
   */
  zIndex: t.maybe(t.Number),
  /**
   * avoid propagation for scroll events
   */
  stopScrollPropagation: t.maybe(t.Boolean),
  /**
   * called when user clicks outside children
   */
  onClickOutside: t.maybe(t.Function),
  className: t.maybe(t.String),
  id: t.maybe(t.String),
  style: t.maybe(t.Object)
})
export default class BackgroundDimmer extends React.Component {

  static defaultProps = {
    color: 'black',
    alpha: 0.5,
    zIndex: 99999
  };

  componentDidMount() {
    if (this.props.stopScrollPropagation) {
      this.disableScrollPropagation();
    }
  }

  enableScrollPropagation = () => {
    const dimmedBackground = React.findDOMNode(this.refs.dimmedBackground);
    const childrenWrapper = React.findDOMNode(this.refs.childrenWrapper);
    dimmedBackground.removeEventListener('wheel', this.stopScrollPropagation);
    dimmedBackground.removeEventListener('touchmove', this.stopScrollPropagation);
    childrenWrapper.removeEventListener('wheel', this.preventDefault);
    childrenWrapper.removeEventListener('touchmove', this.preventDefault);
  };

  disableScrollPropagation = () => {
    const dimmedBackground = React.findDOMNode(this.refs.dimmedBackground);
    const childrenWrapper = React.findDOMNode(this.refs.childrenWrapper);
    dimmedBackground.addEventListener('wheel', this.stopScrollPropagation);
    dimmedBackground.addEventListener('touchmove', this.stopScrollPropagation);
    childrenWrapper.addEventListener('wheel', this.preventDefault);
    childrenWrapper.addEventListener('touchmove', this.preventDefault);
  };

  isEventOutsideChildren = (e) => {
    const el = e.target || e.srcElement;
    return el === React.findDOMNode(this.refs.dimmedBackground);
  };

  onClick = (e) => {
    const { onClickOutside } = this.props;
    if (onClickOutside && this.isEventOutsideChildren(e)) {
      onClickOutside(e);
    }
  };

  preventDefault = (e) => e.preventDefault();

  stopScrollPropagation = (e) => {
    const { stopScrollPropagation } = this.props;
    if (stopScrollPropagation && this.isEventOutsideChildren(e)) {
      this.preventDefault(e);
    }
  };

  getDimmedBackground = () => {
    const { color, alpha, zIndex } = this.props;
    const style = {
      position: 'fixed',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: color,
      opacity: String(alpha),
      zIndex
    };
    return
;
  };

  render() {
    const { style, className, id, children, zIndex } = this.props;
    const props = {
      style: { position: 'relative', ...style },
      className,
      id
    };
    const flexViewProps = {
      style: {
        position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: (zIndex + 1), pointerEvents: 'none'
      },
      vAlignContent: 'center',
      hAlignContent: 'center'
    };
    const childrenWrapperProps = {
      style: { pointerEvents: 'auto' },
      ref: 'childrenWrapper'
    };

    return (

        {this.getDimmedBackground()}
        <FlexView {...flexViewProps}>

            {children}

        </FlexView>

    );
  }

  componentWillReceiveProps(nextProps) {
    if (!nextProps.stopScrollPropagation && this.props.stopScrollPropagation) {
      this.enableScrollPropagation();
    } else if (nextProps.stopScrollPropagation && !this.props.stopScrollPropagation) {
      this.disableScrollPropagation();
    }
  }

  componentWillUnmount() {
    if (this.props.stopScrollPropagation) {
      this.enableScrollPropagation();
    }
  }

}
<commit_msg>use React event listeners<commit_after>import React from 'react';
import { props, t } from '../utils';
import FlexView from '../flex/FlexView';

/**
 * ### Creates a full-page dimmed background for its children nodes
 */
@props({
  /**
   * children nodes/elements
   */
  children: t.ReactNode,
  /**
   * background-color
   */
  color: t.maybe(t.String),
  /**
   * opacity
   */
  alpha: t.maybe(t.Number),
  /**
   * z-index (BackgroundDimmer has `position: fixed`)
   */
  zIndex: t.maybe(t.Number),
  /**
   * avoid propagation for scroll events
   */
  stopScrollPropagation: t.maybe(t.Boolean),
  /**
   * called when user clicks outside children
   */
  onClickOutside: t.maybe(t.Function),
  className: t.maybe(t.String),
  id: t.maybe(t.String),
  style: t.maybe(t.Object)
})
export default class BackgroundDimmer extends React.Component {

  static defaultProps = {
    color: 'black',
    alpha: 0.5,
    zIndex: 99999
  };

  isEventOutsideChildren = (e) => {
    const el = e.target || e.srcElement;
    return el === React.findDOMNode(this.refs.dimmedBackground);
  };

  onClick = (e) => {
    const { onClickOutside } = this.props;
    if (onClickOutside && this.isEventOutsideChildren(e)) {
      onClickOutside(e);
    }
  };

  preventDefault = (e) => e.preventDefault();

  stopScrollPropagation = (e) => {
    const { stopScrollPropagation } = this.props;
    if (stopScrollPropagation && this.isEventOutsideChildren(e)) {
      this.preventDefault(e);
    }
  };

  getDimmedBackground = () => {
    const { color, alpha, zIndex } = this.props;
    const style = {
      position: 'fixed',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: color,
      opacity: String(alpha),
      zIndex
    };
    return
;
  };

  render() {
    const { style, className, id, children, zIndex } = this.props;
    const props = {
      style: { position: 'relative', ...style },
      className,
      id
    };
    const flexViewProps = {
      style: {
        position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, zIndex: (zIndex + 1), pointerEvents: 'none'
      },
      vAlignContent: 'center',
      hAlignContent: 'center'
    };
    const childrenWrapperProps = {
      style: { pointerEvents: 'auto' },
      ref: 'childrenWrapper'
    };

    return (

        {this.getDimmedBackground()}
        <FlexView {...flexViewProps}>

            {children}

        </FlexView>

    );
  }

}
<|endoftext|>


<commit_before>/* ************************************************************************

    License: MIT Licence

    Description:
        Simple synchronizer/chainer for callback list of functions
        synchronizer : Load many functions at same time, when they all finish
                       raise the final callback
        chainer : Load many functions one by one, when last one finish raise
                  the final callback

************************************************************************ */

//Simple synchronizer/chainer for Array of functions
a.callback = {};

/**
 * Load many functions at same time,
 * when they all finish raise the final callback
 *
 * Examples: here
 *
 * @class synchronizer
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.synchronizer = function(callbacks, success, error) {
    return a.extend(
            new a.callback.synchronizerInstance(
                callbacks,
                success,
                error
            ),
            new a.eventEmitter('a.callback.synchronizer')
        );
};

/**
 * synchronizerInstance, NEVER use like this,
 * use a.callback.synchronizer instead.
 *
 * @class synchronizerInstance
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.synchronizerInstance = function(callbacks, success, error) {
    this.callbacks       = callbacks || [];
    this.successFunction = success;
    this.errorFunction   = error;
    this.data            = {};
    this.resultScope     = null;
    this.scope           = null;
    this.parrallelCount  = 0;
    this.running         = false;
};

a.callback.synchronizerInstance.prototype = {
    /**
     * Add callback to existing callback list.
     * If the system is started, also append this callback to waiting queue.
     *
     * @method addCallback
     *
     * @param {Array}                       Any number of functions to chain
     *                                      The first function will be executed
     *                                      at first, and the last at last, in
     *                                      the order you give to that fct.
    */
    addCallback: function() {
        var args = a.toArray(arguments);

        this.callbacks = this.callbacks.concat(args);

        if(this.isRunning()) {
            var scope = this.scope || this,
                result = this.getResultObject();

            a.each(args, function(callback) {
                callback.call(scope, result);
            });
        }
    },

    /**
     * Remove callback from existing callback list.
     *
     * @method removeCallback
     *
     * @param fct {Function}                The function to remove from list
    */
    removeCallback: function(fct) {
        this.callbacks = a.without(this.callbacks, fct);
    },

    /**
     * Apply this scope to all callback function
     *
     * @method setScope
     *
     * @param scope {Object}                The scope to apply to callbacks
    */
    setScope: function(scope) {
        if(a.isTrueObject(scope)) {
            this.scope = scope;
        }
    },

    /**
     * Get a currently stored data.
     *
     * @method getData
     *
     * @param key {String}                  The key linked to value to get data
     * @return {Object | null}              The value previously stored and
     *                                      content
    */
    getData: function(key) {
        return this.data[key] || null;
    },

    /**
     * Set a new data stored into container
     *
     * @method setData
     *
     * @param key {String}                  The key to retrieve value later
     * @param value {Object}                Any value to store, a null or
     *                                      undefined element will erase key
     *                                      from store
    */
    setData: function(key, value) {
        if(a.isNone(value)) {
            delete this.data[key];
        } else {
            this.data[key] = value;
        }
    },

    /**
     * Get the main callback object to manipulate chain from it.
     *
     * @method getResultObject
     *
     * @return {Object}                     An object ready to use for
     *                                      controlling chain process
    */
    getResultObject: function() {
        var n = a.scope(this.next, this),
            s = a.scope(this.stop, this);
        return {
            next: n, done: n, success: n,
            fail: s, error: s, stop: s,
            setData: a.scope(this.setData, this),
            getData: a.scope(this.getData, this)
        };
    },

    /**
     * This function keeps chain to release success/error function when all
     * functions will finish their job.
     *
     * @method next
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to next callback
     *                                      as parameters
    */
    next: function() {
        this.parrallelCount--;

        // We have to raise final callback (success or error)
        // The error function is managed by stop function
        if(this.parrallelCount == 0 && this.running) {
            this.running = false;
            this.dispatch('success');

            // We raise final success function
            if(a.isFunction(this.successFunction)) {
                var scope = this.resultScope || this.scope || this;
                this.successFunction.call(scope, this.getResultObject());
            }
        }
    },

    /**
     * Stop the callback chain.
     *
     * @method stop
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to error callback
     *                                      as parameters
    */
    stop: function() {
        this.parrallelCount = 0;
        var wasRunning      = this.running;
        this.running        = false;

        var scope  = this.resultScope || this.scope || this,
            args   = a.toArray(arguments);

        this.dispatch('error');
        if(wasRunning && a.isFunction(this.errorFunction)) {
            args.push(this.getResultObject());
            this.errorFunction.apply(scope, args);
        }
    },

    /**
     * Start chainer queue.
     *
     * @method start
     *
     * @method
    */
    start: function() {
        this.parrallelCount = this.callbacks.length;
        this.running = true;

        this.dispatch('start');

        // There is no callback, we directly jump on success
        if(this.parrallelCount <= 0) {
            // We fake parallel count to let next think it's a function
            // ending (normal process ending)
            this.parrallelCount = 1;
            this.next();
            return;
        }

        // For every callbacks existing, we start it
        var scope = this.scope || this,
            args  = a.toArray(arguments);

        args.push(this.getResultObject());

        for(var i=0, l=this.callbacks.length; i<l; ++i) {
            var callback = this.callbacks[i];
            callback.apply(scope, args);
        }
    },

    /**
     * Get if the chain system is currently running or not
     *
     * @method isRunning
     *
     * @return {Boolean}                    True: currently running
     *                                      False: currently stopped
    */
    isRunning: function() {
        return this.running;
    }
};

// Alias
a.callback.synchronizerInstance.prototype.success =
                                a.callback.synchronizerInstance.prototype.next;
a.callback.synchronizerInstance.prototype.done    =
                                a.callback.synchronizerInstance.prototype.next;
a.callback.synchronizerInstance.prototype.fail    =
                                a.callback.synchronizerInstance.prototype.stop;
a.callback.synchronizerInstance.prototype.error   =
                                a.callback.synchronizerInstance.prototype.stop;

/**
 * Load many functions one by one, when last one finish raise the final callback
 *
 * Examples: here
 *
 * @class chainer
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.chainer = function(callbacks, success, error) {
    return a.extend(
            new a.callback.chainerInstance(
                callbacks,
                success,
                error
            ),
            new a.eventEmitter('a.callback.chainer')
        );
};

/**
 * chainerInstance, NEVER use like this, use a.callback.chainer instead.
 *
 * @class chainerInstance
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.chainerInstance = function(callbacks, success, error) {
    this.callbacks       = callbacks || [];
    this.queue           = [];
    this.successFunction = success;
    this.errorFunction   = error;
    this.data            = {};
    this.resultScope     = null;
    this.scope           = null;
};

a.callback.chainerInstance.prototype = {
    /**
     * Add callback to existing callback list.
     * If the system is started, also append this callback to waiting queue.
     *
     * @method addCallback
     *
     * @param {Array}                       Any number of functions to chain
     *                                      The first function will be executed
     *                                      at first, and the last at last, in
     *                                      the order you give to that fct.
    */
    addCallback: function() {
        var args = a.toArray(arguments);

        this.callbacks = this.callbacks.concat(args);

        if(this.isRunning()) {
            this.queue = this.queue.concat(args);
        }
    },

    /**
     * Remove callback from existing callback list.
     *
     * @method removeCallback
     *
     * @param fct {Function}                The function to remove from list
    */
    removeCallback: function(fct) {
        this.callbacks = a.without(this.callbacks, fct);
        this.queue     = a.without(this.without, fct);
    },

    /**
     * Apply this scope to all callback function
     *
     * @method setScope
     *
     * @param scope {Object}                The scope to apply to callbacks
    */
    setScope: function(scope) {
        if(a.isTrueObject(scope)) {
            this.scope = scope;
        }
    },

    /**
     * Get a currently stored data.
     *
     * @method getData
     *
     * @param key {String}                  The key linked to value to get data
     * @return {Object | null}              The value previously stored and
     *                                      content
    */
    getData: function(key) {
        return this.data[key] || null;
    },

    /**
     * Set a new data stored into container
     *
     * @method setData
     *
     * @param key {String}                  The key to retrieve value later
     * @param value {Object}                Any value to store, a null or
     *                                      undefined element will erase key
     *                                      from store
    */
    setData: function(key, value) {
        if(a.isNone(value)) {
            delete this.data[key];
        } else {
            this.data[key] = value;
        }
    },

    /**
     * Get the main callback object to manipulate chain from it.
     *
     * @method getResultObject
     *
     * @return {Object}                     An object ready to use for
     *                                      controlling chain process
    */
    getResultObject: function() {
        return {
            next:    a.scope(this.next, this),
            done:    a.scope(this.next, this),
            success: a.scope(this.next, this),
            fail:    a.scope(this.stop, this),
            error:   a.scope(this.stop, this),
            stop:    a.scope(this.stop, this),
            setData: a.scope(this.setData, this),
            getData: a.scope(this.getData, this)
        };
    },

    /**
     * Go to the next function in callback chain.
     *
     * @method next
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to next callback
     *                                      as parameters
    */
    next: function() {
        var args = a.toArray(arguments),
            scope = this.scope || this;

        // We add at the end the chain/result object
        var that = this;
        args.push(this.getResultObject());

        // We stop if queue is ended
        if(!this.queue.length) {
            this.dispatch('success');

            // Success is now launched
            if(a.isFunction(this.successFunction)) {
                scope = this.resultScope || scope;
                this.successFunction.apply(scope, args);
            }
            return;
        }

        // Getting the callback
        var callback = this.queue.shift();
        if(a.isFunction(callback)) {
            // We transfert arguments from next to next callback
            callback.apply(scope, args);
        }
    },

    /**
     * Stop the callback chain.
     *
     * @method stop
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to error callback
     *                                      as parameters
    */
    stop: function() {
        this.queue = [];
        var scope  = this.scope || this,
            args   = a.toArray(arguments);

        this.dispatch('stop');
        if(a.isFunction(this.errorFunction)) {
            args.push(this.getResultObject());
            this.errorFunction.apply(scope, args);
        }
    },

    /**
     * Start chainer queue.
     *
     * @method start
    */
    start: function() {
        if(this.queue.length) {
            return;
        }

        // Preparing queue
        this.queue = a.deepClone(this.callbacks);
        this.dispatch('start');

        // Starting queue
        this.next();
    },

    /**
     * Get if the chain system is currently running or not
     *
     * @method isRunning
     *
     * @return {Boolean}                    True: currently running
     *                                      False: currently stopped
    */
    isRunning: function() {
        return this.queue.length ? true : false;
    }
};

// Alias
a.callback.chainerInstance.prototype.success =
                                a.callback.chainerInstance.prototype.next;
a.callback.chainerInstance.prototype.done    =
                                a.callback.chainerInstance.prototype.next;
a.callback.chainerInstance.prototype.fail    =
                                a.callback.chainerInstance.prototype.stop;
a.callback.chainerInstance.prototype.error   =
                                a.callback.chainerInstance.prototype.stop;
<commit_msg>Doc rework.<commit_after>/* ************************************************************************

    License: MIT Licence

    Description:
        Simple synchronizer/chainer for callback list of functions
        synchronizer : Load many functions at same time, when they all finish
                       raise the final callback
        chainer : Load many functions one by one, when last one finish raise
                  the final callback

************************************************************************ */

//Simple synchronizer/chainer for Array of functions
a.callback = {};

/**
 * Load many functions at same time,
 * when they all finish raise the final callback
 *
 * @class synchronizer
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.synchronizer = function(callbacks, success, error) {
    return a.extend(
            new a.callback.synchronizerInstance(
                callbacks,
                success,
                error
            ),
            new a.eventEmitter('a.callback.synchronizer')
        );
};

/**
 * synchronizerInstance, NEVER use like this,
 * use a.callback.synchronizer instead.
 *
 * @class synchronizerInstance
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.synchronizerInstance = function(callbacks, success, error) {
    this.callbacks       = callbacks || [];
    this.successFunction = success;
    this.errorFunction   = error;
    this.data            = {};
    this.resultScope     = null;
    this.scope           = null;
    this.parrallelCount  = 0;
    this.running         = false;
};

a.callback.synchronizerInstance.prototype = {
    /**
     * Add callback to existing callback list.
     * If the system is started, also append this callback to waiting queue.
     *
     * @method addCallback
     *
     * @param {Array}                       Any number of functions to chain
     *                                      The first function will be executed
     *                                      at first, and the last at last, in
     *                                      the order you give to that fct.
    */
    addCallback: function() {
        var args = a.toArray(arguments);

        this.callbacks = this.callbacks.concat(args);

        if(this.isRunning()) {
            var scope = this.scope || this,
                result = this.getResultObject();

            a.each(args, function(callback) {
                callback.call(scope, result);
            });
        }
    },

    /**
     * Remove callback from existing callback list.
     *
     * @method removeCallback
     *
     * @param fct {Function}                The function to remove from list
    */
    removeCallback: function(fct) {
        this.callbacks = a.without(this.callbacks, fct);
    },

    /**
     * Apply this scope to all callback function
     *
     * @method setScope
     *
     * @param scope {Object}                The scope to apply to callbacks
    */
    setScope: function(scope) {
        if(a.isTrueObject(scope)) {
            this.scope = scope;
        }
    },

    /**
     * Get a currently stored data.
     *
     * @method getData
     *
     * @param key {String}                  The key linked to value to get data
     * @return {Object | null}              The value previously stored and
     *                                      content
    */
    getData: function(key) {
        return this.data[key] || null;
    },

    /**
     * Set a new data stored into container
     *
     * @method setData
     *
     * @param key {String}                  The key to retrieve value later
     * @param value {Object}                Any value to store, a null or
     *                                      undefined element will erase key
     *                                      from store
    */
    setData: function(key, value) {
        if(a.isNone(value)) {
            delete this.data[key];
        } else {
            this.data[key] = value;
        }
    },

    /**
     * Get the main callback object to manipulate chain from it.
     *
     * @method getResultObject
     *
     * @return {Object}                     An object ready to use for
     *                                      controlling chain process
    */
    getResultObject: function() {
        var n = a.scope(this.next, this),
            s = a.scope(this.stop, this);
        return {
            next: n, done: n, success: n,
            fail: s, error: s, stop: s,
            setData: a.scope(this.setData, this),
            getData: a.scope(this.getData, this)
        };
    },

    /**
     * This function keeps chain to release success/error function when all
     * functions will finish their job.
     *
     * @method next
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to next callback
     *                                      as parameters
    */
    next: function() {
        this.parrallelCount--;

        // We have to raise final callback (success or error)
        // The error function is managed by stop function
        if(this.parrallelCount == 0 && this.running) {
            this.running = false;
            this.dispatch('success');

            // We raise final success function
            if(a.isFunction(this.successFunction)) {
                var scope = this.resultScope || this.scope || this;
                this.successFunction.call(scope, this.getResultObject());
            }
        }
    },

    /**
     * Stop the callback chain.
     *
     * @method stop
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to error callback
     *                                      as parameters
    */
    stop: function() {
        this.parrallelCount = 0;
        var wasRunning      = this.running;
        this.running        = false;

        var scope  = this.resultScope || this.scope || this,
            args   = a.toArray(arguments);

        this.dispatch('error');
        if(wasRunning && a.isFunction(this.errorFunction)) {
            args.push(this.getResultObject());
            this.errorFunction.apply(scope, args);
        }
    },

    /**
     * Start chainer queue.
     *
     * @method start
     *
     * @method
    */
    start: function() {
        this.parrallelCount = this.callbacks.length;
        this.running = true;

        this.dispatch('start');

        // There is no callback, we directly jump on success
        if(this.parrallelCount <= 0) {
            // We fake parallel count to let next think it's a function
            // ending (normal process ending)
            this.parrallelCount = 1;
            this.next();
            return;
        }

        // For every callbacks existing, we start it
        var scope = this.scope || this,
            args  = a.toArray(arguments);

        args.push(this.getResultObject());

        for(var i=0, l=this.callbacks.length; i<l; ++i) {
            var callback = this.callbacks[i];
            callback.apply(scope, args);
        }
    },

    /**
     * Get if the chain system is currently running or not
     *
     * @method isRunning
     *
     * @return {Boolean}                    True: currently running
     *                                      False: currently stopped
    */
    isRunning: function() {
        return this.running;
    }
};

// Alias
a.callback.synchronizerInstance.prototype.success =
        a.callback.synchronizerInstance.prototype.next;
a.callback.synchronizerInstance.prototype.done    =
        a.callback.synchronizerInstance.prototype.next;
a.callback.synchronizerInstance.prototype.fail    =
        a.callback.synchronizerInstance.prototype.stop;
a.callback.synchronizerInstance.prototype.error   =
        a.callback.synchronizerInstance.prototype.stop;

/**
 * Load many functions one by one, when last one finish raise the final
 * callback
 *
 * @class chainer
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.chainer = function(callbacks, success, error) {
    return a.extend(
        new a.callback.chainerInstance(
            callbacks,
            success,
            error
        ),
        new a.eventEmitter('a.callback.chainer')
    );
};

/**
 * chainerInstance, NEVER use like this, use a.callback.chainer instead.
 *
 * @class chainerInstance
 * @namespace a.callback
 * @constructor
 * @async
*/
a.callback.chainerInstance = function(callbacks, success, error) {
    this.callbacks       = callbacks || [];
    this.queue           = [];
    this.successFunction = success;
    this.errorFunction   = error;
    this.data            = {};
    this.resultScope     = null;
    this.scope           = null;
};

a.callback.chainerInstance.prototype = {
    /**
     * Add callback to existing callback list.
     * If the system is started, also append this callback to waiting queue.
     *
     * @method addCallback
     *
     * @param {Array}                       Any number of functions to chain
     *                                      The first function will be executed
     *                                      at first, and the last at last, in
     *                                      the order you give to that fct.
    */
    addCallback: function() {
        var args = a.toArray(arguments);

        this.callbacks = this.callbacks.concat(args);

        if(this.isRunning()) {
            this.queue = this.queue.concat(args);
        }
    },

    /**
     * Remove callback from existing callback list.
     *
     * @method removeCallback
     *
     * @param fct {Function}                The function to remove from list
    */
    removeCallback: function(fct) {
        this.callbacks = a.without(this.callbacks, fct);
        this.queue     = a.without(this.without, fct);
    },

    /**
     * Apply this scope to all callback function
     *
     * @method setScope
     *
     * @param scope {Object}                The scope to apply to callbacks
    */
    setScope: function(scope) {
        if(a.isTrueObject(scope)) {
            this.scope = scope;
        }
    },

    /**
     * Get a currently stored data.
     *
     * @method getData
     *
     * @param key {String}                  The key linked to value to get data
     * @return {Object | null}              The value previously stored and
     *                                      content
    */
    getData: function(key) {
        return this.data[key] || null;
    },

    /**
     * Set a new data stored into container
     *
     * @method setData
     *
     * @param key {String}                  The key to retrieve value later
     * @param value {Object}                Any value to store, a null or
     *                                      undefined element will erase key
     *                                      from store
    */
    setData: function(key, value) {
        if(a.isNone(value)) {
            delete this.data[key];
        } else {
            this.data[key] = value;
        }
    },

    /**
     * Get the main callback object to manipulate chain from it.
     *
     * @method getResultObject
     *
     * @return {Object}                     An object ready to use for
     *                                      controlling chain process
    */
    getResultObject: function() {
        return {
            next:    a.scope(this.next, this),
            done:    a.scope(this.next, this),
            success: a.scope(this.next, this),
            fail:    a.scope(this.stop, this),
            error:   a.scope(this.stop, this),
            stop:    a.scope(this.stop, this),
            setData: a.scope(this.setData, this),
            getData: a.scope(this.getData, this)
        };
    },

    /**
     * Go to the next function in callback chain.
     *
     * @method next
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to next callback
     *                                      as parameters
    */
    next: function() {
        var args = a.toArray(arguments),
            scope = this.scope || this;

        // We add at the end the chain/result object
        var that = this;
        args.push(this.getResultObject());

        // We stop if queue is ended
        if(!this.queue.length) {
            this.dispatch('success');

            // Success is now launched
            if(a.isFunction(this.successFunction)) {
                scope = this.resultScope || scope;
                this.successFunction.apply(scope, args);
            }
            return;
        }

        // Getting the callback
        var callback = this.queue.shift();
        if(a.isFunction(callback)) {
            // We transfert arguments from next to next callback
            callback.apply(scope, args);
        }
    },

    /**
     * Stop the callback chain.
     *
     * @method stop
     *
     * @param {Array}                       Any arguments given to that one
     *                                      will be transfert to error callback
     *                                      as parameters
    */
    stop: function() {
        this.queue = [];
        var scope  = this.scope || this,
            args   = a.toArray(arguments);

        this.dispatch('stop');
        if(a.isFunction(this.errorFunction)) {
            args.push(this.getResultObject());
            this.errorFunction.apply(scope, args);
        }
    },

    /**
     * Start chainer queue.
     *
     * @method start
    */
    start: function() {
        if(this.queue.length) {
            return;
        }

        // Preparing queue
        this.queue = a.deepClone(this.callbacks);
        this.dispatch('start');

        // Starting queue
        this.next();
    },

    /**
     * Get if the chain system is currently running or not
     *
     * @method isRunning
     *
     * @return {Boolean}                    True: currently running
     *                                      False: currently stopped
    */
    isRunning: function() {
        return this.queue.length ? true : false;
    }
};

// Alias
a.callback.chainerInstance.prototype.success =
        a.callback.chainerInstance.prototype.next;
a.callback.chainerInstance.prototype.done    =
        a.callback.chainerInstance.prototype.next;
a.callback.chainerInstance.prototype.fail    =
        a.callback.chainerInstance.prototype.stop;
a.callback.chainerInstance.prototype.error   =
        a.callback.chainerInstance.prototype.stop;
<|endoftext|>


<commit_before>$(function(){
	var theCanvas = $("canvas").get(0).getContext('2d');
	theCanvas.fillRect(10, 10, 40, 40);
});

<commit_msg>javascript foundations<commit_after>;(function(){
	var canvas = {dom: null, ctx: null, h: null, w: null};
	var gameRes = {w: 2, h: 2};
	var mapPixel = function(x, y) {
		return {
			x: (canvas.w / gameRes.w) * x,
			y: (canvas.h / gameRes.h) * y
		};
	};
$(function(){
	var canvas = {dom:$("canvas").get(0)};
	canvas.ctx = canvas.dom.getContext('2d');
	canvas.w = canvas.dom.width;
	canvas.h = canvas.dom.height;

	console.log(canvas.dom.height);
});
})();

<|endoftext|>