<commit_before>var fs = require('fs');
var path = require('path');
var browserify = require('browserify');
var helpers = require('broccoli-kitchen-sink-helpers');
var RSVP = require('rsvp');
var CoreObject = require('core-object');
var mapSeries = require('promise-map-series');
var merge    = require('lodash').merge;
var rimraf = require('rimraf');
var symlinkOrCopy = require('symlink-or-copy');
var quickTemp = require('quick-temp');
var mkdirp = require('mkdirp');
var derequire = require('derequire');

var through = require('through2');

module.exports = CoreObject.extend({
  init: function(inputTree, options){
    if (!options) {
      options = {};
    }
    this.inputTree = inputTree;
    this.root = options.root;
    this.inFile = options.inFile || 'browserify_stubs.js';
    this.browserifyOptions = options.browserifyOptions;
    this.enableSourcemap = options.enableSourcemap;
    this.fullPaths = typeof options.fullPaths !== 'undefined' ? options.fullPaths : true;
    this.outputFile = options.outputFile || 'browserify/browserify.js';
    this.cache = {};
    quickTemp.makeOrRemake(this, '_inputStaging');
    quickTemp.makeOrRemake(this, '_destDir');
  },

  description: 'ember-browserify',

  cleanup: function() {
    if (this._destDir) {
      rimraf.sync(this._destDir);
    }
    if (this._inputStaging) {
      rimraf.sync(this._inputStaging);
    }
    if (this._outputCache) {
      rimraf.sync(this._outputCache);
    }
  },

  read: function (readTree) {
    var self = this;
    return readTree(this.inputTree).then(function(inDir){
      return self.checkCache(inDir).then(function(cacheValid){
        if (!self._outputCache || !cacheValid) {
          return self._rebuild(inDir);
        }
      });
    }).then(function(){
      rimraf.sync(self._destDir);
      symlinkOrCopy.sync(self._outputCache, self._destDir);
      return self.watchNodeModules(readTree).then(function(){
        return self._destDir;
      });
    });
  },

  _rebuild: function(inDir){
    var self = this;
    this._watchModules = Object.create(null);

    // _inputStaging needs to stay at the same path, because the
    // browserify bundler uses it as a fixed baseDir.
    rimraf.sync(this._inputStaging);
    mkdirp.sync(this._inputStaging);

    symlinkOrCopy.sync(path.join(inDir, this.inFile), path.join(this._inputStaging, this.inFile));
    quickTemp.makeOrRemake(this, '_outputCache');
    return this.updateCache(this._outputCache).catch(function(err){
      rimraf.sync(self._outputCache);
      delete self._outputCache;
      throw err;
    });
  },

  bundler: function() {
    if (!this._bundler) {
      this._bundler = this.makeBundler();
    }
    return this._bundler;
  },

  makeBundler: function() {
    var self = this;
    var opts = merge({
      cache: this.cache,
      packageCache: {},
      fullPaths: this.fullPaths,
      basedir: this._inputStaging,
      debug: this.enableSourcemap
    }, this.browserifyOptions);

    var b = browserify(opts);
    ['transforms', 'externals', 'ignores', 'includes'].forEach(function(thing) {
      if (!opts[thing]) { return; }
      opts[thing].forEach(function(args){
        if (!Array.isArray(args)) {
          args = [args];
        }
        // If we leave it up to browserify to load named transform
        // modules, it will load them relative to the discovered
        // source, not relative to us. So we do it here instead.
        if (thing === 'transforms' && typeof(args[0]) === 'string') {
          args[0] = require(args[0]);
        }
        b = b[thing.replace(/s$/, '')].apply(b, args);
      });
    });
    b.add('./' + self.inFile);

    b.on('package', function(pkg){
      // browserify *used to* reliably put the package's directory in
      // pkg.__dirname. But as of browser-resolve 1.7.0 that isn't
      // true, and we sometimes get a value here like
      // '/path/to/your-module/index' instead of
      // '/path/to/your-module'. So we do our own checking.
      var pkgDir = pkg.__dirname;
      var stat;
      try {
        stat = fs.statSync(pkgDir);
      } catch(err) {
        if (err.code !== 'ENOENT') {
          throw err;
        }
      }
      if (!stat || !stat.isDirectory()) {
        pkgDir = path.dirname(pkgDir);
      }
      self._watchModules[pkgDir] = true;
    });

    b.on('dep', function (dep) {
      dep.source = derequire(dep.source);
      if (typeof dep.id === 'string') {
        self.cache[dep.id] = dep;

        // Even though we have a dep.source field available, we need
        // to reread the file as it appears on disk, because
        // dep.source is post-transformed.
        var filename = dep.file;
        if (filename.slice(0,1) === '.') {
          filename = path.join(self._inputStaging, filename);
        }
        dep.hash = helpers.hashStrings([fs.readFileSync(filename)]);
      }
    });

    if (opts.debug) {
      // Adds a semicolon to the end of the browserified code. This is required
      // to prevent unwanted interactions with other code when concatenated
      // See https://github.com/ef4/ember-browserify/issues/63
      // and https://github.com/substack/node-browserify/issues/806
      b.pipeline.get('wrap').push(through.obj(function (row, enc, next) {
        var contents = row.toString();
        if (contents[contents.length - 1] === ')') {
          contents += ';';
          this.push(contents);
        } else {
          this.push(row);
        }
        next();
      }));
    }

    return b;
  },

  updateCache: function(destDir) {
    var outPath = path.join(destDir, this.outputFile);
    var self = this;
    fs.mkdirSync(path.dirname(outPath));
    return new RSVP.Promise(function (resolve, reject) {
      self.bundler().bundle(function (err, data) {
        if (err) {
          reject(err);
        } else {
          fs.writeFileSync(outPath, data);
          resolve(destDir);
        }
      });
    });
  },

  watchNodeModules: function(readTree) {
    var self = this;
    return mapSeries(Object.keys(self._watchModules), function(dir){
      if (self.normalizePath(dir) !== self.normalizePath(self.root)){
        return readTree(dir);
      }
    });
  },

  // Changes path drive-letter to lowercase for Windows
  normalizePath: function(path) {
    return (path && path.match(/^[a-z]:\\/i)) ? path.charAt(0).toLowerCase() + path.slice(1) : path;
  },

  // Due to the way CJS dependencies work, the appearance of a new
  // file in our input trees doesn't invalidate our cache -- because
  // unless one of the existing files changes, nobody depends on the
  // new file.
  //
  // Therefore we can iterate through only the files we already have
  // in cache.
  checkCache: function(inDir) {
    var file, entry;
    var ids = Object.keys(this.cache);
    var cache = this.cache;
    var cacheValid = true;

    for (var i=0; i<ids.length; i++) {
      entry = cache[ids[i]];
      if (!entry.file) {
        console.log(JSON.stringify(entry, null, 2));
      }
      if (entry.file.slice(0,1) === '.') {
        file = path.join(inDir, entry.file);
      } else {
        file = entry.file;
      }
      if (hashFile(file) !== entry.hash) {
        delete cache[entry.id];
        cacheValid = false;
      }
    }
    return RSVP.resolve(cacheValid);
  }
});

function hashFile(file) {
  var buf;
  try {
    buf = fs.readFileSync(file);
    return helpers.hashStrings([buf]);
  } catch(err) {
    return null;
  }
}
<commit_msg>Prevent watching parent directories of project root<commit_after>var fs = require('fs');
var path = require('path');
var browserify = require('browserify');
var helpers = require('broccoli-kitchen-sink-helpers');
var RSVP = require('rsvp');
var CoreObject = require('core-object');
var mapSeries = require('promise-map-series');
var merge    = require('lodash').merge;
var rimraf = require('rimraf');
var symlinkOrCopy = require('symlink-or-copy');
var quickTemp = require('quick-temp');
var mkdirp = require('mkdirp');
var derequire = require('derequire');

var through = require('through2');

