<commit_before>var joli = {
  each: function(collection, iterator, bind) {
    switch (joli.getType(collection)) {
      case "array":
        for (var i = 0, l = collection.length; i < l; i++) {
          iterator.call(bind, collection[i], i);
        }
        break;
      case "object":
        for (var property in collection) {
          if (collection.hasOwnProperty(property)) {
            iterator.call(bind, collection[property], property);
          }
        }
        break;
    }
  },

  extend: function(baseClass, options) {
    if (!this.options) {
      this.options = {};
    }

    this.parent = new baseClass(options);

    for (var prop in this.parent) {
      this[prop] = this[prop] || this.parent[prop];
    }

    // copy base options over
    for (var opt in this.parent.options) {
      this.options[opt] = this.options[opt] || this.parent.options[opt];
    }
  },

  getType: function(obj) {
    if (typeof obj === "undefined" || obj === null || (typeof obj === "number" && isNaN(obj))) {
      return false;
    } else if (obj.constructor === Array) {
      return "array";
    } else {
      return typeof obj;
    }
  },

  jsonParse: function(json) {
    return eval('(' + json + ')');
  },

  merge: function() {
    var mergedObject = {};

    for (var i = 0, l = arguments.length; i < l; i++) {
      var object = arguments[i];

      if (joli.getType(object) !== "object") {
        continue;
      }

      for (var prop in object) {
        var objectProp = object[prop], mergedProp = mergedObject[prop];

        if (mergedProp && joli.getType(objectProp) === "object" && joli.getType(mergedProp) === "object") {
          mergedObject[prop] = joli.merge(mergedProp, objectProp);
        } else {
          mergedObject[prop] = objectProp;
        }
      }
    }
    return mergedObject;
  },

  setOptions: function(options, defaults) {
    if (!options) {
      options = {};
    }

    if (!this.options) {
      this.options = {};
    }

    mergedOptions = joli.merge(defaults, options);

    for (var opt in defaults) {
      this.options[opt] = mergedOptions[opt];
    }
  },

  toQueryString: function(obj) {
    var queryStringComponents = [];

    if (!obj) {
      obj = {};
    }

    joli.each(obj, function(val, key) {
      var result = null;

      switch (joli.getType(val)) {
        case "object":
          result = joli.toQueryString(val);
          break;
        case "array":
          result = joli.toQueryString(val);
          break;
        default:
          result = encodeURIComponent(val);
      }

      if (result) {
        queryStringComponents.push(key + '=' + result);
      }
    });

    return '[' + queryStringComponents.join("&") + ']';
  },

  typeValue: function(val) {
    if (!joli.getType(val)) {
      return "NULL";
    } else {
      if (joli.getType(val) === "string") {
        // escape single quotes and dollar signs.
        // quotes are escaped for SQLite
        val = val.replace(/'/g, "''");
        // dollar signs are escaped for use in calling str.replace in JazzRecord.replaceAndClean()
        val = val.replace(/\$/g, "$$$$");
        val = "'" + val + "'";
      } else if (joli.getType(val) === "boolean") {
        if (val) {
          return 1;
        } else {
          return 0;
        }
      }
    }

    return val;
  }
};

/**
 * Connection
 */
joli.Connection = function(database) {
  this.dbname = database;
  this.database = Titanium.Database.open(this.dbname);
  this.database.execute('PRAGMA read_uncommitted=true');
};

joli.Connection.prototype = {
  execute: function(query) {
    // Titanium.API.log('debug', query);
    return this.database.execute(query);
  },

  lastInsertRowId: function() {
    return this.database.lastInsertRowId;
  }
};

/**
 * Model description
 */
joli.model = function(options) {
  var defaults = {
    table: '',
    columns: {}
  };

  if (options.methods) {
    joli.each(options.methods, function(method, name) {
      this[name] = method;
    }, this);
  }

  joli.setOptions.call(this, options, defaults);
  this.table = this.options.table;

  if (!joli.models.has(this.table)) {
    joli.models.set(this.table, this);
  }
};

joli.model.prototype = {
  all: function(constraints) {
    var q = new joli.query().select().from(this.table);

    if (!constraints) {
      constraints = {};
    }

    if (constraints.where) {
      joli.each(constraints.where, function(value, field) {
        q.where(field, value);
      });
    }

    if (constraints.order) {
      q.order(constraints.order);
    }

    return q.execute();
  },

  count: function(constraints) {
    var q = new joli.query().count().from(this.table);

    if (!constraints) {
      constraints = {};
    }

    if (constraints.where) {
      joli.each(constraints.where, function(value, field) {
        q.where(field, value);
      });
    }

    return parseInt(q.execute(), 10);
  },

  // no callbacks, more efficient
  deleteRecords: function(id) {
    var q = new joli.query().destroy().from(this.table);

    if (joli.getType(id) === 'number') {
      q.where('id = ?', id);
    } else if(joli.getType(id) === 'array') {
      q.whereIn('id', id);
    }

    return q.execute();
  },

  exists: function(id) {
    var count = new joli.query().count().from(this.table).where('id = ?', id).execute();
    return (count > 0);
  },

  findBy: function(field, value) {
    return new joli.query().select().from(this.table).where(field + ' = ?', value).execute();
  },

  findById: function(value) {
    return this.findBy('id', value);
  },

  findOneBy: function(field, value) {
    var result = new joli.query().select().from(this.table).where(field + ' = ?', value).limit(1).execute();

    if (result.length == 0) {
      return false;
    } else {
      return result[0];
    }
  },

  findOneById: function(value) {
    return this.findOneBy('id', value);
  },

  getColumns: function() {
    return this.options.columns;
  },

  newRecord: function(values) {
    if (!values) {
      values = {};
    }

    var data = {};

    joli.each(this.options.columns, function(colType, colName) {
      data[colName] = (values[colName] === undefined) ? null : values[colName];
    });

    var record = new joli.record(this).fromArray(data);

    record.isNew = function() {
      return true;
    };

    return record;
  },

  save: function(data) {
    if (data.data.length == 0) {
      return;
    }

    var q = new joli.query();

    if (data.originalData) {
      // existing record
      q.update(this.table).set(data.data).where('id = ?', data.originalData.id);
    } else {
      // new record
      q.insertInto(this.table).values(data.data);
    }

    return q.execute();
  }
};

joli.Models = function() {
  this.models = {};
};

joli.Models.prototype = {
  get: function(table) {
    return this.models[table];
  },

  has: function(table) {
    if (this.models[table]) {
      return true;
    } else {
      return false;
    }
  },

  initialize: function() {
    joli.each(this.models, function(model, modelName) {
      var columns = [];

      joli.each(model.options.columns, function(type, name) {
        columns.push(name + ' ' + type);
      });
      var query = 'CREATE TABLE IF NOT EXISTS ' + modelName + ' (' + columns.join(', ') + ')';
      joli.connection.execute(query);
    });
  },

  set: function(table, model) {
    this.models[table] = model;
  }
};

joli.models = new joli.Models();

joli.query = function() {
  this.data = {
    from:            null,
    join:            [],
    limit:           null,
    operation:       null,
    order:           [],
    select_columns:  '*',
    set:             [],
    values:          [],
    where:           null
  };
};

joli.query.prototype = {
  count: function() {
    this.data.operation = 'count';
    return this;
  },

  destroy: function() {
    this.data.operation = 'delete';
    return this;
  },

  execute: function() {
    return this.executeQuery(this.getQuery());
  },

  executeQuery: function(query) {
    var rows;

    switch (this.data.operation) {
      case 'count':
        rows = joli.connection.execute(query);
        return this.getCount(rows);
      case 'insert_into':
        joli.connection.execute(query);
        return joli.connection.lastInsertRowId();
      case 'select':
        rows = joli.connection.execute(query);
        return this.hydrate(rows);
      default:
        return joli.connection.execute(query);
    }
  },

  from: function(table) {
    this.data.from = table;
    return this;
  },

  getCount: function(rows) {
    var result;

    if (null == rows) {
      return 0;
    }

    if (0 == rows.rowCount) {
      result = 0;
    } else {
      result = rows.fieldByName('total');
    }

    rows.close();
    return result;
  },

  getOperation: function() {
    switch (this.data.operation) {
      case 'count':
        return 'select count(*) as total from ' + this.data.from;
      case 'delete':
        return 'delete from ' + this.data.from;
      case 'insert_into':
        return 'insert into ' + this.data.from + ' (' + this.data.set.join(', ') + ') values (' + this.data.values.join(', ') + ')';
      case 'select':
        var join = '';

        if (this.data.join.length > 0) {
          joli.each(this.data.join, function(value, key) {
            join = join + ' left outer join ' + value[0] + ' ON ' + value[1] + '=' + value[2];
          });
        }

        return 'select ' + this.data.select_columns + ' from ' + this.data.from + join;
      case 'update':
        return 'update ' + this.data.from + ' set ' + this.data.values.join(', ');
      default:
        throw("Operation type Error. joli.query operation type must be an insert, a delete, a select or an update.");
    }
  },

  getQuery: function() {
    var query = this.getOperation();

    if (this.data.where) {
      query += ' where ' + this.data.where;
    }

    if (this.data.order.length > 0) {
      query += ' order by ' + this.data.order.join(', ');
    }

    if (this.data.limit) {
      query += ' limit ' + this.data.limit;
    }

    return query;
  },

  hydrate: function(rows) {
    var result = [];

    if (!rows) {
      return result;
    }

    //Titanium.API.log('debug', 'hydrating ' + rows.rowCount + ' rows.');
    var i;
    var rowData;

    if (Titanium.Platform.name != 'android') {
      var fieldCount = rows.fieldCount();
    } else {
      var fieldCount = rows.fieldCount;
    }

    while (rows.isValidRow()) {
      i = 0;
      rowData = {};

      while (i < fieldCount) {
        rowData[rows.fieldName(i)] = rows.field(i);
        i++;
      }

    	result.push(rowData);
    	rows.next();
    }

    rows.close();
    return result;
  },

  insertInto: function(table) {
    this.data.operation = 'insert_into';
    this.data.from = table;
    return this;
  },

  join: function(table, local_id, foreign_id) {
    this.data.join.push([table, local_id, foreign_id]);
    return this;
  },

  limit: function(limit) {
    this.data.limit = limit;
    return this;
  },

  order: function(order) {
    if ('string' == joli.getType(order)) {
      order = [order];
    }

    this.data.order = order;
    return this;
  },

  select: function(columns) {
    this.data.operation = 'select';

    if (columns) {
      this.data.select_columns = columns;
    }

    return this;
  },

  set: function(values) {
    joli.each(values, function(expression, value) {
      this.data.set.push(value + ' = ' + joli.typeValue(expression));
    }, this);
    return this;
  },

  update: function(table) {
    this.data.operation = 'update';
    this.data.from = table;
    return this;
  },

  values: function(values) {
    joli.each(values, function(expression, value) {
      this.data.set.push(value);
      this.data.values.push(joli.typeValue(expression));
    }, this);
    return this;
  },

  where: function(expression, value) {
    if (null != this.data.where) {
      this.data.where += ' and ';
    } else {
      this.data.where = '';
    }

    this.data.where += expression.replace(/\?/gi, '"' + value + '"');
    return this;
  },

  whereIn: function(expression, value) {
    if (null != this.data.where) {
      this.data.where += ' and ';
    } else {
      this.data.where = '';
    }

    this.data.where += expression + ' IN ' + value;
    return this;
  }
};

joli.record = function(table) {
  this._options = {
    table: table,
    columns: table.getColumns()
  };
  this._data = {};
};

joli.record.prototype = {
  destroy: function() {
    if (!this.id) {
      throw("Unsaved record cannot be destroyed");
    } else {
      this._options.table.deleteRecords(this.id);
    }
  },

  fromArray: function(data) {
    if (data.id) {
      this._originalData = {};
      this.isNew = function() {
        return false;
      };
    } else {
      this._originalData = null;
      this.isNew = function() {
        return true;
      };
    }

    joli.each(this._options.columns, function(colType, colName) {
      this[colName] = null;
      this[colName] = data[colName];
      this._data[colName] = null;
      this._data[colName] = data[colName];

      if (this._originalData) {
        this._originalData[colName] = data[colName];
      }
    }, this);

    return this;
  },

  isChanged: function() {
    if (this.isNew()) {
      return false;
    }

    return !(this.id && joli.toQueryString(this._data) === joli.toQueryString(this._originalData));
  },

  save: function() {
    var data = { data: this._data };

    if (this.isChanged()) {
      data.originalData = this._originalData;
      this._options.table.save(data);
    } else if(this.isNew()) {
      this._data.id = this._options.table.save(data);
    }

    // overwrite original data so it is no longer "dirty" OR so it is no longer new
    this._originalData = {};

    joli.each(this._options.columns, function(colType, colName) {
      this._originalData[colName] = this._data[colName];
    }, this);

    this._data = this._originalData;

    this.isNew = function() {
      return false;
    };

    return true;
  }
};
<commit_msg>fixed update() queries<commit_after>var joli = {
  each: function(collection, iterator, bind) {
    switch (joli.getType(collection)) {
      case "array":
        for (var i = 0, l = collection.length; i < l; i++) {
          iterator.call(bind, collection[i], i);
        }
        break;
      case "object":
        for (var property in collection) {
          if (collection.hasOwnProperty(property)) {
            iterator.call(bind, collection[property], property);
          }
        }
        break;
    }
  },

  extend: function(baseClass, options) {
    if (!this.options) {
      this.options = {};
    }

    this.parent = new baseClass(options);

    for (var prop in this.parent) {
      this[prop] = this[prop] || this.parent[prop];
    }

    // copy base options over
    for (var opt in this.parent.options) {
      this.options[opt] = this.options[opt] || this.parent.options[opt];
    }
  },

  getType: function(obj) {
    if (typeof obj === "undefined" || obj === null || (typeof obj === "number" && isNaN(obj))) {
      return false;
    } else if (obj.constructor === Array) {
      return "array";
    } else {
      return typeof obj;
    }
  },

  jsonParse: function(json) {
    return eval('(' + json + ')');
  },

  merge: function() {
    var mergedObject = {};

    for (var i = 0, l = arguments.length; i < l; i++) {
      var object = arguments[i];

      if (joli.getType(object) !== "object") {
        continue;
      }

      for (var prop in object) {
        var objectProp = object[prop], mergedProp = mergedObject[prop];

        if (mergedProp && joli.getType(objectProp) === "object" && joli.getType(mergedProp) === "object") {
          mergedObject[prop] = joli.merge(mergedProp, objectProp);
        } else {
          mergedObject[prop] = objectProp;
        }
      }
    }
    return mergedObject;
  },

  setOptions: function(options, defaults) {
    if (!options) {
      options = {};
    }

    if (!this.options) {
      this.options = {};
    }

    mergedOptions = joli.merge(defaults, options);

    for (var opt in defaults) {
      this.options[opt] = mergedOptions[opt];
    }
  },

  toQueryString: function(obj) {
    var queryStringComponents = [];

    if (!obj) {
      obj = {};
    }

    joli.each(obj, function(val, key) {
      var result = null;

      switch (joli.getType(val)) {
        case "object":
          result = joli.toQueryString(val);
          break;
        case "array":
          result = joli.toQueryString(val);
          break;
        default:
          result = encodeURIComponent(val);
      }

      if (result) {
        queryStringComponents.push(key + '=' + result);
      }
    });

    return '[' + queryStringComponents.join("&") + ']';
  },

  typeValue: function(val) {
    if (!joli.getType(val)) {
      return "NULL";
    } else {
      if (joli.getType(val) === "string") {
        // escape single quotes and dollar signs.
        // quotes are escaped for SQLite
        val = val.replace(/'/g, "''");
        // dollar signs are escaped for use in calling str.replace in JazzRecord.replaceAndClean()
        val = val.replace(/\$/g, "$$$$");
        val = "'" + val + "'";
      } else if (joli.getType(val) === "boolean") {
        if (val) {
          return 1;
        } else {
          return 0;
        }
      }
    }

    return val;
  }
};

/**
 * Connection
 */
joli.Connection = function(database) {
  this.dbname = database;
  this.database = Titanium.Database.open(this.dbname);
  this.database.execute('PRAGMA read_uncommitted=true');
};

joli.Connection.prototype = {
  execute: function(query) {
    Titanium.API.log('debug', query);
    return this.database.execute(query);
  },

  lastInsertRowId: function() {
    return this.database.lastInsertRowId;
  }
};

/**
 * Model description
 */
joli.model = function(options) {
  var defaults = {
    table: '',
    columns: {}
  };

  if (options.methods) {
    joli.each(options.methods, function(method, name) {
      this[name] = method;
    }, this);
  }

  joli.setOptions.call(this, options, defaults);
  this.table = this.options.table;

  if (!joli.models.has(this.table)) {
    joli.models.set(this.table, this);
  }
};

joli.model.prototype = {
  all: function(constraints) {
    var q = new joli.query().select().from(this.table);

    if (!constraints) {
      constraints = {};
    }

    if (constraints.where) {
      joli.each(constraints.where, function(value, field) {
        q.where(field, value);
      });
    }

    if (constraints.order) {
      q.order(constraints.order);
    }

    return q.execute();
  },

  count: function(constraints) {
    var q = new joli.query().count().from(this.table);

    if (!constraints) {
      constraints = {};
    }

    if (constraints.where) {
      joli.each(constraints.where, function(value, field) {
        q.where(field, value);
      });
    }

    return parseInt(q.execute(), 10);
  },

  // no callbacks, more efficient
  deleteRecords: function(id) {
    var q = new joli.query().destroy().from(this.table);

    if (joli.getType(id) === 'number') {
      q.where('id = ?', id);
    } else if(joli.getType(id) === 'array') {
      q.whereIn('id', id);
    }

    return q.execute();
  },

  exists: function(id) {
    var count = new joli.query().count().from(this.table).where('id = ?', id).execute();
    return (count > 0);
  },

  findBy: function(field, value) {
    return new joli.query().select().from(this.table).where(field + ' = ?', value).execute();
  },

  findById: function(value) {
    return this.findBy('id', value);
  },

  findOneBy: function(field, value) {
    var result = new joli.query().select().from(this.table).where(field + ' = ?', value).limit(1).execute();

    if (result.length == 0) {
      return false;
    } else {
      return result[0];
    }
  },

  findOneById: function(value) {
    return this.findOneBy('id', value);
  },

  getColumns: function() {
    return this.options.columns;
  },

  newRecord: function(values) {
    if (!values) {
      values = {};
    }

    var data = {};

    joli.each(this.options.columns, function(colType, colName) {
      data[colName] = (values[colName] === undefined) ? null : values[colName];
    });

    var record = new joli.record(this).fromArray(data);

    record.isNew = function() {
      return true;
    };

    return record;
  },

  save: function(data) {
    if (data.data.length == 0) {
      return;
    }

    var q = new joli.query();

    if (data.originalData) {
      // existing record
      q.update(this.table).set(data.data).where('id = ?', data.originalData.id);
    } else {
      // new record
      q.insertInto(this.table).values(data.data);
    }

    return q.execute();
  }
};

joli.Models = function() {
  this.models = {};
};

joli.Models.prototype = {
  get: function(table) {
    return this.models[table];
  },

  has: function(table) {
    if (this.models[table]) {
      return true;
    } else {
      return false;
    }
  },

  initialize: function() {
    joli.each(this.models, function(model, modelName) {
      var columns = [];

      joli.each(model.options.columns, function(type, name) {
        columns.push(name + ' ' + type);
      });
      var query = 'CREATE TABLE IF NOT EXISTS ' + modelName + ' (' + columns.join(', ') + ')';
      joli.connection.execute(query);
    });
  },

  set: function(table, model) {
    this.models[table] = model;
  }
};

joli.models = new joli.Models();

joli.query = function() {
  this.data = {
    from:            null,
    join:            [],
    limit:           null,
    operation:       null,
    order:           [],
    select_columns:  '*',
    set:             [],
    values:          [],
    where:           null
  };
};

joli.query.prototype = {
  count: function() {
    this.data.operation = 'count';
    return this;
  },

  destroy: function() {
    this.data.operation = 'delete';
    return this;
  },

  execute: function() {
    return this.executeQuery(this.getQuery());
  },

  executeQuery: function(query) {
    var rows;

    switch (this.data.operation) {
      case 'count':
        rows = joli.connection.execute(query);
        return this.getCount(rows);
      case 'insert_into':
        joli.connection.execute(query);
        return joli.connection.lastInsertRowId();
      case 'select':
        rows = joli.connection.execute(query);
        return this.hydrate(rows);
      default:
        return joli.connection.execute(query);
    }
  },

  from: function(table) {
    this.data.from = table;
    return this;
  },

  getCount: function(rows) {
    var result;

    if (null == rows) {
      return 0;
    }

    if (0 == rows.rowCount) {
      result = 0;
    } else {
      result = rows.fieldByName('total');
    }

    rows.close();
    return result;
  },

  getOperation: function() {
    switch (this.data.operation) {
      case 'count':
        return 'select count(*) as total from ' + this.data.from;
      case 'delete':
        return 'delete from ' + this.data.from;
      case 'insert_into':
        return 'insert into ' + this.data.from + ' (' + this.data.set.join(', ') + ') values (' + this.data.values.join(', ') + ')';
      case 'select':
        var join = '';

        if (this.data.join.length > 0) {
          joli.each(this.data.join, function(value, key) {
            join = join + ' left outer join ' + value[0] + ' ON ' + value[1] + '=' + value[2];
          });
        }

        return 'select ' + this.data.select_columns + ' from ' + this.data.from + join;
      case 'update':
        return 'update ' + this.data.from + ' set ' + this.data.set.join(', ');
      default:
        throw("Operation type Error. joli.query operation type must be an insert, a delete, a select or an update.");
    }
  },

  getQuery: function() {
    var query = this.getOperation();

    if (this.data.where) {
      query += ' where ' + this.data.where;
    }

    if (this.data.order.length > 0) {
      query += ' order by ' + this.data.order.join(', ');
    }

    if (this.data.limit) {
      query += ' limit ' + this.data.limit;
    }

    return query;
  },

  hydrate: function(rows) {
    var result = [];

    if (!rows) {
      return result;
    }

    //Titanium.API.log('debug', 'hydrating ' + rows.rowCount + ' rows.');
    var i;
    var rowData;

    if (Titanium.Platform.name != 'android') {
      var fieldCount = rows.fieldCount();
    } else {
      var fieldCount = rows.fieldCount;
    }

    while (rows.isValidRow()) {
      i = 0;
      rowData = {};

      while (i < fieldCount) {
        rowData[rows.fieldName(i)] = rows.field(i);
        i++;
      }

    	result.push(rowData);
    	rows.next();
    }

    rows.close();
    return result;
  },

  insertInto: function(table) {
    this.data.operation = 'insert_into';
    this.data.from = table;
    return this;
  },

  join: function(table, local_id, foreign_id) {
    this.data.join.push([table, local_id, foreign_id]);
    return this;
  },

  limit: function(limit) {
    this.data.limit = limit;
    return this;
  },

  order: function(order) {
    if ('string' == joli.getType(order)) {
      order = [order];
    }

    this.data.order = order;
    return this;
  },

  select: function(columns) {
    this.data.operation = 'select';

    if (columns) {
      this.data.select_columns = columns;
    }

    return this;
  },

  set: function(values) {
    joli.each(values, function(expression, value) {
      this.data.set.push(value + ' = ' + joli.typeValue(expression));
    }, this);
    return this;
  },

  update: function(table) {
    this.data.operation = 'update';
    this.data.from = table;
    return this;
  },

  values: function(values) {
    joli.each(values, function(expression, value) {
      this.data.set.push(value);
      this.data.values.push(joli.typeValue(expression));
    }, this);
    return this;
  },

  where: function(expression, value) {
    if (null != this.data.where) {
      this.data.where += ' and ';
    } else {
      this.data.where = '';
    }

    this.data.where += expression.replace(/\?/gi, '"' + value + '"');
    return this;
  },

  whereIn: function(expression, value) {
    if (null != this.data.where) {
      this.data.where += ' and ';
    } else {
      this.data.where = '';
    }

    this.data.where += expression + ' IN ' + value;
    return this;
  }
};

joli.record = function(table) {
  this._options = {
    table: table,
    columns: table.getColumns()
  };
  this._data = {};
};

joli.record.prototype = {
  destroy: function() {
    if (!this.id) {
      throw("Unsaved record cannot be destroyed");
    } else {
      this._options.table.deleteRecords(this.id);
    }
  },

  fromArray: function(data) {
    if (data.id) {
      this._originalData = {};
      this.isNew = function() {
        return false;
      };
    } else {
      this._originalData = null;
      this.isNew = function() {
        return true;
      };
    }

    joli.each(this._options.columns, function(colType, colName) {
      this[colName] = null;
      this[colName] = data[colName];
      this._data[colName] = null;
      this._data[colName] = data[colName];

      if (this._originalData) {
        this._originalData[colName] = data[colName];
      }
    }, this);

    return this;
  },

  isChanged: function() {
    if (this.isNew()) {
      return false;
    }

    return !(this.id && joli.toQueryString(this._data) === joli.toQueryString(this._originalData));
  },

  save: function() {
    var data = { data: this._data };

    if (this.isChanged()) {
      data.originalData = this._originalData;
      this._options.table.save(data);
    } else if(this.isNew()) {
      this._data.id = this._options.table.save(data);
    }

    // overwrite original data so it is no longer "dirty" OR so it is no longer new
    this._originalData = {};

    joli.each(this._options.columns, function(colType, colName) {
      this._originalData[colName] = this._data[colName];
    }, this);

    this._data = this._originalData;

    this.isNew = function() {
      return false;
    };

    return true;
  }
};
<|endoftext|>


<commit_before>var clc = require('cli-color');
var maxNickLen = 12;
var fileBuf = 4096; // how many characters of file to remember
var path = require('path');
var myNick = "FruitieX";
var hilight_re = new RegExp(".*" + myNick + ".*", 'i');
var ansi_escape_re = /\x1b[^m]*m/;

var separatorColor = clc.xterm(239);
var numColor = clc.xterm(232).bgXterm(255);
var chanColor = clc.xterm(255).bgXterm(239);
var myNickColor = 1;
var hilightColor = 3;

// we need this in global scope so we can remove the event listeners!
var out;
var openChan = function(filePath) {
	var outFile = filePath + '/out';
	var inFile = filePath + '/in';
	var file = require('fs').readFileSync(outFile, 'utf8');

	var bn = path.basename(filePath);
	var num_s = ' ' + bn.substring(0, bn.indexOf('_')) + ' ';
	var num = numColor(num_s);
	var chan_s = ' ' + bn.substring(bn.indexOf('_') + 1, bn.length) + ' ';
	var chan = chanColor(chan_s);

	var printLine = function(line) {
		var hilight = false;

		// clear channel name
		process.stdout.write('\r' + Array(num_s.length + chan_s.length + 1).join(' ') + '\r');

		// remove timestamps
		line = line.replace(/^([^ ]+ ){2}/, '');
		line = line.split(' ');
		// remove <> around nick
		var nick = line[0].substring(1, line[0].length - 1);

		if(nick === myNick) {
			clrnick = myNickColor;
		} else {
			// nick color, avoids dark colors
			var clrnick = 0;
			for(var i = 0; i < nick.length; i++) {
				clrnick += nick.charCodeAt(i);
			}
			clrnick = Math.pow(clrnick, 2) + clrnick * 2;
			clrnick = clrnick % 255;
			switch(clrnick) {
				case 18: case 22: case 23: case 24:
					clrnick += 3; break;
				case 52: case 53: case 54: case 55: case 56: case 57: case 88: case 89:
					clrnick += 6; break;
				case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239:
					clrnick += 8; break;
				case 0: case 8: case 19: case 22:
					clrnick++; break;
			}
		}

		// limit nicklen
		nick = nick.substr(0, maxNickLen);
		// align nicks
		process.stdout.write(Array(maxNickLen - nick.length + 1).join(' '));
		process.stdout.write(clc.xterm(clrnick)(nick) + separatorColor(':'));
		process.stdout.write(' ');

		// remove nick from line array, now contains space separated text message
		line.shift();

		for (var i = 0; i < line.length; i++) {
			if(line[i].match(hilight_re))
				hilight = true;
		}

		var textColor = 15;
		if (nick === myNick)
			textColor = myNickColor;
		else if (hilight)
			textColor = hilightColor;

		// TODO: wrap at whitespace
		for (var i = 0; i < line.length; i++) {
			line[i] = line[i].replace(ansi_escape_re, '');
			process.stdout.write(clc.xterm(textColor)(line[i]));
			if(i != line.length - 1)
				process.stdout.write(' ');
		}
		process.stdout.write('\n');

		// print channel name again
		process.stdout.write(num + chan + ' ');
	};

	// log entire file
	var printFile = function(file) {
		var start = 0;
		while (start < file.length) {
			var end = file.indexOf('\n', start);
			if(end === -1)
				break;
			printLine(file.substring(start, end));
			start = end + 1;
		}
	};

	// limit file to size fileBuf
	if(file.length >= fileBuf) {
		file = file.substr(file.length - fileBuf, file.length);
		file = file.substr(file.indexOf("\n") + 1, file.length);
	}

	// set raw mode
	var stdin = process.stdin;
	stdin.setRawMode(true);
	stdin.resume();
	stdin.setEncoding('utf8');

	// handle 'q', 'ctrl-c' by quitting
	stdin.on('data', function(key) {
		if(key == 'q' || key == '\u0003') process.exit();
	});

	// clear terminal and print
	process.stdout.write('\u001B[2J\u001B[0;0f');
	printFile(file);

	// watch file
	Tail = require('tail').Tail;
	out = new Tail(outFile);

	out.on('line', function(data) {
		file += data + '\n';
		// limit file to size fileBuf
		if(file.length >= fileBuf) {
			file = file.substr(file.length - fileBuf, file.length);
			file = file.substr(file.indexOf("\n") + 1, file.length);
		}

		// print the line
		printLine(data);
	});

	process.stdout.on('resize', function() {
		process.stdout.write('\u001B[2J\u001B[0;0f');
		printFile(file);
	});
};

// TODO: changing channels?
var currentChan = process.argv[2];
openChan(process.argv[2]);
<commit_msg>prettier line wrap<commit_after>var clc = require('cli-color');
var maxNickLen = 12;
var fileBuf = 4096; // how many characters of file to remember
var path = require('path');
var myNick = "FruitieX";
var hilight_re = new RegExp(".*" + myNick + ".*", 'i');
var ansi_escape_re = /\x1b[^m]*m/;

var separatorColor = clc.xterm(239);
var numColor = clc.xterm(232).bgXterm(255);
var chanColor = clc.xterm(255).bgXterm(239);
var myNickColor = 1;
var hilightColor = 3;

// we need this in global scope so we can remove the event listeners!
var out;
var openChan = function(filePath) {
	var outFile = filePath + '/out';
	var inFile = filePath + '/in';
	var file = require('fs').readFileSync(outFile, 'utf8');

	var bn = path.basename(filePath);
	var num_s = ' ' + bn.substring(0, bn.indexOf('_')) + ' ';
	var num = numColor(num_s);
	var chan_s = ' ' + bn.substring(bn.indexOf('_') + 1, bn.length) + ' ';
	var chan = chanColor(chan_s);

	var printLine = function(line) {
		var hilight = false;

		// clear channel name
		process.stdout.write('\r' + Array(num_s.length + chan_s.length + 1).join(' ') + '\r');

		// remove timestamps
		line = line.replace(/^([^ ]+ ){2}/, '');
		line = line.split(' ');
		// remove <> around nick
		var nick = line[0].substring(1, line[0].length - 1);

		if(nick === myNick) {
			clrnick = myNickColor;
		} else {
			// nick color, avoids dark colors
			var clrnick = 0;
			for(var i = 0; i < nick.length; i++) {
				clrnick += nick.charCodeAt(i);
			}
			clrnick = Math.pow(clrnick, 2) + clrnick * 2;
			clrnick = clrnick % 255;
			switch(clrnick) {
				case 18: case 22: case 23: case 24:
					clrnick += 3; break;
				case 52: case 53: case 54: case 55: case 56: case 57: case 88: case 89:
					clrnick += 6; break;
				case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239:
					clrnick += 8; break;
				case 0: case 8: case 19: case 22:
					clrnick++; break;
			}
		}

		// limit nicklen
		nick = nick.substr(0, maxNickLen);
		// align nicks
		process.stdout.write(Array(maxNickLen - nick.length + 1).join(' '));
		process.stdout.write(clc.xterm(clrnick)(nick) + separatorColor(':'));
		process.stdout.write(' ');

		// remove nick from line array, now contains space separated text message
		line.shift();
		line = line.join(' ');
		line = line.replace(ansi_escape_re, '');

		if(line.match(hilight_re))
			hilight = true;

		var textColor = 15;
		if (nick === myNick)
			textColor = myNickColor;
		else if (hilight)
			textColor = hilightColor;

		// separator takes up 1 char + whitespace
		var availWidth = process.stdout.columns - maxNickLen - 2;

		// TODO: wrap at whitespace
		for (var i = 0; i < Math.ceil(line.length / availWidth); i++) {
			var curLine = line.substr(i * availWidth, availWidth);

			// empty space on line wrap
			if (i > 0)
				process.stdout.write(Array(process.stdout.columns - availWidth + 1).join(' '));

			process.stdout.write(clc.xterm(textColor)(curLine));
			process.stdout.write('\n');
		}

		// print channel name again
		process.stdout.write(num + chan + ' ');
	};

	// log entire file
	var printFile = function(file) {
		var start = 0;
		while (start < file.length) {
			var end = file.indexOf('\n', start);
			if(end === -1)
				break;
			printLine(file.substring(start, end));
			start = end + 1;
		}
	};

	// limit file to size fileBuf
	if(file.length >= fileBuf) {
		file = file.substr(file.length - fileBuf, file.length);
		file = file.substr(file.indexOf("\n") + 1, file.length);
	}

	// set raw mode
	var stdin = process.stdin;
	stdin.setRawMode(true);
	stdin.resume();
	stdin.setEncoding('utf8');

	// handle 'q', 'ctrl-c' by quitting
	stdin.on('data', function(key) {
		if(key == 'q' || key == '\u0003') process.exit();
	});

	// clear terminal and print
	process.stdout.write('\u001B[2J\u001B[0;0f');
	printFile(file);

	// watch file
	Tail = require('tail').Tail;
	out = new Tail(outFile);

	out.on('line', function(data) {
		file += data + '\n';
		// limit file to size fileBuf
		if(file.length >= fileBuf) {
			file = file.substr(file.length - fileBuf, file.length);
			file = file.substr(file.indexOf("\n") + 1, file.length);
		}

		// print the line
		printLine(data);
	});

	process.stdout.on('resize', function() {
		process.stdout.write('\u001B[2J\u001B[0;0f');
		printFile(file);
	});
};

// TODO: changing channels?
var currentChan = process.argv[2];
openChan(process.argv[2]);
<|endoftext|>


<commit_before>function intersect(array1, array2) {
	return array1.filter(function(n) {
	    return array2.indexOf(n) != -1;
	});
}

function subset(subarray, superarray) {
	return subarray.reduce(function(o, k) { o[k] = superarray[k]; return o; }, {});
}

function transpose(array) {
	var i = 0;
	var dict = {};
	for (i = 0; i < array.length; i++) {
		for (var key in array[i]) {
		   if (array[i].hasOwnProperty(key)) {
		   		if (i == 0) {
		   			dict[key] = [];
		   		}
		   		dict[key].push(array[i][key]);
		   }
		}
	}
	return dict;
}

function exit(error) {
	throw new Error(error);
}

class Database {

	constructor() {
		this.tables = {};
		this.foreign_keys = [];
	}

	insert(rows, table) {
		var table = this.tables[table];
		table.insert(rows);
		this.verify_foreign_keys();
	}

	update(fields, table, where) {
		var table = this.tables[table];
		table.update(fields, where);
		this.verify_foreign_keys();
	}

	delete(table, where) {
		var table = this.tables[table];
		table.delete(where);
		this.verify_foreign_keys();
	}

	select(fields, tables, where) {
		var i = 0;
		var queryset = [];
		for (i = 0; i < tables.length; i++) {
			var name = tables[i]['name'];
			var on = tables[i]['on'];
			var table = this.tables[name];
			var condition = on.replace(/`(\w+)`\.`(\w+)`/g, "this.tables['$1']['$2']");
			if (eval(condition)) {
				// This needs work.  Should use Object.assign somewhere to combine the objects in each row.
				queryset = queryset.concat(table.select(fields, where));
			}
		}
		return queryset;
	}

	create_table(name, structure) {
		this.tables[name] = new Table(name, structure);
		this.foreign_keys = this.foreign_keys.concat(this.tables[name].foreign_keys);
	}

	verify_foreign_keys() {
		var i = 0;
		var foreign_keys = this.foreign_keys;
		var tables = this.tables;
		for (i = 0; i < foreign_keys.length; i++) {
			var foreign_key = foreign_keys[i];
			var values = tables[foreign_key["table"]].indices[foreign_key["field"]];
			if (!values) {
				continue;
			}
			values.every(function(value) {
				if (!tables.hasOwnProperty(foreign_key["target_table"]) || !tables[foreign_key["target_table"]].indices.hasOwnProperty(foreign_key["target_field"]) || tables[foreign_key["target_table"]].indices[foreign_key["target_field"]].indexOf(value) == -1) {
					throw new Error("Foreign key constraint.  The field "+foreign_key["table"]+"."+foreign_key["field"]+" is linked to "+foreign_key["target_table"]+"."+foreign_key["target_field"]+".  This constraint is being violated where the former equals "+value+".  The latter contains no such value.");
				}
			});
		}
	}

}

class Table {

	constructor(name, structure) {
		this.name = name;
		this.structure = structure;
		this.data = [];
		this.indices = {};
		this.foreign_keys = [];
		var self = this;
		var keys = Object.keys(structure);
		keys.forEach(function(key) {
			if (structure[key].constructor.name == 'ForeignKeyField') {
				var target = structure[key].target;
				var target = target.replace('`.`', '`');
				var target_table = target.split("`")[1];
				var target_field = target.split("`")[2];
				self.foreign_keys.push({"table": name, "field": key, "target_table": target_table, "target_field": target_field});
			}
		});
	}

	index() {
		this.indices = transpose(this.data);
	}

	insert(rows) {
		var i = 0;
		for (i = 0; i < rows.length; i++) {
			var json = rows[i];
			var keys = Object.keys(json);
			var structure = this.structure;
			var indices = this.indices;
			keys.forEach(function(key) {
				if (!structure[key].validate(json[key])) {
					throw new Error("Invalid data type.");
				}
				if (structure[key].unique) {
					if (indices && indices.hasOwnProperty(key) && indices[key].indexOf(json[key]) > -1) {
						throw new Error(key+"' field is unique.  Cannot add duplicate value "+json[key]);
					}
				}
			});
			this.data.push(json);
			this.index();
		}
	}

	update(fields, where) {
		this.indexed_update(fields, where);
	}

	delete(where) {
		this.indexed_delete(where);
	}

	select(fields, where) {
		return this.indexed_select(fields, where);
	}

	indexed_update(fields, where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			condition = condition.replace(/`(\w+)`/g, "this.indices['$1'][i]");
			if (eval(condition)) {
				Object.assign(this.data[i], fields);
			}
		}
		this.index();
	}

	indexed_delete(where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			condition = condition.replace(/`(\w+)`/g, "this.indices['$1'][i]");
			if (eval(condition)) {
				this.data.splice(i, 1);
			}
		}
		this.index();
	}

	indexed_select(fields, where) {
		var i = 0;
		var results = [];
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			condition = condition.replace(/`(\w+)`/g, "this.indices['$1'][i]");
			if (eval(condition)) {
				var sub = {};
				var ii = 0;
				for (ii = 0; ii < fields.length; ii++) {
					sub[fields[ii]] = this.indices[fields[ii]][i];
				}
				results.push(sub);
			}
		}
		return results;
	}

	full_scan_update(fields, where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			var keys = Object.keys(this.data[i]);
			keys.forEach(function(key) {
				condition = condition.replace("`"+key+"`", "this.data[i]['"+key+"']");
			});
			if (eval(condition)) {
				Object.assign(this.data[i], fields);
			}
		}
		this.index();
	}

	full_scan_delete(where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			var keys = Object.keys(this.data[i]);
			keys.forEach(function(key) {
				condition = condition.replace("`"+key+"`", "this.data[i]['"+key+"']");
			});
			if (eval(condition)) {
				this.data.splice(i, 1);
			}
		}
		this.index();
	}

	full_scan_select(fields, where) {
		var i = 0;
		var results = [];
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			var keys = Object.keys(this.data[i]);
			keys.forEach(function(key) {
				condition = condition.replace("`"+key+"`", "this.data[i]['"+key+"']");
			});
			if (eval(condition)) {
				if (intersect(fields, keys)) {
					var sub = subset(fields, this.data[i]);
					results.push(sub);
				}
			}
		}
		return results;
	}

}

