<commit_before>import React from 'react';
import { render } from 'react-dom';

const CHALLENGES = [
  {
    instructions: "Which CSS property would we use to italicize text?",
    optionList: [
      {
        id: 0,
        title: "text-decoration",
        order: 1
      },
      {
        id: 1,
        title: "font-style",
        order: 2
      },
      {
        id: 2,
        title: "text-transform",
        order: 3
      }
    ],
    preview: {
      css: (answer)=> {
        return ```
          .italicized {
            ${answer}: italic;
          }
        ```;
      },
      html: `

This text has been italicized.

      `
    }
  },
  {
    instructions:

We want to create an element that has a blue box-shadow with a blur-radius of 2px. This shadow is also offset 1 pixel to the left and 4 pixels down.

Click the elements to insert them in the correct order wed use to write these style attributes.

,
    optionList: [
      {
        id: 0,
        title: "-4px",
        order: 1
      },
      {
        id: 1,
        title: "blue",
        order: 3
      },
      {
        id: 2,
        title: "1px",
        order: 2
      },
      {
        id: 3,
        title: "2px",
        order: 4
      }
    ]
  }
];

const sortedOptions = (list) => list.sort((a, b) => a.order >= b.order);

class Dialogue extends React.Component {

  constructor(){
    super();
    this.state = {
    };
  }

  render() {
    return(

Success!

        <button onClick={this.props.onContinue.bind(this)}>Continue</button>

    );
  }
}

class Option extends React.Component {
  render() {
    return (
{this.props.title}
);
  }
}

class Answer extends React.Component {
  render() {
    return (
Box {this.props.order}
);
  }
}

class OptionList extends React.Component {
  render() {
    return (

          {this.props.options.map((props) => {
            return (<Option key={props.id} {...props}></Option>)
          })}

    );
  }
}

class AnswerList extends React.Component {
  render() {
    return (

        {sortedOptions(this.props.options).map((o) => <Answer key={o.id} {...o} />)}

    );
  }
}

class Challenge extends React.Component {
  constructor() {
    super();
    this.state = {
      complete: false,
    }
  }
  onPassChallenge(){
    this.setState({complete: true});
  }
  render () {
    var challengeClass = 'challenge';
    if(this.state.complete)
      challengeClass += ' is-complete';

    return (

{this.props.instructions}

          <AnswerList options={this.props.optionList}></AnswerList>

          <OptionList options={this.props.optionList} />

          <Dialogue onContinue={this.props.onAdvance.bind(this)}/>

);
  }
}

class Challenges extends React.Component {
  constructor() {
    super();
    this.challenges = CHALLENGES;
    this.state = {
      challengeIndex: 0,
      challengeLength: this.challenges.length
    };
  }

  onContinue() {
    let nextChallengeIndex = this.state.challengeIndex + 1;
    if (this.state.challengeIndex >= this.state.challengeLength) {
      nextChallengeIndex = 0;
    }
    this.setState({
      challengeIndex: nextChallengeIndex
    });
  }

  render() {
    return (
      <Challenge onAdvance={this.onContinue.bind(this)} {...this.challenges[this.state.challengeIndex]} />
    );
  }
}

render(
  <Challenges />,
  document.getElementById('app')
);
<commit_msg>Put apostrophe back<commit_after>import React from 'react';
import { render } from 'react-dom';

const CHALLENGES = [
  {
    instructions: "Which CSS property would we use to italicize text?",
    optionList: [
      {
        id: 0,
        title: "text-decoration",
        order: 1
      },
      {
        id: 1,
        title: "font-style",
        order: 2
      },
      {
        id: 2,
        title: "text-transform",
        order: 3
      }
    ],
    preview: {
      css: (answer)=> {
        return ```
          .italicized {
            ${answer}: italic;
          }
        ```;
      },
      html: `

This text has been italicized.

      `
    }
  },
  {
    instructions:

We want to create an element that has a blue box-shadow with a blur-radius of 2px. This shadow is also offset 1 pixel to the left and 4 pixels down.

Click the elements to insert them in the correct order we'd use to write these style attributes.

,
    optionList: [
      {
        id: 0,
        title: "-4px",
        order: 1
      },
      {
        id: 1,
        title: "blue",
        order: 3
      },
      {
        id: 2,
        title: "1px",
        order: 2
      },
      {
        id: 3,
        title: "2px",
        order: 4
      }
    ]
  }
];

const sortedOptions = (list) => list.sort((a, b) => a.order >= b.order);

class Dialogue extends React.Component {

  constructor(){
    super();
    this.state = {
    };
  }

  render() {
    return(

Success!

        <button onClick={this.props.onContinue.bind(this)}>Continue</button>

    );
  }
}

class Option extends React.Component {
  render() {
    return (
{this.props.title}
);
  }
}

class Answer extends React.Component {
  render() {
    return (
Box {this.props.order}
);
  }
}

class OptionList extends React.Component {
  render() {
    return (

          {this.props.options.map((props) => {
            return (<Option key={props.id} {...props}></Option>)
          })}

    );
  }
}

class AnswerList extends React.Component {
  render() {
    return (

        {sortedOptions(this.props.options).map((o) => <Answer key={o.id} {...o} />)}

    );
  }
}

class Challenge extends React.Component {
  constructor() {
    super();
    this.state = {
      complete: false,
    }
  }
  onPassChallenge(){
    this.setState({complete: true});
  }
  render () {
    var challengeClass = 'challenge';
    if(this.state.complete)
      challengeClass += ' is-complete';

    return (

{this.props.instructions}

          <AnswerList options={this.props.optionList}></AnswerList>

          <OptionList options={this.props.optionList} />

          <Dialogue onContinue={this.props.onAdvance.bind(this)}/>

);
  }
}

class Challenges extends React.Component {
  constructor() {
    super();
    this.challenges = CHALLENGES;
    this.state = {
      challengeIndex: 0,
      challengeLength: this.challenges.length
    };
  }

  onContinue() {
    let nextChallengeIndex = this.state.challengeIndex + 1;
    if (this.state.challengeIndex >= this.state.challengeLength) {
      nextChallengeIndex = 0;
    }
    this.setState({
      challengeIndex: nextChallengeIndex
    });
  }

  render() {
    return (
      <Challenge onAdvance={this.onContinue.bind(this)} {...this.challenges[this.state.challengeIndex]} />
    );
  }
}

render(
  <Challenges />,
  document.getElementById('app')
);
<|endoftext|>


<commit_before>//Dependencies
var fs       = require('fs'),
    path     = require('path'),
    mongo    = require('mongodb'),
    ObjectID = require('bson').ObjectID,
    async    = require('async'),
    _        = require('underscore'),
    basePath = path.dirname(module.parent.filename);

/**
 * Helper function that creates a MongoDB ObjectID given a hex string
 * @param {String|ObjectId}  Optional hard-coded Object ID as string
 */
exports.createObjectId = function(id) {
  if (!id) return new ObjectID();

  //Allow cloning ObjectIDs
  if (id.constructor.name == 'ObjectID') id = id.toString();

  return new ObjectID(id);
};

/**
 * Main method for connecting to the database and returning the fixture loader (Loader)
 *
 * @param {String}  Database name
 * @param {Object}  Connection options: host ('localhost'), port (27017)
 */
exports.connect = function(dbName, options) {
  return new Loader(dbName, options);
}

/**
 * Loader constructor
 *
 * @param {String} dbName           Database name
 * @param {Object} [options]        Connection options
 * @param {String} [options.host]   Default: 'localhost'
 * @param {Number} [options.port]   Defualt: 27017
 * @param {String} [options.user]   Username
 * @param {String} [options.pass]   Password
 */
var Loader = exports.Loader = function(dbName, options) {
  options = _.extend({
    db: dbName,
    host: 'localhost',
    port: 27017,
    user: null,
    pass: null
  }, options);

  this.options = options;
  this.modifiers = [];
};

/**
 * Inserts data
 *
 * @param {Mixed}       The data to load. This parameter accepts either:
 *                          String: Path to a file or directory to load
 *                          Object: Object literal in the form described in docs
 * @param {Function}    Callback(err)
 */
Loader.prototype.load = function(fixtures, cb) {
  var self = this;

  _mixedToObject(fixtures, function(err, data) {
    if (err) return cb(err);

    _loadData(self, data, cb);
  });
};

/**
 * Add a modifier function.
 *
 * Modifier functions get called (in the order in which they were added) for each document, prior to it being loaded.
 * The result from each modifier is fed into the next modifier as its input, and so on until the final result which is
 * then inserted into the db.
 *
 * @param {Function} cb        The modifier callback function with signature (collectionName, document, callback).
 */
Loader.prototype.addModifier = function(cb) {
  this.modifiers.push(cb);
};

/**
 * loader.clear(cb) : Clears (drops) the entire database
 *
 * loader.clear(collectionNames, cb) : Clears only the given collection(s)
 *
 * @param {String|Array}    Optional. Name of collection to clear or an array of collection names
 * @param {Function}        Callback(err)
 */
Loader.prototype.clear = function(collectionNames, cb) {
  //Normalise arguments
  if (arguments.length == 1) { //cb
    cb = collectionNames;
    collectionNames = null;
  }

  var self = this;

  var results = {};

  async.series([
    function connect(cb) {
      _connect(self, function(err, db) {
        if (err) return cb(err);

        results.db = db;
        cb();
      })
    },

    function getCollectionNames(cb) {
      //If collectionNames not passed we clear all of them
      if (!collectionNames) {
        results.db.collectionNames(function(err, names) {
          if (err) return cb(err);

          //Get the real collection names
          names = _.map(names, function(nameObj) {
            var fullName = nameObj.name,
                parts = fullName.split('.');

            //Remove DB name
            parts.shift();

            //Skip system collections
            if (parts[0] == 'system' || parts[0] == 'local') return;

            return parts.join('.');
          });

          results.collectionNames = _.compact(names);

          cb();
        })
      } else {
        //Convert single collection as string to array
        if (!_.isArray(collectionNames)) collectionNames = [collectionNames];

        results.collectionNames = collectionNames;

        cb();
      }
    },

    function clearCollections() {
      async.forEach(results.collectionNames, function(name, cb) {
        results.db.collection(name, function(err, collection) {
          if (err) return cb(err);

          collection.remove({}, {safe: true}, cb);
        });
      }, cb);
    }
  ], cb)
};

/**
 * Drops the database and inserts data
 *
 * @param {Mixed}           The data to load. This parameter accepts either:
 *                              String: Path to a file or directory to load
 *                              Object: Object literal in the form described in docs
 * @param {Function}        Callback(err)
 */
Loader.prototype.clearAllAndLoad = function(fixtures, cb) {
  var self = this;

  self.clear(function(err) {
    if (err) return cb(err);

    self.load(fixtures, function(err) {
      cb(err);
    });
	});
};

/**
 * Clears only the collections that have documents to be inserted, then inserts data
 *
 * @param {Mixed}           The data to load. This parameter accepts either:
 *                              String: Path to a file or directory to load
 *                              Object: Object literal in the form described in docs
 * @param {Function}        Callback(err)
 */