module.exports = CoreObject.extend({
  init: function(inputTree, options){
    if (!options) {
      options = {};
    }
    this.inputTree = inputTree;
    this.root = options.root;
    this.inFile = options.inFile || 'browserify_stubs.js';
    this.browserifyOptions = options.browserifyOptions;
    this.enableSourcemap = options.enableSourcemap;
    this.fullPaths = typeof options.fullPaths !== 'undefined' ? options.fullPaths : true;
    this.outputFile = options.outputFile || 'browserify/browserify.js';
    this.cache = {};
    quickTemp.makeOrRemake(this, '_inputStaging');
    quickTemp.makeOrRemake(this, '_destDir');
  },

  description: 'ember-browserify',

  cleanup: function() {
    if (this._destDir) {
      rimraf.sync(this._destDir);
    }
    if (this._inputStaging) {
      rimraf.sync(this._inputStaging);
    }
    if (this._outputCache) {
      rimraf.sync(this._outputCache);
    }
  },

  read: function (readTree) {
    var self = this;
    return readTree(this.inputTree).then(function(inDir){
      return self.checkCache(inDir).then(function(cacheValid){
        if (!self._outputCache || !cacheValid) {
          return self._rebuild(inDir);
        }
      });
    }).then(function(){
      rimraf.sync(self._destDir);
      symlinkOrCopy.sync(self._outputCache, self._destDir);
      return self.watchNodeModules(readTree).then(function(){
        return self._destDir;
      });
    });
  },

  _rebuild: function(inDir){
    var self = this;
    this._watchModules = Object.create(null);

    // _inputStaging needs to stay at the same path, because the
    // browserify bundler uses it as a fixed baseDir.
    rimraf.sync(this._inputStaging);
    mkdirp.sync(this._inputStaging);

    symlinkOrCopy.sync(path.join(inDir, this.inFile), path.join(this._inputStaging, this.inFile));
    quickTemp.makeOrRemake(this, '_outputCache');
    return this.updateCache(this._outputCache).catch(function(err){
      rimraf.sync(self._outputCache);
      delete self._outputCache;
      throw err;
    });
  },

  bundler: function() {
    if (!this._bundler) {
      this._bundler = this.makeBundler();
    }
    return this._bundler;
  },

  makeBundler: function() {
    var self = this;
    var opts = merge({
      cache: this.cache,
      packageCache: {},
      fullPaths: this.fullPaths,
      basedir: this._inputStaging,
      debug: this.enableSourcemap
    }, this.browserifyOptions);

    var b = browserify(opts);
    ['transforms', 'externals', 'ignores', 'includes'].forEach(function(thing) {
      if (!opts[thing]) { return; }
      opts[thing].forEach(function(args){
        if (!Array.isArray(args)) {
          args = [args];
        }
        // If we leave it up to browserify to load named transform
        // modules, it will load them relative to the discovered
        // source, not relative to us. So we do it here instead.
        if (thing === 'transforms' && typeof(args[0]) === 'string') {
          args[0] = require(args[0]);
        }
        b = b[thing.replace(/s$/, '')].apply(b, args);
      });
    });
    b.add('./' + self.inFile);

    b.on('package', function(pkg){
      // browserify *used to* reliably put the package's directory in
      // pkg.__dirname. But as of browser-resolve 1.7.0 that isn't
      // true, and we sometimes get a value here like
      // '/path/to/your-module/index' instead of
      // '/path/to/your-module'. So we do our own checking.
      var pkgDir = pkg.__dirname;
      var stat;
      try {
        stat = fs.statSync(pkgDir);
      } catch(err) {
        if (err.code !== 'ENOENT') {
          throw err;
        }
      }
      if (!stat || !stat.isDirectory()) {
        pkgDir = path.dirname(pkgDir);
      }
      self._watchModules[pkgDir] = true;
    });

    b.on('dep', function (dep) {
      dep.source = derequire(dep.source);
      if (typeof dep.id === 'string') {
        self.cache[dep.id] = dep;

        // Even though we have a dep.source field available, we need
        // to reread the file as it appears on disk, because
        // dep.source is post-transformed.
        var filename = dep.file;
        if (filename.slice(0,1) === '.') {
          filename = path.join(self._inputStaging, filename);
        }
        dep.hash = helpers.hashStrings([fs.readFileSync(filename)]);
      }
    });

    if (opts.debug) {
      // Adds a semicolon to the end of the browserified code. This is required
      // to prevent unwanted interactions with other code when concatenated
      // See https://github.com/ef4/ember-browserify/issues/63
      // and https://github.com/substack/node-browserify/issues/806
      b.pipeline.get('wrap').push(through.obj(function (row, enc, next) {
        var contents = row.toString();
        if (contents[contents.length - 1] === ')') {
          contents += ';';
          this.push(contents);
        } else {
          this.push(row);
        }
        next();
      }));
    }

    return b;
  },

  updateCache: function(destDir) {
    var outPath = path.join(destDir, this.outputFile);
    var self = this;
    fs.mkdirSync(path.dirname(outPath));
    return new RSVP.Promise(function (resolve, reject) {
      self.bundler().bundle(function (err, data) {
        if (err) {
          reject(err);
        } else {
          fs.writeFileSync(outPath, data);
          resolve(destDir);
        }
      });
    });
  },

  watchNodeModules: function(readTree) {
    var self = this;
    var root = self.normalizePath(self.root);

    return mapSeries(Object.keys(self._watchModules), function(dir){
      if (!root || root.indexOf(self.normalizePath(dir)) !== 0){
        return readTree(dir);
      }
    });
  },

  // Changes path drive-letter to lowercase for Windows
  normalizePath: function(path) {
    return (path && path.match(/^[a-z]:\\/i)) ? path.charAt(0).toLowerCase() + path.slice(1) : path;
  },

  // Due to the way CJS dependencies work, the appearance of a new
  // file in our input trees doesn't invalidate our cache -- because
  // unless one of the existing files changes, nobody depends on the
  // new file.
  //
  // Therefore we can iterate through only the files we already have
  // in cache.
  checkCache: function(inDir) {
    var file, entry;
    var ids = Object.keys(this.cache);
    var cache = this.cache;
    var cacheValid = true;

    for (var i=0; i<ids.length; i++) {
      entry = cache[ids[i]];
      if (!entry.file) {
        console.log(JSON.stringify(entry, null, 2));
      }
      if (entry.file.slice(0,1) === '.') {
        file = path.join(inDir, entry.file);
      } else {
        file = entry.file;
      }
      if (hashFile(file) !== entry.hash) {
        delete cache[entry.id];
        cacheValid = false;
      }
    }
    return RSVP.resolve(cacheValid);
  }
});

function hashFile(file) {
  var buf;
  try {
    buf = fs.readFileSync(file);
    return helpers.hashStrings([buf]);
  } catch(err) {
    return null;
  }
}
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react'
import serializeError from 'serialize-error'
import Button from '../../../../components/button'
import getStudentInfo, {checkIfLoggedIn} from '../../../../helpers/import-student'
import convertStudent from '../../../../helpers/convert-imported-student'
import StudentSummary from '../../../app_content_student/components/student-summary'
import map from 'lodash/map'
import groupBy from 'lodash/groupBy'
import sortBy from 'lodash/sortBy'
import semesterName from '../../../../helpers/semester-name'
import RadioGroup from 'react-radio-group'
import { push } from 'react-router-redux'
import { initStudent } from '../../../../redux/students/actions/init-student'
import { connect } from 'react-redux'

export default class SISImportScreen extends Component {
	static propTypes = {
		dispatch: PropTypes.func.isRequired, // redux
	};

	state = {
		loggedIn: null,
		checkingLogin: true,
		data: {},
		error: null,
		ids: [],
		selectedId: null,
	};

	componentWillMount() {
		this.checkLoginState()
	}

	checkLoginState = () => {
		checkIfLoggedIn()
			.then(ids => {
				if (ids.length === 1) {
					this.setState({loggedIn: true, checkingLogin: false, selectedId: ids[0]})
				}
				else {
					this.setState({loggedIn: true, checkingLogin: false, ids})
				}
			})
			.catch(() => this.setState({loggedIn: false, checkingLogin: false}))
	};

	handleImportData = id => {
		getStudentInfo(id)
			.then(data => {
				console.log(data)
				return data
			})
			.then(convertStudent)
			.then(student => this.setState({student}))
			.catch(err => {
				console.error(err)
				this.setState({error: serializeError(err)})
			})
	};

	handleCreateStudent = () => {
		let action = initStudent(this.state.student)
		this.props.dispatch(action)
		this.props.dispatch(push(`/s/${action.payload.id}`))
	};

	handleSelectId = value => {
		this.setState({selectedId: value})
		this.handleImportData(value)
	};

	render() {
		let {
			student,
			checkingLogin,
			loggedIn,
			error,
			ids,
		} = this.state

		return (

Import from the SIS

					Warning! Currently under development. Will most likely be broken.

					{checkingLogin
						? 'Checking login…'
						: loggedIn
							? "Great! You\'re logged in."
							: 'Not logged in. Please log in to the SIS in another tab.'}

				{error ?
{error.message}
 : null}

				{!loggedIn ? <Button disabled={checkingLogin} onClick={this.checkLoginState}>Check Again</Button> : null}

				{ids.length > 1 ?

Hang on one second… we found multiple student IDs. Which one is yours?

					<RadioGroup name='student-id' value={this.state.selectedId} onChange={this.handleSelectId}>
						{Radio =>

								{map(ids, id =>
									<Radio value={id} /> {id}
								)}

						}
					</RadioGroup>

 : null}

				{student
					?

						<StudentSummary student={student} showMessage={false} />

							{/*
2011:

Non-St. Olaf

{deptnum} – {title}

*/}
							{map(groupBy(student.schedules, 'year'), (schedules, year) =>

{year}:

										{map(sortBy(schedules, 'semester'), schedule =>

{semesterName(schedule.semester)}:

													{map(schedules.courses, course => {

{course.deptnum} – {course.name}

													})}

										)}

							)}

					: null}

					{loggedIn && student
						? <Button onClick={this.handleCreateStudent}>Import Student</Button>
						: <Button disabled={!loggedIn} onClick={this.handleImportData}>Fetch Student</Button>}

		)
	}
}