class Field {

	constructor(max_length = false, unique=false) {
		if (max_length) {
			this.max_length = max_length;
		}
		if (unique) {
			this.unique = true;
		}
	}

	validate(value) {
		if (value.length > this.max_length) {
			throw new Error(value+"' is greater than '"+this.max_length+"' characters.");
		}
		return true;
	}

}

class CharField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (typeof value !== 'string') {
			throw new Error(value+"' is not a string.");
		}
		return true;
	}

}

class FloatField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class IntegerField extends FloatField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (!Number.isInteger(value)) {
			throw new Error(value+"' is not an integer.");
		}
		return true;
	}

}

class EmailField extends CharField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		var re = /^.+@.+$/;
		if (!re.test(value)) {
			throw new Error(value+"' is not an email address.");
		}
		return true;
	}

}

class UrlField extends CharField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		var re = /^https?:\/\/.+\..+$/;
		if (!re.test(value)) {
			throw new Error(value+"' is not a url.");
		}
		return true;
	}

}

class ForeignKeyField extends IntegerField {

	constructor(target, max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
		this.target = target;
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class BooleanField extends IntegerField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class AutoField extends IntegerField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class SlugField extends CharField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class PositiveIntegerField extends IntegerField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class DateTimeField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class DecimalField extends FloatField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class DateField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

var test = new Database();
test.create_table(name='test', structure={
		'id': new IntegerField(max_length=false, unique=true),
		'name': new CharField(),
		'floorplan': new ForeignKeyField('`floorplan`.`id`'),
	}
);
test.create_table(name='floorplan', structure={
		'id': new IntegerField(max_length=false, unique=true),
	}
);
test.insert(rows=[{"id": 1}], table='floorplan');
test.insert(rows=[{"id": 1, "name":"Bob"}], table='test');
test.insert(rows=[{"id": 3, "name":"Sam"}], table='test');
test.update(fields={"id": 2, "name":"Tony", "floorplan": 2}, table='test', where='`id` == 1');
test2 = test.select(fields=["id"], tables=[{'name':'test', 'on':'true'}], where='`id` == 2');
// console.log(test2);
<commit_msg>Added autofield<commit_after>function intersect(array1, array2) {
	return array1.filter(function(n) {
	    return array2.indexOf(n) != -1;
	});
}

function subset(subarray, superarray) {
	return subarray.reduce(function(o, k) { o[k] = superarray[k]; return o; }, {});
}

function transpose(array) {
	var i = 0;
	var dict = {};
	for (i = 0; i < array.length; i++) {
		for (var key in array[i]) {
		   if (array[i].hasOwnProperty(key)) {
		   		if (!dict.hasOwnProperty(key)) {
		   			dict[key] = [];
		   		}
		   		dict[key].push(array[i][key]);
		   }
		}
	}
	return dict;
}

function exit(error) {
	throw new Error(error);
}

class Database {

	constructor() {
		this.tables = {};
		this.foreign_keys = [];
	}

	insert(rows, table) {
		var table = this.tables[table];
		table.insert(rows);
		this.verify_foreign_keys();
	}

	update(fields, table, where) {
		var table = this.tables[table];
		table.update(fields, where);
		this.verify_foreign_keys();
	}

	delete(table, where) {
		var table = this.tables[table];
		table.delete(where);
		this.verify_foreign_keys();
	}

	select(fields, tables, where) {
		var i = 0;
		var queryset = [];
		for (i = 0; i < tables.length; i++) {
			var name = tables[i]['name'];
			var on = tables[i]['on'];
			var table = this.tables[name];
			var condition = on.replace(/`(\w+)`\.`(\w+)`/g, "this.tables['$1']['$2']");
			if (eval(condition)) {
				// This needs work.  Should use Object.assign somewhere to combine the objects in each row.
				queryset = queryset.concat(table.select(fields, where));
			}
		}
		return queryset;
	}

	create_table(name, structure) {
		this.tables[name] = new Table(name, structure);
		this.foreign_keys = this.foreign_keys.concat(this.tables[name].foreign_keys);
	}

	verify_foreign_keys() {
		var i = 0;
		var foreign_keys = this.foreign_keys;
		var tables = this.tables;
		for (i = 0; i < foreign_keys.length; i++) {
			var foreign_key = foreign_keys[i];
			var values = tables[foreign_key["table"]].indices[foreign_key["field"]];
			if (!values) {
				continue;
			}
			values.every(function(value) {
				if (!tables.hasOwnProperty(foreign_key["target_table"]) || !tables[foreign_key["target_table"]].indices.hasOwnProperty(foreign_key["target_field"]) || tables[foreign_key["target_table"]].indices[foreign_key["target_field"]].indexOf(value) == -1) {
					throw new Error("Foreign key constraint.  The field "+foreign_key["table"]+"."+foreign_key["field"]+" is linked to "+foreign_key["target_table"]+"."+foreign_key["target_field"]+".  This constraint is being violated where the former equals "+value+".  The latter contains no such value.");
				}
			});
		}
	}

}

class Table {

	constructor(name, structure) {
		this.name = name;
		this.structure = structure;
		this.data = [];
		this.indices = {};
		this.foreign_keys = [];
		var self = this;
		var keys = Object.keys(structure);
		keys.forEach(function(key) {
			if (structure[key].constructor.name == 'ForeignKeyField') {
				var target = structure[key].target;
				var target = target.replace('`.`', '`');
				var target_table = target.split("`")[1];
				var target_field = target.split("`")[2];
				self.foreign_keys.push({"table": name, "field": key, "target_table": target_table, "target_field": target_field});
			}
		});
	}

	index() {
		this.indices = transpose(this.data);
	}

	insert(rows) {
		var i = 0;
		for (i = 0; i < rows.length; i++) {
			var json = rows[i];
			var keys = Object.keys(json);
			var structure = this.structure;
			var structure_keys = Object.keys(structure);
			var indices = this.indices;
			keys.forEach(function(key) {
				if (!structure[key].validate(json[key])) {
					throw new Error("Invalid data type.");
				}
				if (structure[key].unique) {
					if (indices && indices.hasOwnProperty(key) && indices[key].indexOf(json[key]) > -1) {
						throw new Error(key+"' field is unique.  Cannot add duplicate value "+json[key]);
					}
				}
			});
			structure_keys.forEach(function(key) {
				if (structure[key].constructor.name == 'AutoField' && !json.hasOwnProperty(key)) {
					var values = indices[key];
					values.push(structure[key].counter);
					var next = Math.max.apply(Math, values) + 1;
					json[key] = next;
					structure[key].counter = next;
				}
				if (!json.hasOwnProperty(key) && structure[key].defaul) {
					json[key] = structure[key].defaul;
				}
				else if (!json.hasOwnProperty(key) && structure[key].blank) {
					json[key] = null;
				}
				else if (!json.hasOwnProperty(key) && !structure[key].blank && !structure[key].defaul) {
					throw new Error(key+" is a required field.");
				}
			});
			this.data.push(json);
			this.index();
		}
	}

	update(fields, where) {
		this.indexed_update(fields, where);
	}

	delete(where) {
		this.indexed_delete(where);
	}

	select(fields, where) {
		return this.indexed_select(fields, where);
	}

	indexed_update(fields, where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			condition = condition.replace(/`(\w+)`/g, "this.indices['$1'][i]");
			if (eval(condition)) {
				Object.assign(this.data[i], fields);
			}
		}
		this.index();
	}

	indexed_delete(where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			condition = condition.replace(/`(\w+)`/g, "this.indices['$1'][i]");
			if (eval(condition)) {
				this.data.splice(i, 1);
			}
		}
		this.index();
	}