Loader.prototype.clearAndLoad = function(fixtures, cb) {
  var self = this;

  _mixedToObject(fixtures, function(err, objData) {
    if (err) return cb(err);

    var collections = Object.keys(objData);

    self.clear(collections, function(err) {
      if (err) return cb(err);

      _loadData(self, objData, cb);
  	});
  });
};

//PRIVATE METHODS

var noop = function() {};

/**
 * Connects to the database and returns the client. If a connection has already been established it is used.
 *
 * @param {Loader}       The configured loader
 * @param {Function}     Callback(err, client)
 */
var _connect = function(loader, cb) {
  if (loader.client) return cb(null, loader.client);

  var options = loader.options;

  var db = new mongo.Db(options.db, new mongo.Server(options.host, options.port, {}));

  db.open(function(err, db) {
    if (err) return cb(err);

    loader.client = db;

    //Authenticate if required
    if (!options.user) return cb(null, db);

    db.authenticate(options.user, options.pass, function(err, result) {
      if (err) return cb(err);

      cb(null, db);
    });
  });
};

/**
 * Inserts the given data (object or array) as new documents
 *
 * @param {Loader}       The configured loader
 * @param {Object|Array} The data to load
 * @param {Function}     Callback(err)
 * @api private
 */
var _loadData = function(loader, data, cb) {
	cb = cb || noop;

	var collectionNames = Object.keys(data);

	_connect(loader, function(err, db) {
		if (err) return cb(err);

		async.forEach(collectionNames, function(collectionName, cbForEachCollection) {
			var collectionData = data[collectionName];

      //Convert object to array
      var items;
      if (Array.isArray(collectionData)) {
        items = collectionData.slice();
      } else {
        items = _.values(collectionData);
      }

      var modifiedItems = [];

      async.forEach(items, function(item, cbForEachItem) {
        // apply modifiers
        async.forEach(loader.modifiers, function(modifier, cbForEachModifier) {
          modifier.call(modifier, collectionName, item, function(err, modifiedDoc) {
            if (err) return cbForEachModifier(err);

            item = modifiedDoc;

            cbForEachModifier();
          });
        }, function(err) {
          if (err) return cbForEachItem(err);

          modifiedItems.push(item);

          cbForEachItem();
        });
      }, function(err) {
        if (err) return cbForEachCollection(err);

        db.collection(collectionName, function(err, collection) {
          if (err) return cbForEachCollection(err);

          collection.insert(modifiedItems, { safe: true }, cbForEachCollection);
        });
      });
		}, cb);
	});
};

/**
 * Determine the type of fixtures being passed in (object, array, file, directory) and return
 * an object keyed by collection name.
 *
 * @param {Object|String}       Fixture data (object, filename or dirname)
 * @param {Function}            Optional callback(err, data)
 * @api private
 */
var _mixedToObject = function(fixtures, cb) {
  if (typeof fixtures == 'object') return cb(null, fixtures);

  //As it's not an object, it should now be a file or directory path (string)
  if (typeof fixtures != 'string') {
    return cb(new Error('Data must be an object, array or string (file or dir path)'));
  }

  // Resolve relative paths if necessary.
  fixtures = path.resolve(basePath, fixtures);

  //Determine if fixtures is pointing to a file or directory
  fs.stat(fixtures, function(err, stats) {
    if (err) return cb(err);

    if (stats.isDirectory()) {
      _dirToObject(fixtures, cb);
    } else { //File
      _fileToObject(fixtures, cb);
    }
  });
}

/**
 * Get data from one file as an object
 *
 * @param {String}      The full path to the file to load
 * @param {Function}    Optional callback(err, data)
 * @api private
 */
var _fileToObject = function(file, cb) {
  cb = cb || noop;

  // Resolve relative paths if necessary.
  file = path.resolve(basePath, file);

  var data = require(file);

  cb(null, data);
}

/**
 * Get and compile data from all files in a directory, as an object
 *
 * @param {String}      The directory path to load e.g. 'data/fixtures' or '../data'
 * @param {Function}    Optional callback(err)
 * @api private
 */
var _dirToObject = function(dir, cb) {
  cb = cb || noop;

  // Resolve relative paths if necessary.
  dir = path.resolve(basePath, dir);

  async.waterfall([
    function readDir(cb) {
      fs.readdir(dir, cb)
    },

    function filesToObjects(files, cb) {
      async.map(files, function processFile(file, cb) {
        var path = dir + '/' + file;

        // Determine if it's a file or directory
        fs.stat(path, function(err, stats) {
          if (err) return cb(err);

          if (stats.isDirectory()) {
            cb(null, {});
          } else { //File
            _fileToObject(path, cb);
          }
        });
      }, cb);
    },

    function combineObjects(results, cb) {
      //Where all combined data will be kept, keyed by collection name
      var collections = {};

      results.forEach(function(fileObj) {
        _.each(fileObj, function(docs, name) {
          //Convert objects to array
          if (_.isObject(docs)) {
            docs = _.values(docs);
          }

          //Create array for collection if it doesn't exist yet
          if (!collections[name]) collections[name] = [];

          //Add docs to collection
          collections[name] = collections[name].concat(docs);
        });
      });

      cb(null, collections)
    }
  ], function(err, combinedData) {
    if (err) return cb(err);

    cb(null, combinedData);
  });
};
<commit_msg>added safe option of db<commit_after>//Dependencies
var fs       = require('fs'),
    path     = require('path'),
    mongo    = require('mongodb'),
    ObjectID = require('bson').ObjectID,
    async    = require('async'),
    _        = require('underscore'),
    basePath = path.dirname(module.parent.filename);

/**
 * Helper function that creates a MongoDB ObjectID given a hex string
 * @param {String|ObjectId}  Optional hard-coded Object ID as string
 */
exports.createObjectId = function(id) {
  if (!id) return new ObjectID();

  //Allow cloning ObjectIDs
  if (id.constructor.name == 'ObjectID') id = id.toString();

  return new ObjectID(id);
};

/**
 * Main method for connecting to the database and returning the fixture loader (Loader)
 *
 * @param {String}  Database name
 * @param {Object}  Connection options: host ('localhost'), port (27017)
 */
exports.connect = function(dbName, options) {
  return new Loader(dbName, options);
}

/**
 * Loader constructor
 *
 * @param {String} dbName           Database name
 * @param {Object} [options]        Connection options
 * @param {String} [options.host]   Default: 'localhost'
 * @param {Number} [options.port]   Default: 27017
 * @param {String} [options.user]   Username
 * @param {String} [options.pass]   Password
 * @param {Boolean} [options.safe]  Default: false
 */
var Loader = exports.Loader = function(dbName, options) {
  options = _.extend({
    db: dbName,
    host: 'localhost',
    port: 27017,
    user: null,
    pass: null,
    safe: false
  }, options);

  this.options = options;
  this.modifiers = [];
};

/**
 * Inserts data
 *
 * @param {Mixed}       The data to load. This parameter accepts either:
 *                          String: Path to a file or directory to load
 *                          Object: Object literal in the form described in docs
 * @param {Function}    Callback(err)
 */
Loader.prototype.load = function(fixtures, cb) {
  var self = this;

  _mixedToObject(fixtures, function(err, data) {
    if (err) return cb(err);

    _loadData(self, data, cb);
  });
};

/**
 * Add a modifier function.
 *
 * Modifier functions get called (in the order in which they were added) for each document, prior to it being loaded.
 * The result from each modifier is fed into the next modifier as its input, and so on until the final result which is
 * then inserted into the db.
 *
 * @param {Function} cb        The modifier callback function with signature (collectionName, document, callback).
 */
Loader.prototype.addModifier = function(cb) {
  this.modifiers.push(cb);
};

/**
 * loader.clear(cb) : Clears (drops) the entire database
 *
 * loader.clear(collectionNames, cb) : Clears only the given collection(s)
 *
 * @param {String|Array}    Optional. Name of collection to clear or an array of collection names
 * @param {Function}        Callback(err)
 */
Loader.prototype.clear = function(collectionNames, cb) {
  //Normalise arguments
  if (arguments.length == 1) { //cb
    cb = collectionNames;
    collectionNames = null;
  }

  var self = this;

  var results = {};

  async.series([
    function connect(cb) {
      _connect(self, function(err, db) {
        if (err) return cb(err);

        results.db = db;
        cb();
      })
    },

    function getCollectionNames(cb) {
      //If collectionNames not passed we clear all of them
      if (!collectionNames) {
        results.db.collectionNames(function(err, names) {
          if (err) return cb(err);

          //Get the real collection names
          names = _.map(names, function(nameObj) {
            var fullName = nameObj.name,
                parts = fullName.split('.');

            //Remove DB name
            parts.shift();

            //Skip system collections
            if (parts[0] == 'system' || parts[0] == 'local') return;

            return parts.join('.');
          });

          results.collectionNames = _.compact(names);

          cb();
        })
      } else {
        //Convert single collection as string to array
        if (!_.isArray(collectionNames)) collectionNames = [collectionNames];

        results.collectionNames = collectionNames;

        cb();
      }
    },

    function clearCollections() {
      async.forEach(results.collectionNames, function(name, cb) {
        results.db.collection(name, function(err, collection) {
          if (err) return cb(err);

          collection.remove({}, {safe: true}, cb);
        });
      }, cb);
    }
  ], cb)
};

/**
 * Drops the database and inserts data
 *
 * @param {Mixed}           The data to load. This parameter accepts either:
 *                              String: Path to a file or directory to load
 *                              Object: Object literal in the form described in docs
 * @param {Function}        Callback(err)
 */
Loader.prototype.clearAllAndLoad = function(fixtures, cb) {
  var self = this;

  self.clear(function(err) {
    if (err) return cb(err);

    self.load(fixtures, function(err) {
      cb(err);
    });
	});
};

/**
 * Clears only the collections that have documents to be inserted, then inserts data
 *
 * @param {Mixed}           The data to load. This parameter accepts either:
 *                              String: Path to a file or directory to load
 *                              Object: Object literal in the form described in docs
 * @param {Function}        Callback(err)
 */
Loader.prototype.clearAndLoad = function(fixtures, cb) {
  var self = this;

  _mixedToObject(fixtures, function(err, objData) {
    if (err) return cb(err);

    var collections = Object.keys(objData);

    self.clear(collections, function(err) {
      if (err) return cb(err);

      _loadData(self, objData, cb);
  	});
  });
};

//PRIVATE METHODS

var noop = function() {};

/**
 * Connects to the database and returns the client. If a connection has already been established it is used.
 *
 * @param {Loader}       The configured loader
 * @param {Function}     Callback(err, client)
 */
var _connect = function(loader, cb) {
  if (loader.client) return cb(null, loader.client);

  var options = loader.options;

  var db = new mongo.Db(options.db, new mongo.Server(options.host, options.port, {}), {safe: options.safe});

  db.open(function(err, db) {
    if (err) return cb(err);

    loader.client = db;

    //Authenticate if required
    if (!options.user) return cb(null, db);

    db.authenticate(options.user, options.pass, function(err, result) {
      if (err) return cb(err);

      cb(null, db);
    });
  });
};

