<commit_before>var gulp = require('gulp');
var gutil = require('gulp-util');

var path = require('path');

// this gulpfile uses gulp-load-plugins to load plugins, which saves
// having to load everything separately.
// info: https://github.com/jackfranklin/gulp-load-plugins
var plugins = require('gulp-load-plugins')();

// paths this project uses
var cssSourcePaths = [
    'climasng/src/css/page-*.less'
];
var jsSourcePaths = [
    'climasng/src/js/page-*.js'
];
var jsLintablePaths = jsSourcePaths.concat([
    'gulpfile.js'
]);
var coffeeSourcePaths = [
    'climasng/src/js/oldreports/**/*.coffee'
];

// returns a string consisting of prefix + filePath + postfix,
// with nice colours highlighting the filepath.
function colorFileMsg(prefix, filePath, postfix) {
    return (
        gutil.colors.gray(prefix) +
        gutil.colors.magenta(path.dirname(filePath) + '/') +
        gutil.colors.bold.magenta(path.basename(filePath)) +
        gutil.colors.gray(postfix)
    );
}

// **returns a function** that when invoked with an event argument,
// logs a nice message saying that the event will be handled.
// Arg 'message' is what to say instead of "Handled" in the message.
function adviseOfEvent(message) {
    return (function(event) {
        var msg = message || 'Handling';
        gutil.log(colorFileMsg(msg + ' ', event.path, ' for you...'));
    });
}

// ----------------------------------------------------- default task
gulp.task('default', ['build', 'watch'], function() {
    console.log('Running the default task.');
    console.log('This will watch your files and do all the ' +
        'necessary compilation etc.');
    console.log(
        'If this is not what you expected, you should press ' +
        'Ctrl+c to quit this, then run "gulp tasks" to see ' +
        'what else you can do.'
    );
});

// --------------------------------------------- build ALL the things
gulp.task('build', ['cssbuild', 'jsbuild', 'coffeebuild']);

// ------------------------------------------------- lint your source
gulp.task('lint', ['jslint']);

// -------------------------------------------- react to file updates
gulp.task('watch', function() {
    gulp.watch(cssSourcePaths, ['cssbuild'])
        .on('change', adviseOfEvent('Building'));

    gulp.watch(jsLintablePaths, ['jslint'])
        .on('change', adviseOfEvent('Linting'));

    gulp.watch(jsSourcePaths, ['jsbuild'])
        .on('change', adviseOfEvent('Building'));

    gulp.watch(coffeeSourcePaths, ['coffeebuild'])
        .on('change', adviseOfEvent('Compiling'));
});

// ------------------------------------------------------ compile css
gulp.task('cssbuild', ['cssclean'], function() {
    return gulp.src(cssSourcePaths)
        .pipe(plugins.less())
        .on('error', gutil.log).on('error', gutil.beep)
        .pipe(plugins.autoprefixer())
        .pipe(gulp.dest('climasng/static/css/'))
        .pipe(plugins.rename({suffix: '.min'}))
        .pipe(plugins.minifyCss())
        .pipe(gulp.dest('climasng/static/css/')) ;
});

// ------------------------------------------------- delete built css
gulp.task('cssclean', function() {
    return gulp.src(['climasng/static/css/*.css'], {read: false})
        .pipe(plugins.clean());
});

// ------------------------------------------------------- compile js
gulp.task('jsbuild', ['jsclean', 'jslint'], function() {
    return gulp.src(jsSourcePaths)
        .pipe(plugins.browserify({
            debug: !gutil.env.production
        }))
        .on('error', gutil.log).on('error', gutil.beep)
        .pipe(gulp.dest('climasng/static/js/'))
        .pipe(plugins.rename({suffix: '.min'}))
        .pipe(plugins.uglify())
        .pipe(gulp.dest('climasng/static/js/')) ;
});

// ---------------------------------------------------------- lint js
gulp.task('jslint', function() {
    return gulp.src(jsLintablePaths)
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter('default'));
});

// -------------------------------------------------- delete built js
gulp.task('jsclean', function() {
    return gulp.src(['climasng/static/js/*.js'], {read: false})
        .pipe(plugins.clean());
});

// --------------------------------------------- compile coffeescript
gulp.task('coffeebuild', [], function() {
    return gulp.src(coffeeSourcePaths)
        .pipe(plugins.coffee({ bare: true }))
        .on('error', gutil.log).on('error', gutil.beep)
        .pipe(gulp.dest('climasng/static/js/oldreports/')) ;
});

// ===================================================== meta stuff..

// --------------------------------------------- show available tasks
gulp.task('tasks', function() {
    console.log(gutil.linefeed + 'Available tasks:');
    var taskNames = Object.keys(gulp.tasks);
    var maxLen = taskNames.reduce(function(prev, current) {
        return Math.max(prev, current.length);
    }, 0);

    taskNames.forEach( function(taskName) {
        var task = gulp.tasks[taskName];
        var depList = '';
        if (task.dep.length > 0) {
            depList = Array(maxLen - taskName.length + 1).join(' ');
            depList = depList + '  (' + task.dep.join(', ') + ')';
        }
        console.log('    ' + taskName + depList);
    });
    console.log('execute "gulp <taskname>" to perform a task ' +
        'or "gulp" to perform the default task).' +
        gutil.linefeed
    );
});

// -------------------------------------- show all the loaded plugins
gulp.task('plugins', function() {
    // just outputs your loaded plugins
    console.log(gutil.linefeed + 'Available plugins:');
    Object.keys(plugins).forEach( function(pluginName) {
        console.log('    ' + pluginName);
    });
    console.log();
});

<commit_msg>gulp taskrunner improvements<commit_after>var gulp = require('gulp');
var gutil = require('gulp-util');

var path = require('path');

// this gulpfile uses gulp-load-plugins to load plugins, which saves
// having to load everything separately.
// info: https://github.com/jackfranklin/gulp-load-plugins
var plugins = require('gulp-load-plugins')();

// paths this project uses
var cssSourcePaths = [
    'climasng/src/css/page-*.less'
];
var jsSourcePaths = [
    'climasng/src/js/page-*.js'
];
var jsLintablePaths = jsSourcePaths.concat([
    'gulpfile.js'
]);
var coffeeSourcePaths = [
    'climasng/src/js/oldreports/**/*.coffee'
];

// returns a string consisting of prefix + filePath + postfix,
// with colour highlighting of the filepath.
function colorFileMsg(prefix, filePath, postfix) {
    // maybe a relative path is clearer..
    var nicePath = path.relative('.', filePath);
    if (nicePath.substr(0,2) === '..') {
        nicePath = filePath;
    }
    return (
        gutil.colors.gray(prefix + path.dirname(nicePath) + '/') +
        gutil.colors.magenta(path.basename(nicePath)) +
        gutil.colors.gray(postfix)
    );
}

// **returns a function** that when invoked with an event argument,
// logs a nice message saying that the event will be handled.
// Arg 'message' is what to say instead of "Handled" in the message.
function adviseOfEvent(message) {
    return (function(event) {
        var msg = message || 'Handling';
        gutil.log(colorFileMsg(msg + ' ', event.path, ' for you...'));
    });
}

// ----------------------------------------------------- default task
gulp.task('default', ['build', 'watch'], function() {
    console.log('Running the default task.');
    console.log('This will watch your files and do all the ' +
        'necessary compilation etc.');
    console.log(
        'If this is not what you expected, you should press ' +
        'Ctrl+c to quit this, then run "gulp tasks" to see ' +
        'what else you can do.'
    );
});

// --------------------------------------------- build ALL the things
gulp.task('build', ['cssbuild', 'jsbuild', 'coffeebuild']);

// ------------------------------------------------- lint your source
gulp.task('lint', ['jslint']);

// -------------------------------------------- react to file updates
gulp.task('watch', function() {
    gulp.watch(cssSourcePaths, ['cssbuild'])
        .on('change', adviseOfEvent('Building'));

    gulp.watch(jsLintablePaths, ['jslint'])
        .on('change', adviseOfEvent('Linting'));

    gulp.watch(jsSourcePaths, ['jsbuild'])
        .on('change', adviseOfEvent('Building'));

    gulp.watch(coffeeSourcePaths, ['coffeebuild'])
        .on('change', adviseOfEvent('Compiling'));
});

// ------------------------------------------------------ compile css
gulp.task('cssbuild', ['cssclean'], function() {
    return gulp.src(cssSourcePaths)
        .pipe(plugins.less())
        .on('error', gutil.log).on('error', gutil.beep)
        .pipe(plugins.autoprefixer())
        .pipe(gulp.dest('climasng/static/css/'))
        .pipe(plugins.rename({suffix: '.min'}))
        .pipe(plugins.minifyCss())
        .pipe(gulp.dest('climasng/static/css/')) ;
});

// ------------------------------------------------- delete built css
gulp.task('cssclean', function() {
    return gulp.src(['climasng/static/css/*.css'], {read: false})
        .pipe(plugins.clean());
});

// ------------------------------------------------------- compile js
gulp.task('jsbuild', ['jsclean', 'jslint'], function() {
    return gulp.src(jsSourcePaths)
        .pipe(plugins.browserify({
            debug: !gutil.env.production
        }))
        .on('error', gutil.log).on('error', gutil.beep)
        .pipe(gulp.dest('climasng/static/js/'))
        .pipe(plugins.rename({suffix: '.min'}))
        .pipe(plugins.uglify())
        .pipe(gulp.dest('climasng/static/js/')) ;
});

// ---------------------------------------------------------- lint js
gulp.task('jslint', function() {
    return gulp.src(jsLintablePaths)
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter('default'));
});

// -------------------------------------------------- delete built js
gulp.task('jsclean', function() {
    return gulp.src(['climasng/static/js/*.js'], {read: false})
        .pipe(plugins.clean());
});

// --------------------------------------------- compile coffeescript
gulp.task('coffeebuild', [], function() {
    return gulp.src(coffeeSourcePaths)
        .pipe(plugins.coffee({ bare: true }))
        .on('error', gutil.log).on('error', gutil.beep)
        .pipe(gulp.dest('climasng/static/js/oldreports/')) ;
});

// ===================================================== meta stuff..

// --------------------------------------------- show available tasks
gulp.task('tasks', function() {
    console.log(gutil.linefeed + 'Available tasks:');
    var taskNames = Object.keys(gulp.tasks);
    var maxLen = taskNames.reduce(function(prev, current) {
        return Math.max(prev, current.length);
    }, 0);

    taskNames.forEach( function(taskName) {
        var task = gulp.tasks[taskName];
        var depList = '';
        if (task.dep.length > 0) {
            depList = Array(maxLen - taskName.length + 1).join(' ');
            depList = depList + '  (' + task.dep.join(', ') + ')';
        }
        console.log('    ' + taskName + depList);
    });
    console.log('execute "gulp <taskname>" to perform a task ' +
        'or "gulp" to perform the default task).' +
        gutil.linefeed
    );
});

// -------------------------------------- show all the loaded plugins
gulp.task('plugins', function() {
    // just outputs your loaded plugins
    console.log(gutil.linefeed + 'Available plugins:');
    Object.keys(plugins).forEach( function(pluginName) {
        console.log('    ' + pluginName);
    });
    console.log();
});

<|endoftext|>


<commit_before>import AbstractDoc from './AbstractDoc.js';
import MethodDoc from './MethodDoc.js';
import ParamParser from '../Parser/ParamParser.js'