	indexed_select(fields, where) {
		var i = 0;
		var results = [];
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			condition = condition.replace(/`(\w+)`/g, "this.indices['$1'][i]");
			if (eval(condition)) {
				var sub = {};
				var ii = 0;
				for (ii = 0; ii < fields.length; ii++) {
					sub[fields[ii]] = this.indices[fields[ii]][i];
				}
				results.push(sub);
			}
		}
		return results;
	}

	full_scan_update(fields, where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			var keys = Object.keys(this.data[i]);
			keys.forEach(function(key) {
				condition = condition.replace("`"+key+"`", "this.data[i]['"+key+"']");
			});
			if (eval(condition)) {
				Object.assign(this.data[i], fields);
			}
		}
		this.index();
	}

	full_scan_delete(where) {
		var i = 0;
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			var keys = Object.keys(this.data[i]);
			keys.forEach(function(key) {
				condition = condition.replace("`"+key+"`", "this.data[i]['"+key+"']");
			});
			if (eval(condition)) {
				this.data.splice(i, 1);
			}
		}
		this.index();
	}

	full_scan_select(fields, where) {
		var i = 0;
		var results = [];
		for (i = 0; i < this.data.length; i++) {
			var condition = where;
			var keys = Object.keys(this.data[i]);
			keys.forEach(function(key) {
				condition = condition.replace("`"+key+"`", "this.data[i]['"+key+"']");
			});
			if (eval(condition)) {
				if (intersect(fields, keys)) {
					var sub = subset(fields, this.data[i]);
					results.push(sub);
				}
			}
		}
		return results;
	}

}