/**
 * Inserts the given data (object or array) as new documents
 *
 * @param {Loader}       The configured loader
 * @param {Object|Array} The data to load
 * @param {Function}     Callback(err)
 * @api private
 */
var _loadData = function(loader, data, cb) {
	cb = cb || noop;

	var collectionNames = Object.keys(data);

	_connect(loader, function(err, db) {
		if (err) return cb(err);

		async.forEach(collectionNames, function(collectionName, cbForEachCollection) {
			var collectionData = data[collectionName];

      //Convert object to array
      var items;
      if (Array.isArray(collectionData)) {
        items = collectionData.slice();
      } else {
        items = _.values(collectionData);
      }

      var modifiedItems = [];

      async.forEach(items, function(item, cbForEachItem) {
        // apply modifiers
        async.forEach(loader.modifiers, function(modifier, cbForEachModifier) {
          modifier.call(modifier, collectionName, item, function(err, modifiedDoc) {
            if (err) return cbForEachModifier(err);

            item = modifiedDoc;

            cbForEachModifier();
          });
        }, function(err) {
          if (err) return cbForEachItem(err);

          modifiedItems.push(item);

          cbForEachItem();
        });
      }, function(err) {
        if (err) return cbForEachCollection(err);

        db.collection(collectionName, function(err, collection) {
          if (err) return cbForEachCollection(err);

          collection.insert(modifiedItems, { safe: true }, cbForEachCollection);
        });
      });
		}, cb);
	});
};

/**
 * Determine the type of fixtures being passed in (object, array, file, directory) and return
 * an object keyed by collection name.
 *
 * @param {Object|String}       Fixture data (object, filename or dirname)
 * @param {Function}            Optional callback(err, data)
 * @api private
 */
var _mixedToObject = function(fixtures, cb) {
  if (typeof fixtures == 'object') return cb(null, fixtures);

  //As it's not an object, it should now be a file or directory path (string)
  if (typeof fixtures != 'string') {
    return cb(new Error('Data must be an object, array or string (file or dir path)'));
  }

  // Resolve relative paths if necessary.
  fixtures = path.resolve(basePath, fixtures);

  //Determine if fixtures is pointing to a file or directory
  fs.stat(fixtures, function(err, stats) {
    if (err) return cb(err);

    if (stats.isDirectory()) {
      _dirToObject(fixtures, cb);
    } else { //File
      _fileToObject(fixtures, cb);
    }
  });
}

/**
 * Get data from one file as an object
 *
 * @param {String}      The full path to the file to load
 * @param {Function}    Optional callback(err, data)
 * @api private
 */
var _fileToObject = function(file, cb) {
  cb = cb || noop;

  // Resolve relative paths if necessary.
  file = path.resolve(basePath, file);

  var data = require(file);

  cb(null, data);
}

/**
 * Get and compile data from all files in a directory, as an object
 *
 * @param {String}      The directory path to load e.g. 'data/fixtures' or '../data'
 * @param {Function}    Optional callback(err)
 * @api private
 */
var _dirToObject = function(dir, cb) {
  cb = cb || noop;

  // Resolve relative paths if necessary.
  dir = path.resolve(basePath, dir);

  async.waterfall([
    function readDir(cb) {
      fs.readdir(dir, cb)
    },

    function filesToObjects(files, cb) {
      async.map(files, function processFile(file, cb) {
        var path = dir + '/' + file;

        // Determine if it's a file or directory
        fs.stat(path, function(err, stats) {
          if (err) return cb(err);

          if (stats.isDirectory()) {
            cb(null, {});
          } else { //File
            _fileToObject(path, cb);
          }
        });
      }, cb);
    },

    function combineObjects(results, cb) {
      //Where all combined data will be kept, keyed by collection name
      var collections = {};

      results.forEach(function(fileObj) {
        _.each(fileObj, function(docs, name) {
          //Convert objects to array
          if (_.isObject(docs)) {
            docs = _.values(docs);
          }

          //Create array for collection if it doesn't exist yet
          if (!collections[name]) collections[name] = [];

          //Add docs to collection
          collections[name] = collections[name].concat(docs);
        });
      });

      cb(null, collections)
    }
  ], function(err, combinedData) {
    if (err) return cb(err);

    cb(null, combinedData);
  });
};
<|endoftext|>


<commit_before>
// TODO: export to external module
//       when i do, add option to not fully traverse into a directory,
//       and option to not traverse into symlinks
//
// TODO: limit max parallellism
// TODO: UNIT TESTS!!! ROBUST CONTRACTS W.R.T. INPUT AND OUTPUT

// tries to match globs with a target destination, so things like
// {src: ['packages/*/src'], dest: ['dist']} are possible.
//
// eventually, something like
//
// {src: ['packages/*/src/*'], dest: ['packages/*/lib']}
//
// should be possible.
//
// For now, something like `scripts/_get-test-directories.sh` can be used for this multi src-dest purpose.

import path from "path";
import glob from "glob";
import globCommon from "glob/common";
const {Glob} = glob;
import isGlob from "is-glob";
import forEach from "lodash/forEach";
import map from "lodash/map";
import isArray from "lodash/isArray";

function ownProp (obj, field) {
  return Object.prototype.hasOwnProperty.call(obj, field)
}

export default function walk(args: Object, eachCb, cb) {
  return new Walker(eachCb, cb).walk(args);
}

// it's a zombie! run!
export class Walker {
  constructor(...args) {
    this.cb = args.pop();
    this.eachCb = args.pop();
    const opts = args[0] || {};

    // cwd & root logic borrowed from glob, used in makeAbs
    this.changedCwd = false;
    const cwd = process.cwd();
    if (!ownProp(opts, "cwd"))
      this.cwd = cwd;
    else {
      this.cwd = path.resolve(opts.cwd);
      this.changedCwd = this.cwd !== cwd;
    }

    this.root = opts.root ? path.resolve(opts.root) : path.resolve(this.cwd, "/");
    if (process.platform === "win32") this.root = this.root.replace(/\\/g, "/")

    this._pending = 0;

    this.nextGlobId = 0;
    this.pendingGlobs = Object.create(null);

    this.aborted = false;
    this.seen = Object.create(null);

    // Shared cache between different glob invokations
    this.realpathCache = Object.create(null);
    this.globCache = Object.create(null);
    this.statCache = Object.create(null);
    this.symlinks = Object.create(null);
  }

  walk({src: globs, dest: dests}) {
    if (this._pending > 0) throw new Error("Not reentrant");

    const hasDest = !!dests;
    const oneDest = hasDest && dests.length === 1;
    // assert oneDest || dests.length === globs.length
    for (let i = 0, len = globs.length; i < len; i++) {
      const globPat = globs[i];
      const dest = hasDest && (oneDest ? dests[0] : dests[i]);
      if (isGlob(globPat)) {
        this.walkGlob(globPat, dest);
      } else {
        this.walkPathDirect(globPat, dest);
      }
    }
    return this;
  }

  _globOpts(opts = {}) {
    opts.realpathCache = this.realpathCache;
    opts.cache = this.globCache;
    opts.statCache = this.statCache;
    opts.symlinks = this.symlinks;
    return opts;
  }

  walkGlob(globPat, dest) {
    // TODO: match dest according to globbing
    const dests = dest ? [dest] : false;

    const globberId = this.nextGlobId++;
    // stat all paths, since we'll be walking into found directories
    this.retain();
    this.pendingGlobs[globberId] = new Glob(globPat, this._globOpts({stat: true}), (err, matches) => {
      if (this.aborted) return;
      if (err) {
        this.abort();
        return (0, this.cb)(err);
      }

      this.pendingGlobs[globberId] = undefined;
      this.walkPaths(matches, dests, globPat.slice(-1) !== "*");
      this.release();
    });
  }

  walkPaths(paths, dests, direct) {
    const hasDest = !!dests;
    const oneDest = hasDest && dests.length === 1;
    // assert oneDest || dests.length === paths.length
    for (let i = 0, len = paths.length; i < len; i++) {
      const src = paths[i]
      const dest = hasDest && (oneDest ? path.join(dests[0], path.basename(src)) : dests[i]);
      this.walkPath(src, dest, direct);
    }
  }

  _makeAbs(f) {
    return globCommon.makeAbs(this, f);
  }

  walkPathDirect(src, dest) {
    // perform a glob just to populate the stat cache
    const globberId = this.nextGlobId++;
    this.retain();
    this.pendingGlobs[globberId] = new Glob(src, this._globOpts({stat: true}), (err) => {
      if (this.aborted) return;
      if (err) {
        this.abort();
        return (0, this.cb)(err);
      }

      this.pendingGlobs[globberId] = undefined;
      this.walkPath(src, dest, true);
      this.release();
    });
  }

  walkPath(src, dest, direct = false) {
    const cacheKey = this._makeAbs(src);
    const seen = this.seen[cacheKey];
    if (seen) return;
    this.seen[cacheKey] = true;

    const cacheItem = this.globCache[cacheKey];

    if (cacheItem === "DIR" || isArray(cacheItem)) {
      const globberId = this.nextGlobId++;
      this.retain();
      this.pendingGlobs[globberId] = new Glob(src + "/**", this._globOpts(), (err, matches) => {
        if (this.aborted) return;
        if (err) {
          this.abort();
          return (0, this.cb)(err);
        }

        this.pendingGlobs[globberId] = undefined;

        if (direct) {
          const dests = map(matches, (match) => path.join(dest, match));

          this.walkPaths(matches, dests);
        } else {
          const srcPathLen = src.length;
          const dests = map(matches, (match) => dest + match.slice(srcPathLen));

          this.walkPaths(matches, dests);
        }
        this.release();
      });
    } else {
      (0, this.eachCb)(src, dest);
    }
  }

  abort() {
    this.aborted = true;
    forEach(this.pendingGlobs, (globber) => { globber && globber.abort(); });
    this.pendingGlobs = Object.create(null);
  }

  retain() {
    this._pending++;
  }
  release() {
    if (this.aborted) return;
    this._pending--;
    if (this._pending === 0) {
      (0, this.cb)();
    } else if (this._pending < 0) {
      throw new Error("retain/release mismatch");
    }
  }
}
<commit_msg>refactor(tacoscript/cli): use retain/release module for walker<commit_after>
// TODO: export to external module
//       when i do, add option to not fully traverse into a directory,
//       and option to not traverse into symlinks
//
// TODO: limit max parallellism
// TODO: UNIT TESTS!!! ROBUST CONTRACTS W.R.T. INPUT AND OUTPUT

// tries to match globs with a target destination, so things like
// {src: ['packages/*/src'], dest: ['dist']} are possible.
//
// eventually, something like
//
// {src: ['packages/*/src/*'], dest: ['packages/*/lib']}
//
// should be possible.
//
// For now, something like `scripts/_get-test-directories.sh` can be used for this multi src-dest purpose.

import path from "path";
import glob from "glob";
import globCommon from "glob/common";
const {Glob} = glob;
import isGlob from "is-glob";
import forEach from "lodash/forEach";
import map from "lodash/map";
import isArray from "lodash/isArray";

import asyncBlock from "./_async-block";