let mapDispatch = dispatch => ({dispatch})

export default connect(undefined, mapDispatch)(SISImportScreen)
<commit_msg>hide fetch student button when not logged in<commit_after>import React, {Component, PropTypes} from 'react'
import serializeError from 'serialize-error'
import Button from '../../../../components/button'
import getStudentInfo, {checkIfLoggedIn} from '../../../../helpers/import-student'
import convertStudent from '../../../../helpers/convert-imported-student'
import StudentSummary from '../../../app_content_student/components/student-summary'
import map from 'lodash/map'
import groupBy from 'lodash/groupBy'
import sortBy from 'lodash/sortBy'
import semesterName from '../../../../helpers/semester-name'
import RadioGroup from 'react-radio-group'
import { push } from 'react-router-redux'
import { initStudent } from '../../../../redux/students/actions/init-student'
import { connect } from 'react-redux'

export default class SISImportScreen extends Component {
	static propTypes = {
		dispatch: PropTypes.func.isRequired, // redux
	};

	state = {
		loggedIn: null,
		checkingLogin: true,
		data: {},
		error: null,
		ids: [],
		selectedId: null,
	};

	componentWillMount() {
		this.checkLoginState()
	}

	checkLoginState = () => {
		checkIfLoggedIn()
			.then(ids => {
				if (ids.length === 1) {
					this.setState({loggedIn: true, checkingLogin: false, selectedId: ids[0]})
				}
				else {
					this.setState({loggedIn: true, checkingLogin: false, ids})
				}
			})
			.catch(() => this.setState({loggedIn: false, checkingLogin: false}))
	};

	handleImportData = id => {
		getStudentInfo(id)
			.then(data => {
				console.log(data)
				return data
			})
			.then(convertStudent)
			.then(student => this.setState({student}))
			.catch(err => {
				console.error(err)
				this.setState({error: serializeError(err)})
			})
	};

	handleCreateStudent = () => {
		let action = initStudent(this.state.student)
		this.props.dispatch(action)
		this.props.dispatch(push(`/s/${action.payload.id}`))
	};

	handleSelectId = value => {
		this.setState({selectedId: value})
		this.handleImportData(value)
	};

	render() {
		let {
			student,
			checkingLogin,
			loggedIn,
			error,
			ids,
		} = this.state

		return (

Import from the SIS

					Warning! Currently under development. Will most likely be broken.

					{checkingLogin
						? 'Checking login…'
						: loggedIn
							? "Great! You\'re logged in."
							: 'Not logged in. Please log in to the SIS in another tab.'}

				{error ?
{error.message}
 : null}

				{!loggedIn ? <Button disabled={checkingLogin} onClick={this.checkLoginState}>Check Again</Button> : null}

				{ids.length > 1 ?

Hang on one second… we found multiple student IDs. Which one is yours?

					<RadioGroup name='student-id' value={this.state.selectedId} onChange={this.handleSelectId}>
						{Radio =>

								{map(ids, id =>
									<Radio value={id} /> {id}
								)}

						}
					</RadioGroup>

 : null}

				{student
					?

						<StudentSummary student={student} showMessage={false} />

							{/*
2011:

Non-St. Olaf

{deptnum} – {title}

*/}
							{map(groupBy(student.schedules, 'year'), (schedules, year) =>

{year}:

										{map(sortBy(schedules, 'semester'), schedule =>

{semesterName(schedule.semester)}:

													{map(schedules.courses, course => {

{course.deptnum} – {course.name}

													})}

										)}

							)}

					: null}

					{loggedIn ? student
						? <Button onClick={this.handleCreateStudent}>Import Student</Button>
						: <Button disabled={!loggedIn} onClick={this.handleImportData}>Fetch Student</Button>
					: null}

		)
	}
}

let mapDispatch = dispatch => ({dispatch})

export default connect(undefined, mapDispatch)(SISImportScreen)
<|endoftext|>