class Field {

	constructor(max_length = false, unique=false, blank=true, defaul=false) {
		if (max_length) {
			this.max_length = max_length;
		}
		if (unique) {
			this.unique = true;
		}
		if (blank) {
			this.blank = true;
		}
		if (defaul) {
			this.defaul = defaul;
		}
		if (!this.defaul && !this.blank) {
			throw new Error("Required fields must have a default value.");
		}
	}

	validate(value) {
		if (value.length > this.max_length) {
			throw new Error(value+"' is greater than '"+this.max_length+"' characters.");
		}
		return true;
	}

}

class CharField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (typeof value !== 'string') {
			throw new Error(value+"' is not a string.");
		}
		return true;
	}

}

class FloatField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class IntegerField extends FloatField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (!Number.isInteger(value)) {
			throw new Error(value+"' is not an integer.");
		}
		return true;
	}

}

class EmailField extends CharField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		var re = /^.+@.+$/;
		if (!re.test(value)) {
			throw new Error(value+"' is not an email address.");
		}
		return true;
	}

}

class UrlField extends CharField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		var re = /^https?:\/\/.+\..+$/;
		if (!re.test(value)) {
			throw new Error(value+"' is not a url.");
		}
		return true;
	}

}

class ForeignKeyField extends IntegerField {