function ownProp (obj, field) {
  return Object.prototype.hasOwnProperty.call(obj, field)
}

export default function walk(args: Object, eachCb, cb) {
  return new Walker(args, eachCb, cb).walk(args);
}

// it's a zombie! run!
export class Walker {
  constructor(...args) {
    this.cb = args.pop();
    this.eachCb = args.pop();
    const opts = args[0] || {};

    this._asyncBlock = asyncBlock((err) => {
      if (err) {
        if (!this.aborted) this.abort();
        return (0, this.cb)(err);
      }
      return (0, this.cb)();
    });
    this.retain = this._asyncBlock.retain;
    this.release = this._asyncBlock.release;

    // cwd & root logic borrowed from glob, used in makeAbs
    this.changedCwd = false;
    const cwd = process.cwd();
    if (!ownProp(opts, "cwd"))
      this.cwd = cwd;
    else {
      this.cwd = path.resolve(opts.cwd);
      this.changedCwd = this.cwd !== cwd;
    }

    this.root = opts.root ? path.resolve(opts.root) : path.resolve(this.cwd, "/");
    if (process.platform === "win32") this.root = this.root.replace(/\\/g, "/")

    this._pending = 0;

    this.nextGlobId = 0;
    this.pendingGlobs = Object.create(null);

    this.aborted = false;
    this.seen = Object.create(null);

    // Shared cache between different glob invokations
    this.realpathCache = Object.create(null);
    this.globCache = Object.create(null);
    this.statCache = Object.create(null);
    this.symlinks = Object.create(null);
  }

  walk({src: globs, dest: dests}) {
    if (this._pending > 0) throw new Error("Not reentrant");

    const hasDest = !!dests;
    const oneDest = hasDest && dests.length === 1;
    // assert oneDest || dests.length === globs.length
    for (let i = 0, len = globs.length; i < len; i++) {
      const globPat = globs[i];
      const dest = hasDest && (oneDest ? dests[0] : dests[i]);
      if (isGlob(globPat)) {
        this.walkGlob(globPat, dest);
      } else {
        this.walkPathDirect(globPat, dest);
      }
    }
    return this;
  }

  _globOpts(opts = {}) {
    opts.realpathCache = this.realpathCache;
    opts.cache = this.globCache;
    opts.statCache = this.statCache;
    opts.symlinks = this.symlinks;
    return opts;
  }

  walkGlob(globPat, dest) {
    // TODO: match dest according to globbing
    const dests = dest ? [dest] : false;

    const globberId = this.nextGlobId++;
    // stat all paths, since we'll be walking into found directories
    this.retain();
    this.pendingGlobs[globberId] = new Glob(globPat, this._globOpts({stat: true}), (err, matches) => {
      if (this.aborted) return;
      if (err) return this.abort(err);

      this.pendingGlobs[globberId] = undefined;
      this.walkPaths(matches, dests, globPat.slice(-1) !== "*");
      this.release();
    });
  }

  walkPaths(paths, dests, direct) {
    const hasDest = !!dests;
    const oneDest = hasDest && dests.length === 1;
    // assert oneDest || dests.length === paths.length
    for (let i = 0, len = paths.length; i < len; i++) {
      const src = paths[i]
      const dest = hasDest && (oneDest ? path.join(dests[0], path.basename(src)) : dests[i]);
      this.walkPath(src, dest, direct);
    }
  }

  _makeAbs(f) {
    return globCommon.makeAbs(this, f);
  }

  walkPathDirect(src, dest) {
    // perform a glob just to populate the stat cache
    const globberId = this.nextGlobId++;
    this.retain();
    this.pendingGlobs[globberId] = new Glob(src, this._globOpts({stat: true}), (err) => {
      if (this.aborted) return;
      if (err) return this.abort(err);

      this.pendingGlobs[globberId] = undefined;
      this.walkPath(src, dest, true);
      this.release();
    });
  }

  walkPath(src, dest, direct = false) {
    const cacheKey = this._makeAbs(src);
    const seen = this.seen[cacheKey];
    if (seen) return;
    this.seen[cacheKey] = true;

    const cacheItem = this.globCache[cacheKey];

    if (cacheItem === "DIR" || isArray(cacheItem)) {
      const globberId = this.nextGlobId++;
      this.retain();
      this.pendingGlobs[globberId] = new Glob(src + "/**", this._globOpts(), (err, matches) => {
        if (this.aborted) return;
        if (err) return this.abort(err);

        this.pendingGlobs[globberId] = undefined;

        if (direct) {
          const dests = map(matches, (match) => path.join(dest, match));

          this.walkPaths(matches, dests);
        } else {
          const srcPathLen = src.length;
          const dests = map(matches, (match) => dest + match.slice(srcPathLen));

          this.walkPaths(matches, dests);
        }
        this.release();
      });
    } else {
      (0, this.eachCb)(src, dest);
    }
  }

  abort(err) {
    this.aborted = true;
    forEach(this.pendingGlobs, (globber) => { globber && globber.abort(); });
    this.pendingGlobs = Object.create(null);
    this._asyncBlock.error(err);
  }
}
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import Form from './components/Form.react';

var formDef = {
  name: 'My Form',
  dropdowns: [
    [
      {
        name: 'California',
        value: 'CA'
      },
      {
        name: 'Alaska',
        value: 'AK'
      }
    ],
    [
      {
        name: 'California',
        value: 'CA'
      },
      {
        name: 'Alaska',
        value: 'AK'
      }
    ]
  ]
};

ReactDOM.render(
  <Form form={formDef} />,
  document.getElementById('mount')
);
<commit_msg>name change<commit_after>'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import Form from './components/Form.react';

var formDef = {
  name: 'My Form',
  dropdowns: [
    [
      {
        name: 'California',
        value: 'CA'
      },
      {
        name: 'Alaska',
        value: 'AK'
      }
    ],
    [
      {
        name: 'California',
        value: 'CA'
      },
      {
        name: 'Alaska',
        value: 'AK'
      }
    ]
  ],
  textInputs: []
};

ReactDOM.render(
  <Form form={formDef} />,
  document.getElementById('mount')
);
<|endoftext|>


<commit_before>import * as i18nActions from './actions';
export { i18nActions };
export { default as i18nReducer } from './reducer'

// You can use the complete scheme of store/reducers/actions proposed by the author
export { default as Loc } from './Loc';

// Or you can just take a presentational container and map state to props by yourself
export { default as LocPresentational } from './LocPresentational';<commit_msg>delete "the"<commit_after>import * as i18nActions from './actions';
export { i18nActions };
export { default as i18nReducer } from './reducer'

// You can use the complete scheme of store/reducers/actions proposed by author
export { default as Loc } from './Loc';

// Or you can just take a presentational container and map state to props by yourself
export { default as LocPresentational } from './LocPresentational';<|endoftext|>


<commit_before>export AdminBuilder from './AdminBuilder';
export ResourceGuesser from './ResourceGuesser';
export Create from './Create';
export Edit from './Edit';
export fieldFactory from './fieldFactory';
export inputFactory from './inputFactory';
export resourceFactory from './resourceFactory';
export List from './List';
export Show from './Show';
export InputGuesser from './InputGuesser';
export * from './hydra';
export * from './docsUtils';
<commit_msg>add exports<commit_after>export CreateGuesser from './CreateGuesser';
export EditGuesser from './EditGuesser';
export FieldGuesser from './FieldGuesser';
export InputGuesser from './InputGuesser';
export ListGuesser from './ListGuesser';
export ResourceGuesser from './ResourceGuesser';
export ShowGuesser from './ShowGuesser';
export * from './hydra';
export * from './docsUtils';
<|endoftext|>


<commit_before>import assign from 'object-assign';
import pascalCase from 'pascal-case';
import React from 'react';
import ReactDOM from 'react-dom';

const defaults = {
  React: React || window.React,
  ReactDOM: ReactDOM || window.ReactDOM,
};

function syncEvent(node, eventName, newEventHandler) {
  const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
  const eventStore = node.__events || (node.__events = {});
  const oldEventHandler = eventStore[eventNameLc];

  // Remove old listener so they don't double up.
  if (oldEventHandler) {
    node.removeEventListener(eventNameLc, oldEventHandler);
  }

  // Bind new listener.
  if (newEventHandler) {
    node.addEventListener(eventNameLc, eventStore[eventNameLc] = function handler(e) {
      newEventHandler.call(this, e);
    });
  }
}

export default function (CustomElement, opts) {
  opts = assign(defaults, opts);
  const tagName = (new CustomElement()).tagName;
  const displayName = pascalCase(tagName);
  const { React, ReactDOM } = opts;

  if (!React || !ReactDOM) {
    throw new Error('React and ReactDOM must be dependencies, globally on your `window` object or passed via opts.');
  }

  class ReactComponent extends React.Component {
    static get displayName() {
      return displayName;
    }
    static get propTypes() {
      return {
        children: React.PropTypes.any,
        style: React.PropTypes.any,
      };
    }
    componentDidMount() {
      this.componentWillReceiveProps(this.props);
    }
    componentWillReceiveProps(props) {
      const node = ReactDOM.findDOMNode(this);
      Object.keys(props).forEach(name => {
        if (name === 'children' || name === 'style') {
          return;
        }

        if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
          syncEvent(node, name.substring(2), props[name]);
        } else {
          node[name] = props[name];
        }
      });
    }
    render() {
      return React.createElement(tagName, { style: this.props.style }, this.props.children);
    }
  };

  const proto = CustomElement.prototype
  Object.keys(proto).forEach(prop => {
    if (typeof proto[prop] === 'function') {
      ReactComponent.prototype[prop] = proto[prop].bind(proto);
    }
  });

  return ReactComponent;
}
<commit_msg>refactor(global): no need to fallback<commit_after>import assign from 'object-assign';
import pascalCase from 'pascal-case';
import React from 'react';
import ReactDOM from 'react-dom';

const defaults = {
  React,
  ReactDOM,
};

function syncEvent(node, eventName, newEventHandler) {
  const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
  const eventStore = node.__events || (node.__events = {});
  const oldEventHandler = eventStore[eventNameLc];

  // Remove old listener so they don't double up.
  if (oldEventHandler) {
    node.removeEventListener(eventNameLc, oldEventHandler);
  }

  // Bind new listener.
  if (newEventHandler) {
    node.addEventListener(eventNameLc, eventStore[eventNameLc] = function handler(e) {
      newEventHandler.call(this, e);
    });
  }
}

export default function (CustomElement, opts) {
  opts = assign(defaults, opts);
  const tagName = (new CustomElement()).tagName;
  const displayName = pascalCase(tagName);
  const { React, ReactDOM } = opts;

  if (!React || !ReactDOM) {
    throw new Error('React and ReactDOM must be dependencies, globally on your `window` object or passed via opts.');
  }

  class ReactComponent extends React.Component {
    static get displayName() {
      return displayName;
    }
    static get propTypes() {
      return {
        children: React.PropTypes.any,
        style: React.PropTypes.any,
      };
    }
    componentDidMount() {
      this.componentWillReceiveProps(this.props);
    }
    componentWillReceiveProps(props) {
      const node = ReactDOM.findDOMNode(this);
      Object.keys(props).forEach(name => {
        if (name === 'children' || name === 'style') {
          return;
        }

        if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
          syncEvent(node, name.substring(2), props[name]);
        } else {
          node[name] = props[name];
        }
      });
    }
    render() {
      return React.createElement(tagName, { style: this.props.style }, this.props.children);
    }
  };

  const proto = CustomElement.prototype
  Object.keys(proto).forEach(prop => {
    if (typeof proto[prop] === 'function') {
      ReactComponent.prototype[prop] = proto[prop].bind(proto);
    }
  });

  return ReactComponent;
}
<|endoftext|>