/**
 * Doc Class from Member Expression AST node.
 */
export default class MemberDoc extends AbstractDoc {
  /**
   * apply own tag.
   * @private
   */
  _apply() {
    super._apply();

    delete this._value.export;
    delete this._value.importPath;
    delete this._value.importStyle;
  }

  /** specify ``member`` to kind. */
  ['@_kind']() {
    super['@_kind']();
    if (this._value.kind) return;
    this._value.kind = 'member';
  }

  /** use static property in class */
  ['@_static']() {
    let tag = this._find(['@_static']);
    if (tag) {
      let value = ['', 'true', true].includes(tag.tagValue);
      this._value.static = value;
      return;
    }

    let parent = this._node.parent;
    while (parent) {
      if (parent.type === 'MethodDefinition') {
        this._value.static = parent.static;
        break;
      }
      parent = parent.parent;
    }
  }

  /** take out self name from self node */
  ['@_name']() {
    let name;
    let tags = this._findAll(['@_name', '@_member']);
    if (tags) {
      for (let tag of tags) {
        let {tagName, tagValue} = tag;
        if (tagName === '@_name') {
          name = tagValue;
        } else if (tagName === '@_member') {
          let {paramName} = ParamParser.parseParamValue(value, true, true, false);
          name = paramName;
        }
      }

    } else {
      let node = this._node;
      name = this._flattenMemberExpression(node.left).replace(/^this\./, '');
    }

    this._value.name = name;
  }

  /** borrow {@link MethodDoc#@_memberof} */
  ['@_memberof']() {
    MethodDoc.prototype['@_memberof'].call(this);
  }

  /** if @type is not exists, guess type by using self node */
  ['@type']() {
    super['@type']();
    if (this._value.type) return;

    this._value.type = ParamParser.guessType(this._node.right);
  }
}
<commit_msg>fix: typo<commit_after>import AbstractDoc from './AbstractDoc.js';
import MethodDoc from './MethodDoc.js';
import ParamParser from '../Parser/ParamParser.js'

/**
 * Doc Class from Member Expression AST node.
 */
export default class MemberDoc extends AbstractDoc {
  /**
   * apply own tag.
   * @private
   */
  _apply() {
    super._apply();

    delete this._value.export;
    delete this._value.importPath;
    delete this._value.importStyle;
  }

  /** specify ``member`` to kind. */
  ['@_kind']() {
    super['@_kind']();
    if (this._value.kind) return;
    this._value.kind = 'member';
  }

  /** use static property in class */
  ['@_static']() {
    let tag = this._find(['@_static']);
    if (tag) {
      let value = ['', 'true', true].includes(tag.tagValue);
      this._value.static = value;
      return;
    }

    let parent = this._node.parent;
    while (parent) {
      if (parent.type === 'MethodDefinition') {
        this._value.static = parent.static;
        break;
      }
      parent = parent.parent;
    }
  }

  /** take out self name from self node */
  ['@_name']() {
    let name;
    let tags = this._findAll(['@_name', '@_member']);
    if (tags) {
      for (let tag of tags) {
        let {tagName, tagValue} = tag;
        if (tagName === '@_name') {
          name = tagValue;
        } else if (tagName === '@_member') {
          let {paramName} = ParamParser.parseParamValue(tagValue, true, true, false);
          name = paramName;
        }
      }

    } else {
      let node = this._node;
      name = this._flattenMemberExpression(node.left).replace(/^this\./, '');
    }

    this._value.name = name;
  }

  /** borrow {@link MethodDoc#@_memberof} */
  ['@_memberof']() {
    MethodDoc.prototype['@_memberof'].call(this);
  }

  /** if @type is not exists, guess type by using self node */
  ['@type']() {
    super['@type']();
    if (this._value.type) return;

    this._value.type = ParamParser.guessType(this._node.right);
  }
}
<|endoftext|>


<commit_before>(function(){
  "use strict";

  $("[data-toggle-nav]").on("click", function () {
    var el = $(this),
          target = $(el.attr("data-toggle-nav"));

          target.toggleClass("hidden-xs");
          el.toggleClass("active");
  });

}());
<commit_msg>Add prevent default<commit_after>(function(){
  "use strict";

  $("[data-toggle-nav]").on("click", function () {
    event.preventDefault();

    var el = $(this),
          target = $(el.attr("data-toggle-nav"));

          target.toggleClass("hidden-xs");
          el.toggleClass("active");
  });

}());
<|endoftext|>


<commit_before>/*
 * Copyrights phwang
 * Written by Paul Hwang
 * File name: queue_module.js
 */

var holder_pool = require("./holder_pool_module.js");

module.exports = {
    malloc: function () {
        que = new QueueObject();
        return que;
    },

    enqueue: function (queue_val, data_val) {
        if (!queue_val) {
            return;
        }
        queue_val.enQueue(data_val);
    },

    dequeue: function (queue_val) {
        return queue_val.deQueue();
    },

    queue_size: function (queue_val) {
        return queue_val.size;
    },

    remove: function (queue_val, func_val, input_val1, input_val2, input_val3) {
        queue_val.removeElement(func_val, input_val1, input_val2, input_val3);
    },

    search: function (queue_val, func_val, input_val1, input_val2, input_val3) {
        return queue_val.searchIt(func_val, input_val1, input_val2, input_val3);
    },
};

function QueueObject () {
    "use strict";

    this.objectName = function () {
        return "QueueObject";
    };

    this.utilModule = function () {
        return this.theUtilModule;
    };

    this.enQueue = function (data_val) {
        if (!data_val) {
            this.abend("enQueue", "null data_val");
            return;
        }

        this.abendIt();

        var data_entry = holder_pool.malloc(data_val);
        if (!data_entry) {
            this.abend("enQueue", "null data_entry");
            return;
        }

        this.size += 1;
        if (!this.head) {
            //logit("enqueueIt", "1000");
            data_entry.prev = null;
            data_entry.next = null;
            this.head = data_entry;
            this.tail = data_entry;
        } else {
            //logit("enqueueIt", "2000");
            this.tail.next = data_entry;
            data_entry.prev = this.tail;
            data_entry.next = null;
            this.tail = data_entry;
        }
        this.abendIt();
    };

    this.deQueue = function () {
        var data_entry;
        var data;

        this.abendIt();

        if (!this.head) {
            data_entry = null;
            data = null;
        } else if (this.head === this.tail) {
            this.size -= 1;
            data_entry = this.head;
            data = data_entry.data;
            this.head = null;
            this.tail = null;
        } else {
            this.size -= 1;
            data_entry = this.head;
            data = data_entry.data;
            this.head = this.head.next;
            this.head.prev = null;
        }

        if (data_entry) {
            this.logit("deQueue", "data=" + data_entry.data);
            holder_pool.free(data_entry);
        }
        else {
            this.logit("deQueue", "6000");
        }

        this.abendIt();
        return data;
    };

    this.removeElement = function (func_val, input_val1, input_val2, input_val3) {
        this.abendIt();

        var p = this.head;
        while (p) {
            this.debug(false, "removeElement", "in while loop");
            if (func_val(p.data, input_val1, input_val2, input_val3)) {
                this.debug(false, "removeElement", "found");
                if (p.prev) {
                    p.prev.next = p.next;
                } else {
                    this.head = p.next;
                }
                if (p.next) {
                    p.next.prev = p.prev;
                } else {
                    this.tail = p.prev;
                }
                this.size -= 1;
                return;
            }
            p = p.next;
        }
        this.abendIt();
        this.debug(false, "removeElement", "not found");
    };

    this.searchIt = function (func_val, input_val1, input_val2, input_val3) {
        var p = this.head;
        while (p) {
            this.debug(false, "searchIt", "in while loop");
            if (func_val(p.data, input_val1, input_val2, input_val3)) {
            this.debug(false, "searchIt", "found");
                return p.data;
            }
            p = p.next;
        }
        this.debug(false, "searchIt", "not found");
        return null;
    };

    this.abendIt = function () {
        var i = 0;
        var p = this.head;
        while (p) {
            p = p.next;
            i += 1;
        }
        if (i !== this.size) {
            this.abend("abendIt", "head: size=" + this.size + " i=" + i);
        }

        i = 0;
        p = this.tail;
        while (p) {
            p = p.prev;
            i += 1;
        }
        if (i !== this.size) {
            this.abend("abendIt", "tail: size=" + this.size + " i=" + i);
        }
    };

    this.debug = function (debug_val, str1_val, str2_val) {
        if (debug_val) {
            this.logit(str1_val, "==" + str2_val);
        }
    };

    this.abend = function (str1_val, str2_val) {
        this.utilModule().abend(this.objectName() + "." + str1_val, str2_val);
    };

    this.logit = function (str1_val, str2_val) {
        this.utilModule().logit(this.objectName() + "." + str1_val, str2_val);
    };

    this.theUtilModule = require("./util_module.js");
    this.head = null;
    this.tail = null;
    this.size = 0;
}
<commit_msg>queue<commit_after>/*
 * Copyrights phwang
 * Written by Paul Hwang
 * File name: queue_module.js
 */

module.exports = {
    malloc: function () {
        que = new QueueObject();
        return que;
    },

    enqueue: function (queue_val, data_val) {
        if (!queue_val) {
            return;
        }
        queue_val.enQueue(data_val);
    },

    dequeue: function (queue_val) {
        return queue_val.deQueue();
    },

    queue_size: function (queue_val) {
        return queue_val.size;
    },

    remove: function (queue_val, func_val, input_val1, input_val2, input_val3) {
        queue_val.removeElement(func_val, input_val1, input_val2, input_val3);
    },

    search: function (queue_val, func_val, input_val1, input_val2, input_val3) {
        return queue_val.searchIt(func_val, input_val1, input_val2, input_val3);
    },
};