	constructor(target, max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
		this.target = target;
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class BooleanField extends IntegerField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (typeof value !== 'boolean') {
			throw new Error(value+" is not a boolean.");
		}
		return true;
	}

}

class AutoField extends IntegerField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
		this.counter = 0;
	}

	validate(value) {
		super.validate(value);
		return true;
	}

}

class SlugField extends CharField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		var re = /^[\w\d_-]*$/;
		if (!re.test(value)) {
			throw new Error(value+"' is not a slug.");
		}
		return true;
	}

}

class PositiveIntegerField extends IntegerField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (value < 0) {
			throw new Error(value+" is not positive.");
		}
		return true;
	}

}

class DateTimeField extends Field {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		if (!value instanceof Date) {
			throw new Exception(value+" is not a datetime.");
		}
		return true;
	}

}

class DecimalField extends FloatField {

	constructor(max_length=false, unique=false) {
		super(max_length=max_length, unique=unique);
	}

	validate(value) {
		super.validate(value);
		var num = float(value.toFixed(2));
		if (value !== num) {
			throw new Error(value+" is not a decimal.");
		}
		return true;
	}

}

var test = new Database();
test.create_table(name='test', structure={
		'id': new AutoField(max_length=false, unique=true),
		'name': new CharField(),
	}
);
test.create_table(name='floorplan', structure={
		'id': new IntegerField(max_length=false, unique=true),
	}
);
test.insert(rows=[{"id": 0}], table='test');
test.insert(rows=[{"name":"Bob"}], table='test');
test.insert(rows=[{"name":"Sam"}], table='test');
// test.update(fields={"name":"Tony", "floorplan": 1}, table='test', where='`id` == 1');
test2 = test.select(fields=["id", "name"], tables=[{'name':'test', 'on':'true'}], where='true');
console.log(test2);
<|endoftext|>