<commit_before>/* @flow */

/*
 * Types
 */

type CSSClass = string;

type Elements = Array<Element>;

type PredicateFn = (element: Element) => boolean;

type Selector = string;

/*
 * Utilities
 */

function toArray(arrayLike: any): Array<any> {
  return Array.prototype.slice.call(arrayLike);
}

/*
 * Queries
 */

function query(element: Element, selector: Selector): ?Element {
  return element.querySelector(selector);
}

function queryAll(element: Element, selector: Selector): Elements {
  const elements = element.querySelectorAll(selector);

  return (toArray(elements): Elements);
}

type MatchesFn = (selector: Selector) => boolean;

function getMatchesFn(): MatchesFn {
  const element = (document.createElement("div"): any);

  return (
    element.matches ||
    element.matchesSelector ||
    element.msMatchesSelector ||
    element.mozMatchesSelector ||
    element.webkitMatchesSelector ||
    element.oMatchesSelector
  );
}

const matchesFn: MatchesFn = getMatchesFn();

function matches(element: Element, selector: Selector): boolean {
  return matchesFn.call(element, selector);
}

/*
 * Classes
 */

function hasClass(element: Element, cssClass: CSSClass): boolean {
  return element.classList.contains(cssClass);
}

function addClass(element: Element, cssClass: CSSClass): boolean {
  const result = !hasClass(element, cssClass);

  if (result) {
    element.classList.add(cssClass);
  }

  return result;
}

function removeClass(element: Element, cssClass: CSSClass): boolean {
  const result = hasClass(element, cssClass);

  if (result) {
    element.classList.remove(cssClass);
  }

  return result;
}

function toggleClass(
  element: Element,
  cssClass: CSSClass,
  state?: boolean
): boolean {
  const target = arguments.length === 3 ? state : !hasClass(element, cssClass);

  return target ? addClass(element, cssClass) : removeClass(element, cssClass);
}

/*
 * Dataset
 */

type Dataset = { [key: string]: string };
type DatasetFn = (element: HTMLElement) => Dataset;

function nativeDataset(element: HTMLElement): Dataset {
  return element.dataset;
}

function polyfillDataset(element: HTMLElement): Dataset {
  return [].slice.call(element.attributes).reduce((data, { name, value }) => {
    if (/^data-(.+)/.test(name)) {
      const normalizedName = name
        .substr(5)
        .replace(/-\w/g, str => str[1].toUpperCase());

      data[normalizedName] = value;
    }

    return data;
  }, {});
}

function getDatasetFn() {
  const element = document.createElement("div");

  return element.dataset ? nativeDataset : polyfillDataset;
}

const datasetFn: DatasetFn = getDatasetFn();

function dataset(element: HTMLElement): Dataset {
  return datasetFn(element);
}

/*
 * Traverse
 */

function parent(element: Element): ?Element {
  return element.parentElement;
}

function parentBy(
  element: Element,
  condition: PredicateFn | Selector
): ?Element {
  const predicate = typeof condition === "string"
    ? e => matches(e, ((condition: any): string))
    : condition;

  let current = parent(element);

  while (current) {
    if (predicate(current)) {
      return current;
    }

    current = parent(current);
  }

  return null;
}

function parents(element: Element): Elements {
  const result = [];

  let current = element.parentElement;

  while (current) {
    result.push(current);

    current = current.parentElement;
  }

  return result;
}

function parentsBy(
  element: Element,
  condition: Selector | PredicateFn
): Elements {
  const predicate = typeof condition === "string"
    ? e => matches(e, condition)
    : condition;

  return parents(element).filter(predicate);
}

/*
 * Manipulate
 */

function remove(element: Element): boolean {
  const parentElement = parent(element);

  if (parentElement) {
    parentElement.removeChild(element);

    return true;
  }

  return false;
}

/*
 * Exports
 */

export {
  /* queries */
  query,
  queryAll,
  matches,
  /* classes */
  hasClass,
  addClass,
  removeClass,
  toggleClass,
  /* dataset */
  dataset,
  /* traverse */
  parent,
  parentBy,
  parents,
  parentsBy,
  /* manipulate */
  remove
};
<commit_msg>:cop:<commit_after>/* @flow */

/*
 * Types
 */

type CSSClass = string;

type Elements = Array<Element>;

type PredicateFn = (element: Element) => boolean;

type Selector = string;

/*
 * Utilities
 */

function toArray(arrayLike: any): Array<any> {
  return Array.prototype.slice.call(arrayLike);
}

/*
 * Queries
 */

function query(element: Element, selector: Selector): ?Element {
  return element.querySelector(selector);
}

function queryAll(element: Element, selector: Selector): Elements {
  const elements = element.querySelectorAll(selector);

  return (toArray(elements): Elements);
}

type MatchesFn = (selector: Selector) => boolean;

function getMatchesFn(): MatchesFn {
  const element = (document.createElement("div"): any);

  return (
    element.matches ||
    element.matchesSelector ||
    element.msMatchesSelector ||
    element.mozMatchesSelector ||
    element.webkitMatchesSelector ||
    element.oMatchesSelector
  );
}

const matchesFn: MatchesFn = getMatchesFn();

function matches(element: Element, selector: Selector): boolean {
  return matchesFn.call(element, selector);
}

/*
 * Classes
 */

function hasClass(element: Element, cssClass: CSSClass): boolean {
  return element.classList.contains(cssClass);
}

function addClass(element: Element, cssClass: CSSClass): boolean {
  const result = !hasClass(element, cssClass);

  if (result) {
    element.classList.add(cssClass);
  }

  return result;
}

function removeClass(element: Element, cssClass: CSSClass): boolean {
  const result = hasClass(element, cssClass);

  if (result) {
    element.classList.remove(cssClass);
  }

  return result;
}

function toggleClass(
  element: Element,
  cssClass: CSSClass,
  state?: boolean
): boolean {
  const target = arguments.length === 3 ? state : !hasClass(element, cssClass);

  return target ? addClass(element, cssClass) : removeClass(element, cssClass);
}

/*
 * Dataset
 */

type Dataset = { [key: string]: string };

type DatasetFn = (element: HTMLElement) => Dataset;

function nativeDataset(element: HTMLElement): Dataset {
  return element.dataset;
}

function polyfillDataset(element: HTMLElement): Dataset {
  return [].slice.call(element.attributes).reduce((data, { name, value }) => {
    if (/^data-(.+)/.test(name)) {
      const normalizedName = name
        .substr(5)
        .replace(/-\w/g, str => str[1].toUpperCase());

      data[normalizedName] = value;
    }

    return data;
  }, {});
}

function getDatasetFn(): DatasetFn {
  const element = document.createElement("div");

  return element.dataset ? nativeDataset : polyfillDataset;
}

const datasetFn: DatasetFn = getDatasetFn();

function dataset(element: HTMLElement): Dataset {
  return datasetFn(element);
}

/*
 * Traverse
 */

function parent(element: Element): ?Element {
  return element.parentElement;
}

function parentBy(
  element: Element,
  condition: PredicateFn | Selector
): ?Element {
  const predicate = typeof condition === "string"
    ? e => matches(e, condition)
    : condition;

  let current = parent(element);

  while (current) {
    if (predicate(current)) {
      return current;
    }

    current = parent(current);
  }

  return null;
}

function parents(element: Element): Elements {
  const result = [];

  let current = element.parentElement;

  while (current) {
    result.push(current);

    current = current.parentElement;
  }

  return result;
}

function parentsBy(
  element: Element,
  condition: Selector | PredicateFn
): Elements {
  const predicate = typeof condition === "string"
    ? e => matches(e, condition)
    : condition;

  return parents(element).filter(predicate);
}

/*
 * Manipulate
 */

function remove(element: Element): boolean {
  const parentElement = parent(element);

  if (parentElement) {
    parentElement.removeChild(element);

    return true;
  }

  return false;
}

/*
 * Exports
 */

export {
  /* queries */
  query,
  queryAll,
  matches,
  /* classes */
  hasClass,
  addClass,
  removeClass,
  toggleClass,
  /* dataset */
  dataset,
  /* traverse */
  parent,
  parentBy,
  parents,
  parentsBy,
  /* manipulate */
  remove
};
<|endoftext|>


<commit_before>import throng from 'throng'

const WORKERS = process.env.WEB_CONCURRENCY || 1
const PORT = process.env.PORT || 3000