function QueueObject () {
    "use strict";
    this.theUtilModule = require("./util_module.js");
    this.theHolderPoolModule = require("./holder_pool_module.js");

    this.objectName = function () {
        return "QueueObject";
    };

    this.utilModule = function () {
        return this.theUtilModule;
    };

    this.holderPoolModule = function () {
        return this.theHolderPoolModule;
    };

    this.enQueue = function (data_val) {
        if (!data_val) {
            this.abend("enQueue", "null data_val");
            return;
        }

        this.abendIt();

        var data_entry = this.holderPoolModule().malloc(data_val);
        if (!data_entry) {
            this.abend("enQueue", "null data_entry");
            return;
        }

        this.size += 1;
        if (!this.head) {
            //logit("enqueueIt", "1000");
            data_entry.prev = null;
            data_entry.next = null;
            this.head = data_entry;
            this.tail = data_entry;
        } else {
            //logit("enqueueIt", "2000");
            this.tail.next = data_entry;
            data_entry.prev = this.tail;
            data_entry.next = null;
            this.tail = data_entry;
        }
        this.abendIt();
    };

    this.deQueue = function () {
        var data_entry;
        var data;

        this.abendIt();

        if (!this.head) {
            data_entry = null;
            data = null;
        } else if (this.head === this.tail) {
            this.size -= 1;
            data_entry = this.head;
            data = data_entry.data;
            this.head = null;
            this.tail = null;
        } else {
            this.size -= 1;
            data_entry = this.head;
            data = data_entry.data;
            this.head = this.head.next;
            this.head.prev = null;
        }

        if (data_entry) {
            this.logit("deQueue", "data=" + data_entry.data);
            this.holderPoolModule().free(data_entry);
        }
        else {
            this.logit("deQueue", "6000");
        }

        this.abendIt();
        return data;
    };

    this.removeElement = function (func_val, input_val1, input_val2, input_val3) {
        this.abendIt();

        var p = this.head;
        while (p) {
            this.debug(false, "removeElement", "in while loop");
            if (func_val(p.data, input_val1, input_val2, input_val3)) {
                this.debug(false, "removeElement", "found");
                if (p.prev) {
                    p.prev.next = p.next;
                } else {
                    this.head = p.next;
                }
                if (p.next) {
                    p.next.prev = p.prev;
                } else {
                    this.tail = p.prev;
                }
                this.size -= 1;
                return;
            }
            p = p.next;
        }
        this.abendIt();
        this.debug(false, "removeElement", "not found");
    };

    this.searchIt = function (func_val, input_val1, input_val2, input_val3) {
        var p = this.head;
        while (p) {
            this.debug(false, "searchIt", "in while loop");
            if (func_val(p.data, input_val1, input_val2, input_val3)) {
            this.debug(false, "searchIt", "found");
                return p.data;
            }
            p = p.next;
        }
        this.debug(false, "searchIt", "not found");
        return null;
    };

    this.abendIt = function () {
        var i = 0;
        var p = this.head;
        while (p) {
            p = p.next;
            i += 1;
        }
        if (i !== this.size) {
            this.abend("abendIt", "head: size=" + this.size + " i=" + i);
        }

        i = 0;
        p = this.tail;
        while (p) {
            p = p.prev;
            i += 1;
        }
        if (i !== this.size) {
            this.abend("abendIt", "tail: size=" + this.size + " i=" + i);
        }
    };

    this.debug = function (debug_val, str1_val, str2_val) {
        if (debug_val) {
            this.logit(str1_val, "==" + str2_val);
        }
    };

    this.abend = function (str1_val, str2_val) {
        this.utilModule().abend(this.objectName() + "." + str1_val, str2_val);
    };

    this.logit = function (str1_val, str2_val) {
        this.utilModule().logit(this.objectName() + "." + str1_val, str2_val);
    };

    this.head = null;
    this.tail = null;
    this.size = 0;
}
<|endoftext|>


<commit_before>/*! backbone-paginated-collection */
;(function (root) { function moduleDefinition(Backbone, _) {

function recalculatePagination() {
  var length = this.superset().length;
  var perPage = this.getPerPage();

  // If the # of objects can be exactly divided by the number
  // of pages, it would leave an empty last page if we took
  // the floor.
  var totalPages = length % perPage === 0 ?
    (length / perPage) : Math.floor(length / perPage) + 1;

  this._totalPages = totalPages;

  // If the current page no longer exists, switch to the last
  // existing page in the set.
  if (this.getPage() >= totalPages) {
    this.setPage(totalPages - 1);
  } else {
    updatePagination.call(this);
  }
}

function updatePagination() {
  var start = this.getPage() * this.getPerPage();
  var end = start + this.getPerPage();

  this._collection.reset(this.superset().toArray().slice(start, end));
  this.length = this._collection.length;
}

function pipeEvents() {
  var args = _.toArray(arguments);

  // replace any references to `this._collection` with `this`
  for (var i = 1; i < args.length; i++) {
    // Is there a better way to check for this?
    // List all of the possible events?
    if (args[i].models && args[i].models.length === this._collection.models.length) {
      args[i] = this;
    }
  }

  this.trigger.apply(this, args);
}

function Paginated(superset, options) {
  // Save a reference to the original collection
  this._superset = superset;

  // The idea is to keep an internal backbone collection with the paginated
  // set, and expose limited functionality.
  this._collection = new Backbone.Collection(superset.toArray());
  this.setPerPage(options ? options.perPage: this._defaultPerPage);

  this.listenTo(this._superset, 'add', recalculatePagination);
  this.listenTo(this._superset, 'remove', recalculatePagination);
  this.listenTo(this._superset, 'reset', recalculatePagination);
  this.listenTo(this._collection, 'all', pipeEvents);
}

var methods = {

  _defaultPerPage: 20,

  setPerPage: function(perPage) {
    this._perPage = perPage;
    recalculatePagination.call(this);
    this.setPage(0);

    this.trigger('paginated:change:perPage', {
      perPage: perPage,
      numPages: this.getNumPages()
    });
  },

  setPage: function(page) {
    // The lowest page we could set
    var lowerLimit = 0;
    // The highest page we could set
    var upperLimit = this.getNumPages() - 1;

    // If the page is higher or lower than these limits,
    // set it to the limit.
    page = page > lowerLimit ? page : lowerLimit;
    page = page < upperLimit ? page : upperLimit;

    this._page = page;
    updatePagination.call(this);

    this.trigger('paginated:change:page', { page: page });
  },

  getPerPage: function() {
    return this._perPage;
  },

  getNumPages: function() {
    return this._totalPages;
  },

  getPage: function() {
    return this._page;
  },

  hasNextPage: function() {
    return this.getPage() < this.getNumPages() - 1;
  },

  hasPrevPage: function() {
    return this.getPage() > 0;
  },

  nextPage: function() {
    this.movePage(1);
  },

  prevPage: function() {
    this.movePage(-1);
  },

  movePage: function(delta) {
    this.setPage(this.getPage() + delta);
  },

  superset: function() {
    return this._superset;
  }

};

// Methods on `this._collection` we will expose to the outside world
var collectionMethods = [
  'toJSON', 'first', 'last', 'at', 'get', 'map',
  'each', 'slice', 'where', 'findWhere', 'contains',
  'indexOf', 'toArray', 'pluck'
];

_.each(collectionMethods, function(method) {
  methods[method] = function() {
    return Backbone.Collection.prototype[method].apply(this._collection, arguments);
  };
});

// Build up the prototype
_.extend(Paginated.prototype, methods, Backbone.Events);

return Paginated;

// ---------------------------------------------------------------------------
} if (typeof exports === 'object') {
  // node export
  module.exports = moduleDefinition(require('backbone'), require('underscore'));
} else if (typeof define === 'function' && define.amd) {
  // amd anonymous module registration
  define(['backbone', 'underscore'], moduleDefinition);
} else {
  // browser global
  root.PaginatedCollection = moduleDefinition(root.Backbone, root._);
}}(this));
<commit_msg>A more thorough list of collection methods forwarded<commit_after>/*! backbone-paginated-collection */
;(function (root) { function moduleDefinition(Backbone, _) {

function recalculatePagination() {
  var length = this.superset().length;
  var perPage = this.getPerPage();

  // If the # of objects can be exactly divided by the number
  // of pages, it would leave an empty last page if we took
  // the floor.
  var totalPages = length % perPage === 0 ?
    (length / perPage) : Math.floor(length / perPage) + 1;

  this._totalPages = totalPages;

  // If the current page no longer exists, switch to the last
  // existing page in the set.
  if (this.getPage() >= totalPages) {
    this.setPage(totalPages - 1);
  } else {
    updatePagination.call(this);
  }
}

function updatePagination() {
  var start = this.getPage() * this.getPerPage();
  var end = start + this.getPerPage();

  this._collection.reset(this.superset().toArray().slice(start, end));
  this.length = this._collection.length;
}

function pipeEvents() {
  var args = _.toArray(arguments);

  // replace any references to `this._collection` with `this`
  for (var i = 1; i < args.length; i++) {
    // Is there a better way to check for this?
    // List all of the possible events?
    if (args[i].models && args[i].models.length === this._collection.models.length) {
      args[i] = this;
    }
  }

  this.trigger.apply(this, args);
}

function Paginated(superset, options) {
  // Save a reference to the original collection
  this._superset = superset;

  // The idea is to keep an internal backbone collection with the paginated
  // set, and expose limited functionality.
  this._collection = new Backbone.Collection(superset.toArray());
  this.setPerPage(options ? options.perPage: this._defaultPerPage);

  this.listenTo(this._superset, 'add', recalculatePagination);
  this.listenTo(this._superset, 'remove', recalculatePagination);
  this.listenTo(this._superset, 'reset', recalculatePagination);
  this.listenTo(this._collection, 'all', pipeEvents);
}

var methods = {

  _defaultPerPage: 20,

  setPerPage: function(perPage) {
    this._perPage = perPage;
    recalculatePagination.call(this);
    this.setPage(0);

    this.trigger('paginated:change:perPage', {
      perPage: perPage,
      numPages: this.getNumPages()
    });
  },

  setPage: function(page) {
    // The lowest page we could set
    var lowerLimit = 0;
    // The highest page we could set
    var upperLimit = this.getNumPages() - 1;

    // If the page is higher or lower than these limits,
    // set it to the limit.
    page = page > lowerLimit ? page : lowerLimit;
    page = page < upperLimit ? page : upperLimit;

    this._page = page;
    updatePagination.call(this);

    this.trigger('paginated:change:page', { page: page });
  },

  getPerPage: function() {
    return this._perPage;
  },

  getNumPages: function() {
    return this._totalPages;
  },

  getPage: function() {
    return this._page;
  },

  hasNextPage: function() {
    return this.getPage() < this.getNumPages() - 1;
  },

  hasPrevPage: function() {
    return this.getPage() > 0;
  },

  nextPage: function() {
    this.movePage(1);
  },

  prevPage: function() {
    this.movePage(-1);
  },

  movePage: function(delta) {
    this.setPage(this.getPage() + delta);
  },

  superset: function() {
    return this._superset;
  }

};

// Methods on `this._collection` we will expose to the outside world
var collectionMethods = [
  'toJSON', 'forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
  'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
  'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
  'max', 'min', 'groupBy', 'sortedIndex', 'shuffle', 'toArray', 'size',
  'first', 'head', 'take', 'initial', 'rest', 'tail', 'drop', 'last',
  'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'chain', 'pluck',
  'findWhere', 'get', 'at', 'slice', 'where', 'findWhere'
];

_.each(collectionMethods, function(method) {
  methods[method] = function() {
    return Backbone.Collection.prototype[method].apply(this._collection, arguments);
  };
});

// Build up the prototype
_.extend(Paginated.prototype, methods, Backbone.Events);

return Paginated;

// ---------------------------------------------------------------------------
} if (typeof exports === 'object') {
  // node export
  module.exports = moduleDefinition(require('backbone'), require('underscore'));
} else if (typeof define === 'function' && define.amd) {
  // amd anonymous module registration
  define(['backbone', 'underscore'], moduleDefinition);
} else {
  // browser global
  root.PaginatedCollection = moduleDefinition(root.Backbone, root._);
}}(this));
<|endoftext|>


<commit_before>// @flow weak

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { createStyleSheet } from 'jss-theme-reactor';
import Modal from '../internal/Modal';
import customPropTypes from '../utils/customPropTypes';
import Slide from '../transitions/Slide';
import Paper from '../Paper';
import { duration } from '../styles/transitions';

function getSlideDirection(anchor) {
  if (anchor === 'left') {
    return 'right';
  } else if (anchor === 'right') {
    return 'left';
  } else if (anchor === 'top') {
    return 'down';
  }
  // (anchor === 'bottom')
  return 'up';
}