<commit_before>console.log("june");<commit_msg>修改june.js<commit_after>console.log("june + weineel");
<|endoftext|>


<commit_before>'use strict';

angular.module('app').directive('bubbles', [function() {
    var computeNodes = function(data) {
        var nodes = _.map(data, function(d) {
            return {
                id : d,
                r : 10,
                x : 0,
                y : 0,
                cluster : Math.round(Math.random())
            };
        });

        return nodes;
    };

    var getClusterCenter = function(data, clusterBy, width, height) {
        return [
            { x : width / 3, y : height / 2 },
            { x : (width / 3) * 2, y : height / 2 },
        ];
    };

    return {
        restrict : 'EA',
        scope : {
            data : '='
        },
        templateUrl : 'directives/bubbles/bubbles.html',
        link : function($scope, element) {
            var svg = angular.element(element).find('div.d3');

            var padding = {
                top : 100,
                right : 100,
                bottom : 100,
                left : 100
            };
            var width = svg.width();
            var height = svg.height();

            svg = d3.select(svg.get(0))
                    .append('svg')
                    .attr('width', width)
                    .attr('height', height);

            width = width - padding.left - padding.right;
            height = height - padding.top - padding.bottom;
            svg = svg.append('g')
                     .attr('width', width)
                     .attr('height', height)
                     .translate([padding.left, padding.top]);

            $scope.data = _.range(30);
            var nodes = computeNodes($scope.data);

            var clusterCenters = getClusterCenter(nodes, '', width, height);
            _.each(nodes, function(d) {
                d.x = clusterCenters[d.cluster].x;
                d.y = clusterCenters[d.cluster].y;
            });

            var bubbles = svg.selectAll('circle.bubble')
                             .data(nodes, d3.f('id'));
            // Create bubbles
            bubbles.enter().append('circle')
                           .attr('class', 'bubble')
                           .attr('r', d3.f('r'))
                           .on('mouseenter', function() {
                                d3.select(this).classed('hover', true);
                           })
                           .on('mouseleave', function() {
                                d3.select(this).classed('hover', false);
                           });

            var force = d3.layout.force().nodes(nodes).size([width, height]);
            var charge = function(d) {
                return -Math.pow(d.r, 2.0) / 3;
            };
            // Enable and start the force layout
            force.gravity(-0.01).charge(charge).friction(0.9).on('tick', function(e) {
                bubbles.each(function(d) {
                    d.x = d.x + (clusterCenters[d.cluster].x - d.x) * e.alpha * (0.3);
                    d.y = d.y + (clusterCenters[d.cluster].y - d.y) * e.alpha * (0.3);
                }).attr('cx', d3.f('x'))
                  .attr('cy', d3.f('y'));
            });
            force.start();

            // Switches
            $scope.switches = [
                2008,
                2015
            ];

            $scope.activateSwitch = function(theSwitch) {
                if ($scope.switches.indexOf(theSwitch) >= 0) {
                    $scope.activeSwitch = theSwitch;
                }
            };

            $scope.activateSwitch($scope.switches[0]);
        }
    };
}]);<commit_msg>colorFromString util function<commit_after>'use strict';

angular.module('app').directive('bubbles', [function() {
    var colorFromString = function(str) {
        str = str || '';

        var i, hash;
        i = hash = 0;
        while (i < str.length) {
            hash = str.charCodeAt(i++) + ((hash << 5) - hash);
        }

        var colour = '#';
        for (i = 0; i < 3; ++i) {
            var part = (hash >> i * 8) & 0xFF;
            part = Math.max(Math.min(part, 200), 50);
            colour += ('00' + part.toString(16)).slice(-2);
        }
        return colour;
    };

    var computeNodes = function(data, clusterBy) {
        var nodes = _.map(data, function(d) {
            return {
                id : d,
                r : 10,
                x : 0,
                y : 0,
                cluster : Math.round(Math.random())
            };
        });

        return nodes;
    };

    var getClusterCenter = function(data, clusterBy, width, height) {
        return [
            { x : width / 3, y : height / 2 },
            { x : (width / 3) * 2, y : height / 2 },
        ];
    };

    return {
        restrict : 'EA',
        scope : {
            data : '='
        },
        templateUrl : 'directives/bubbles/bubbles.html',
        link : function($scope, element) {
            var svg = angular.element(element).find('div.d3');

            var padding = {
                top : 100,
                right : 100,
                bottom : 100,
                left : 100
            };
            var width = svg.width();
            var height = svg.height();

            svg = d3.select(svg.get(0))
                    .append('svg')
                    .attr('width', width)
                    .attr('height', height);

            width = width - padding.left - padding.right;
            height = height - padding.top - padding.bottom;
            svg = svg.append('g')
                     .attr('width', width)
                     .attr('height', height)
                     .translate([padding.left, padding.top]);

            $scope.data = _.range(30);
            var nodes = computeNodes($scope.data);

            var clusterCenters = getClusterCenter(nodes, '', width, height);
            _.each(nodes, function(d) {
                d.x = clusterCenters[d.cluster].x;
                d.y = clusterCenters[d.cluster].y;
            });

            var bubbles = svg.selectAll('circle.bubble')
                             .data(nodes, d3.f('id'));
            // Create bubbles
            bubbles.enter().append('circle')
                           .attr('class', 'bubble')
                           .attr('r', d3.f('r'))
                           .on('mouseenter', function() {
                                d3.select(this).classed('hover', true);
                           })
                           .on('mouseleave', function() {
                                d3.select(this).classed('hover', false);
                           });

            var force = d3.layout.force().nodes(nodes).size([width, height]);
            var charge = function(d) {
                return -Math.pow(d.r, 2.0) / 3;
            };
            // Enable and start the force layout
            force.gravity(-0.01).charge(charge).friction(0.9).on('tick', function(e) {
                bubbles.each(function(d) {
                    d.x = d.x + (clusterCenters[d.cluster].x - d.x) * e.alpha * (0.3);
                    d.y = d.y + (clusterCenters[d.cluster].y - d.y) * e.alpha * (0.3);
                }).attr('cx', d3.f('x'))
                  .attr('cy', d3.f('y'));
            });
            force.start();

            // Switches
            $scope.switches = [
                2008,
                2015
            ];

            $scope.activateSwitch = function(theSwitch) {
                if ($scope.switches.indexOf(theSwitch) >= 0) {
                    $scope.activeSwitch = theSwitch;
                }
            };

            $scope.activateSwitch($scope.switches[0]);
        }
    };
}]);<|endoftext|>