function start (id) {
  console.log(`Started worker ${id}.`)

  const _ = require('lodash')
  const express = require('express')
  const compression = require('compression')
  const { default: TravisClient } = require('./travis')
  const { default: SauceClient } = require('./sauce')
  const { default: getShieldsBadge } = require('./shields')
  const { default: getBrowsersBadge, BROWSERS, getGroupedBrowsers } = require('./browsers')
  const { default: getFileSize } = require('./size')

  const app = express()
  app.set('etag', true)
  app.use(compression())

  function handleBrowsersBadge (req, res, browsers) {
    const query = { style: req.query.style }
    Promise.resolve(browsers).then((browsers) => {
      if (browsers.length) {
        const options = {
          logos: req.query.logos,
          labels: req.query.labels,
          exclude: req.query.exclude,
          sortBy: req.query.sortBy,
          versionDivider: req.query.versionDivider,
          ...query
        }
        return getBrowsersBadge({ browsers, options })
      } else {
        return getShieldsBadge('browsers', 'unknown', 'lightgrey', query)
      }
    }).catch((err) => {
      console.error(`Error: ${err}`)
      return getShieldsBadge('browsers', 'unknown', 'lightgrey', query)
    }).then((body) => {
      res.write(body)
      res.end()
    })
  }

  /**
   * Helper for sending an SVG response given a promise of SauceLabs jobs
   * or a browser-matrix SVG badge to transform. Need this because there are
   * two handlers that have a lot of overlap:
   * - /sauce/:user - Get jobs for any build (regardless of CI service).
   * - /travis/:user/:repo/sauce - Get jobs for a Travis build.
   */
  function handleSauceBadge (req, res, client, source, jobs) {
    let browsers
    if (source === 'svg') {
      browsers = client.getLatestSVGBrowsers()
    } else {
      browsers = Promise.resolve(jobs).then((jobs) => {
        const filters = {
          name: req.query.name,
          tag: req.query.tag
        }
        jobs = client.filterJobs(jobs, filters)
        return client.aggregateBrowsers(jobs)
      })
    }
    browsers = browsers.then(getGroupedBrowsers)
    return handleBrowsersBadge(req, res, browsers)
  }

  app.get('/', (req, res) => {
    res.redirect('https://github.com/exogen/badge-matrix')
  })

  app.get('/sauce/:user', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const user = req.params.user
    let source = req.query.source || 'svg'
    const build = req.query.build // If undefined, will try to get the latest.
    const query = {}
    if (req.query.from) {
      query.from = parseInt(req.query.from, 10) || void 0
    }
    if (req.query.to) {
      query.to = parseInt(req.query.to, 10) || void 0
    }
    if (req.query.skip) {
      query.skip = parseInt(req.query.skip, 10) || void 0
    }
    if (build || req.query.name || req.query.tag || req.query.from ||
        req.query.to || req.query.skip) {
      source = 'api'
    }
    const sauce = new SauceClient(user)
    const jobs = source === 'api' ? sauce.getBuildJobs(build, query) : []
    return handleSauceBadge(req, res, sauce, source, jobs)
  })

  app.get('/travis/:user/:repo', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const user = req.params.user
    const repo = req.params.repo
    const branch = req.query.branch || 'master'
    const label = req.query.label || req.params.repo
    const travis = new TravisClient(user, repo)
    const query = { style: req.query.style }
    travis.getLatestBranchBuild(branch).then((build) => {
      const filters = {
        env: req.query.env
      }
      const jobs = travis.filterJobs(build.jobs, filters)
      const status = travis.aggregateStatus(jobs)
      const color = {
        passed: 'brightgreen',
        failed: 'red'
      }[status] || 'lightgrey'
      return getShieldsBadge(label, status, color, query)
    }).catch((err) => {
      console.error(`Error: ${err}`)
      return getShieldsBadge(label, 'error', 'lightgrey', query)
    }).then((body) => {
      res.write(body)
      res.end()
    })
  })

  app.get('/travis/:user/:repo/sauce/:sauceUser?', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const user = req.params.user
    const repo = req.params.repo
    const sauceUser = req.params.sauceUser || user
    const branch = req.query.branch || 'master'
    const travis = new TravisClient(user, repo)
    const sauce = new SauceClient(sauceUser)
    const jobs = travis.getLatestBranchBuild(branch).then((build) => {
      return sauce.getTravisBuildJobs(build)
    })
    return handleSauceBadge(req, res, sauce, 'api', jobs)
  })

  app.get('/size/:source/*', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const source = req.params.source
    const path = req.params[0]
    const color = req.query.color || 'brightgreen'
    const options = { gzip: req.query.gzip === 'true' }
    const query = { style: req.query.style }
    let url
    // Express' path-to-regexp business is too insane to easily do this above.
    if (path.match(/^\w/)) {
      if (source === 'github') {
        url = `https://raw.githubusercontent.com/${path}`
      } else if (source === 'npm') {
        url = `https://unpkg.com/${path}`
      }
    }
    const label = req.query.label || (options.gzip ? 'size (gzip)' : 'size')
    getFileSize(url, options).then((size) => {
      return getShieldsBadge(label, size, color, query)
    }).catch((err) => {
      console.error(`Error: ${err}`)
      return getShieldsBadge(label, 'error', 'lightgrey', query)
    }).then((body) => {
      res.write(body)
      res.end()
    })
  })

  app.get('/browsers', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    let browsers = {}
    _.forEach(BROWSERS, (value, browser) => {
      const versionNumbers = (req.query[browser] || '').split(',')
      versionNumbers.reduce((browsers, version) => {
        if (!version) {
          return browsers
        }
        let status = {
          '!': 'error',
          '-': 'failed',
          '+': 'passed'
        }[version.charAt(0)]
        if (status) {
          version = version.slice(1)
        } else {
          status = 'passed'
        }
        const versions = browsers[browser] = browsers[browser] || {}
        const browserData = versions[version] = versions[version] || {
          browser,
          version,
          status: 'unknown'
        }
        browserData.status = status
        return browsers
      }, browsers)
    })
    browsers = getGroupedBrowsers(browsers)
    handleBrowsersBadge(req, res, browsers)
  })

  const server = app.listen(PORT, () => {
    const port = server.address().port
    console.log(`Listening on port ${port}`)
  })
}

throng({ start, workers: WORKERS, lifetime: Infinity })
<commit_msg>Allow scoped packages /size/ endpoint (#16)<commit_after>import throng from 'throng'

const WORKERS = process.env.WEB_CONCURRENCY || 1
const PORT = process.env.PORT || 3000

function start (id) {
  console.log(`Started worker ${id}.`)

  const _ = require('lodash')
  const express = require('express')
  const compression = require('compression')
  const { default: TravisClient } = require('./travis')
  const { default: SauceClient } = require('./sauce')
  const { default: getShieldsBadge } = require('./shields')
  const { default: getBrowsersBadge, BROWSERS, getGroupedBrowsers } = require('./browsers')
  const { default: getFileSize } = require('./size')

  const app = express()
  app.set('etag', true)
  app.use(compression())

  function handleBrowsersBadge (req, res, browsers) {
    const query = { style: req.query.style }
    Promise.resolve(browsers).then((browsers) => {
      if (browsers.length) {
        const options = {
          logos: req.query.logos,
          labels: req.query.labels,
          exclude: req.query.exclude,
          sortBy: req.query.sortBy,
          versionDivider: req.query.versionDivider,
          ...query
        }
        return getBrowsersBadge({ browsers, options })
      } else {
        return getShieldsBadge('browsers', 'unknown', 'lightgrey', query)
      }
    }).catch((err) => {
      console.error(`Error: ${err}`)
      return getShieldsBadge('browsers', 'unknown', 'lightgrey', query)
    }).then((body) => {
      res.write(body)
      res.end()
    })
  }

  /**
   * Helper for sending an SVG response given a promise of SauceLabs jobs
   * or a browser-matrix SVG badge to transform. Need this because there are
   * two handlers that have a lot of overlap:
   * - /sauce/:user - Get jobs for any build (regardless of CI service).
   * - /travis/:user/:repo/sauce - Get jobs for a Travis build.
   */
  function handleSauceBadge (req, res, client, source, jobs) {
    let browsers
    if (source === 'svg') {
      browsers = client.getLatestSVGBrowsers()
    } else {
      browsers = Promise.resolve(jobs).then((jobs) => {
        const filters = {
          name: req.query.name,
          tag: req.query.tag
        }
        jobs = client.filterJobs(jobs, filters)
        return client.aggregateBrowsers(jobs)
      })
    }
    browsers = browsers.then(getGroupedBrowsers)
    return handleBrowsersBadge(req, res, browsers)
  }

  app.get('/', (req, res) => {
    res.redirect('https://github.com/exogen/badge-matrix')
  })

  app.get('/sauce/:user', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const user = req.params.user
    let source = req.query.source || 'svg'
    const build = req.query.build // If undefined, will try to get the latest.
    const query = {}
    if (req.query.from) {
      query.from = parseInt(req.query.from, 10) || void 0
    }
    if (req.query.to) {
      query.to = parseInt(req.query.to, 10) || void 0
    }
    if (req.query.skip) {
      query.skip = parseInt(req.query.skip, 10) || void 0
    }
    if (build || req.query.name || req.query.tag || req.query.from ||
        req.query.to || req.query.skip) {
      source = 'api'
    }
    const sauce = new SauceClient(user)
    const jobs = source === 'api' ? sauce.getBuildJobs(build, query) : []
    return handleSauceBadge(req, res, sauce, source, jobs)
  })

  app.get('/travis/:user/:repo', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const user = req.params.user
    const repo = req.params.repo
    const branch = req.query.branch || 'master'
    const label = req.query.label || req.params.repo
    const travis = new TravisClient(user, repo)
    const query = { style: req.query.style }
    travis.getLatestBranchBuild(branch).then((build) => {
      const filters = {
        env: req.query.env
      }
      const jobs = travis.filterJobs(build.jobs, filters)
      const status = travis.aggregateStatus(jobs)
      const color = {
        passed: 'brightgreen',
        failed: 'red'
      }[status] || 'lightgrey'
      return getShieldsBadge(label, status, color, query)
    }).catch((err) => {
      console.error(`Error: ${err}`)
      return getShieldsBadge(label, 'error', 'lightgrey', query)
    }).then((body) => {
      res.write(body)
      res.end()
    })
  })

  app.get('/travis/:user/:repo/sauce/:sauceUser?', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const user = req.params.user
    const repo = req.params.repo
    const sauceUser = req.params.sauceUser || user
    const branch = req.query.branch || 'master'
    const travis = new TravisClient(user, repo)
    const sauce = new SauceClient(sauceUser)
    const jobs = travis.getLatestBranchBuild(branch).then((build) => {
      return sauce.getTravisBuildJobs(build)
    })
    return handleSauceBadge(req, res, sauce, 'api', jobs)
  })

  app.get('/size/:source/*', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    const source = req.params.source
    const path = req.params[0]
    const color = req.query.color || 'brightgreen'
    const options = { gzip: req.query.gzip === 'true' }
    const query = { style: req.query.style }
    let url
    // Express' path-to-regexp business is too insane to easily do this above.
    if (path.length > 0) {
      if (source === 'github') {
        url = `https://raw.githubusercontent.com/${path}`
      } else if (source === 'npm') {
        url = `https://unpkg.com/${path}`
      }
    }
    const label = req.query.label || (options.gzip ? 'size (gzip)' : 'size')
    getFileSize(url, options).then((size) => {
      return getShieldsBadge(label, size, color, query)
    }).catch((err) => {
      console.error(`Error: ${err}`)
      return getShieldsBadge(label, 'error', 'lightgrey', query)
    }).then((body) => {
      res.write(body)
      res.end()
    })
  })

  app.get('/browsers', (req, res) => {
    res.status(200)
    res.set('Content-Type', 'image/svg+xml')
    res.set('Cache-Control', 'public, must-revalidate, max-age=30')
    res.flushHeaders()

    console.log(`Incoming request from referrer: ${req.get('Referrer')}`)

    let browsers = {}
    _.forEach(BROWSERS, (value, browser) => {
      const versionNumbers = (req.query[browser] || '').split(',')
      versionNumbers.reduce((browsers, version) => {
        if (!version) {
          return browsers
        }
        let status = {
          '!': 'error',
          '-': 'failed',
          '+': 'passed'
        }[version.charAt(0)]
        if (status) {
          version = version.slice(1)
        } else {
          status = 'passed'
        }
        const versions = browsers[browser] = browsers[browser] || {}
        const browserData = versions[version] = versions[version] || {
          browser,
          version,
          status: 'unknown'
        }
        browserData.status = status
        return browsers
      }, browsers)
    })
    browsers = getGroupedBrowsers(browsers)
    handleBrowsersBadge(req, res, browsers)
  })

  const server = app.listen(PORT, () => {
    const port = server.address().port
    console.log(`Listening on port ${port}`)
  })
}

throng({ start, workers: WORKERS, lifetime: Infinity })
<|endoftext|>


<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

export {default as Table} from './lib/table/table';
export {default as Treemap} from './lib/treemap/treemap';

export {default as XYPlot} from './lib/plot/xy-plot';