export const styleSheet = createStyleSheet('MuiDrawer', (theme) => {
  return {
    paper: {
      overflowY: 'auto',
      display: 'flex',
      flexDirection: 'column',
      height: '100vh',
      flex: '1 0 auto',
      position: 'fixed',
      top: 0,
      zIndex: theme.zIndex.navDrawer,
      willChange: 'transform',
      '&:focus': {
        outline: 'none',
      },
      WebkitOverflowScrolling: 'touch', // Add iOS momentum scrolling.
    },
    left: {
      left: 0,
      right: 'auto',
    },
    right: {
      left: 'auto',
      right: 0,
    },
    top: {
      top: 0,
      left: 0,
      bottom: 'auto',
      right: 0,
      height: 'auto',
      maxHeight: '100vh',
    },
    bottom: {
      top: 'auto',
      left: 0,
      bottom: 0,
      right: 0,
      height: 'auto',
      maxHeight: '100vh',
    },
    docked: {
      flex: '0 0 auto',
      '& $paper': {
        borderRight: `1px solid ${theme.palette.text.divider}`,
      },
    },
    modal: {
    },
  };
});

/**
 * This is a drawer.
 */
export default class Drawer extends Component {
  static propTypes = {
    /**
     * Side which will the drawer will appear from.
     */
    anchor: PropTypes.oneOf(['left', 'top', 'right', 'bottom']),
    /**
     * The contents of the drawer.
     */
    children: PropTypes.node,
    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,
    /**
     * If `true`, the drawer will dock itself
     * and will no longer slide in with an overlay.
     */
    docked: PropTypes.bool,
    /**
     * The elevation of the drawer.
     */
    elevation: PropTypes.number,
    /**
     * Customizes duration of enter animation (ms)
     */
    enterTransitionDuration: PropTypes.number,
    /**
     * Customizes duration of leave animation (ms)
     */
    leaveTransitionDuration: PropTypes.number,
    /**
     * Callback fired when the internal modal requests to be closed.
     */
    onRequestClose: PropTypes.func,
    /**
     * If `true`, the drawer is open.
     */
    open: PropTypes.bool,
    /**
     * The CSS class name of the paper element.
     */
    paperClassName: PropTypes.string,
  };

  static defaultProps = {
    anchor: 'left',
    docked: false,
    enterTransitionDuration: duration.enteringScreen,
    leaveTransitionDuration: duration.leavingScreen,
    open: false,
    elevation: 16,
  };

  static contextTypes = {
    styleManager: customPropTypes.muiRequired,
  };

  render() {
    const {
      anchor: anchorProp,
      children,
      className,
      docked,
      enterTransitionDuration,
      leaveTransitionDuration,
      open,
      paperClassName,
      elevation,
      ...other
    } = this.props;

    const { theme: { dir }, render } = this.context.styleManager;
    const classes = render(styleSheet);
    const rtl = dir === 'rtl';
    let anchor = anchorProp;
    if (rtl && ['left', 'right'].includes(anchor)) {
      anchor = (anchor === 'left') ? 'right' : 'left';
    }

    const slideDirection = getSlideDirection(anchor);

    const drawer = (
      <Slide
        in={open}
        direction={slideDirection}
        enterTransitionDuration={enterTransitionDuration}
        leaveTransitionDuration={leaveTransitionDuration}
        transitionAppear
      >
        <Paper
          elevation={docked ? 0 : elevation}
          square
          className={classNames(classes.paper, classes[anchor], paperClassName)}
        >
          {children}
        </Paper>
      </Slide>
    );

    const containerProps = {
      className: classNames(classes.modal, className),
      ...other,
    };

    if (docked) {
      return (

          {drawer}

      );
    }

    return (
      <Modal
        backdropTransitionDuration={open ? enterTransitionDuration : leaveTransitionDuration}
        {...containerProps}
        show={open}
      >
        {drawer}
      </Modal>
    );
  }
}
<commit_msg>[Drawer] Small refactoring<commit_after>// @flow weak

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { createStyleSheet } from 'jss-theme-reactor';
import Modal from '../internal/Modal';
import customPropTypes from '../utils/customPropTypes';
import Slide from '../transitions/Slide';
import Paper from '../Paper';
import { duration } from '../styles/transitions';

function getSlideDirection(anchor) {
  if (anchor === 'left') {
    return 'right';
  } else if (anchor === 'right') {
    return 'left';
  } else if (anchor === 'top') {
    return 'down';
  }
  // (anchor === 'bottom')
  return 'up';
}

export const styleSheet = createStyleSheet('MuiDrawer', (theme) => {
  return {
    paper: {
      overflowY: 'auto',
      display: 'flex',
      flexDirection: 'column',
      height: '100vh',
      flex: '1 0 auto',
      position: 'fixed',
      top: 0,
      zIndex: theme.zIndex.navDrawer,
      willChange: 'transform',
      '&:focus': {
        outline: 'none',
      },
      WebkitOverflowScrolling: 'touch', // Add iOS momentum scrolling.
    },
    left: {
      left: 0,
      right: 'auto',
    },
    right: {
      left: 'auto',
      right: 0,
    },
    top: {
      top: 0,
      left: 0,
      bottom: 'auto',
      right: 0,
      height: 'auto',
      maxHeight: '100vh',
    },
    bottom: {
      top: 'auto',
      left: 0,
      bottom: 0,
      right: 0,
      height: 'auto',
      maxHeight: '100vh',
    },
    docked: {
      flex: '0 0 auto',
      '& $paper': {
        borderRight: `1px solid ${theme.palette.text.divider}`,
      },
    },
    modal: {
    },
  };
});

/**
 * This is a drawer.
 */
export default class Drawer extends Component {
  static propTypes = {
    /**
     * Side which will the drawer will appear from.
     */
    anchor: PropTypes.oneOf(['left', 'top', 'right', 'bottom']),
    /**
     * The contents of the drawer.
     */
    children: PropTypes.node,
    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,
    /**
     * If `true`, the drawer will dock itself
     * and will no longer slide in with an overlay.
     */
    docked: PropTypes.bool,
    /**
     * The elevation of the drawer.
     */
    elevation: PropTypes.number,
    /**
     * Customizes duration of enter animation (ms)
     */
    enterTransitionDuration: PropTypes.number,
    /**
     * Customizes duration of leave animation (ms)
     */
    leaveTransitionDuration: PropTypes.number,
    /**
     * Callback fired when the internal modal requests to be closed.
     */
    onRequestClose: PropTypes.func,
    /**
     * If `true`, the drawer is open.
     */
    open: PropTypes.bool,
    /**
     * The CSS class name of the paper element.
     */
    paperClassName: PropTypes.string,
  };

  static defaultProps = {
    anchor: 'left',
    docked: false,
    enterTransitionDuration: duration.enteringScreen,
    leaveTransitionDuration: duration.leavingScreen,
    open: false,
    elevation: 16,
  };

  static contextTypes = {
    styleManager: customPropTypes.muiRequired,
  };

  render() {
    const {
      anchor: anchorProp,
      children,
      className,
      docked,
      enterTransitionDuration,
      leaveTransitionDuration,
      open,
      paperClassName,
      elevation,
      ...other
    } = this.props;

    const { theme: { dir }, render } = this.context.styleManager;
    const classes = render(styleSheet);
    const rtl = dir === 'rtl';
    let anchor = anchorProp;
    if (rtl && ['left', 'right'].includes(anchor)) {
      anchor = (anchor === 'left') ? 'right' : 'left';
    }

    const slideDirection = getSlideDirection(anchor);

    const drawer = (
      <Slide
        in={open}
        direction={slideDirection}
        enterTransitionDuration={enterTransitionDuration}
        leaveTransitionDuration={leaveTransitionDuration}
        transitionAppear
      >
        <Paper
          elevation={docked ? 0 : elevation}
          square
          className={classNames(classes.paper, classes[anchor], paperClassName)}
        >
          {children}
        </Paper>
      </Slide>
    );

    if (docked) {
      return (

          {drawer}

      );
    }

    return (
      <Modal
        backdropTransitionDuration={open ? enterTransitionDuration : leaveTransitionDuration}
        className={classNames(classes.modal, className)}
        {...other}
        show={open}
      >
        {drawer}
      </Modal>
    );
  }
}
<|endoftext|>


<commit_before>{
  const http = require('http');
  const Timer = require('./Timer');

  /** Energenie power strip core actions */
  const CORE_ACTIONS = {
    LOGIN: { PATH: '/login.html', METHOD: 'POST' }
  };

  /** Options to control Energenie power strip. */
  let options = {};
  let logoutTimer = new Timer();

  /**
   * Login to Energenie via http request. Detect if login worked by searching
   * in the script tag for the sockstates in the response.
   *
   * @param {Object} postData_ Postdata for overwriting standard from options.
   * @return {Promise} boolean If login was successfull.
   */
  function login(postData_) {
    return new Promise(function (resolve, reject) {
      let postData = 'pw=' + options.password;
      if (postData_)
        postData = postData_;

      if (logoutTimer.stillTicking()) {
        resolve(true); return;
      }

      let request = http.request({
        method: CORE_ACTIONS.LOGIN.METHOD,
        port: options.port,
        host: options.host,
        path: CORE_ACTIONS.LOGIN.PATH,
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': Buffer.byteLength(postData)
        }
      }, function(res) {
        let data = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
          data += chunk;
        });
        res.on('end', () => {
          logout();
          if (!postData_) {
            // Searching for "var sockstates = [0,0,0,0]" from index.html
            var regexSock = /var sockstates = \[(.*?)\];/;
            var sockContent = regexSock.exec(data);
            if (!sockContent) {
              reject(new Error('LOGIN_FAILED')); return;
            }
          }

          resolve(true);
        });
      }, function(err) {
        if (!postData_)
          console.error('LOGIN_ERR', err);

        reject(err);
      });

      request.write(postData);
      request.end();
    });
  }

  /**
   * Logout from Energenie via http request. Logout is made by
   * sending a http request to login.html without post data.
   * The logout waits until the TIMEOUT is up to logout from the
   * system.
   *
   * @return {Promise} boolean If login was successfull.
   */
  function logout() {
    return new Promise(function (resolve, reject) {
      logoutTimer.set(() => {
        login('')
          .then(function() {
            console.log('LOGOUT');
            resolve(true);
          }).catch(function(err) {
            console.error('LOGOUT_ERROR', err);
            reject(err);
          });
      });
    });
  }

  /**
   * Does the http request, collecting the data and return it.
   *
   * @return {Promise} Collected data from http request.
   */
  function request(action, postData) {
    return new Promise((resolve, reject) => {
      let request = http.request({
        method: action.METHOD,
        port: options.port,
        host: options.host,
        path: action.PATH,
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': Buffer.byteLength(postData)
        }
      }, function(res) {
        let data = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          logoutTimer.reset();
          resolve(data);
        });
      }, function(err) {
        reject(err);
      });

      request.write(postData);
      request.end();
    });
  }

  /**
   * Energie Core Class.
   *
   * @author Michael Kolodziejczyk
   */
  class EnergenieCore {

    constructor(options_) {
      options = options_;
    }

    /**
     * Sending http request to Energenie power stripe by using the request
     * function.
     * You can use the defined *_ACTIONS ENUMS.
     *
     * @param {Enum} action Http-request path and method.
     * @param {Object} postData Data to send.
     * @return {Promise} Collected data from http request.
     */
    sendPostRequest(action, postData) {
      return new Promise(function(resolve, reject) {
        login().then(() => {
          request(action, postData).then((data) => {
            resolve(data);
          }).catch((err) => {
            reject(err);
          });
        });
      });
    }
  }

  module.exports = EnergenieCore;
}<commit_msg>logout bug fixed<commit_after>{
  const http = require('http');
  const Timer = require('./Timer');

  /** Energenie power strip core actions */
  const CORE_ACTIONS = {
    LOGIN: { PATH: '/login.html', METHOD: 'POST' },
    LOGOUT: { PATH: '/login.html', METHOD: 'GET' }
  };

  /** Options to control Energenie power strip. */
  let options = {};
  let logoutTimer = new Timer();

  /**
   * Login to Energenie via http request. Detect if login worked by searching
   * in the script tag for the sockstates in the response.
   *
   * @param {Object} postData_ Postdata for overwriting standard from options.
   * @return {Promise} boolean If login was successfull.
   */
  function login() {
    return new Promise(function (resolve, reject) {
      if (logoutTimer.stillTicking()) {
        resolve(true); return;
      }

      request(CORE_ACTIONS.LOGIN, 'pw=' + options.password)
        .then((data) => {
          logout();
          // Searching for "var sockstates = [0,0,0,0]" from index.html
          var regexSock = /var sockstates = \[(.*?)\];/;
          var sockContent = regexSock.exec(data);
          if (!sockContent) {
            reject(new Error('LOGIN_FAILED')); return;
          }
          resolve(true);
        });
    }).catch((err) => {
      if (!postData_)
        console.error('LOGIN_ERR', err);

      reject(err);
    });
  }

  /**
   * Logout from Energenie via http request. Logout is made by
   * sending a http request to login.html without post data.
   * The logout waits until the TIMEOUT is up to logout from the
   * system.
   *
   * @return {Promise} boolean If login was successfull.
   */
  function logout() {
    return logoutTimer.set(
      () => {
        request(CORE_ACTIONS.LOGOUT, '')
          .then(function() {
            console.info('LOGOUT');
            return true;
          }).catch(function(err) {
            return new Error('LOGOUT_ERROR', err);
          });
      });
  }

  /**
   * Collecting the data from the http-request and return it.
   * Resetting logout timer while getting data and being requested.
   *
   * @return {Promise} Collected data from http request.
   */
  function request(action, postData) {
    return new Promise((resolve, reject) => {
      let request = http.request({
        method: action.METHOD,
        port: options.port,
        host: options.host,
        path: action.PATH,
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': Buffer.byteLength(postData)
        }
      }, function(res) {
        let data = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          logoutTimer.reset();
          resolve(data);
        });
      }, function(err) {
        reject(err);
      });

      request.write(postData);
      request.end();
    });
  }

  /**
   * Energie Core Class.
   *
   * Handling the http requests. Holds a session to
   * Energenie strip 30 seconds, before logout. Every
   * time a request is done while the session is still
   * alive the logoutTimeoutHandler will be reset to
   * wait 30 seconds before logging out from power strip.
   *
   * @author Michael Kolodziejczyk
   */
  class EnergenieCore {

    constructor(options_) {
      options = options_;
    }

    /**
     * Sending http request to Energenie power strip by using the request
     * function.
     * You can use the defined *_ACTIONS ENUMS.
     *
     * @param {Enum} action Http-request path and method.
     * @param {Object} postData Data to send.
     * @return {Promise} Collected data from http request.
     */
    sendPostRequest(action, postData) {
      return new Promise(function(resolve, reject) {
        login().then(() => {
          request(action, postData).then((data) => {
            resolve(data);
          }).catch((err) => {
            reject(err);
          });
        });
      });
    }
  }

  module.exports = EnergenieCore;
}<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