<commit_before>/* jshint node: true */
/* jshint esnext: true */
'use strict';

const Q = require('q');
const _ = require('lodash');
const router = require('express').Router();
const accesscontrol = require('../../acl/accesscontrol');
const isAuthenticated = require('../../acl/authentication');
const pdfGeneration = require('../../pdf/pdfGeneration');
const registration = require('../../service/registration');
const stats = require('../../service/stats');
const participants = require('../../service/participants');

let canViewAdminPage = (role) => accesscontrol.hasPermissionTo(role, 'view admin page');

let renderNotAllowed = (res) => res.render('error', {
  message: 'Bitte anmelden',
  error: {
    status: 'Nur Administratoren können diese Seite einsehen'
  }
});

router.get('/', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    Q.allSettled(
      [stats.shirtOrders(), stats.confirmedParticipantsCount(), stats.unconfirmedParticipantsCount(), stats.usageData()])
      .then((results) => {
        let r = results.map(r => r.value);

        participants.get.blancParticipants().then((blancParticipants) => {
          res.render('admin/admin', {
              numBlancParticipants: blancParticipants.length,
              registrationsData: r[3].registrations,
              confirmationsData: r[3].confirmations,
              labels: r[3].dates,
              shirts: {
                regular: stats.reqularShirts(r[0]),
                slim: stats.slimShirts(r[0])
              },
              participants: {
                confirmed: r[1][0].count,
                unconfirmed: r[2][0].count
              }
            }
          );
        });
      });
  } else {
    renderNotAllowed(res);
  }
});

router.get('/generate-start-numbers', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    pdfGeneration.generateRegistered(res);
  }
});

router.post('/generate-on-site-start-numbers', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    participants.saveBlancParticipants(_.toInteger(req.body.amountOnSite)).then(() => {
      pdfGeneration.generateOnSite(res);
    });
  }
});

router.get('/bulkmail', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    participants.bulkmail().then(result =>
      res.render('admin/bulkmail', {count: result})
    );
  } else {
    renderNotAllowed(res);
  }
});

router.post('/close-registration', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    registration.close().then(() =>
      res.render('admin/closeRegistration/success')
    );
  }
});

router.post('/reopen-registration', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    registration.reopen().then(() =>
      res.redirect('/admin')
    );
  }
});

module.exports = router;
<commit_msg>Old start numbers generations disabled in the ui<commit_after>/* jshint node: true */
/* jshint esnext: true */
'use strict';

const Q = require('q');
const _ = require('lodash');
const router = require('express').Router();
const accesscontrol = require('../../acl/accesscontrol');
const isAuthenticated = require('../../acl/authentication');
const pdfGeneration = require('../../pdf/pdfGeneration');
const registration = require('../../service/registration');
const stats = require('../../service/stats');
const participants = require('../../service/participants');

let canViewAdminPage = (role) => accesscontrol.hasPermissionTo(role, 'view admin page');

let renderNotAllowed = (res) => res.render('error', {
  message: 'Bitte anmelden',
  error: {
    status: 'Nur Administratoren können diese Seite einsehen'
  }
});

router.get('/', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    Q.allSettled(
      [stats.shirtOrders(), stats.confirmedParticipantsCount(), stats.unconfirmedParticipantsCount(), stats.usageData()])
      .then((results) => {
        let r = results.map(r => r.value);

        participants.get.blancParticipants().then((blancParticipants) => {
          res.render('admin/admin', {
              numBlancParticipants: blancParticipants.length,
              registrationsData: r[3].registrations,
              confirmationsData: r[3].confirmations,
              labels: r[3].dates,
              shirts: {
                regular: stats.reqularShirts(r[0]),
                slim: stats.slimShirts(r[0])
              },
              participants: {
                confirmed: r[1][0].count,
                unconfirmed: r[2][0].count
              }
            }
          );
        });
      });
  } else {
    renderNotAllowed(res);
  }
});

router.get('/generate-start-numbers', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
      console.error('Currently disabled'); //todo
  }
});

router.post('/generate-on-site-start-numbers', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
      console.error('Currently disabled'); //todo
    // participants.saveBlancParticipants(_.toInteger(req.body.amountOnSite)).then(() => {
    //  pdfGeneration.generateOnSite();
    //});
  }
});

router.get('/bulkmail', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    participants.bulkmail().then(result =>
      res.render('admin/bulkmail', {count: result})
    );
  } else {
    renderNotAllowed(res);
  }
});

router.post('/close-registration', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    registration.close().then(() =>
      res.render('admin/closeRegistration/success')
    );
  }
});

router.post('/reopen-registration', isAuthenticated, (req, res) => {
  if (canViewAdminPage(req.user.role)) {
    registration.reopen().then(() =>
      res.redirect('/admin')
    );
  }
});

module.exports = router;
<|endoftext|>


<commit_before>var helpers = require('./helpers.js'),
  fs = require('fs'),
  jf = require('jsonfile'),

  exports = module.exports = {};

var pruneArrayAndObject = function () {
  delete Array.prototype.equals;
  delete Object.prototype.equals;
}

var extendArrayAndObject = function() {
  Array.prototype.equals = function(array) {
    if (!array)
      return false;

    if (this.length !== array.length)
      return false;

    for (var i = 0, l = this.length; i < l; i++) {
      if (this[i] instanceof Array && array[i] instanceof Array) {
        if (!this[i].equals(array[i]))
          return false;
      } else if (this[i] instanceof Object && array[i] instanceof Object) {
        if (!this[i].equals(array[i]))
          return false;
      } else if (this[i] !== array[i]) {
        return false;
      }
    }
    return true;
  };

  Object.prototype.equals = function(object2) {
    var propName;
    for (propName in this) {
      if (this.hasOwnProperty(propName) !== object2.hasOwnProperty(propName)) {
        return false;
      } else if (typeof this[propName] !== typeof object2[propName]) {
        return false;
      }
    }

    for (propName in object2) {
      if (this.hasOwnProperty(propName) !== object2.hasOwnProperty(propName)) {
        return false;
      } else if (typeof this[propName] !== typeof object2[propName]) {
        return false;
      }

      if (!this.hasOwnProperty(propName))
        continue;

      if (this[propName] instanceof Array && object2[propName] instanceof Array) {
        if (!this[propName].equals(object2[propName]))
          return false;
      } else if (this[propName] instanceof Object && object2[propName] instanceof Object) {
        if (!this[propName].equals(object2[propName]))
          return false;
      } else if (this[propName] !== object2[propName]) {
        return false;
      }
    }
    return true;
  };
};