export {default as LineSeries} from './lib/plot/series/line-series';
export {default as VerticalBarSeries}
  from './lib/plot/series/vertical-bar-series';
export {default as HorizontalBarSeries}
  from './lib/plot/series/horizontal-bar-series';
export {default as MarkSeries} from './lib/plot/series/mark-series';
export {default as HeatmapSeries} from './lib/plot/series/heatmap-series';
export {default as AreaSeries} from './lib/plot/series/area-series';
export {default as LineMarkSeries} from './lib/plot/series/line-mark-series';

export {default as Hint} from './lib/plot/hint';
export {default as Crosshair} from './lib/plot/crosshair';

export {default as XAxis} from './lib/plot/axis/x-axis';
export {default as YAxis} from './lib/plot/axis/y-axis';

export {default as VerticalGridLines} from './lib/plot/vertical-grid-lines';
export {default as HorizontalGridLines} from './lib/plot/horizontal-grid-lines';

export {default as RadialChart} from './lib/radial-chart/radial-chart';

export {default as DiscreteColorLegend}
  from './lib/legends/discrete-color-legend';

export {default as SearchableDiscreteColorLegend}
  from './lib/legends/searchable-discrete-color-legend';

export {default as ContinuousColorLegend}
  from './lib/legends/continuous-color-legend';

export {default as ContinuousSizeLegend}
  from './lib/legends/continuous-size-legend';