var GoogleSpreadsheet = require('google-spreadsheet');
var Sheet = new GoogleSpreadsheet('1LI4OBEGMPKxBwtGY_xJ1yT_G-rrCJxl8FaC0q0DaBl0');

var Firebase = require('firebase');

var Moment = require('moment-timezone');

// const TS_PATH = './node_modules/jstrueskill/lib/racingjellyfish/jstrueskill';
// var TrueSkillCalculator = require(`${TS_PATH}/TrueSkillCalculator`);
// var GameInfo = require(`${TS_PATH}/GameInfo`).getDefaultGameInfo();
// var Player = require(`${TS_PATH}/Player`);
// var Team = require(`${TS_PATH}/Team`);

// spreadsheet key is the long id in the sheets URL

function getSheetRows(sheet, tab) {
  return new Promise((resolve,reject) => {
    Sheet.getRows(tab, (err, rows) => {
      if (err !== null) {
        reject(err);
      }
      resolve(rows);
    });
  });
}

//Participants
function getParticipantMap(rows) {
  return rows.reduce((participantMap, row) => {
    let participantId = Number.parseInt(row.participantid);
    let fullname = `${row.last}, ${row.first}`;
    // if (row.nickname.length > 0) {fullname += ` (${row.nickname})`;}
    participantMap.set(participantId, {
      fullname: fullname,
      name: {
        lastname: row.last,
        firstname: row.first,
        nickname: row.nickname,
      },
    });
    return participantMap;
  }, new Map());
}

//Races
function getRaceMap(rows) {
  return rows.reduce((raceMap, row) => {
    raceMap.set(row.raceid, {
      raceId: row.raceid,
      name: `${row.year} ${row.racename}`,
      theme: row.theme,
      abbreviation: row.raceabbr,
      date: Moment.tz(row.date, 'MM/DD/YYYY', 'America/New_York').toDate(),
      numLegs: Number.parseInt(row.numlegs),
      legs: [],
      toScore: row.toscore === 'TRUE' ? true : false,
      start: row.start,
      finish: row.finish,
      distance: row.distance
    });
    return raceMap;
  }, new Map());
}

function getLegResults(rows, raceMap) {
  let results = rows.map((row) => {
    const RACE_DATE = Moment(raceMap.get(row.raceid).date);
    return {
      raceId: row.raceid,
      leg: Number.parseInt(row.leg),
      driverId: Number.parseInt(row.driverid),
      navigatorId: 'n/a',
      start: RACE_DATE.clone().add(Moment.duration(row.starttime)),
      end: RACE_DATE.clone().add(Moment.duration(row.finishtime)),
      duration: Moment.duration(row.legduration),
      rank: null, //placeholder
      dnf: row.dnf === 'TRUE' ? true : false,
      unknownTime: row.unknown === 'TRUE' ? true : false
    };
  });
  return results;
}

// function buildRaceTables(rows, participantMap, raceMap) {
//
//   let raceTables = new Map();
//
//   let results = rows.map((row) => {
//     return {
//       raceId: row.raceid,
//       leg: Number.parseInt(row.leg),
//       driverId: Number.parseInt(row.driverid),
//       navigatorId: 'n/a',
//       startTime: Moment.duration(row.starttime),
//       endTime: Moment.duration(row.finishtime),
//       legTime: Moment.duration(row.legduration),
//       dnf: row.dnf === 'TRUE' ? true : false,
//       unknownTime: row.unknown === 'TRUE' ? true : false
//     };
//   }).sort((r1, r2) => {
//     if (r1.raceId !== r2.raceId) {
//       return r1.date - r2.date;
//     } else if (r1.leg !== r2.leg) {
//       return r1.leg - r2.leg;
//     }
//     let t1 = r1.dnf ? Number.MAX_VALUE : r1.legTime;
//     let t2 = r2.dnf ? Number.MAX_VALUE : r2.legTime;
//     return t1 - t2;
//   }).reduce((results, result) => {
//     if ((results.length === 0) || (results[length - 1][0].raceId !== result.raceId)) {
//       results.push(new Array(raceMap.get(result.raceId).numLegs + 1));
//     }
//     results[results.length - 1][result.leg].push(result);
//   }, []);
//
//   for (let race of raceMap.values()) {
//     //TODO: fix this timezone hack
//     const RACE_DATE = Moment(raceMap.get(race.raceId).date);
//
//     let table = results.filter((result) => {
//       return result.raceId === race.raceId;
//     }).sort((res1, res2) => {
//       if (res1.driverId !== res2.driverId) {
//         return res1.driverId - res2.driverId;
//       } else { return res1.leg - res2.leg; }
//     }).filter((result) => {
//       return result.leg === 1;
//     }).sort((r1, r2) => {
//       //TODO: eliminate duplicated code
//       let t1 = r1.dnf ? Number.MAX_VALUE : r1.legTime;
//       let t2 = r2.dnf ? Number.MAX_VALUE : r2.legTime;
//       return t1 - t2;
//     }).map((result, index, results) => {
//       return {
//         driverId: result.driverId,
//         driverName: participantMap.get(result.driverId).fullname,
//         navigatorName: 'tbd',
//         legStartTime: RACE_DATE.clone().add(result.startTime).format(),
//         legEndTime: RACE_DATE.clone().add(result.endTime).format(),
//         legElapsedTime: result.legTime.toJSON(),
//         legRank: result.dnf ? results.length : index + 1
//       };
//     });
//     raceTables.set(race.raceId, table);
//   }
//
//   return raceTables;
// }

// function scoreLeg(results, participantMap) {
//   results = results.filter(result => result.unknownTime === false)
//   .sort((r1, r2) => {
//     let t1 = r1.dnf ? Number.MAX_VALUE : r1.legTime;
//     let t2 = r2.dnf ? Number.MAX_VALUE : r2.legTime;
//     return t1 - t2;
//   });
//
//   //create teams
//   let teams = results.map(result => {
//     let driver = new Player(result.driverId);
//     let driverSkill = participantMap.get(result.driverId).driverSkill;
//     if (typeof driverSkill === 'undefined') {driverSkill = GameInfo.getDefaultRating();}
//     return new Team(result.driverId.toString(), driver, driverSkill);
//   });
//
//   //create rankings`
//   //TODO: change to a map
//   let finishOrder = [];
//   results.forEach((result, index) => {
//     finishOrder.push(result.dnf ? results.length : index + 1);
//   });
//
//   let newSkills = TrueSkillCalculator.calculateNewRatings(GameInfo, teams, finishOrder);
//
//   //update scores
//   teams.forEach(team => {
//     let player = team.getPlayers()[0];
//     participantMap.get(player.getId()).driverSkill = newSkills[player];
//   });
//
// }

function resultCompare(r1, r2) {
  let compareVal = (r => {
    if (r.dnf === true) { return Number.MAX_VALUE / 2; }
    if (r.unknownTime === true) { return Number.MAX_VALUE; }
    return r.duration.asMilliseconds();
  });
  return compareVal(r1) - compareVal(r2);
}