var parseTypoghraphy = function(theme, sass) {
  var weights,
    typography,
    type,
    rawTypoArray,
    fontValue,
    variableName,
    index,
    length;

  //matches everything between //-- typo:start --// and //-- typo:end --/ including these markers
  typography = sass.match(/\/\/-- typo:start[\d\D]*?typo:end --\/\//gi);
  rawTypoArray = typography[0].split('\n');

  //removing markers
  rawTypoArray.shift();
  rawTypoArray.pop();

  rawTypoArray = rawTypoArray.filter(helpers.filterOutNotVars);

  //Constructing types array
  typography = [];

  for (index = 0, length = rawTypoArray.length; index < length; index++) {
    //matches from $ to :, including $. To take variable name.
    variableName = rawTypoArray[index].match(/\$[\d\D]*?(?=:)/gi)[0];
    //matches from : to ;, including :. To take variable value.
    fontValue = rawTypoArray[index].match(/(?=:)[\d\D]*?(?=;)/)[0];
    //matches everything in between (), including (. To take font weights.
    weights = rawTypoArray[index].match(/(?=\/)[\d\D]+/gi);

    fontValue = fontValue.substring(1, fontValue.length).trim();

    if (weights) {
      weights = weights[0];
      weights = weights.replace(/\//g, '').replace(/ /g, '').split(',');
      weights = weights.map(helpers.convertToWeightObject);
    } else {
      console.log('Weights were not found for ' + variableName + '.');
    }

    type = {
      variable: variableName,
      value: fontValue,
      weights: weights
    };

    typography.push(type);
  }

  theme.typography = typography;
};

var parseColors = function(theme, sass, maxSassIterations) {
  //matches everything between //-- colors:start --// and //-- colors:end --/ including these markers
  var rawColArray = sass.match(/\/\/-- colors:start[\d\D]*?colors:end --\/\//gi),
    unassignedColors = [],
    assignedColors = {},
    iterations = 0,
    index,
    length,
    variableName,
    hexOrVarValue,
    color;

  for (index = 0, length = rawColArray.length; index < length; index++) {
    rawColArray[index] = rawColArray[index].split('\n');

    //remove dom markers
    rawColArray[index].shift();
    rawColArray[index].pop();

    unassignedColors = unassignedColors.concat(rawColArray[index]);
  }

  unassignedColors = unassignedColors.filter(helpers.filterOutNotVars);

  //prepare array structure
  for (index = 0, length = unassignedColors.length; index < length; index++) {
    //matches from $ to :, including $. To take variable name.
    variableName = unassignedColors[index].match(/\$[\d\D]*?(?=:)/gi)[0];
    //matches from : to ;, including :. To take variable value.
    hexOrVarValue = unassignedColors[index].match(/\:[\d\D]*?(?=;)/gi)[0];

    hexOrVarValue = hexOrVarValue.substring(1, hexOrVarValue.length).trim();

    unassignedColors[index] = {
      variable: variableName,
      value: hexOrVarValue
    };
  }

  while (iterations < maxSassIterations && unassignedColors.length) {
    for (index = 0; index < unassignedColors.length; index++) {
      if (unassignedColors[index].value.indexOf('$') !== 0) {
        if (assignedColors[unassignedColors[index].value]) {
          assignedColors[unassignedColors[index].value].push(unassignedColors[index].variable);
        } else {
          assignedColors[unassignedColors[index].value] = [unassignedColors[index].variable];
        }

        unassignedColors.splice(index, 1);
      } else {
        for (color in assignedColors) {
          if (assignedColors[color].indexOf(unassignedColors[index].value) !== -1) {
            assignedColors[color].push(unassignedColors[index].variable);
            unassignedColors.splice(index, 1);
            break;
          }
        }
      }
    }
    iterations++;
  }

  if (iterations === maxSassIterations) {
    console.log('Iterations reached max size, your variables json file could be inaccurate!\nThis means, that variable r-value is trying to show to non existing variable!');
  }

  theme.colors = assignedColors;
};

var compareForReport = function(theme, report, config) {
  var oldData = jf.readFileSync(config.sassData, {throws: false}) || [],
    len = oldData.length,
    index;

  for (index = 0; index < len; index++) {
    if (oldData[index].name === theme.name) {
      oldData = oldData[index];
      break;
    }
  }

  if (!oldData.hasOwnProperty('colors')) {
    oldData = {
      colors: {},
      typography: []
    };
  }

  report.themeName = theme.name;
  report.uniqueColVals = Object.keys(theme.colors).length;
  report.diffOfColVals = report.uniqueColVals - Object.keys(oldData.colors).length;

  if (theme.typography && !theme.typography.equals(oldData.typography)) {
    report.oldTypo = oldData.typography;
    report.newTypo = theme.typography;
  }
};

exports.scrapeTheme = function(themeIndex, result, sassPaths, maxSassIterations, config) {
  var sass,
    theme = {},
    report = {};

  sass = fs.readFileSync(sassPaths[themeIndex], {
    encoding: 'utf-8'
  });
  theme.name = sassPaths[themeIndex];

  //matches everything between //-- typo:start --// and //-- typo:end --/ including these markers
  if (sass.search(/\/\/-- typo:start[\d\D]*?typo:end --\/\//gi) !== -1) {
    parseTypoghraphy(theme, sass);
  } else {
    console.log('Typography markers not found in ' + theme.name + '.');
  }

  //matches everything between //-- colors:start --// and //-- colors:end --/ including these markers
  if (sass.search(/\/\/-- colors:start[\d\D]*?colors:end --\/\//gi) !== -1) {
    parseColors(theme, sass, maxSassIterations);
  } else {
    console.log('Color markers not found in ' + theme.name + '.');
  }

  extendArrayAndObject();

  compareForReport(theme, report, config);

  pruneArrayAndObject();

  result.push(theme);

  return report;
};<commit_msg>Check if colors exist in scrapped file. Fixes #93<commit_after>var helpers = require('./helpers.js'),
  fs = require('fs'),
  jf = require('jsonfile'),

  exports = module.exports = {};

var pruneArrayAndObject = function () {
  delete Array.prototype.equals;
  delete Object.prototype.equals;
}

var extendArrayAndObject = function() {
  Array.prototype.equals = function(array) {
    if (!array)
      return false;

    if (this.length !== array.length)
      return false;

    for (var i = 0, l = this.length; i < l; i++) {
      if (this[i] instanceof Array && array[i] instanceof Array) {
        if (!this[i].equals(array[i]))
          return false;
      } else if (this[i] instanceof Object && array[i] instanceof Object) {
        if (!this[i].equals(array[i]))
          return false;
      } else if (this[i] !== array[i]) {
        return false;
      }
    }
    return true;
  };

  Object.prototype.equals = function(object2) {
    var propName;
    for (propName in this) {
      if (this.hasOwnProperty(propName) !== object2.hasOwnProperty(propName)) {
        return false;
      } else if (typeof this[propName] !== typeof object2[propName]) {
        return false;
      }
    }

    for (propName in object2) {
      if (this.hasOwnProperty(propName) !== object2.hasOwnProperty(propName)) {
        return false;
      } else if (typeof this[propName] !== typeof object2[propName]) {
        return false;
      }

      if (!this.hasOwnProperty(propName))
        continue;

      if (this[propName] instanceof Array && object2[propName] instanceof Array) {
        if (!this[propName].equals(object2[propName]))
          return false;
      } else if (this[propName] instanceof Object && object2[propName] instanceof Object) {
        if (!this[propName].equals(object2[propName]))
          return false;
      } else if (this[propName] !== object2[propName]) {
        return false;
      }
    }
    return true;
  };
};

var parseTypoghraphy = function(theme, sass) {
  var weights,
    typography,
    type,
    rawTypoArray,
    fontValue,
    variableName,
    index,
    length;

  //matches everything between //-- typo:start --// and //-- typo:end --/ including these markers
  typography = sass.match(/\/\/-- typo:start[\d\D]*?typo:end --\/\//gi);
  rawTypoArray = typography[0].split('\n');

  //removing markers
  rawTypoArray.shift();
  rawTypoArray.pop();

  rawTypoArray = rawTypoArray.filter(helpers.filterOutNotVars);

  //Constructing types array
  typography = [];

  for (index = 0, length = rawTypoArray.length; index < length; index++) {
    //matches from $ to :, including $. To take variable name.
    variableName = rawTypoArray[index].match(/\$[\d\D]*?(?=:)/gi)[0];
    //matches from : to ;, including :. To take variable value.
    fontValue = rawTypoArray[index].match(/(?=:)[\d\D]*?(?=;)/)[0];
    //matches everything in between (), including (. To take font weights.
    weights = rawTypoArray[index].match(/(?=\/)[\d\D]+/gi);

    fontValue = fontValue.substring(1, fontValue.length).trim();

    if (weights) {
      weights = weights[0];
      weights = weights.replace(/\//g, '').replace(/ /g, '').split(',');
      weights = weights.map(helpers.convertToWeightObject);
    } else {
      console.log('Weights were not found for ' + variableName + '.');
    }

    type = {
      variable: variableName,
      value: fontValue,
      weights: weights
    };

    typography.push(type);
  }

  theme.typography = typography;
};

var parseColors = function(theme, sass, maxSassIterations) {
  //matches everything between //-- colors:start --// and //-- colors:end --/ including these markers
  var rawColArray = sass.match(/\/\/-- colors:start[\d\D]*?colors:end --\/\//gi),
    unassignedColors = [],
    assignedColors = {},
    iterations = 0,
    index,
    length,
    variableName,
    hexOrVarValue,
    color;

  for (index = 0, length = rawColArray.length; index < length; index++) {
    rawColArray[index] = rawColArray[index].split('\n');

    //remove dom markers
    rawColArray[index].shift();
    rawColArray[index].pop();

    unassignedColors = unassignedColors.concat(rawColArray[index]);
  }

  unassignedColors = unassignedColors.filter(helpers.filterOutNotVars);

  //prepare array structure
  for (index = 0, length = unassignedColors.length; index < length; index++) {
    //matches from $ to :, including $. To take variable name.
    variableName = unassignedColors[index].match(/\$[\d\D]*?(?=:)/gi)[0];
    //matches from : to ;, including :. To take variable value.
    hexOrVarValue = unassignedColors[index].match(/\:[\d\D]*?(?=;)/gi)[0];

    hexOrVarValue = hexOrVarValue.substring(1, hexOrVarValue.length).trim();

    unassignedColors[index] = {
      variable: variableName,
      value: hexOrVarValue
    };
  }

  while (iterations < maxSassIterations && unassignedColors.length) {
    for (index = 0; index < unassignedColors.length; index++) {
      if (unassignedColors[index].value.indexOf('$') !== 0) {
        if (assignedColors[unassignedColors[index].value]) {
          assignedColors[unassignedColors[index].value].push(unassignedColors[index].variable);
        } else {
          assignedColors[unassignedColors[index].value] = [unassignedColors[index].variable];
        }

        unassignedColors.splice(index, 1);
      } else {
        for (color in assignedColors) {
          if (assignedColors[color].indexOf(unassignedColors[index].value) !== -1) {
            assignedColors[color].push(unassignedColors[index].variable);
            unassignedColors.splice(index, 1);
            break;
          }
        }
      }
    }
    iterations++;
  }

  if (iterations === maxSassIterations) {
    console.log('Iterations reached max size, your variables json file could be inaccurate!\nThis means, that variable r-value is trying to show to non existing variable!');
  }

  theme.colors = assignedColors;
};

var compareForReport = function(theme, report, config) {
  var oldData = jf.readFileSync(config.sassData, {throws: false}) || [],
    len = oldData.length,
    index;

  for (index = 0; index < len; index++) {
    if (oldData[index].name === theme.name) {
      oldData = oldData[index];
      break;
    }
  }

  if (!oldData.hasOwnProperty('colors')) {
    oldData = {
      colors: {},
      typography: []
    };
  }

  report.themeName = theme.name;
  report.uniqueColVals = theme.colors ? Object.keys(theme.colors).length : 0;
  report.diffOfColVals = report.uniqueColVals - Object.keys(oldData.colors).length;

  if (theme.typography && !theme.typography.equals(oldData.typography)) {
    report.oldTypo = oldData.typography;
    report.newTypo = theme.typography;
  }
};

exports.scrapeTheme = function(themeIndex, result, sassPaths, maxSassIterations, config) {
  var sass,
    theme = {},
    report = {};

  sass = fs.readFileSync(sassPaths[themeIndex], {
    encoding: 'utf-8'
  });
  theme.name = sassPaths[themeIndex];

  //matches everything between //-- typo:start --// and //-- typo:end --/ including these markers
  if (sass.search(/\/\/-- typo:start[\d\D]*?typo:end --\/\//gi) !== -1) {
    parseTypoghraphy(theme, sass);
  } else {
    console.log('Typography markers not found in ' + theme.name + '.');
  }

  //matches everything between //-- colors:start --// and //-- colors:end --/ including these markers
  if (sass.search(/\/\/-- colors:start[\d\D]*?colors:end --\/\//gi) !== -1) {
    parseColors(theme, sass, maxSassIterations);
  } else {
    console.log('Color markers not found in ' + theme.name + '.');
  }

  extendArrayAndObject();

  compareForReport(theme, report, config);

  pruneArrayAndObject();

  result.push(theme);

  return report;
};<|endoftext|>