export {default as makeWidthFlexible} from './lib/make-vis-flexible';
<commit_msg>Expose GridLines, AxisUtils, ScaleUtils (#183)<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

export {default as Table} from './lib/table/table';
export {default as Treemap} from './lib/treemap/treemap';

export {default as XYPlot} from './lib/plot/xy-plot';

export {default as LineSeries} from './lib/plot/series/line-series';
export {default as VerticalBarSeries}
  from './lib/plot/series/vertical-bar-series';
export {default as HorizontalBarSeries}
  from './lib/plot/series/horizontal-bar-series';
export {default as MarkSeries} from './lib/plot/series/mark-series';
export {default as HeatmapSeries} from './lib/plot/series/heatmap-series';
export {default as AreaSeries} from './lib/plot/series/area-series';
export {default as LineMarkSeries} from './lib/plot/series/line-mark-series';

export {default as Hint} from './lib/plot/hint';
export {default as Crosshair} from './lib/plot/crosshair';

export {default as XAxis} from './lib/plot/axis/x-axis';
export {default as YAxis} from './lib/plot/axis/y-axis';

export {default as GridLines} from './lib/plot/grid-lines';
export {default as VerticalGridLines} from './lib/plot/vertical-grid-lines';
export {default as HorizontalGridLines} from './lib/plot/horizontal-grid-lines';

export {default as RadialChart} from './lib/radial-chart/radial-chart';

export {default as DiscreteColorLegend}
  from './lib/legends/discrete-color-legend';

export {default as SearchableDiscreteColorLegend}
  from './lib/legends/searchable-discrete-color-legend';

export {default as ContinuousColorLegend}
  from './lib/legends/continuous-color-legend';

export {default as ContinuousSizeLegend}
  from './lib/legends/continuous-size-legend';

export {default as makeWidthFlexible} from './lib/make-vis-flexible';

export {default as AxisUtils} from './lib/utils/axis-utils';
export {default as ScaleUtils} from './lib/utils/scales-utils';
<|endoftext|>


<commit_before>import {asyncReplace, isArray, isString, md5} from 'stc-helper';
import {isMaster} from 'cluster';
import PluginInvoke from 'stc-plugin-invoke';
import path from 'path';
import url from 'url';

/**
 * get ast cache instance
 */
const getAstCacheInstance = (stc, extname) => {
  let astCacheKey = '__ast__';
  let astCacheInstance = stc.cacheInstances[astCacheKey];
  if(!astCacheInstance){
    astCacheInstance = new stc.cache({
      type: (stc.config.product || 'default') + '/ast/' + extname
    });
    stc.cacheInstances[astCacheKey] = astCacheInstance;
  }
  return astCacheInstance;
};

/**
 * get file ast
 */
const getAst = async (instance, content, fn) => {
  let astCacheInstance = null, cacheKey = '';
  // get ast from cache
  if(instance.stc.config.cache !== false){
    astCacheInstance = getAstCacheInstance(instance.stc, instance.file.extname);
    cacheKey = md5(content);
    let cacheData = await astCacheInstance.get(cacheKey);
    if(cacheData){
      let debug = instance.stc.debug('cache');
      debug('getAst from cache, file is `' + instance.file.path + '`');
      instance.file.setAst(cacheData);
      return cacheData;
    }
  }
  let ast = await fn();
  if(astCacheInstance){
    await astCacheInstance.set(cacheKey, ast);
  }
  return ast;
};

/**
 * stc plugin abstract class
 */
export default class StcPlugin {
  /**
   * constructor
   */
  constructor(file, opts = {}){
    this.file = file;
    this.options = opts.options || {};
    this.stc = opts.stc;
    this.config = this.stc.config;
    this.TokenType = this.stc.flkit.TokenType;
    //can not use ext in sub plugins
    this.ext = opts.ext || {};
    //store other properties
    this._prop = {};
  }
  /**
   * get or set properties
   */
  prop(name, value){
    if(value === undefined){
      return this._prop[name];
    }
    this._prop[name] = value;
    return this;
  }
  /**
   * get md5 value of plugin
   */
  getMd5(){
    if(this.prop('md5')){
      return this.prop('md5');
    }
    let value = md5(this.constructor.toString());
    this.prop('md5', value);
    return value;
  }
  /**
   * get file content
   */
  getContent(encoding){
    if(isMaster){
      return this.file.getContent(encoding);
    }
    return this.stc.cluster.workerInvoke({
      method: 'getContent',
      args: [encoding],
      file: this.file.path
    });
  }
  /**
   * set file content
   */
  setContent(content){
    if(!isMaster){
      throw new Error('setContent method must be invoked in master');
    }
    this.file.setContent(content);
    return this;
  }
  /**
   * get file ast
   */
  async getAst(){
    let content = await this.getContent('utf8');

    if(isMaster){
      if(this.file.hasAst()){
        return this.file.getAst();
      }
      return getAst(this, content, async () => {
        // turn off cluster
        if(this.config.cluster === false){
          return this.file.getAst();
        }
        //get ast in worker parsed
        let ret = await this.stc.cluster.masterInvoke({
          type: 'getAst',
          content,
          file: this.file.path
        });
        this.file.setAst(ret);
        return ret;
      });
    }

    return getAst(this, content, async () => {
      // if have ast in master, return directory
      let ast = await this.stc.cluster.workerInvoke({
        method: 'getAst',
        file: this.file.path
      });
      if(ast){
        return ast;
      }
      this.file.setContent(content);
      let ret = await this.file.getAst();
      // update ast in master
      await this.stc.cluster.workerInvoke({
        method: 'updateAst',
        file: this.file.path,
        ast: ret
      });
      return ret;
    });
  }
  /**
   * set ast
   */
  setAst(ast){
    if(!isMaster){
      throw new Error('setAst must be invoked in master');
    }
    this.file.setAst(ast);
    return this;
  }
  /**
   * add file dependence
   */
  addDependence(dependencies){
    if(!isMaster){
      throw new Error('addDependence must be invoked in master');
    }
    if(!isArray(dependencies)){
      dependencies = [dependencies];
    }
    dependencies = dependencies.map(item => {
      if(!isString(item)){
        return item;
      }
      let filepath = this.getResolvePath(item);
      let file = this.stc.resource.getFileByPath(filepath, this.file.path);
      if(!file){
        throw new Error(`file ${item} is not exist in ${this.file.path}`);
      }
      return file;
    });
    this.file.dependence.add(dependencies);
    return dependencies;
  }
  /**
   * get file dependence
   */
  getDependence(file = this.file){
    return file.dependence.get();
  }

  /**
   * add file
   */
  async addFile(filepath, content, virtual){
    let resolvePath = this.getResolvePath(filepath);
    if(isMaster){
      return this.stc.resource.addFile(resolvePath, content, virtual);
    }
    await this.stc.cluster.workerInvoke({
      method: 'addFile',
      file: resolvePath,
      virtual,
      content
    });
    return this.stc.resource.createFile(resolvePath, content, virtual);
  }
  /**
   * get resolve path
   */
  getResolvePath(filepath){
    // parse filepath, remove query & hash in filepath
    filepath = decodeURIComponent(url.parse(filepath).pathname);
    let currentFilePath = path.dirname(this.file.path);
    let resolvePath = path.resolve(currentFilePath, filepath);
    let currentPath = process.cwd() + path.sep;
    if(resolvePath.indexOf(currentPath) === 0){
      resolvePath = resolvePath.slice(currentPath.length);
    }
    return resolvePath;
  }
  /**
   * get file by path
   */
  getFileByPath(filepath){
    filepath = this.getResolvePath(filepath);
    if(isMaster){
      return this.stc.resource.getFileByPath(filepath, this.file.path);
    }
    return this.stc.resource.createFile(filepath);
  }

  /**
   * invoke self plugin
   */
  async invokeSelf(file = this.file){
    if(isString(file)){
      file = this.getFileByPath(file);
    }
    return this.invokePlugin(this.constructor, file);
  }

  /**
   * invoke plugin
   */
  async invokePlugin(plugin, file = this.file){
    if(isString(file)){
      file = this.getFileByPath(file);
    }
    let instance = new PluginInvoke(plugin, file, {
      stc: this.stc,
      options: this.options,
      ext: plugin === this.constructor ? this.ext : {}
    });
    return instance.run();
  }

  /**
   * async content replace
   * must be use RegExp
   */
  asyncReplace(content = '', replace, callback){
    return asyncReplace(content, replace, callback);
  }
  /**
   * get or set cache
   */
  async cache(name, value){
    if(isMaster){
      let md5Value = this.getMd5();
      if(!this.stc.cacheInstances[md5Value]){
        this.stc.cacheInstances[md5Value] = new this.stc.cache({
          onlyMemory: true
        });
      }
      let instance = this.stc.cacheInstances[md5Value];
      if(value === undefined){
        return instance.get(name);
      }
      instance.set(name, value);
      return this;
    }
    //get or set cache from master
    return this.stc.cluster.workerInvoke({
      method: 'cache',
      key: this.getMd5(),
      name,
      value
    });
  }
  /**
   * create token
   */
  createToken(type, value, referToken){
    return this.stc.flkit.createToken(type, value, referToken);
  }
  /**
   * throw fatal error
   */
  fatal(message, line, column, file = this.file.path){
    let error = new Error(message);
    error.className = this.constructor.name;
    error.file = file;
    error.line = line + 1;
    error.column = column + 1;
    throw error;
  }

  /**
   * show error log
   */
  error(message, line, column, file = this.file.path){
    this.stc.log.error({
      message,
      line: line + 1,
      column: column + 1,
      file,
      className: this.constructor.name
    });
  }

   /**
   * show warning log
   */
  warning(message, line, column, file = this.file.path){
    this.stc.log.warning({
      message,
      line: line + 1,
      column: column + 1,
      file,
      className: this.constructor.name
    });
  }

   /**
   * show notice log
   */
  notice(message, line, column, file = this.file.path){
    this.stc.log.notice({
      message,
      line: line + 1,
      column: column + 1,
      file,
      className: this.constructor.name
    });
  }

  /**
   * run
   */
  run(){

  }
  /**
   * update
   */
  update(){

  }
}<commit_msg>Check some method only allow invoked in update method<commit_after>import {asyncReplace, isArray, isString, md5} from 'stc-helper';
import {isMaster} from 'cluster';
import PluginInvoke from 'stc-plugin-invoke';
import path from 'path';
import url from 'url';

/**
 * get ast cache instance
 */
const getAstCacheInstance = (stc, extname) => {
  let astCacheKey = '__ast__';
  let astCacheInstance = stc.cacheInstances[astCacheKey];
  if(!astCacheInstance){
    astCacheInstance = new stc.cache({
      type: (stc.config.product || 'default') + '/ast/' + extname
    });
    stc.cacheInstances[astCacheKey] = astCacheInstance;
  }
  return astCacheInstance;
};

/**
 * check run method is execute
 */
const checkRunIsExecute = (instance, method) => {
  if(!instance.prop('__isRun__')){
    throw new Error(`${method} only allow invoked in update method`);
  }
}
/**
 * check in master
 */
const checkInMaster = method => {
  if(!isMaster){
    throw new Error(`${method} method must be invoked in master`);
  }
}

/**
 * get file ast
 */
const getAst = async (instance, content, fn) => {
  let astCacheInstance = null, cacheKey = '';
  // get ast from cache
  if(instance.stc.config.cache !== false){
    astCacheInstance = getAstCacheInstance(instance.stc, instance.file.extname);
    cacheKey = md5(content);
    let cacheData = await astCacheInstance.get(cacheKey);
    if(cacheData){
      let debug = instance.stc.debug('cache');
      debug('getAst from cache, file is `' + instance.file.path + '`');
      instance.file.setAst(cacheData);
      return cacheData;
    }
  }
  let ast = await fn();
  if(astCacheInstance){
    await astCacheInstance.set(cacheKey, ast);
  }
  return ast;
};

/**
 * stc plugin abstract class
 */
export default class StcPlugin {
  /**
   * constructor
   */
  constructor(file, opts = {}){
    this.file = file;
    this.options = opts.options || {};
    this.stc = opts.stc;
    this.config = this.stc.config;
    this.TokenType = this.stc.flkit.TokenType;
    //can not use ext in sub plugins
    this.ext = opts.ext || {};
    //store other properties
    this._prop = {};
  }
  /**
   * get or set properties
   */
  prop(name, value){
    if(value === undefined){
      return this._prop[name];
    }
    this._prop[name] = value;
    return this;
  }
  /**
   * get md5 value of plugin
   */
  getMd5(){
    if(this.prop('md5')){
      return this.prop('md5');
    }
    let value = md5(this.constructor.toString());
    this.prop('md5', value);
    return value;
  }
  /**
   * get file content
   */
  getContent(encoding){
    if(isMaster){
      return this.file.getContent(encoding);
    }
    return this.stc.cluster.workerInvoke({
      method: 'getContent',
      args: [encoding],
      file: this.file.path
    });
  }
  /**
   * set file content
   */
  setContent(content){
    checkInMaster('setContent');
    checkRunIsExecute(this, 'setContent');
    this.file.setContent(content);
    return this;
  }
  /**
   * get file ast
   */
  async getAst(){
    let content = await this.getContent('utf8');

    if(isMaster){
      if(this.file.hasAst()){
        return this.file.getAst();
      }
      return getAst(this, content, async () => {
        // turn off cluster
        if(this.config.cluster === false){
          return this.file.getAst();
        }
        //get ast in worker parsed
        let ret = await this.stc.cluster.masterInvoke({
          type: 'getAst',
          content,
          file: this.file.path
        });
        this.file.setAst(ret);
        return ret;
      });
    }

    return getAst(this, content, async () => {
      // if have ast in master, return directory
      let ast = await this.stc.cluster.workerInvoke({
        method: 'getAst',
        file: this.file.path
      });
      if(ast){
        return ast;
      }
      this.file.setContent(content);
      let ret = await this.file.getAst();
      // update ast in master
      await this.stc.cluster.workerInvoke({
        method: 'updateAst',
        file: this.file.path,
        ast: ret
      });
      return ret;
    });
  }
  /**
   * set ast
   */
  setAst(ast){
    checkInMaster('setAst');
    checkRunIsExecute(this, 'setAst');
    this.file.setAst(ast);
    return this;
  }
  /**
   * add file dependence
   */
  addDependence(dependencies){
    if(!isMaster){
      throw new Error('addDependence must be invoked in master');
    }
    if(!isArray(dependencies)){
      dependencies = [dependencies];
    }
    dependencies = dependencies.map(item => {
      if(!isString(item)){
        return item;
      }
      let filepath = this.getResolvePath(item);
      let file = this.stc.resource.getFileByPath(filepath, this.file.path);
      if(!file){
        throw new Error(`file ${item} is not exist in ${this.file.path}`);
      }
      return file;
    });
    this.file.dependence.add(dependencies);
    return dependencies;
  }
  /**
   * get file dependence
   */
  getDependence(file = this.file){
    return file.dependence.get();
  }

  /**
   * add file
   */
  async addFile(filepath, content, virtual){
    if(!virtual){
      checkRunIsExecute(this, 'addFile');
    }
    let resolvePath = this.getResolvePath(filepath);
    if(isMaster){
      return this.stc.resource.addFile(resolvePath, content, virtual);
    }
    await this.stc.cluster.workerInvoke({
      method: 'addFile',
      file: resolvePath,
      virtual,
      content
    });
    return this.stc.resource.createFile(resolvePath, content, virtual);
  }
  /**
   * get resolve path
   */
  getResolvePath(filepath){
    // parse filepath, remove query & hash in filepath
    filepath = decodeURIComponent(url.parse(filepath).pathname);
    let currentFilePath = path.dirname(this.file.path);
    let resolvePath = path.resolve(currentFilePath, filepath);
    let currentPath = process.cwd() + path.sep;
    if(resolvePath.indexOf(currentPath) === 0){
      resolvePath = resolvePath.slice(currentPath.length);
    }
    return resolvePath;
  }
  /**
   * get file by path
   */
  getFileByPath(filepath){
    filepath = this.getResolvePath(filepath);
    if(isMaster){
      return this.stc.resource.getFileByPath(filepath, this.file.path);
    }
    return this.stc.resource.createFile(filepath);
  }

  /**
   * invoke self plugin
   */
  async invokeSelf(file = this.file){
    if(isString(file)){
      file = this.getFileByPath(file);
    }
    return this.invokePlugin(this.constructor, file);
  }

  /**
   * invoke plugin
   */
  async invokePlugin(plugin, file = this.file){
    if(isString(file)){
      file = this.getFileByPath(file);
    }
    let instance = new PluginInvoke(plugin, file, {
      stc: this.stc,
      options: this.options,
      ext: plugin === this.constructor ? this.ext : {}
    });
    return instance.run();
  }

  /**
   * async content replace
   * must be use RegExp
   */
  asyncReplace(content = '', replace, callback){
    return asyncReplace(content, replace, callback);
  }
  /**
   * get or set cache
   */
  async cache(name, value){
    if(isMaster){
      let md5Value = this.getMd5();
      if(!this.stc.cacheInstances[md5Value]){
        this.stc.cacheInstances[md5Value] = new this.stc.cache({
          onlyMemory: true
        });
      }
      let instance = this.stc.cacheInstances[md5Value];
      if(value === undefined){
        return instance.get(name);
      }
      instance.set(name, value);
      return this;
    }
    //get or set cache from master
    return this.stc.cluster.workerInvoke({
      method: 'cache',
      key: this.getMd5(),
      name,
      value
    });
  }
  /**
   * create token
   */
  createToken(type, value, referToken){
    return this.stc.flkit.createToken(type, value, referToken);
  }
  /**
   * throw fatal error
   */
  fatal(message, line, column, file = this.file.path){
    let error = new Error(message);
    error.className = this.constructor.name;
    error.file = file;
    error.line = line + 1;
    error.column = column + 1;
    throw error;
  }

  /**
   * show error log
   */
  error(message, line, column, file = this.file.path){
    this.stc.log.error({
      message,
      line: line + 1,
      column: column + 1,
      file,
      className: this.constructor.name
    });
  }

   /**
   * show warning log
   */
  warning(message, line, column, file = this.file.path){
    this.stc.log.warning({
      message,
      line: line + 1,
      column: column + 1,
      file,
      className: this.constructor.name
    });
  }

   /**
   * show notice log
   */
  notice(message, line, column, file = this.file.path){
    this.stc.log.notice({
      message,
      line: line + 1,
      column: column + 1,
      file,
      className: this.constructor.name
    });
  }

  /**
   * run
   */
  run(){

  }
  /**
   * update
   */
  update(){

  }
}<|endoftext|>


<commit_before>var q = require('q');
var JobQueueService = function (storage) {
  if (!storage) { throw new Error('Instance of storage service required'); }
  var _storage = storage;

  this.addJob = function (type, data) {
    var record = _storage.createRecord('_queue/task')
    return record
    .update({
      type: type,
      data: data
    })
    .then(function () { return record; });
  }

  this.createWorker = function (provider, processJob) {
    if (!provider) { throw new Error('Job queue provider required'); }
    if (!processJob) { throw new Error('Process job callback required'); }
    provider.createWorker(processJob);
  }
}

module.exports = JobQueueService;
<commit_msg>docs(JobQueueService): document constructor and methods<commit_after>var q = require('q');
/**
 * A tool for creating and processing background jobs
 * @class JobQueueService
 * @param {StorageService} storage an instance of le-storage-service that is used to create records
 * @returns {service}
 */
var JobQueueService = function (storage) {
  if (!storage) { throw new Error('Instance of storage service required'); }
  var _storage = storage;
  /**
   * Stores a new job to process
   * @function addJob
   * @memberof JobQueueService
   * @instance
   * @param {string} type the type of the job, used to determine how it should be processed
   * @param {string} data the data necessary to complete the job
   * @returns {promise} resolves with the newly created job record
   */
  this.addJob = function (type, data) {
    var record = _storage.createRecord('_queue/task')
    return record
    .update({
      type: type,
      data: data
    })
    .then(function () { return record; });
  }
  /**
   * Creates a worker to process the jobs in the queue
   * @function createWorker
   * @memberof JobQueueService
   * @instance
   * @param {JobQueueProvider} provider the provider this service delegates to
   * @param {Function}  processJob function that processes the job. Called with two params `data` and `complete`. processJob must call `complete()` to finish processing a job
   */
  this.createWorker = function (provider, processJob) {
    if (!provider) { throw new Error('Job queue provider required'); }
    if (!processJob) { throw new Error('Process job callback required'); }
    provider.createWorker(processJob);
  }
}

module.exports = JobQueueService;
<|endoftext|>