function getRaceResults(legResults, participantMap, raceMap) {
  let raceResults = legResults
  //first get leg rankings by doing some cool sorting work
  .sort((r1, r2) => {
    if (r1.raceId !== r2.raceId) { return r1.start - r2.start; }
    if (r1.leg !== r2.leg) { return r1.leg - r2.leg; }
    return resultCompare(r1, r2);
  })
  //rank the individual race legs
  .map((curResult, index, allResults) => {
    let rank = 1;
    if (index !== 0) {
      let prevResult = allResults[index - 1];
      if ((prevResult.raceId === curResult.raceId) && (prevResult.leg === curResult.leg)) {
        //curResult is in the same leg
        if (curResult.duration === prevResult.duration) {
          rank = prevResult.rank;
        }  else {
          rank = prevResult.rank + 1;
        }
      }
    }
    curResult.rank = rank;
    return curResult;
  })

  //then start to process it into a race result table
  .reduce((races, curLeg) => {
    if (!races.hasOwnProperty(curLeg.raceId)) {
      races[curLeg.raceId] = {};
    }
    if (!races[curLeg.raceId].hasOwnProperty(curLeg.driverId)) {
      races[curLeg.raceId][curLeg.driverId] = {
        driverId: curLeg.driverId,
        driverName: participantMap.get(curLeg.driverId).fullname,
        navigatorName: 'n/a',
        duration: Moment.duration(0),
        rank: null,
        legs: {},
        dnf: false,
        unknownTime: false
      };
    }
    races[curLeg.raceId][curLeg.driverId].legs[curLeg.leg] = {
      start: curLeg.start,
      end: curLeg.end,
      duration: curLeg.duration,
      rank: curLeg.rank,
      dnf: curLeg.dnf,
      unknownTime: curLeg.unknownTime
    };
    races[curLeg.raceId][curLeg.driverId].duration.add(curLeg.duration);
    if (curLeg.dnf === true) { races[curLeg.raceId][curLeg.driverId].dnf = true; }
    if (curLeg.unknownTime === true) { races[curLeg.raceId][curLeg.driverId].unknownTime = true; }
    return races;
  }, {});

  // convert the participant list to an array sorted by total duration
  Object.keys(raceResults).forEach(raceId => {
    raceResults[raceId] = Object.keys(raceResults[raceId])
    .map(driverId => raceResults[raceId][driverId])
    .sort(resultCompare)
    .map((result, index, prevResults) => {
      // TODO - handle DNFs and unknown times
      if (index === 0) {
        result.rank = 1;
      } else {
        let prevResult = prevResults[index - 1];
        if (resultCompare(result, prevResult) === 0) {
          result.rank = prevResult.rank;
        } else {
          result.rank = prevResult.rank + 1;
        }
      }
      return result;
    })
    // HACK - convert all Durations to JSON to fix serialization issue w/Firebase
    .map(result => {
      result.duration = result.duration.toJSON();
      Object.keys(result.legs).forEach(function(leg) {
        result.legs[leg].start = result.legs[leg].start.toJSON();
        result.legs[leg].end = result.legs[leg].end.toJSON();
        result.legs[leg].duration = result.legs[leg].duration.toJSON();
      });
      return result;
    });
  });

  return Object.keys(raceResults)
  .reduce((results, raceId) => {
    let result = raceResults[raceId];
    results[raceId] = {
      raceId: raceId,
      name: raceMap.get(raceId).name,
      theme: raceMap.get(raceId).theme,
      date: raceMap.get(raceId).date.toJSON(),
      start: raceMap.get(raceId).start,
      end: raceMap.get(raceId).finish,
      distance: raceMap.get(raceId).distance,
      numLegs: raceMap.get(raceId).numLegs,
      time: result[0].duration,
      // speed: raceMap.get(raceId).distance /
      // Moment.duration(result[0].duration).asSeconds() / 3600,
      winner: result[0].driverName,
      results: raceResults[raceId]
    };
    return results;
  }, {});

}

function getRaceList(raceResults) {

  //deep copy
  let clonedResults = JSON.parse(JSON.stringify(raceResults)) ;

  let list = Object.keys(clonedResults)
  .map(raceId => {
    delete clonedResults[raceId].results;
    return clonedResults[raceId];
  })
  .sort((r1, r2) => {
    return new Date(r1.date) - new Date(r2.date);
  });

  return list;
}

// function getScores(raceResults) {
//   let scores = Object.keys(raceResults).map(key => {return raceResults[key];})
//   .map(race => {
//
//   })
// }

function getDriverStats(raceResults, participantMap) {
  let stats = Object.keys(raceResults).map(key => {return raceResults[key].results;})
  .reduce((drivers, curRace) => {
    curRace.forEach(driver => {
      if (!drivers.has(driver.driverId)) {
        drivers.set(driver.driverId, {
          id: driver.driverId,
          name: participantMap.get(driver.driverId).fullname,
          starts: 0,
          finishes: 0,
          wins: 0,
          podiums: 0,
          skillRanking: null
        });
      }

      let thisDriver = drivers.get(driver.driverId);
      thisDriver.starts++;
      if (!driver.dnf) {thisDriver.finishes++;}
      if (driver.rank === 1) {thisDriver.wins++;}
      if (driver.rank <= 3) {thisDriver.podiums++;}
    });
    return drivers;
  }, new Map());
  return Array.from(stats.values());
}

function main() {
  console.log('Getting data from Google Sheets');
  Promise.all([getSheetRows(Sheet, 1).then(getParticipantMap),
               getSheetRows(Sheet, 3).then(getRaceMap),
               getSheetRows(Sheet, 2)
             ])
  .then((promiseResults) => {
    console.log('Data from sheets pulled');
    let participantMap = promiseResults[0];
    let raceMap = promiseResults[1];
    let legResults = getLegResults(promiseResults[2], raceMap);

    let raceResults = getRaceResults(legResults, participantMap, raceMap);
    let raceList = getRaceList(raceResults, raceMap);

    // let driverScores = getScores(raceResults);

    let driverStats = getDriverStats(raceResults, participantMap);

    console.log('Updating Firebase');
    let fbRef = new Firebase('https://dttdata.firebaseio.com/');
    fbRef.set({
      races: raceList,
      raceResults: raceResults,
      driverStats: driverStats
    });

    // //output the ranked list of drivers
    // let rankedDrivers = Array.from(participantMap.values())
    // .filter(a => { return typeof a.driverSkill !== 'undefined';})
    // .sort((a,b) => {
    //   return b.driverSkill.conservativeRating - a.driverSkill.conservativeRating;
    // });
    // rankedDrivers.forEach((driver,index) => {
    //   console.log(`${index} ${driver.driverSkill.conservativeRating.toFixed(3)} ` +
    //               `${driver.driverSkill.mean.toFixed(3)} ` +
    //               `${driver.name.lastname}, ${driver.name.firstname}`);
    // });
    //
    console.log('all done');
  })
  .catch(err => {
    console.error(`ERROR: ${err}`);
    console.error(err.stack);
    throw err;
  });
}

main();
<commit_msg>first version... has problems with maximum iterations<commit_after>#!/usr/bin/env node
'use strict';

// spreadsheet key is the long id in the sheets URL
var GoogleSpreadsheet = require('google-spreadsheet');
var Sheet = new GoogleSpreadsheet('1LI4OBEGMPKxBwtGY_xJ1yT_G-rrCJxl8FaC0q0DaBl0');

var Firebase = require('firebase');

var Moment = require('moment-timezone');

const TS_PATH = './node_modules/jstrueskill/lib/racingjellyfish/jstrueskill';
var TrueSkillCalculator = require(`${TS_PATH}/TrueSkillCalculator`);
var GameInfo = require(`${TS_PATH}/GameInfo`).getDefaultGameInfo();
var Player = require(`${TS_PATH}/Player`);
var Team = require(`${TS_PATH}/Team`);

function getSheetRows(sheet, tab) {
  return new Promise((resolve,reject) => {
    Sheet.getRows(tab, (err, rows) => {
      if (err !== null) {
        reject(err);
      }
      resolve(rows);
    });
  });
}

//Participants
function getParticipantMap(rows) {
  return rows.reduce((participantMap, row) => {
    let participantId = Number.parseInt(row.participantid);
    let fullname = `${row.last}, ${row.first}`;
    // if (row.nickname.length > 0) {fullname += ` (${row.nickname})`;}
    participantMap.set(participantId, {
      fullname: fullname,
      name: {
        lastname: row.last,
        firstname: row.first,
        nickname: row.nickname,
      },
      driverSkill: GameInfo.getDefaultRating()
    });
    return participantMap;
  }, new Map());
}

//Races
function getRaceMap(rows) {
  return rows.reduce((raceMap, row) => {
    raceMap.set(row.raceid, {
      raceId: row.raceid,
      name: `${row.year} ${row.racename}`,
      theme: row.theme,
      abbreviation: row.raceabbr,
      date: Moment.tz(row.date, 'MM/DD/YYYY', 'America/New_York').toDate(),
      numLegs: Number.parseInt(row.numlegs),
      legs: [],
      toScore: row.toscore === 'TRUE' ? true : false,
      start: row.start,
      finish: row.finish,
      distance: row.distance
    });
    return raceMap;
  }, new Map());
}

function getLegResults(rows, raceMap) {
  let results = rows.map((row) => {
    const RACE_DATE = Moment(raceMap.get(row.raceid).date);
    return {
      raceId: row.raceid,
      leg: Number.parseInt(row.leg),
      driverId: Number.parseInt(row.driverid),
      navigatorId: 'n/a',
      start: RACE_DATE.clone().add(Moment.duration(row.starttime)),
      end: RACE_DATE.clone().add(Moment.duration(row.finishtime)),
      duration: Moment.duration(row.legduration),
      rank: null, //placeholder
      dnf: row.dnf === 'TRUE' ? true : false,
      unknownTime: row.unknown === 'TRUE' ? true : false
    };
  });
  return results;
}

function resultCompare(r1, r2) {
  let compareVal = (r => {
    if (r.dnf === true) { return Number.MAX_VALUE / 2; }
    if (r.unknownTime === true) { return Number.MAX_VALUE; }
    return r.duration.asMilliseconds();
  });
  return compareVal(r1) - compareVal(r2);
}

function getRaceResults(legResults, participantMap, raceMap) {
  let raceResults = legResults
  //first get leg rankings by doing some cool sorting work
  .sort((r1, r2) => {
    if (r1.raceId !== r2.raceId) { return r1.start - r2.start; }
    if (r1.leg !== r2.leg) { return r1.leg - r2.leg; }
    return resultCompare(r1, r2);
  })
  //rank the individual race legs
  .map((curResult, index, allResults) => {
    let rank = 1;
    if (index !== 0) {
      let prevResult = allResults[index - 1];
      if ((prevResult.raceId === curResult.raceId) && (prevResult.leg === curResult.leg)) {
        //curResult is in the same leg
        if (curResult.duration === prevResult.duration) {
          rank = prevResult.rank;
        }  else {
          rank = prevResult.rank + 1;
        }
      }
    }
    curResult.rank = rank;
    return curResult;
  })

  //then start to process it into a race result table
  .reduce((races, curLeg) => {
    if (!races.hasOwnProperty(curLeg.raceId)) {
      races[curLeg.raceId] = {};
    }
    if (!races[curLeg.raceId].hasOwnProperty(curLeg.driverId)) {
      races[curLeg.raceId][curLeg.driverId] = {
        driverId: curLeg.driverId,
        driverName: participantMap.get(curLeg.driverId).fullname,
        navigatorName: 'n/a',
        duration: Moment.duration(0),
        rank: null,
        legs: {},
        dnf: false,
        unknownTime: false
      };
    }
    races[curLeg.raceId][curLeg.driverId].legs[curLeg.leg] = {
      start: curLeg.start,
      end: curLeg.end,
      duration: curLeg.duration,
      rank: curLeg.rank,
      dnf: curLeg.dnf,
      unknownTime: curLeg.unknownTime
    };
    races[curLeg.raceId][curLeg.driverId].duration.add(curLeg.duration);
    if (curLeg.dnf === true) { races[curLeg.raceId][curLeg.driverId].dnf = true; }
    if (curLeg.unknownTime === true) { races[curLeg.raceId][curLeg.driverId].unknownTime = true; }
    return races;
  }, {});

  // convert the participant list to an array sorted by total duration
  Object.keys(raceResults).forEach(raceId => {
    raceResults[raceId] = Object.keys(raceResults[raceId])
    .map(driverId => raceResults[raceId][driverId])
    .sort(resultCompare)
    .map((result, index, prevResults) => {
      // TODO - handle DNFs and unknown times
      if (index === 0) {
        result.rank = 1;
      } else {
        let prevResult = prevResults[index - 1];
        if (resultCompare(result, prevResult) === 0) {
          result.rank = prevResult.rank;
        } else {
          result.rank = prevResult.rank + 1;
        }
      }
      return result;
    })
    // HACK - convert all Durations to JSON to fix serialization issue w/Firebase
    .map(result => {
      result.duration = result.duration.toJSON();
      Object.keys(result.legs).forEach(function(leg) {
        result.legs[leg].start = result.legs[leg].start.toJSON();
        result.legs[leg].end = result.legs[leg].end.toJSON();
        result.legs[leg].duration = result.legs[leg].duration.toJSON();
      });
      return result;
    });
  });

  return Object.keys(raceResults)
  .reduce((results, raceId) => {
    let result = raceResults[raceId];
    results[raceId] = {
      raceId: raceId,
      name: raceMap.get(raceId).name,
      theme: raceMap.get(raceId).theme,
      date: raceMap.get(raceId).date.toJSON(),
      start: raceMap.get(raceId).start,
      end: raceMap.get(raceId).finish,
      distance: raceMap.get(raceId).distance,
      numLegs: raceMap.get(raceId).numLegs,
      time: result[0].duration,
      // speed: raceMap.get(raceId).distance /
      // Moment.duration(result[0].duration).asSeconds() / 3600,
      winner: result[0].driverName,
      results: raceResults[raceId]
    };
    return results;
  }, {});

}