<commit_before>$(function() {
    function TemperatureViewModel(parameters) {
        var self = this;

        self.loginState = parameters[0];
        self.settingsViewModel = parameters[1];

        self._createToolEntry = function() {
            return {
                name: ko.observable(),
                key: ko.observable(),
                actual: ko.observable(0),
                target: ko.observable(0),
                offset: ko.observable(0),
                newTarget: ko.observable(),
                newOffset: ko.observable()
            }
        };

        self.tools = ko.observableArray([]);
        self.hasBed = ko.observable(true);
        self.bedTemp = self._createToolEntry();
        self.bedTemp["name"](gettext("Bed"));
        self.bedTemp["key"]("bed");

        self.isErrorOrClosed = ko.observable(undefined);
        self.isOperational = ko.observable(undefined);
        self.isPrinting = ko.observable(undefined);
        self.isPaused = ko.observable(undefined);
        self.isError = ko.observable(undefined);
        self.isReady = ko.observable(undefined);
        self.isLoading = ko.observable(undefined);

        self.temperature_profiles = self.settingsViewModel.temperature_profiles;
        self.temperature_cutoff = self.settingsViewModel.temperature_cutoff;

        self.heaterOptions = ko.observable({});

        self._printerProfileUpdated = function() {
            var graphColors = ["red", "orange", "green", "brown", "purple"];
            var heaterOptions = {};
            var tools = self.tools();

            // tools
            var numExtruders = self.settingsViewModel.printerProfiles.currentProfileData().extruder.count();
            if (numExtruders && numExtruders > 1) {
                // multiple extruders
                for (var extruder = 0; extruder < numExtruders; extruder++) {
                    var color = graphColors.shift();
                    if (!color) color = "black";
                    heaterOptions["tool" + extruder] = {name: "T" + extruder, color: color};

                    if (tools.length <= extruder || !tools[extruder]) {
                        tools[extruder] = self._createToolEntry();
                    }
                    tools[extruder]["name"](gettext("Tool") + " " + extruder);
                    tools[extruder]["key"]("tool" + extruder);
                }
            } else {
                // only one extruder, no need to add numbers
                var color = graphColors[0];
                heaterOptions["tool0"] = {name: "T", color: color};

                if (tools.length < 1 || !tools[0]) {
                    tools[0] = self._createToolEntry();
                }
                tools[0]["name"](gettext("Hotend"));
                tools[0]["key"]("tool0");
            }

            // print bed
            if (self.settingsViewModel.printerProfiles.currentProfileData().heatedBed()) {
                self.hasBed(true);
                heaterOptions["bed"] = {name: gettext("Bed"), color: "blue"};
            } else {
                self.hasBed(false);
            }

            // write back
            self.heaterOptions(heaterOptions);
            self.tools(tools);
        };
        self.settingsViewModel.printerProfiles.currentProfileData.subscribe(function() {
            self._printerProfileUpdated();
            self.settingsViewModel.printerProfiles.currentProfileData().extruder.count.subscribe(self._printerProfileUpdated);
            self.settingsViewModel.printerProfiles.currentProfileData().heatedBed.subscribe(self._printerProfileUpdated());
        });

        self.temperatures = [];
        self.plotOptions = {
            yaxis: {
                min: 0,
                max: 310,
                ticks: 10
            },
            xaxis: {
                mode: "time",
                minTickSize: [2, "minute"],
                tickFormatter: function(val, axis) {
                    if (val == undefined || val == 0)
                        return ""; // we don't want to display the minutes since the epoch if not connected yet ;)

                    // current time in milliseconds in UTC
                    var timestampUtc = Date.now();

                    // calculate difference in milliseconds
                    var diff = timestampUtc - val;

                    // convert to minutes
                    var diffInMins = Math.round(diff / (60 * 1000));
                    if (diffInMins == 0)
                        return gettext("just now");
                    else
                        return "- " + diffInMins + " " + gettext("min");
                }
            },
            legend: {
                position: "sw",
                noColumns: 2,
                backgroundOpacity: 0
            }
        };

        self.fromCurrentData = function(data) {
            self._processStateData(data.state);
            self._processTemperatureUpdateData(data.serverTime, data.temps);
            self._processOffsetData(data.offsets);
        };

        self.fromHistoryData = function(data) {
            self._processStateData(data.state);
            self._processTemperatureHistoryData(data.serverTime, data.temps);
            self._processOffsetData(data.offsets);
        };

        self._processStateData = function(data) {
            self.isErrorOrClosed(data.flags.closedOrError);
            self.isOperational(data.flags.operational);
            self.isPaused(data.flags.paused);
            self.isPrinting(data.flags.printing);
            self.isError(data.flags.error);
            self.isReady(data.flags.ready);
            self.isLoading(data.flags.loading);
        };

        self._processTemperatureUpdateData = function(serverTime, data) {
            if (data.length == 0)
                return;

            var lastData = data[data.length - 1];

            var tools = self.tools();
            for (var i = 0; i < tools.length; i++) {
                if (lastData.hasOwnProperty("tool" + i)) {
                    tools[i]["actual"](lastData["tool" + i].actual);
                    tools[i]["target"](lastData["tool" + i].target);
                }
            }

            if (lastData.hasOwnProperty("bed")) {
                self.bedTemp["actual"](lastData.bed.actual);
                self.bedTemp["target"](lastData.bed.target);
            }

            if (!CONFIG_TEMPERATURE_GRAPH) return;

            self.temperatures = self._processTemperatureData(serverTime, data, self.temperatures);
            self.updatePlot();
        };

        self._processTemperatureHistoryData = function(serverTime, data) {
            self.temperatures = self._processTemperatureData(serverTime, data);
            self.updatePlot();
        };

        self._processOffsetData = function(data) {
            var tools = self.tools();
            for (var i = 0; i < tools.length; i++) {
                if (data.hasOwnProperty("tool" + i)) {
                    tools[i]["offset"](data["tool" + i]);
                }
            }

            if (data.hasOwnProperty("bed")) {
                self.bedTemp["offset"](data["bed"]);
            }
        };

        self._processTemperatureData = function(serverTime, data, result) {
            var types = _.keys(self.heaterOptions());
            var clientTime = Date.now();

            // make sure result is properly initialized
            if (!result) {
                result = {};
            }

            _.each(types, function(type) {
                if (!result.hasOwnProperty(type)) {
                    result[type] = {actual: [], target: []};
                }
                if (!result[type].hasOwnProperty("actual")) result[type]["actual"] = [];
                if (!result[type].hasOwnProperty("target")) result[type]["target"] = [];
            });

            // convert data
            _.each(data, function(d) {
                var timeDiff = (serverTime - d.time) * 1000;
                var time = clientTime - timeDiff;
                _.each(types, function(type) {
                    if (!d[type]) return;
                    result[type].actual.push([time, d[type].actual]);
                    result[type].target.push([time, d[type].target]);
                })
            });

            var filterOld = function(item) {
                return item[0] >= clientTime - self.temperature_cutoff() * 60 * 1000;
            };

            _.each(_.keys(self.heaterOptions()), function(d) {
                result[d].actual = _.filter(result[d].actual, filterOld);
                result[d].target = _.filter(result[d].target, filterOld);
            });

            return result;
        };

        self.updatePlot = function() {
            var graph = $("#temperature-graph");
            if (graph.length) {
                var data = [];
                var heaterOptions = self.heaterOptions();
                if (!heaterOptions) return;

                _.each(_.keys(heaterOptions), function(type) {
                    if (type == "bed" && !self.hasBed()) {
                        return;
                    }

                    var actuals = [];
                    var targets = [];

                    if (self.temperatures[type]) {
                        actuals = self.temperatures[type].actual;
                        targets = self.temperatures[type].target;
                    }

                    var actualTemp = actuals && actuals.length ? formatTemperature(actuals[actuals.length - 1][1]) : "-";
                    var targetTemp = targets && targets.length ? formatTemperature(targets[targets.length - 1][1]) : "-";

                    data.push({
                        label: gettext("Actual") + " " + heaterOptions[type].name + ": " + actualTemp,
                        color: heaterOptions[type].color,
                        data: actuals
                    });
                    data.push({
                        label: gettext("Target") + " " + heaterOptions[type].name + ": " + targetTemp,
                        color: pusher.color(heaterOptions[type].color).tint(0.5).html(),
                        data: targets
                    });
                });

                $.plot(graph, data, self.plotOptions);
            }
        };

        self.setTarget = function(item) {
            var value = item.newTarget();
            if (!value) return;

            self._sendToolCommand("target",
                item.key(),
                item.newTarget(),
                function() {item.newTarget("");}
            );
        };

        self.setTargetFromProfile = function(item, profile) {
            if (!profile) return;

            var value = undefined;
            if (item.key() == "bed") {
                value = profile.bed;
            } else {
                value = profile.extruder;
            }

            self._sendToolCommand("target",
                item.key(),
                value,
                function() {item.newTarget("");}
            );
        };

        self.setTargetToZero = function(item) {
            self._sendToolCommand("target",
                item.key(),
                0,
                function() {item.newTarget("");}
            );
        };

        self.setOffset = function(item) {
            self._sendToolCommand("offset",
                item.key(),
                item.newOffset(),
                function() {item.newOffset("");}
            );
        };

        self._sendToolCommand = function(command, type, temp, successCb, errorCb) {
            var data = {
                command: command
            };

            var endpoint;
            if (type == "bed") {
                if ("target" == command) {
                    data["target"] = parseInt(temp);
                } else if ("offset" == command) {
                    data["offset"] = parseInt(temp);
                } else {
                    return;
                }

                endpoint = "bed";
            } else {
                var group;
                if ("target" == command) {
                    group = "targets";
                } else if ("offset" == command) {
                    group = "offsets";
                } else {
                    return;
                }
                data[group] = {};
                data[group][type] = parseInt(temp);

                endpoint = "tool";
            }

            $.ajax({
                url: API_BASEURL + "printer/" + endpoint,
                type: "POST",
                dataType: "json",
                contentType: "application/json; charset=UTF-8",
                data: JSON.stringify(data),
                success: function() { if (successCb !== undefined) successCb(); },
                error: function() { if (errorCb !== undefined) errorCb(); }
            });

        };

        self.handleEnter = function(event, type, item) {
            if (event.keyCode == 13) {
                if (type == "target") {
                    self.setTarget(item);
                } else if (type == "offset") {
                    self.setOffset(item);
                }
            }
        };

        self.onAfterTabChange = function(current, previous) {
            if (current != "#temp") {
                return;
            }
            self.updatePlot();
        }

    }

    OCTOPRINT_VIEWMODELS.push([
        TemperatureViewModel,
        ["loginStateViewModel", "settingsViewModel"],
        "#temp"
    ]);
});
<commit_msg>Ensure temperature graph to be properly initialized<commit_after>$(function() {
    function TemperatureViewModel(parameters) {
        var self = this;

        self.loginState = parameters[0];
        self.settingsViewModel = parameters[1];

        self._createToolEntry = function() {
            return {
                name: ko.observable(),
                key: ko.observable(),
                actual: ko.observable(0),
                target: ko.observable(0),
                offset: ko.observable(0),
                newTarget: ko.observable(),
                newOffset: ko.observable()
            }
        };

        self.tools = ko.observableArray([]);
        self.hasBed = ko.observable(true);
        self.bedTemp = self._createToolEntry();
        self.bedTemp["name"](gettext("Bed"));
        self.bedTemp["key"]("bed");

        self.isErrorOrClosed = ko.observable(undefined);
        self.isOperational = ko.observable(undefined);
        self.isPrinting = ko.observable(undefined);
        self.isPaused = ko.observable(undefined);
        self.isError = ko.observable(undefined);
        self.isReady = ko.observable(undefined);
        self.isLoading = ko.observable(undefined);

        self.temperature_profiles = self.settingsViewModel.temperature_profiles;
        self.temperature_cutoff = self.settingsViewModel.temperature_cutoff;

        self.heaterOptions = ko.observable({});

        self._printerProfileUpdated = function() {
            var graphColors = ["red", "orange", "green", "brown", "purple"];
            var heaterOptions = {};
            var tools = self.tools();
            var color;

            // tools
            var currentProfileData = self.settingsViewModel.printerProfiles.currentProfileData();
            var numExtruders = (currentProfileData ? currentProfileData.extruder.count() : 0);
            if (numExtruders && numExtruders > 1) {
                // multiple extruders
                for (var extruder = 0; extruder < numExtruders; extruder++) {
                    color = graphColors.shift();
                    if (!color) color = "black";
                    heaterOptions["tool" + extruder] = {name: "T" + extruder, color: color};

                    if (tools.length <= extruder || !tools[extruder]) {
                        tools[extruder] = self._createToolEntry();
                    }
                    tools[extruder]["name"](gettext("Tool") + " " + extruder);
                    tools[extruder]["key"]("tool" + extruder);
                }
            } else if (numExtruders == 1) {
                // only one extruder, no need to add numbers
                color = graphColors[0];
                heaterOptions["tool0"] = {name: "T", color: color};

                if (tools.length < 1 || !tools[0]) {
                    tools[0] = self._createToolEntry();
                }
                tools[0]["name"](gettext("Hotend"));
                tools[0]["key"]("tool0");
            }

            // print bed
            if (currentProfileData && currentProfileData.heatedBed()) {
                self.hasBed(true);
                heaterOptions["bed"] = {name: gettext("Bed"), color: "blue"};
            } else {
                self.hasBed(false);
            }

            // write back
            self.heaterOptions(heaterOptions);
            self.tools(tools);
            self.updatePlot();
        };
        self.settingsViewModel.printerProfiles.currentProfileData.subscribe(function() {
            self._printerProfileUpdated();
            self.settingsViewModel.printerProfiles.currentProfileData().extruder.count.subscribe(self._printerProfileUpdated);
            self.settingsViewModel.printerProfiles.currentProfileData().heatedBed.subscribe(self._printerProfileUpdated);
        });

        self.temperatures = [];
        self.plotOptions = {
            yaxis: {
                min: 0,
                max: 310,
                ticks: 10
            },
            xaxis: {
                mode: "time",
                minTickSize: [2, "minute"],
                tickFormatter: function(val, axis) {
                    if (val == undefined || val == 0)
                        return ""; // we don't want to display the minutes since the epoch if not connected yet ;)

                    // current time in milliseconds in UTC
                    var timestampUtc = Date.now();

                    // calculate difference in milliseconds
                    var diff = timestampUtc - val;

                    // convert to minutes
                    var diffInMins = Math.round(diff / (60 * 1000));
                    if (diffInMins == 0)
                        return gettext("just now");
                    else
                        return "- " + diffInMins + " " + gettext("min");
                }
            },
            legend: {
                position: "sw",
                noColumns: 2,
                backgroundOpacity: 0
            }
        };

        self.fromCurrentData = function(data) {
            self._processStateData(data.state);
            self._processTemperatureUpdateData(data.serverTime, data.temps);
            self._processOffsetData(data.offsets);
        };

        self.fromHistoryData = function(data) {
            self._processStateData(data.state);
            self._processTemperatureHistoryData(data.serverTime, data.temps);
            self._processOffsetData(data.offsets);
        };

        self._processStateData = function(data) {
            self.isErrorOrClosed(data.flags.closedOrError);
            self.isOperational(data.flags.operational);
            self.isPaused(data.flags.paused);
            self.isPrinting(data.flags.printing);
            self.isError(data.flags.error);
            self.isReady(data.flags.ready);
            self.isLoading(data.flags.loading);
        };

        self._processTemperatureUpdateData = function(serverTime, data) {
            if (data.length == 0)
                return;

            var lastData = data[data.length - 1];

            var tools = self.tools();
            for (var i = 0; i < tools.length; i++) {
                if (lastData.hasOwnProperty("tool" + i)) {
                    tools[i]["actual"](lastData["tool" + i].actual);
                    tools[i]["target"](lastData["tool" + i].target);
                }
            }

            if (lastData.hasOwnProperty("bed")) {
                self.bedTemp["actual"](lastData.bed.actual);
                self.bedTemp["target"](lastData.bed.target);
            }

            if (!CONFIG_TEMPERATURE_GRAPH) return;

            self.temperatures = self._processTemperatureData(serverTime, data, self.temperatures);
            self.updatePlot();
        };

        self._processTemperatureHistoryData = function(serverTime, data) {
            self.temperatures = self._processTemperatureData(serverTime, data);
            self.updatePlot();
        };

        self._processOffsetData = function(data) {
            var tools = self.tools();
            for (var i = 0; i < tools.length; i++) {
                if (data.hasOwnProperty("tool" + i)) {
                    tools[i]["offset"](data["tool" + i]);
                }
            }

            if (data.hasOwnProperty("bed")) {
                self.bedTemp["offset"](data["bed"]);
            }
        };

        self._processTemperatureData = function(serverTime, data, result) {
            var types = _.keys(self.heaterOptions());
            var clientTime = Date.now();

            // make sure result is properly initialized
            if (!result) {
                result = {};
            }

            _.each(types, function(type) {
                if (!result.hasOwnProperty(type)) {
                    result[type] = {actual: [], target: []};
                }
                if (!result[type].hasOwnProperty("actual")) result[type]["actual"] = [];
                if (!result[type].hasOwnProperty("target")) result[type]["target"] = [];
            });

            // convert data
            _.each(data, function(d) {
                var timeDiff = (serverTime - d.time) * 1000;
                var time = clientTime - timeDiff;
                _.each(types, function(type) {
                    if (!d[type]) return;
                    result[type].actual.push([time, d[type].actual]);
                    result[type].target.push([time, d[type].target]);
                })
            });

            var filterOld = function(item) {
                return item[0] >= clientTime - self.temperature_cutoff() * 60 * 1000;
            };

            _.each(_.keys(self.heaterOptions()), function(d) {
                result[d].actual = _.filter(result[d].actual, filterOld);
                result[d].target = _.filter(result[d].target, filterOld);
            });

            return result;
        };

        self.updatePlot = function() {
            var graph = $("#temperature-graph");
            if (graph.length) {
                var data = [];
                var heaterOptions = self.heaterOptions();
                if (!heaterOptions) return;

                _.each(_.keys(heaterOptions), function(type) {
                    if (type == "bed" && !self.hasBed()) {
                        return;
                    }

                    var actuals = [];
                    var targets = [];

                    if (self.temperatures[type]) {
                        actuals = self.temperatures[type].actual;
                        targets = self.temperatures[type].target;
                    }

                    var actualTemp = actuals && actuals.length ? formatTemperature(actuals[actuals.length - 1][1]) : "-";
                    var targetTemp = targets && targets.length ? formatTemperature(targets[targets.length - 1][1]) : "-";

                    data.push({
                        label: gettext("Actual") + " " + heaterOptions[type].name + ": " + actualTemp,
                        color: heaterOptions[type].color,
                        data: actuals
                    });
                    data.push({
                        label: gettext("Target") + " " + heaterOptions[type].name + ": " + targetTemp,
                        color: pusher.color(heaterOptions[type].color).tint(0.5).html(),
                        data: targets
                    });
                });

                $.plot(graph, data, self.plotOptions);
            }
        };

        self.setTarget = function(item) {
            var value = item.newTarget();
            if (!value) return;

            self._sendToolCommand("target",
                item.key(),
                item.newTarget(),
                function() {item.newTarget("");}
            );
        };

        self.setTargetFromProfile = function(item, profile) {
            if (!profile) return;

            var value = undefined;
            if (item.key() == "bed") {
                value = profile.bed;
            } else {
                value = profile.extruder;
            }

            self._sendToolCommand("target",
                item.key(),
                value,
                function() {item.newTarget("");}
            );
        };

        self.setTargetToZero = function(item) {
            self._sendToolCommand("target",
                item.key(),
                0,
                function() {item.newTarget("");}
            );
        };

        self.setOffset = function(item) {
            self._sendToolCommand("offset",
                item.key(),
                item.newOffset(),
                function() {item.newOffset("");}
            );
        };

        self._sendToolCommand = function(command, type, temp, successCb, errorCb) {
            var data = {
                command: command
            };

            var endpoint;
            if (type == "bed") {
                if ("target" == command) {
                    data["target"] = parseInt(temp);
                } else if ("offset" == command) {
                    data["offset"] = parseInt(temp);
                } else {
                    return;
                }

                endpoint = "bed";
            } else {
                var group;
                if ("target" == command) {
                    group = "targets";
                } else if ("offset" == command) {
                    group = "offsets";
                } else {
                    return;
                }
                data[group] = {};
                data[group][type] = parseInt(temp);

                endpoint = "tool";
            }

            $.ajax({
                url: API_BASEURL + "printer/" + endpoint,
                type: "POST",
                dataType: "json",
                contentType: "application/json; charset=UTF-8",
                data: JSON.stringify(data),
                success: function() { if (successCb !== undefined) successCb(); },
                error: function() { if (errorCb !== undefined) errorCb(); }
            });

        };

        self.handleEnter = function(event, type, item) {
            if (event.keyCode == 13) {
                if (type == "target") {
                    self.setTarget(item);
                } else if (type == "offset") {
                    self.setOffset(item);
                }
            }
        };

        self.onAfterTabChange = function(current, previous) {
            if (current != "#temp") {
                return;
            }
            self.updatePlot();
        };

        self.onStartupComplete = function() {
            self._printerProfileUpdated();
        };

    }

    OCTOPRINT_VIEWMODELS.push([
        TemperatureViewModel,
        ["loginStateViewModel", "settingsViewModel"],
        "#temp"
    ]);
});
<|endoftext|>