function getRaceList(raceResults) {

  //deep copy
  let clonedResults = JSON.parse(JSON.stringify(raceResults)) ;

  let list = Object.keys(clonedResults)
  .map(raceId => {
    delete clonedResults[raceId].results;
    return clonedResults[raceId];
  })
  .sort((r1, r2) => {
    return new Date(r1.date) - new Date(r2.date);
  });

  return list;
}

function getScores(raceResults, participantMap) {

  Object.keys(raceResults).map(key => {return raceResults[key];})
  .forEach(race => {
    for (let leg = 1; leg <= race.numLegs; leg++) {
      let results = race.results
      .filter(result => {return result.unknownTime === false;})
      .map(result => {
        return {
          driverId: result.driverId,
          rank: result.legs[leg].rank,
        };
      })
      .sort((r1, r2) => {return r1 - r2;})
      .map(result => {
        let driver = new Player(result.driverId);
        let driverSkill = participantMap.get(result.driverId).driverSkill;
        let team =  new Team(result.driverId.toString(), driver, driverSkill)
        return [team, result.rank]
      })
      .reduce((result, item) => {
        result.teams.push(item[0]);
        result.ranks.push(item[1]);
        return result;
      }, {teams: [], ranks: []});

      let newSkills = TrueSkillCalculator.calculateNewRatings(GameInfo, results.teams, results.ranks);

      results.teams.forEach(team => {
        let player = team.getPlayers()[0];
        participantMap.get(player.getId()).driverSkill = newSkills[player];
      });

    }
  });

}

function getDriverStats(raceResults, participantMap) {
  let stats = Object.keys(raceResults).map(key => {return raceResults[key].results;})
  .reduce((drivers, curRace) => {
    curRace.forEach(driver => {
      if (!drivers.has(driver.driverId)) {
        drivers.set(driver.driverId, {
          id: driver.driverId,
          name: participantMap.get(driver.driverId).fullname,
          starts: 0,
          finishes: 0,
          wins: 0,
          podiums: 0,
          skillRanking: null
        });
      }

      let thisDriver = drivers.get(driver.driverId);
      thisDriver.starts++;
      if (!driver.dnf) {thisDriver.finishes++;}
      if (driver.rank === 1) {thisDriver.wins++;}
      if (driver.rank <= 3) {thisDriver.podiums++;}
    });
    return drivers;
  }, new Map());
  return Array.from(stats.values());
}

function main() {
  console.log('Getting data from Google Sheets');
  Promise.all([getSheetRows(Sheet, 1).then(getParticipantMap),
               getSheetRows(Sheet, 3).then(getRaceMap),
               getSheetRows(Sheet, 2)
             ])
  .then((promiseResults) => {
    console.log('Data from sheets pulled');
    let participantMap = promiseResults[0];
    let raceMap = promiseResults[1];
    let legResults = getLegResults(promiseResults[2], raceMap);

    let raceResults = getRaceResults(legResults, participantMap, raceMap);
    let raceList = getRaceList(raceResults, raceMap);

    let driverScores = getScores(raceResults, participantMap);

    let driverStats = getDriverStats(raceResults, participantMap);

    console.log('Updating Firebase');
    let fbRef = new Firebase('https://dttdata.firebaseio.com/');
    fbRef.set({
      races: raceList,
      raceResults: raceResults,
      driverStats: driverStats
    });

    // //output the ranked list of drivers
    // let rankedDrivers = Array.from(participantMap.values())
    // .filter(a => { return typeof a.driverSkill !== 'undefined';})
    // .sort((a,b) => {
    //   return b.driverSkill.conservativeRating - a.driverSkill.conservativeRating;
    // });
    // rankedDrivers.forEach((driver,index) => {
    //   console.log(`${index} ${driver.driverSkill.conservativeRating.toFixed(3)} ` +
    //               `${driver.driverSkill.mean.toFixed(3)} ` +
    //               `${driver.name.lastname}, ${driver.name.firstname}`);
    // });
    //
    console.log('all done');
  })
  .catch(err => {
    console.error(`ERROR: ${err}`);
    console.error(err.stack);
    throw err;
  });
}

main();
<|endoftext|>


<commit_before>/* jshint white: false, curly: false */
'use strict';

var zlib = require('zlib');

function Stream(data){
	this._buffer = new Buffer(data);
	this._nBytes = data.length;
	this.offset  = 0;

	this._bitBuffer  = null;
	this._bitOffset  = 8;
	this._lastOffset = 0;
}
module.exports = Stream;

(function(){

	Stream.prototype = {
		readSI8: function(){
			var val = this._buffer.readInt8(this.offset);
			this.offset += 1;
			return val;
		},

		readSI16: function(){
			var val = this._buffer.readInt16LE(this.offset);
			this.offset += 2;
			return val;
		},

		readSI32: function(){
			var val = this._buffer.readInt32LE(this.offset);
			this.offset += 4;
			return val;
		},

		readUI8: function(){
			var val = this._buffer.readUInt8(this.offset);
			this.offset += 1;
			return val;
		},

		readUI16: function(){
			var val = this._buffer.readUInt16LE(this.offset);
			this.offset += 2;
			return val;
		},

		readUI32: function(){
			var val = this._buffer.readUInt32LE(this.offset);
			this.offset += 4;
			return val;
		},

		readFixed: function(){
			var val = this._buffer.readInt32LE(this.offset) / 65536;
			this.offset += 4;
			return val;
		},

		readFixed8: function(){
			var val = this._buffer.readInt16LE(this.offset) / 256;
			this.offset += 2;
			return val;
		},

		readFloat: function(){
			var val = this._buffer.readFloatLE(this.offset);
			this.offset += 4;
			return val;
		},

		readDouble: function(){
			var val = this._buffer.readDoubleLE(this.offset);
			this.offset += 8;
			return val;
		},

		readEncodedU32: function(){
			var val = this._buffer[this.offset++];
			if (!(val & 0x00000080)) {
				return val;
			}

			val = (val & 0x0000007f) | this._buffer[this.offset++] << 7;
			if (!(val & 0x00004000)) {
				return val;
			}

			val = (val & 0x00003fff) | this._buffer[this.offset++] << 14;
			if (!(val & 0x00200000)) {
				return val;
			}

			val = (val & 0x001fffff) | this._buffer[this.offset++] << 21;
			if (!(val & 0x10000000)) {
				return val;
			}

			val = (val & 0x0fffffff) | this._buffer[this.offset++] << 28;
			return val;
		},

		readUB: function(numBits){
			var val = 0;
			for(var i = 0; i < numBits; i++){
				if(this._lastOffset !== this.offset || this._bitOffset === 8){
					this._bitBuffer = this._buffer[this.offset++];
					this._lastOffset = this.offset;
					this._bitOffset = 0;
				}
				val = (val << 1) | (this._bitBuffer & (0x80 >> this._bitOffset++) ? 1 : 0);
			}
			return val;

			var val = 0;
			for(var i = 0; i < numBits; i++){
				if(8 === this._bitOffset){
					this._bitBuffer = this._buffer[this.offset++];
					this._bitOffset = 0;
				}
				val = (val << 1) | (this._bitBuffer & (0x80 >> this._bitOffset++) ? 1 : 0);
			}
			return val;
		},

		readSB: function(numBits){
			var val = this.readUB(numBits);
			if(val >> (numBits - 1)){ val -= Math.pow(2, numBits); }
			return val;
		},

		readFB: function(numBits){
			return this.readSB(numBits) * Math.pow(2, -16);
		},

		readString: function(numBytes){
			var start = this.offset;
			if (numBytes) {
				this.offset += numBytes;
			} else {
				numBytes = 0;
				while (this._buffer[this.offset++] !== 0) {
					numBytes += 1;
				}
			}
			return this._buffer.slice(start, start + numBytes).toString();
		},

		readBytes: function(numBytes){
			var bytes = this._buffer.slice(this.offset, this.offset + numBytes);
			this.offset += numBytes;
			return bytes;
		},

		readBool: function(numBits){
			return !!this.readUB(numBits || 1);
		},

		pos: function(){
			return this.offset;
		},

		seek: function(offset, absolute){
			this.offset = (absolute ? 0 : this.offset) + offset;
			// this.align();
			return this;
		},

		align: function(){
			this._lastOffset = 0;
			return this;
		},

		readLanguageCode: function(){
			return this.readUI8();
		},

		readRGB: function(){
			return {
				red:   this.readUI8(),
				green: this.readUI8(),
				blue:  this.readUI8()
			};
		},

		readRGBA: function(){
			var rgba = this.readRGB();
			rgba.alpha = this.readUI8() / 255;
			return rgba;
		},

		readARGB: function(){
			var alpha = this.readUI8() / 255;
			var rgba = this.readRGB();
			rgba.alpha = alpha;
			return rgba;
		},

		readRect: function(){
			var numBits = this.readUB(5);
			var rect = {
				left:   this.readSB(numBits),
				right:  this.readSB(numBits),
				top:    this.readSB(numBits),
				bottom: this.readSB(numBits)
			};

			this.align();

			if (numBits === 1) {
				// Hack of the week (the year?)
				// Seems that Flash fails to encode an empty rectangle (all zero);
				// Instead of 2 bytes 08 00 (len=1 on 5 bits, then 4 times "0" bit, so 9 bits + 7 of padding)
				// Flash serializes 08 00 followed by 4 UI32 of value 0 (so 18 bytes in total)
				// We simply skip this useless zeros here, but verify it was actually zeros we skipped.
				// If one day we find something else than an all zero rect (e.g. a 0,0,1,1) we will
				// have to adapt some more here...

				// Edit: it looks like this hack is making the export fail on some swf
				// (may be those from the latest flash versions?)
				// So the bytes need to be tested in order to know whether the hack is necessary
				var nextByte0 = this._buffer.slice(this.offset, this.offset + 1);
				var nextByte1 = this._buffer.slice(this.offset, this.offset + 2);

				if (nextByte0 === 0 && nextByte1 === 0) {
					var empty = this.readBytes(16); //console.warn('numBits=1, rect:',rect)
					for(var i = 0; i < 16; i++) {
						if(empty[i] !== 0) { throw new Error('Unexpected rectangle data'); }
					}
				}
			}
			return rect;
		},

		readMatrix: function(){
			var numBits, scaleX, scaleY, skewX, skewY;

			if (this.readBool() /* has scale */){
				numBits = this.readUB(5);
				scaleX = this.readFB(numBits);
				scaleY = this.readFB(numBits);
			} else {
				scaleX = scaleY = 1.0;
			}

			if (this.readBool() /* has rotation */){
				numBits = this.readUB(5);
				skewX = this.readFB(numBits);
				skewY = this.readFB(numBits);
			} else {
				skewX =  skewY = 0.0;
			}

			numBits = this.readUB(5);
			var matrix = {
				scaleX: scaleX, scaleY: scaleY,
				skewX: skewX, skewY: skewY,
				moveX: this.readSB(numBits), moveY: this.readSB(numBits)
			};

			this.align();
			return matrix;
		},

		readCxform: function(){
			return this._readCxf();
		},

		readCxformA: function(){
			return this._readCxf(true);
		},

		_readCxf: function(withAlpha){
			var hasAddTerms  = this.readBool();
			var hasMultTerms = this.readBool();
			var numBits      = this.readUB(4);

			var multR, multG, multB, multA;
			var addR, addG, addB, addA;
			if (hasMultTerms){
				multR = this.readSB(numBits) / 256;
				multG = this.readSB(numBits) / 256;
				multB = this.readSB(numBits) / 256;
				multA = withAlpha ? this.readSB(numBits) / 256 : 1;
			} else {
				multR = multG = multB = multA = 1;
			}

			if (hasAddTerms){
				addR = this.readSB(numBits) / 256;
				addG = this.readSB(numBits) / 256;
				addB = this.readSB(numBits) / 256;
				addA = withAlpha ? this.readSB(numBits) / 256 : 0;
			} else {
				addR = addG = addB = addA = 0;
			}

			var cxform = {
				multR: multR, multG: multG, multB: multB, multA: multA,
				addR: addR, addG: addG, addB: addB, addA: addA
			};

			this.align();
			return cxform;
		},

		// Decompresses the remaining of the stream and get ready to read from here
		decompressAsync: function(whenDone) {
			var offset = this.offset;
			var data = this._buffer.slice(offset);

			var self = this;
			zlib.inflate(data, function (err, buffer) {
				if(err) { throw new Error('Invalid compressed data. ' + err); }
				self._buffer = Buffer.concat([self._buffer.slice(0, offset), buffer]);
				self._nBytes  = self._buffer.length;
				whenDone();
			});
		}
	};
})();
<commit_msg>* Fixed a bug with empty rectangles parsing<commit_after>/* jshint white: false, curly: false */
'use strict';

var zlib = require('zlib');

function Stream(data){
	this._buffer = new Buffer(data);
	this._nBytes = data.length;
	this.offset  = 0;

	this._bitBuffer  = null;
	this._bitOffset  = 8;
	this._lastOffset = 0;
}
module.exports = Stream;

(function(){

	Stream.prototype = {
		readSI8: function(){
			var val = this._buffer.readInt8(this.offset);
			this.offset += 1;
			return val;
		},

		readSI16: function(){
			var val = this._buffer.readInt16LE(this.offset);
			this.offset += 2;
			return val;
		},

		readSI32: function(){
			var val = this._buffer.readInt32LE(this.offset);
			this.offset += 4;
			return val;
		},

		readUI8: function(){
			var val = this._buffer.readUInt8(this.offset);
			this.offset += 1;
			return val;
		},

		readUI16: function(){
			var val = this._buffer.readUInt16LE(this.offset);
			this.offset += 2;
			return val;
		},

		readUI32: function(){
			var val = this._buffer.readUInt32LE(this.offset);
			this.offset += 4;
			return val;
		},

		readFixed: function(){
			var val = this._buffer.readInt32LE(this.offset) / 65536;
			this.offset += 4;
			return val;
		},

		readFixed8: function(){
			var val = this._buffer.readInt16LE(this.offset) / 256;
			this.offset += 2;
			return val;
		},

		readFloat: function(){
			var val = this._buffer.readFloatLE(this.offset);
			this.offset += 4;
			return val;
		},

		readDouble: function(){
			var val = this._buffer.readDoubleLE(this.offset);
			this.offset += 8;
			return val;
		},

		readEncodedU32: function(){
			var val = this._buffer[this.offset++];
			if (!(val & 0x00000080)) {
				return val;
			}

			val = (val & 0x0000007f) | this._buffer[this.offset++] << 7;
			if (!(val & 0x00004000)) {
				return val;
			}

			val = (val & 0x00003fff) | this._buffer[this.offset++] << 14;
			if (!(val & 0x00200000)) {
				return val;
			}

			val = (val & 0x001fffff) | this._buffer[this.offset++] << 21;
			if (!(val & 0x10000000)) {
				return val;
			}

			val = (val & 0x0fffffff) | this._buffer[this.offset++] << 28;
			return val;
		},

		readUB: function(numBits){
			var val = 0;
			for(var i = 0; i < numBits; i++){
				if(this._lastOffset !== this.offset || this._bitOffset === 8){
					this._bitBuffer = this._buffer[this.offset++];
					this._lastOffset = this.offset;
					this._bitOffset = 0;
				}
				val = (val << 1) | (this._bitBuffer & (0x80 >> this._bitOffset++) ? 1 : 0);
			}
			return val;

			var val = 0;
			for(var i = 0; i < numBits; i++){
				if(8 === this._bitOffset){
					this._bitBuffer = this._buffer[this.offset++];
					this._bitOffset = 0;
				}
				val = (val << 1) | (this._bitBuffer & (0x80 >> this._bitOffset++) ? 1 : 0);
			}
			return val;
		},

		readSB: function(numBits){
			var val = this.readUB(numBits);
			if(val >> (numBits - 1)){ val -= Math.pow(2, numBits); }
			return val;
		},

		readFB: function(numBits){
			return this.readSB(numBits) * Math.pow(2, -16);
		},

		readString: function(numBytes){
			var start = this.offset;
			if (numBytes) {
				this.offset += numBytes;
			} else {
				numBytes = 0;
				while (this._buffer[this.offset++] !== 0) {
					numBytes += 1;
				}
			}
			return this._buffer.slice(start, start + numBytes).toString();
		},

		readBytes: function(numBytes){
			var bytes = this._buffer.slice(this.offset, this.offset + numBytes);
			this.offset += numBytes;
			return bytes;
		},

		readBool: function(numBits){
			return !!this.readUB(numBits || 1);
		},

		pos: function(){
			return this.offset;
		},

		seek: function(offset, absolute){
			this.offset = (absolute ? 0 : this.offset) + offset;
			// this.align();
			return this;
		},

		align: function(){
			this._lastOffset = 0;
			return this;
		},

		readLanguageCode: function(){
			return this.readUI8();
		},

		readRGB: function(){
			return {
				red:   this.readUI8(),
				green: this.readUI8(),
				blue:  this.readUI8()
			};
		},

		readRGBA: function(){
			var rgba = this.readRGB();
			rgba.alpha = this.readUI8() / 255;
			return rgba;
		},

		readARGB: function(){
			var alpha = this.readUI8() / 255;
			var rgba = this.readRGB();
			rgba.alpha = alpha;
			return rgba;
		},

		readRect: function(){
			var numBits = this.readUB(5);
			var rect = {
				left:   this.readSB(numBits),
				right:  this.readSB(numBits),
				top:    this.readSB(numBits),
				bottom: this.readSB(numBits)
			};

			this.align();

			if (numBits === 1) {
				// Hack of the week (the year?)
				// Seems that Flash fails to encode an empty rectangle (all zero);
				// Instead of 2 bytes 08 00 (len=1 on 5 bits, then 4 times "0" bit, so 9 bits + 7 of padding)
				// Flash serializes 08 00 followed by 4 UI32 of value 0 (so 18 bytes in total)
				// We simply skip this useless zeros here, but verify it was actually zeros we skipped.
				// If one day we find something else than an all zero rect (e.g. a 0,0,1,1) we will
				// have to adapt some more here...

				// Edit: it looks like this hack is making the export fail on some swf
				// (may be those from the latest flash versions?)
				// So the bytes need to be tested in order to know whether the hack is necessary
				if (this._buffer.readUInt16LE(this.offset) === 0) {
					var empty = this.readBytes(16); //console.warn('numBits=1, rect:',rect)
					for(var i = 0; i < 16; i++) {
						if(empty[i] !== 0) { throw new Error('Unexpected rectangle data'); }
					}
				}
			}
			return rect;
		},

		readMatrix: function(){
			var numBits, scaleX, scaleY, skewX, skewY;

			if (this.readBool() /* has scale */){
				numBits = this.readUB(5);
				scaleX = this.readFB(numBits);
				scaleY = this.readFB(numBits);
			} else {
				scaleX = scaleY = 1.0;
			}

			if (this.readBool() /* has rotation */){
				numBits = this.readUB(5);
				skewX = this.readFB(numBits);
				skewY = this.readFB(numBits);
			} else {
				skewX =  skewY = 0.0;
			}

			numBits = this.readUB(5);
			var matrix = {
				scaleX: scaleX, scaleY: scaleY,
				skewX: skewX, skewY: skewY,
				moveX: this.readSB(numBits), moveY: this.readSB(numBits)
			};

			this.align();
			return matrix;
		},

		readCxform: function(){
			return this._readCxf();
		},

		readCxformA: function(){
			return this._readCxf(true);
		},

		_readCxf: function(withAlpha){
			var hasAddTerms  = this.readBool();
			var hasMultTerms = this.readBool();
			var numBits      = this.readUB(4);

			var multR, multG, multB, multA;
			var addR, addG, addB, addA;
			if (hasMultTerms){
				multR = this.readSB(numBits) / 256;
				multG = this.readSB(numBits) / 256;
				multB = this.readSB(numBits) / 256;
				multA = withAlpha ? this.readSB(numBits) / 256 : 1;
			} else {
				multR = multG = multB = multA = 1;
			}

			if (hasAddTerms){
				addR = this.readSB(numBits) / 256;
				addG = this.readSB(numBits) / 256;
				addB = this.readSB(numBits) / 256;
				addA = withAlpha ? this.readSB(numBits) / 256 : 0;
			} else {
				addR = addG = addB = addA = 0;
			}

			var cxform = {
				multR: multR, multG: multG, multB: multB, multA: multA,
				addR: addR, addG: addG, addB: addB, addA: addA
			};

			this.align();
			return cxform;
		},

		// Decompresses the remaining of the stream and get ready to read from here
		decompressAsync: function(whenDone) {
			var offset = this.offset;
			var data = this._buffer.slice(offset);

			var self = this;
			zlib.inflate(data, function (err, buffer) {
				if(err) { throw new Error('Invalid compressed data. ' + err); }
				self._buffer = Buffer.concat([self._buffer.slice(0, offset), buffer]);
				self._nBytes  = self._buffer.length;
				whenDone();
			});
		}
	};
})();
<|endoftext|>