<commit_before>'use babel';
import fs from 'fs';
import path from 'path';
import { CompositeDisposable } from 'atom';
import mkdirp from 'mkdirp';
import coffee from 'coffee-script';
import uglifyJS from 'uglify-js';

export var config = {
  compileIfNoOutSpecified: {
    description: 'compile coffeescript files where no "out" parameter is specified at the top of the file',
    type: 'boolean',
    default: false,
  },
};

var disposables;

export function activate() {
  disposables = new CompositeDisposable(
    atom.commands.add('atom-text-editor', 'core:save', event => handleSave(event.currentTarget.getModel())),
  );
}

export function deactivate() {
  disposables.dispose();
  disposables = null;
}

const coffeeScriptGrammar = atom.grammars.grammarForScopeName('source.coffee');

function handleSave(textEditor) {
  if(textEditor.getGrammar() == coffeeScriptGrammar) {
    compile(textEditor);
  }
}

function compile(textEditor) {
  var source = textEditor.getText();

  try {
    // match the first comment in the file while ignoring
    // the hashbang if the file has one
    var [ , paramString ] = source.match(/^(?:#![^\n]*\s*)?#\s*((?:[^\n:]+:[^\n,]+\s*,?\s*)+)\n/);
    var params = parseParams(paramString);
  } catch(e) {
    if(!atom.config.get('coffee-autocompile.compileIfNoOutSpecified')) return;
    params = {};
  }

  params.inPath = textEditor.getURI();
  params.outPath = params.out ?
    path.resolve(path.dirname(params.inPath), params.out) :
    params.inPath.replace(/\.coffee$/, '.js');

  render(source, params);
}

function render(source, params) {
  var renderer = coffee.compile(source, {
    sourceMap: !!params.sourcemap,
  });
  var js;
  var sourcemap;

  if(params.sourcemap) {
    ({ js, sourcemap } = postRenderWithSourcemap(renderer, params));
    writeFile(`${params.outPath}.map`, sourcemap);
  } else {
    js = postRenderNoSourcemap(renderer, params);
  }

  writeFile(params.outPath, js);
}

function postRenderWithSourcemap(renderer, params) {
  var { js, v3SourceMap: sourcemap } = renderer;
  var outSourcemapPath = `${path.basename(params.outPath)}.map`;

  if(params.compress) {
    ({ code: js, map: sourcemap } = uglifyJS.minify(js, {
      fromString: true,
      inSourceMap: JSON.parse(sourcemap),
      outSourceMap: outSourcemapPath,
    }));
  } else {
    js += `\n//# sourceMappingURL=${outSourcemapPath}`;
  }

  return { js, sourcemap };
}

function postRenderNoSourcemap(renderer, params) {
  var js = renderer;

  if(params.compress) {
    ({ code: js } = uglifyJS.minify(js, {
      fromString: true,
    }));
  }

  return js;
}

function writeFile(filePath, content) {
  mkdirp(path.dirname(filePath), error => {
    if(error) return atom.notifications.addError(error.message);

    fs.writeFile(filePath, content, error => {
      if(error) return atom.notifications.addError(error.message);

      atom.notifications.addSuccess(`Saved to ${filePath}`);
    });
  });
}

function parseParams(string) {
  var params = {};
  for(let pair of string.split(',')) {
    pair = pair.trim();
    let [ key, value ] = pair.split(/\s*:\s*/);
    if(['true', 'yes', '1'].indexOf(value) > -1) value = true;
    else if(['false', 'no', '0'].indexOf(value) > -1) value = false;
    params[key] = value;
  }
  return params;
}
<commit_msg>better parameter parsing<commit_after>'use babel';
import fs from 'fs';
import path from 'path';
import { CompositeDisposable } from 'atom';
import mkdirp from 'mkdirp';
import coffee from 'coffee-script';
import uglifyJS from 'uglify-js';

export var config = {
  compileIfNoOutSpecified: {
    description: 'compile coffeescript files where no "out" parameter is specified at the top of the file',
    type: 'boolean',
    default: false,
  },
};

var disposables;

export function activate() {
  disposables = new CompositeDisposable(
    atom.commands.add('atom-text-editor', 'core:save', event => handleSave(event.currentTarget.getModel())),
  );
}

export function deactivate() {
  disposables.dispose();
  disposables = null;
}

const coffeeScriptGrammar = atom.grammars.grammarForScopeName('source.coffee');

function handleSave(textEditor) {
  if(textEditor.getGrammar() == coffeeScriptGrammar) {
    compile(textEditor);
  }
}

function compile(textEditor) {
  var params = getParams(textEditor);
  if(!params.out && !atom.config.get('coffee-autocompile.compileIfNoOutSpecified'))
    return;
  params.inPath = textEditor.getURI();
  params.outPath = params.out ?
    path.resolve(path.dirname(params.inPath), params.out) :
    params.inPath.replace(/\.coffee$/, '.js');
  render(textEditor.getText(), params);
}

function render(source, params) {
  var renderer = coffee.compile(source, {
    sourceMap: !!params.sourcemap,
  });
  var js;
  var sourcemap;

  if(params.sourcemap) {
    ({js, sourcemap} = postRenderWithSourcemap(renderer, params));
    writeFile(`${params.outPath}.map`, sourcemap);
  } else {
    js = postRenderNoSourcemap(renderer, params);
  }

  writeFile(params.outPath, js);
}

function postRenderWithSourcemap(renderer, {outPath, compress}) {
  var {js, v3SourceMap: sourcemap} = renderer;
  var outSourcemapPath = `${path.basename(outPath)}.map`;

  if(compress) {
    ({code: js, map: sourcemap} = uglifyJS.minify(js, {
      fromString: true,
      inSourceMap: JSON.parse(sourcemap),
      outSourceMap: outSourcemapPath,
    }));
  } else {
    js += `\n//# sourceMappingURL=${outSourcemapPath}`;
  }
  return {js, sourcemap};
}

function postRenderNoSourcemap(js, {compress}) {
  return !compress ? js : uglifyJS.minify(js, {
    fromString: true,
  }).code;
}

function writeFile(filePath, content) {
  mkdirp(path.dirname(filePath), error => {
    if(error) return atom.notifications.addError(error.message);

    fs.writeFile(filePath, content, error => {
      if(error) return atom.notifications.addError(error.message);

      atom.notifications.addSuccess(`Saved to ${filePath}`);
    });
  });
}

function getParams(textEditor) {
  var firstLine = textEditor.lineTextForBufferRow(0);
  if(firstLine.match(/^#!/)) firstLine = textEditor.lineTextForBufferRow(1);

  var [, paramString] = firstLine.match(/\s*#\s*(.*)/);
  if(!paramString) return {};
  paramString = paramString.trim();
  var out = getParam(paramString, 'out');
  var compress = getParam(paramString, 'compress', true);
  var sourcemap = getParam(paramString, 'sourcemap', true);
  return {out, compress, sourcemap};
}

function getParam(paramString, key, isBool = false) {
  var regex = new RegExp(`${key}\s*:\s*([^,]+)`);
  var [, value] = paramString.match(regex) || [];
  if(!value) return '';
  value = value.trim();
  if(isBool) value = parseBool(value);
  return value;
}

function parseBool(value) {
  return value == 'false' ? false : !!value;
}
<|endoftext|>


<commit_before>Lottos = new Mongo.Collection('lottos');

Meteor.methods({
  lottoInsert: function (data, autofill) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(data, {
      date: Date
    });
    check(autofill, Boolean);

    var lottoWithSameDate = Lottos.findOne({date: data.date});
    if (lottoWithSameDate) {
      return {
        lottoExists: true,
        _id: lottoWithSameDate._id
      };
    }

    var user = Meteor.user();
    var lotto = _.extend(data, {
      userId: user._id,
      smallpot: 0,
      bigpot: 0,
      entries: [],
      winners: [],
      smallpot_winner: null,
      bigpot_winner: null,
      created: new Date(),
      type: 'double'
    });

    var lastLotto;
    if (autofill) {
      lastLotto = Lottos.findOne({}, {fields: {_id: 1}, sort: {date: -1}});
    }

    var lottoId = Lottos.insert(lotto);

    if (lastLotto && lastLotto._id) {
      Meteor.call('duplicateTiers', lottoId, lastLotto._id);
    } else {
      Meteor.call('createTiers', lottoId);
    }

    return { _id: lottoId };
  },
  lottoPublicToggle:function(lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    //TODO: maybe find a way to make these 2 operations atomic
    //MongoDB has xor (https://jira.mongodb.org/browse/SERVER-4362)
    //but minimongo doesn't support it (https://github.com/meteor/meteor/blob/master/packages/minimongo/modify.js#L410)
    var lotto = Lottos.findOne(lottoId);
    var updated = Lottos.update(lottoId, {
      $set: { public: !lotto.public}
    });
    return {updated: updated};
  },
  lottoToggleClose: function(lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    var lotto = Lottos.findOne(lottoId);
    var updated = Lottos.update(lottoId, {
      $set: { closed: !lotto.closed}
    });
    return {updated: updated};
  },
  entryAdd: function (entryAttributes, lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(entryAttributes, {
      gwuserId: String,
      amount: Number
    });

    var errors = validateEntry(entryAttributes);
    if (errors.gwuserId || errors.amount) {
      throw new Meteor.Error('invalid-entry', 'You must select a user');
    }

    var gwuser = GWUsers.findOne(entryAttributes.gwuserId);
    if (!gwuser) {
      throw new Meteor.Error('invalid-entry', 'User not found');
    }

    var lotto = Lottos.findOne(lottoId);
    if (lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    var entry = {
      gwuserId: replaceDots(entryAttributes.gwuserId),
      amount: entryAttributes.gwuserId,
      _id: Random.id(),
    };

    var allEntries = _.union(lotto.entries, entry);
    var pots = updatePots(allEntries, lotto.type);

    Lottos.update(lottoId, {
      $set: {
        smallpot: pots.smallpot_total,
        bigpot: pots.bigpot_total,
        smallpot_entries: pots.smallpot_entries,
        bigpot_entries: pots.bigpot_entries
      },
      $addToSet: {entries: entry}
    });

    return {_id: entry._id};
  },
  entryRemove: function (entryId, lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(entryId, String);

    var lotto = Lottos.findOne(lottoId);
    if (lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    var entries = _.filter(lotto.entries, function(e) { return e._id !== entryId;});
    var pots = updatePots(entries, lotto.type);

    Lottos.update(lottoId, {
      $set: {
        smallpot: pots.smallpot_total,
        bigpot: pots.bigpot_total,
        smallpot_entries: pots.smallpot_entries,
        bigpot_entries: pots.bigpot_entries
      },
      $pull: {entries: {_id: entryId}}
    });

    return {};
  },
  entriesAdd: function(lottoId, newEntries) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(newEntries, [{
      gwuserId: String,
      amount: Number
    }]);

    const lotto = Lottos.findOne(lottoId);
    if (lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    const entries = newEntries.map((e) => ({
      amount: e.amount,
      gwuserId: replaceDots(e.gwuserId),
      _id: Random.id(),
    }));

    const allEntries = _.union(lotto.entries, entries);
    const pots = updatePots(allEntries, lotto.type);

    const updated = Lottos.update(lottoId, {
      $set: {
        smallpot: pots.smallpot_total,
        bigpot: pots.bigpot_total,
        smallpot_entries: pots.smallpot_entries,
        bigpot_entries: pots.bigpot_entries
      },
      $addToSet: {entries: {$each: entries}}
    });

    return {updated: updated};
  },
  rollForTier: function(lottoId, tier) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(tier, Number);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto || !lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is not closed');
    }
    var entries = _.filter(lotto.entries, function (x) {
      return x.amount === tier;
    });

    if (_.any(entries, function(entry) {return !!entry.winner;})) {
      throw new Meteor.Error('invalid-entry', 'Tier has a winner');
    }

    if (Meteor.isClient) {
      return {};
    }

    var gamblerFactory = require('gambler');

    // Roll for winner
    var winnerEntry = null;
    if (entries.length === 0) {
      return {};
    } else if (entries.length === 1) {
      winnerEntry = entries[0];
    } else {
      var gambler = gamblerFactory(entries);
      winnerEntry = gambler.drawCard();
    }

    // Roll for prize
    var prize = null;
    var tierObj = Tiers.findOne({lottoId: lottoId, tier: tier});
    if (tierObj.prizes.length === 0) {
      return {};
    } else if (tierObj.prizes.length === 1) {
      prize = tierObj.prizes[0];
    } else {
      var prizeGambler = gamblerFactory(tierObj.prizes);
      prize = prizeGambler.drawCard();
    }

    Tiers.update({_id: tierObj._id, prizes: prize}, {
      $set: {'prizes.$.winner': true}
    });

    Lottos.update({_id: lottoId, entries: winnerEntry}, {
      $set: {'entries.$.winner': true}
    });
    return {winnerId: winnerEntry._id, prize: prize.pos};
  },
  rollForPot: function(lottoId, pot) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(pot, String);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto || !lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is not closed');
    }

    if (Meteor.isClient) {
      return {};
    }

    var entries = null;
    var field = null;
    if (pot === 'small') {
      entries = lotto.smallpot_entries;
      field = 'smallpot_winner';
    } else {
      entries = lotto.bigpot_entries;
      field = 'bigpot_winner';
    }

    if (!!lotto[field]) {
      throw new Meteor.Error('invalid-entry', 'Pot has a winner');
    }

    entries = _.flatten(_.map(_.keys(entries), function(e) {a=[];for(var i=0,l=entries[e];i<l;++i){a.push({id: e})}; return a;}));

    var gamblerFactory = require('gambler');

    // Roll for pot winner
    var winner = null;
    if (entries.length === 0) {
      return {};
    } else if (entries.length === 1) {
      winner = entries[0];
    } else {
      var gambler = gamblerFactory(entries);
      winner = gambler.drawCard();
    }

    var update = {};
    update[field] = winner.id;
    Lottos.update(lottoId, {
      $set: update
    });

    return {id: winner.id};
  },
  unrollForPot: function(lottoId, pot) {
    if (!isSuperAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(pot, String);

    var field = null;
    if (pot === 'small') {
      field = 'smallpot_winner';
    } else {
      field = 'bigpot_winner';
    }

    var update = {};
    update[field] = null;

    Lottos.update(lottoId, {
      $set: update
    });
  },
  unrollForTier: function(lottoId, tier) {
    if (!isSuperAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(tier, Number);
    l = Lottos.findOne(lottoId);
    t = Tiers.findOne({lottoId: l._id, tier: tier});
    p = _.find(t.prizes, function(p) {return p.winner === true});
    e = _.find(l.entries, function(e) {return e.amount === tier && e.winner === true});

    Tiers.update({_id: t._id, prizes: p}, {$unset: {'prizes.$.winner': true}});
    Lottos.update({_id: l._id, entries: e}, {$unset: {'entries.$.winner': true}});
  },

  populateItems: function(lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto || lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    if (Meteor.isClient) {
      return {};
    }

    // Populate each tier prize with the items from the API
    Meteor.call('getItemsFromAPI', function(error, items) {
      if (error) {
        throw new Meteor.Error("error", error.reason);
      }

      var tiers = Tiers.find({lottoId: lottoId}).fetch();

      var n = 0;
      _.each(tiers, (tier) => {

        var prizes = [];
        var prizesByPos = _.indexBy(tier.prizes, 'pos');

        _.each(items.slice(n, n + 10), (item, i) => {

          if (item == null) return;

          var prize = prizesByPos[i+1];
          if (prize == null) {
            prize = {_id: Random.id(), pos: i+1};
          }

          prizes.push(_.extend({}, prize, _.pick(item, 'id', 'icon', 'name', 'rarity', 'count') ));
        });

        n += 10;
        Tiers.update({_id: tier._id}, {$set: {prizes: prizes}});
      });
    });
  },

  deleteLotto: function(lottoId) {
    if (!isSuperAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto) {
      throw new Meteor.Error('invalid-entry', "Lotto doesn't exist");
    }

    Lottos.remove({_id: lottoId});
    Tiers.remove({lottoId: lottoId});
  }
});

validateEntry = function (entry) {
  var errors = {};
  if (!entry.gwuserId) {
    errors.gwuserId = 'Please select a user';
  }
  if (!entry.amount) {
    errors.amount = 'Please select an amount';
  }
  if (entry.amount < 1 || entry.amount > 20) {
    errors.amount = 'Wrong amount';
  }
  return errors;
};

// Returns two lists of players belonging to each pot and their totals
updatePots = function (entries, type) {
  var smallpot = {};
  var bigpot = {};

  // an entry with more than 10g goes to the bigpot
  // a total of 20g or more to the bigpot
  _.each(entries, function (entry) {
    var total = (smallpot[entry.gwuserId] || 0) + (bigpot[entry.gwuserId] || 0) + entry.amount;
    if ((type === 'double' && total >= 20) || (type !== 'double' && (entry.amount > 10 || total >= 20))) {
      delete smallpot[entry.gwuserId];
      bigpot[entry.gwuserId] = total;
    } else {
      smallpot[entry.gwuserId] = total;
    }
  });
  var sum = function(m, e) {return m + e;};
  return {
    smallpot_entries: smallpot,
    smallpot_total: _.reduce(smallpot, sum, 0),
    bigpot_entries: bigpot,
    bigpot_total: _.reduce(bigpot, sum, 0)
  };
};
<commit_msg>Fixed bug in entryAdd method<commit_after>Lottos = new Mongo.Collection('lottos');

Meteor.methods({
  lottoInsert: function (data, autofill) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(data, {
      date: Date
    });
    check(autofill, Boolean);

    var lottoWithSameDate = Lottos.findOne({date: data.date});
    if (lottoWithSameDate) {
      return {
        lottoExists: true,
        _id: lottoWithSameDate._id
      };
    }

    var user = Meteor.user();
    var lotto = _.extend(data, {
      userId: user._id,
      smallpot: 0,
      bigpot: 0,
      entries: [],
      winners: [],
      smallpot_winner: null,
      bigpot_winner: null,
      created: new Date(),
      type: 'double'
    });

    var lastLotto;
    if (autofill) {
      lastLotto = Lottos.findOne({}, {fields: {_id: 1}, sort: {date: -1}});
    }

    var lottoId = Lottos.insert(lotto);

    if (lastLotto && lastLotto._id) {
      Meteor.call('duplicateTiers', lottoId, lastLotto._id);
    } else {
      Meteor.call('createTiers', lottoId);
    }

    return { _id: lottoId };
  },
  lottoPublicToggle:function(lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    //TODO: maybe find a way to make these 2 operations atomic
    //MongoDB has xor (https://jira.mongodb.org/browse/SERVER-4362)
    //but minimongo doesn't support it (https://github.com/meteor/meteor/blob/master/packages/minimongo/modify.js#L410)
    var lotto = Lottos.findOne(lottoId);
    var updated = Lottos.update(lottoId, {
      $set: { public: !lotto.public}
    });
    return {updated: updated};
  },
  lottoToggleClose: function(lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    var lotto = Lottos.findOne(lottoId);
    var updated = Lottos.update(lottoId, {
      $set: { closed: !lotto.closed}
    });
    return {updated: updated};
  },
  entryAdd: function (entryAttributes, lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(entryAttributes, {
      gwuserId: String,
      amount: Number
    });

    var errors = validateEntry(entryAttributes);
    if (errors.gwuserId || errors.amount) {
      throw new Meteor.Error('invalid-entry', 'You must select a user');
    }

    var gwuser = GWUsers.findOne(entryAttributes.gwuserId);
    if (!gwuser) {
      throw new Meteor.Error('invalid-entry', 'User not found');
    }

    var lotto = Lottos.findOne(lottoId);
    if (lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    var entry = {
      gwuserId: replaceDots(entryAttributes.gwuserId),
      amount: entryAttributes.amount,
      _id: Random.id(),
    };

    var allEntries = _.union(lotto.entries, entry);
    var pots = updatePots(allEntries, lotto.type);

    Lottos.update(lottoId, {
      $set: {
        smallpot: pots.smallpot_total,
        bigpot: pots.bigpot_total,
        smallpot_entries: pots.smallpot_entries,
        bigpot_entries: pots.bigpot_entries
      },
      $addToSet: {entries: entry}
    });

    return {_id: entry._id};
  },
  entryRemove: function (entryId, lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(entryId, String);

    var lotto = Lottos.findOne(lottoId);
    if (lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    var entries = _.filter(lotto.entries, function(e) { return e._id !== entryId;});
    var pots = updatePots(entries, lotto.type);

    Lottos.update(lottoId, {
      $set: {
        smallpot: pots.smallpot_total,
        bigpot: pots.bigpot_total,
        smallpot_entries: pots.smallpot_entries,
        bigpot_entries: pots.bigpot_entries
      },
      $pull: {entries: {_id: entryId}}
    });

    return {};
  },
  entriesAdd: function(lottoId, newEntries) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(newEntries, [{
      gwuserId: String,
      amount: Number
    }]);

    const lotto = Lottos.findOne(lottoId);
    if (lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    const entries = newEntries.map((e) => ({
      amount: e.amount,
      gwuserId: replaceDots(e.gwuserId),
      _id: Random.id(),
    }));

    const allEntries = _.union(lotto.entries, entries);
    const pots = updatePots(allEntries, lotto.type);

    const updated = Lottos.update(lottoId, {
      $set: {
        smallpot: pots.smallpot_total,
        bigpot: pots.bigpot_total,
        smallpot_entries: pots.smallpot_entries,
        bigpot_entries: pots.bigpot_entries
      },
      $addToSet: {entries: {$each: entries}}
    });

    return {updated: updated};
  },
  rollForTier: function(lottoId, tier) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(tier, Number);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto || !lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is not closed');
    }
    var entries = _.filter(lotto.entries, function (x) {
      return x.amount === tier;
    });

    if (_.any(entries, function(entry) {return !!entry.winner;})) {
      throw new Meteor.Error('invalid-entry', 'Tier has a winner');
    }

    if (Meteor.isClient) {
      return {};
    }

    var gamblerFactory = require('gambler');

    // Roll for winner
    var winnerEntry = null;
    if (entries.length === 0) {
      return {};
    } else if (entries.length === 1) {
      winnerEntry = entries[0];
    } else {
      var gambler = gamblerFactory(entries);
      winnerEntry = gambler.drawCard();
    }

    // Roll for prize
    var prize = null;
    var tierObj = Tiers.findOne({lottoId: lottoId, tier: tier});
    if (tierObj.prizes.length === 0) {
      return {};
    } else if (tierObj.prizes.length === 1) {
      prize = tierObj.prizes[0];
    } else {
      var prizeGambler = gamblerFactory(tierObj.prizes);
      prize = prizeGambler.drawCard();
    }

    Tiers.update({_id: tierObj._id, prizes: prize}, {
      $set: {'prizes.$.winner': true}
    });

    Lottos.update({_id: lottoId, entries: winnerEntry}, {
      $set: {'entries.$.winner': true}
    });
    return {winnerId: winnerEntry._id, prize: prize.pos};
  },
  rollForPot: function(lottoId, pot) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(pot, String);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto || !lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is not closed');
    }

    if (Meteor.isClient) {
      return {};
    }

    var entries = null;
    var field = null;
    if (pot === 'small') {
      entries = lotto.smallpot_entries;
      field = 'smallpot_winner';
    } else {
      entries = lotto.bigpot_entries;
      field = 'bigpot_winner';
    }

    if (!!lotto[field]) {
      throw new Meteor.Error('invalid-entry', 'Pot has a winner');
    }

    entries = _.flatten(_.map(_.keys(entries), function(e) {a=[];for(var i=0,l=entries[e];i<l;++i){a.push({id: e})}; return a;}));

    var gamblerFactory = require('gambler');

    // Roll for pot winner
    var winner = null;
    if (entries.length === 0) {
      return {};
    } else if (entries.length === 1) {
      winner = entries[0];
    } else {
      var gambler = gamblerFactory(entries);
      winner = gambler.drawCard();
    }

    var update = {};
    update[field] = winner.id;
    Lottos.update(lottoId, {
      $set: update
    });

    return {id: winner.id};
  },
  unrollForPot: function(lottoId, pot) {
    if (!isSuperAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(pot, String);

    var field = null;
    if (pot === 'small') {
      field = 'smallpot_winner';
    } else {
      field = 'bigpot_winner';
    }

    var update = {};
    update[field] = null;

    Lottos.update(lottoId, {
      $set: update
    });
  },
  unrollForTier: function(lottoId, tier) {
    if (!isSuperAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);
    check(tier, Number);
    l = Lottos.findOne(lottoId);
    t = Tiers.findOne({lottoId: l._id, tier: tier});
    p = _.find(t.prizes, function(p) {return p.winner === true});
    e = _.find(l.entries, function(e) {return e.amount === tier && e.winner === true});

    Tiers.update({_id: t._id, prizes: p}, {$unset: {'prizes.$.winner': true}});
    Lottos.update({_id: l._id, entries: e}, {$unset: {'entries.$.winner': true}});
  },

  populateItems: function(lottoId) {
    if (!isAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto || lotto.closed) {
      throw new Meteor.Error('invalid-entry', 'Lotto is closed');
    }

    if (Meteor.isClient) {
      return {};
    }

    // Populate each tier prize with the items from the API
    Meteor.call('getItemsFromAPI', function(error, items) {
      if (error) {
        throw new Meteor.Error("error", error.reason);
      }

      var tiers = Tiers.find({lottoId: lottoId}).fetch();

      var n = 0;
      _.each(tiers, (tier) => {

        var prizes = [];
        var prizesByPos = _.indexBy(tier.prizes, 'pos');

        _.each(items.slice(n, n + 10), (item, i) => {

          if (item == null) return;

          var prize = prizesByPos[i+1];
          if (prize == null) {
            prize = {_id: Random.id(), pos: i+1};
          }

          prizes.push(_.extend({}, prize, _.pick(item, 'id', 'icon', 'name', 'rarity', 'count') ));
        });

        n += 10;
        Tiers.update({_id: tier._id}, {$set: {prizes: prizes}});
      });
    });
  },

  deleteLotto: function(lottoId) {
    if (!isSuperAdmin()) {
      throw new Meteor.Error("unauthorized", "You don't have permissions to do this");
    }
    check(lottoId, String);

    var lotto = Lottos.findOne(lottoId);
    if (!lotto) {
      throw new Meteor.Error('invalid-entry', "Lotto doesn't exist");
    }

    Lottos.remove({_id: lottoId});
    Tiers.remove({lottoId: lottoId});
  }
});

validateEntry = function (entry) {
  var errors = {};
  if (!entry.gwuserId) {
    errors.gwuserId = 'Please select a user';
  }
  if (!entry.amount) {
    errors.amount = 'Please select an amount';
  }
  if (entry.amount < 1 || entry.amount > 20) {
    errors.amount = 'Wrong amount';
  }
  return errors;
};

// Returns two lists of players belonging to each pot and their totals
updatePots = function (entries, type) {
  var smallpot = {};
  var bigpot = {};

  // an entry with more than 10g goes to the bigpot
  // a total of 20g or more to the bigpot
  _.each(entries, function (entry) {
    var total = (smallpot[entry.gwuserId] || 0) + (bigpot[entry.gwuserId] || 0) + entry.amount;
    if ((type === 'double' && total >= 20) || (type !== 'double' && (entry.amount > 10 || total >= 20))) {
      delete smallpot[entry.gwuserId];
      bigpot[entry.gwuserId] = total;
    } else {
      smallpot[entry.gwuserId] = total;
    }
  });
  var sum = function(m, e) {return m + e;};
  return {
    smallpot_entries: smallpot,
    smallpot_total: _.reduce(smallpot, sum, 0),
    bigpot_entries: bigpot,
    bigpot_total: _.reduce(bigpot, sum, 0)
  };
};
<|endoftext|>