<commit_before>describe("About Objects", function () {

  describe("Properties", function () {
    var meglomaniac;

    beforeEach(function () {
       meglomaniac = {  mastermind: "Joker", henchwoman: "Harley" };
    });

    it("should confirm objects are collections of properties", function () {
      expect(meglomaniac.mastermind).toBe(FILL_ME_IN);
    });

    it("should confirm that properties are case sensitive", function () {
      expect(meglomaniac.henchwoman).toBe(FILL_ME_IN);
      expect(meglomaniac.henchWoman).toBe(FILL_ME_IN);
    });
  });

  it("should know properties that are functions act like methods", function () {
    var meglomaniac = {
      mastermind : "Brain",
      henchman: "Pinky",
      battleCry: function (noOfBrains) {
        return "They are " + this.henchman + " and the" +
          Array(noOfBrains + 1).join(" " + this.mastermind);
      }
    };

    var battleCry = meglomaniac.battleCry(4);
    expect(FILL_ME_IN).toMatch(battleCry);
  });

  it("should confirm that when a function is attached to an object, 'this' refers to the object", function () {
    var currentDate = new Date()
    var currentYear = (currentDate.getFullYear());
    var meglomaniac = {
      mastermind: "James Wood",
      henchman: "Adam West",
      birthYear: 1970,
      calculateAge: function () {
        return currentYear - this.birthYear;
      }
    };

    expect(currentYear).toBe(FILL_ME_IN);
    expect(meglomaniac.calculateAge()).toBe(FILL_ME_IN);
  });

  describe("'in' keyword", function () {
    var meglomaniac;
    beforeEach(function () {
      meglomaniac = {
        mastermind: "The Monarch",
        henchwoman: "Dr Girlfriend",
        theBomb: true
      };
    });

    it("should have the bomb", function () {

      var hasBomb = "theBomb" in meglomaniac;

      expect(hasBomb).toBe(FILL_ME_IN);
    });

    it("should not have the detonator however", function () {

      var hasDetonator = "theDetonator" in meglomaniac;

      expect(hasDetonator).toBe(FILL_ME_IN);
    });
  });

  it("should know that properties can be added and deleted", function () {
    var meglomaniac = { mastermind : "Agent Smith", henchman: "Agent Smith" };

    expect("secretary" in meglomaniac).toBe(FILL_ME_IN);

    meglomaniac.secretary = "Agent Smith";
    expect("secretary" in meglomaniac).toBe(FILL_ME_IN);

    delete meglomaniac.henchman;
    expect("henchman" in meglomaniac).toBe(FILL_ME_IN);
  });

  it("should use prototype to add to all objects", function () {
    function Circle(radius)
    {
      this.radius = radius;
    }

    var simpleCircle = new Circle(10);
    var colouredCircle = new Circle(5);
    colouredCircle.colour = "red";

    expect(simpleCircle.colour).toBe(FILL_ME_IN);
    expect(colouredCircle.colour).toBe(FILL_ME_IN);

    Circle.prototype.describe = function () {
      return "This circle has a radius of: " + this.radius;
    };

    expect(simpleCircle.describe()).toBe(FILL_ME_IN);
    expect(colouredCircle.describe()).toBe(FILL_ME_IN);
  });
});
<commit_msg>Objects finished<commit_after>describe("About Objects", function () {

  describe("Properties", function () {
    var meglomaniac;

    beforeEach(function () {
       meglomaniac = {  mastermind: "Joker", henchwoman: "Harley" };
    });

    it("should confirm objects are collections of properties", function () {
      expect(meglomaniac.mastermind).toBe("Joker");
    });

    it("should confirm that properties are case sensitive", function () {
      expect(meglomaniac.henchwoman).toBe("Harley");
      expect(meglomaniac.henchWoman).toBe(undefined);
    });
  });

  it("should know properties that are functions act like methods", function () {
    var meglomaniac = {
      mastermind : "Brain",
      henchman: "Pinky",
      battleCry: function (noOfBrains) {
        return "They are " + this.henchman + " and the" +
          Array(noOfBrains + 1).join(" " + this.mastermind);
      }
    };

    var battleCry = meglomaniac.battleCry(4);
    expect("They are Pinky and the Brain Brain Brain Brain Brain").toMatch(battleCry);
  });

  it("should confirm that when a function is attached to an object, 'this' refers to the object", function () {
    var currentDate = new Date()
    var currentYear = (currentDate.getFullYear());
    var meglomaniac = {
      mastermind: "James Wood",
      henchman: "Adam West",
      birthYear: 1970,
      calculateAge: function () {
        return currentYear - this.birthYear;
      }
    };

    expect(currentYear).toBe(2015);
    expect(meglomaniac.calculateAge()).toBe(45);
  });

  describe("'in' keyword", function () {
    var meglomaniac;
    beforeEach(function () {
      meglomaniac = {
        mastermind: "The Monarch",
        henchwoman: "Dr Girlfriend",
        theBomb: true
      };
    });

    it("should have the bomb", function () {

      var hasBomb = "theBomb" in meglomaniac;

      expect(hasBomb).toBe(true);
    });

    it("should not have the detonator however", function () {

      var hasDetonator = "theDetonator" in meglomaniac;

      expect(hasDetonator).toBe(false);
    });
  });

  it("should know that properties can be added and deleted", function () {
    var meglomaniac = { mastermind : "Agent Smith", henchman: "Agent Smith" };

    expect("secretary" in meglomaniac).toBe(false);

    meglomaniac.secretary = "Agent Smith";
    expect("secretary" in meglomaniac).toBe(true);

    delete meglomaniac.henchman;
    expect("henchman" in meglomaniac).toBe(false);
  });

  it("should use prototype to add to all objects", function () {
    function Circle(radius)
    {
      this.radius = radius;
    }

    var simpleCircle = new Circle(10);
    var colouredCircle = new Circle(5);
    colouredCircle.colour = "red";

    expect(simpleCircle.colour).toBe(undefined);
    expect(colouredCircle.colour).toBe("red");

    Circle.prototype.describe = function () {
      return "This circle has a radius of: " + this.radius;
    };

    expect(simpleCircle.describe()).toBe("This circle has a radius of: 10");
    expect(colouredCircle.describe()).toBe("This circle has a radius of: 5");
  });
});
<|endoftext|>


<commit_before>Meteor.subscribe('userData');

Accounts.ui.config({
    passwordSignupFields: 'USERNAME_ONLY'
});

Accounts.onLogin(function () {
    document.cookie = 'simple-portfolio-server=' + Accounts._storedLoginToken();

});

Template.fileUpload.events({
    'click .submit': function(event, template) {
        if (!Meteor.user()) {
            return;
        }

        var files = $('.userFile')[0].files;

        Session.set('message', 'Uploading');
        Session.set('results', []);
        var messages = [];
        for (var index = 0, numFiles = files.length; index < numFiles; index++) {
            var fsFile = new FS.File(files[index]);
            fsFile.owner = Meteor.userId();
            Files.insert(fsFile, function (err, fileObj) {
                // Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
                var result = {};
                if (err) {
                    result.fileMessage = 'Error uploading file' + err;
                    result.link = '';
                }
                else {
                    result.fileMessage = 'Uploaded file';
                    result.link = Meteor.absoluteUrl() + Meteor.user().username + '/' + fileObj.name();
                }
                var results = Session.get('results').concat(result);
                Session.set('results', results);
                if (results.length === numFiles) {
                    Session.set('message', 'Done');
                }
            });
        };
    }
});

Template.uploadedStatus.helpers({
    'message': function () { return Session.get('message'); },
    'results': function () { return Session.get('results'); }
});
<commit_msg>Make sure the cookie isn't set multiple times<commit_after>Meteor.subscribe('userData');

Accounts.ui.config({
    passwordSignupFields: 'USERNAME_ONLY'
});

Accounts.onLogin(function () {
    document.cookie = 'simple-portfolio-server=' + Accounts._storedLoginToken() + '; path=/';

});

Template.fileUpload.events({
    'click .submit': function(event, template) {
        if (!Meteor.user()) {
            return;
        }

        var files = $('.userFile')[0].files;

        Session.set('message', 'Uploading');
        Session.set('results', []);
        var messages = [];
        for (var index = 0, numFiles = files.length; index < numFiles; index++) {
            var fsFile = new FS.File(files[index]);
            fsFile.owner = Meteor.userId();
            Files.insert(fsFile, function (err, fileObj) {
                // Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
                var result = {};
                if (err) {
                    result.fileMessage = 'Error uploading file' + err;
                    result.link = '';
                }
                else {
                    result.fileMessage = 'Uploaded file';
                    result.link = Meteor.absoluteUrl() + Meteor.user().username + '/' + fileObj.name();
                }
                var results = Session.get('results').concat(result);
                Session.set('results', results);
                if (results.length === numFiles) {
                    Session.set('message', 'Done');
                }
            });
        };
    }
});

Template.uploadedStatus.helpers({
    'message': function () { return Session.get('message'); },
    'results': function () { return Session.get('results'); }
});
<|endoftext|>


<commit_before>/**
 * Loop through keys in properties argument and save each to the userProperties for the script
 *
 * @param {object} properties contains all properties that need to be saved to userProperties
 */
function saveProperties(propertiesToSave) {

    var userProperties = PropertiesService.getUserProperties();
    var propsObj = userProperties.getProperties();

    for (var key in propertiesToSave) {

        // skip loop if the property is from prototype
        if(!propsObj.hasOwnProperty(key)) continue;

        userProperties.setProperty(key, propertiesToSave[key]);

    }

    return;

}

/**
 * Create a trigger to run copy() in @param seconds seconds
 * @param {number} seconds number of seconds after present to fire the trigger
 */
function createTrigger(seconds) {
    // create trigger for 'seconds' seconds from now
    ScriptApp.newTrigger('copy')
        .timeBased()
        .after(seconds*1000)
        .create();

    return;
}

/**
 * Returns token for use with Google Picker
 */
function getOAuthToken() {
    return ScriptApp.getOAuthToken();
}

<commit_msg>fixed bug in saveProperties that was causing new properties to be ignored<commit_after>/**
 * Loop through keys in properties argument and save each to the userProperties for the script
 *
 * @param {object} properties contains all properties that need to be saved to userProperties
 */
function saveProperties(propertiesToSave) {

    var userProperties = PropertiesService.getUserProperties();

    for (var key in propertiesToSave) {

        // skip loop if the property is from prototype
        if(!propertiesToSave.hasOwnProperty(key)) continue;

        userProperties.setProperty(key, propertiesToSave[key]);

    }

    return;

}

/**
 * Create a trigger to run copy() in @param seconds seconds
 * @param {number} seconds number of seconds after present to fire the trigger
 */
function createTrigger(seconds) {
    // create trigger for 'seconds' seconds from now
    ScriptApp.newTrigger('copy')
        .timeBased()
        .after(seconds*1000)
        .create();

    return;
}

/**
 * Returns token for use with Google Picker
 */
function getOAuthToken() {
    return ScriptApp.getOAuthToken();
}

<|endoftext|>


<commit_before>// Text rendering style

import Texture from '../../gl/texture';
import WorkerBroker from '../../utils/worker_broker';
import Utils from '../../utils/utils';
import {Points} from '../points/points';
import CanvasText from './canvas_text';
import LabelBuilder from './label_builder';
import TextSettings from './text_settings';
import LayoutSettings from './layout_settings';
import RepeatGroup from './repeat_group';
import {StyleParser} from '../style_parser';

import log from 'loglevel';

export let TextStyle = Object.create(Points);

Object.assign(TextStyle, {
    name: 'text',
    super: Points,
    built_in: true,
    selection: false, // no feature selection for text by default

    init() {

        this.super.init.apply(this, arguments);

        // Provide a hook for this object to be called from worker threads
        this.main_thread_target = 'TextStyle-' + this.name;
        if (Utils.isMainThread) {
            WorkerBroker.addTarget(this.main_thread_target, this);
        }

        // Point style (parent class) requires texturing to be turned on
        // (labels are always drawn with textures)
        this.defines.TANGRAM_POINT_TEXTURE = true;

        // Manually un-multiply alpha, because Canvas text rasterization is pre-multiplied
        this.defines.TANGRAM_UNMULTIPLY_ALPHA = true;

        this.reset();
    },

    reset() {
        this.super.reset.call(this);
        this.texts = {}; // unique texts, grouped by tile, by style
        this.canvas = {};
    },

    // Called on main thread to release tile-specific resources
    freeTile (tile) {
        delete this.texts[tile];
        delete this.canvas[tile];
    },

    // Override
    startData (tile) {
        let tile_data = this.super.startData.apply(this, arguments);
        tile_data.queue = [];
        return tile_data;
    },

    // Override to queue features instead of processing immediately
    addFeature (feature, draw, context) {
        let tile = context.tile;
        if (tile.generation !== this.generation) {
            return;
        }

        // Called here because otherwise it will be delayed until the feature queue is parsed,
        // and we want the preprocessing done before we evaluate text style below
        draw = this.preprocess(draw);
        if (!draw) {
            return;
        }

        // Collect text - default source is feature.properties.name
        let text;
        let source = draw.text_source || 'name';

        if (typeof source === 'string') {
            text = feature.properties[source];
        } else if (typeof source === 'function') {
            text = source(context);
        }

        if (text == null) {
            return; // no text for this feature
        }

        // Compute text style and layout settings for this feature label
        let layout = LayoutSettings.compute(feature, draw, text, context, tile);
        let text_settings = TextSettings.compute(feature, draw, context);
        let text_settings_key = TextSettings.key(text_settings);

        // first label in tile, or with this style?
        this.texts[tile.key] = this.texts[tile.key] || {};
        this.texts[tile.key][text_settings_key] = this.texts[tile.key][text_settings_key] || {};

        // unique text strings, grouped by text drawing style
        if (!this.texts[tile.key][text_settings_key][text]) {
            // first label with this text/style/tile combination, make a new label entry
            this.texts[tile.key][text_settings_key][text] = {
                text_settings,
                ref: 0 // # of times this text/style combo appears in tile
            };
        }

        // Queue the feature for processing
        if (!this.tile_data[tile.key]) {
            this.startData(tile.key);
        }
        this.tile_data[tile.key].queue.push({
            feature, draw, context,
            text, text_settings_key, layout
        });
    },

    // Override
    endData (tile) {
        let tile_data = this.tile_data[tile];
        let count = Object.keys(this.texts[tile]||{}).length;

        if (!count) {
            return Promise.resolve();
        }

        // first call to main thread, ask for text pixel sizes
        return WorkerBroker.postMessage(this.main_thread_target+'.calcTextSizes', tile, this.texts[tile]).then(texts => {
            if (!texts) {
                this.freeTile(tile);
                return this.super.endData.apply(this, arguments);
            }
            this.texts[tile] = texts;

            let labels = this.createLabels(tile, tile_data.queue);
            if (!labels) {
                this.freeTile(tile);
                return this.super.endData.apply(this, arguments);
            }

            labels = this.discardLabels(tile, labels, texts);

            // No labels for this tile
            if (Object.keys(texts).length === 0) {
                this.freeTile(tile);
                WorkerBroker.postMessage(this.main_thread_target+'.freeTile', tile);
                // early exit
                return;
            }

            // second call to main thread, for rasterizing the set of texts
            return WorkerBroker.postMessage(this.main_thread_target+'.rasterizeTexts', tile, texts).then(({ texts, texture }) => {
                if (texts) {
                    this.texts[tile] = texts;

                    // Attach tile-specific label atlas to mesh as a texture uniform
                    tile_data.uniforms = { u_texture: texture };
                    tile_data.textures = [texture]; // assign texture ownership to tile - TODO: implement in VBOMesh

                    // Build queued features
                    labels.forEach(q => {
                        let text = q.label.text;
                        let text_settings_key = q.text_settings_key;
                        let text_info = this.texts[tile] && this.texts[tile][text_settings_key] && this.texts[tile][text_settings_key][text];
                        q.label.texcoords = text_info.texcoords;

                        this.super.addFeature.call(this, q.feature, q.draw, q.context, q.label);
                    });
                }

                tile_data.queue = []; // TODO: free earlier?
                this.freeTile(tile);
                return this.super.endData.apply(this, arguments);
            });
        });
    },

    createLabels (tile, feature_queue) {
        let priorities = {}; // labels, group by priority

        for (let f=0; f < feature_queue.length; f++) {
            let { feature, draw, context, text, text_settings_key, layout } = feature_queue[f];
            let text_info = this.texts[tile][text_settings_key][text];

            let labels = LabelBuilder.buildFromGeometry(text, text_info.size, feature.geometry, layout);
            for (let i = 0; i < labels.length; ++i) {
                let label = labels[i];
                priorities[layout.priority] = priorities[layout.priority] || [];
                priorities[layout.priority].push({
                    feature, draw, context,
                    text, text_settings_key, layout, label
                });
            }
        }

        return priorities;
    },

    // Test labels for collisions, higher to lower priority
    // When two collide, discard the lower-priority label
    discardLabels (tile, labels, texts) {
        let aabbs = [];
        let keep_labels = [];
        RepeatGroup.clear(tile);

        // Process labels by priority
        let priorities = Object.keys(labels).sort((a, b) => a - b);
        for (let priority of priorities) {
            if (!labels[priority]) { // no labels at this priority, skip to next
                continue;
            }

            for (let i = 0; i < labels[priority].length; i++) {
                let { label, text_settings_key, layout } = labels[priority][i];
                let settings = texts[text_settings_key][label.text];

                // test the label for intersections with other labels in the tile
                if (!layout.collide || !label.discard(aabbs)) {
                    // check for repeats
                    let check = RepeatGroup.check(label, layout, tile);
                    if (check) {
                        log.trace(`discard label '${label.text}', (one_per_group: ${check.one_per_group}), dist ${Math.sqrt(check.dist_sq)/layout.units_per_pixel} < ${Math.sqrt(check.repeat_dist_sq)/layout.units_per_pixel}`);
                        continue;
                    }
                    // register as placed for future repeat culling
                    RepeatGroup.add(label, layout, tile);

                    label.add(aabbs); // add label to currently visible set
                    keep_labels.push(labels[priority][i]);

                    // increment a count of how many times this style is used in the tile
                    settings.ref++;
                }
                else if (layout.collide) {
                    log.trace(`discard label '${label.text}' due to collision`);
                }
            }
        }

        // Remove text/style combinations that have no visible labels
        for (let style in texts) {
            for (let text in texts[style]) {
                // no labels for this text
                if (texts[style][text].ref < 1) {
                    delete texts[style][text];
                }
            }
        }
        for (let style in texts) {
            // no labels for this style
            if (Object.keys(texts[style]).length === 0) {
                delete texts[style];
            }
        }

        return keep_labels;
    },

    // Called on main thread from worker, to compute the size of each text string,
    // were it to be rendered. This info is then used to perform initial label culling, *before*
    // labels are actually rendered.
    calcTextSizes (tile, texts) {
        if(!this.canvas[tile]) {
            this.canvas[tile] = new CanvasText();
        }
        return this.canvas[tile].textSizes(tile, texts);
    },

    // Called on main thread from worker, to create atlas of labels for a tile
    rasterizeTexts (tile, texts) {
        if (!this.canvas[tile]) {
            return Promise.resolve({});
        }

        let canvas = this.canvas[tile];
        let texture_size = canvas.setTextureTextPositions(texts);
        log.trace(`text summary for tile ${tile}: fits in ${texture_size[0]}x${texture_size[1]}px`);

        // update canvas size & rasterize all the text strings we need
        canvas.resize(...texture_size);
        canvas.rasterize(tile, texts, texture_size);

        // create a texture
        let t = 'labels-' + tile + '-' + (TextStyle.texture_id++);
        let texture = new Texture(this.gl, t);
        texture.setCanvas(canvas.canvas, {
            filtering: 'linear',
            UNPACK_PREMULTIPLY_ALPHA_WEBGL: true
        });

        // we don't need tile canvas once it has been copied to to GPU
        delete this.canvas[tile];

        return { texts, texture: t }; // texture is returned by name (not instance)
    },

    // Sets up caching for draw rule properties
    _preprocess (draw) {
        if (!draw.font) {
            return;
        }

        // Colors
        draw.font.fill = StyleParser.cacheObject(draw.font.fill);
        if (draw.font.stroke) {
            draw.font.stroke.color = StyleParser.cacheObject(draw.font.stroke.color);
        }

        // Convert font and text stroke sizes
        draw.font.px_size = StyleParser.cacheObject(draw.font.size, CanvasText.fontPixelSize);
        if (draw.font.stroke && draw.font.stroke.width != null) {
            draw.font.stroke.width = StyleParser.cacheObject(draw.font.stroke.width, parseFloat);
        }

        return draw;
    },

    // Parse feature is called "late", after all labels have been created
    // The usual parsing done by _parseFeature() is handled by addFeature() above
    // Here we just pass the label through to the build functions below
    _parseFeature (feature, draw, context, label) {
        this.feature_style.label = label;
        return this.feature_style;
    },

    build (style, vertex_data) {
        let vertex_template = this.makeVertexTemplate(style);
        let label = style.label;

        this.texcoord_scale = label.texcoords;

        this.buildQuad(
            [label.position],
            label.size.logical_size,
            Utils.radToDeg(label.angle) || 0, vertex_data,
            vertex_template, label.options.offset
        );
    },

    buildLines (lines, style, vertex_data) {
        this.build(style, vertex_data);
    },

    buildPoints (points, style, vertex_data) {
        this.build(style, vertex_data);
    },

    buildPolygons (points, style, vertex_data) {
        this.build(style, vertex_data);
    }

});

TextStyle.texture_id = 0; // namespaces per-tile label textures
<commit_msg>labels: prevent main + worker threads from getting out of sync fixes race condition observed as scrambled labels, where tiles were pointing to invalid (previously freed) textures<commit_after>// Text rendering style

import Texture from '../../gl/texture';
import WorkerBroker from '../../utils/worker_broker';
import Utils from '../../utils/utils';
import {Points} from '../points/points';
import CanvasText from './canvas_text';
import LabelBuilder from './label_builder';
import TextSettings from './text_settings';
import LayoutSettings from './layout_settings';
import RepeatGroup from './repeat_group';
import {StyleParser} from '../style_parser';

import log from 'loglevel';

export let TextStyle = Object.create(Points);

Object.assign(TextStyle, {
    name: 'text',
    super: Points,
    built_in: true,
    selection: false, // no feature selection for text by default

    init() {

        this.super.init.apply(this, arguments);

        // Provide a hook for this object to be called from worker threads
        this.main_thread_target = 'TextStyle-' + this.name;
        if (Utils.isMainThread) {
            WorkerBroker.addTarget(this.main_thread_target, this);
        }

        // Point style (parent class) requires texturing to be turned on
        // (labels are always drawn with textures)
        this.defines.TANGRAM_POINT_TEXTURE = true;

        // Manually un-multiply alpha, because Canvas text rasterization is pre-multiplied
        this.defines.TANGRAM_UNMULTIPLY_ALPHA = true;

        this.reset();
    },

    reset() {
        this.super.reset.call(this);
        if (Utils.isMainThread) {
            this.canvas = new CanvasText();
        }
        else if (Utils.isWorkerThread) {
            this.queues = {};
            this.texts = {}; // unique texts, grouped by tile, by style
        }
    },

    // Called on worker thread to release tile-specific resources
    freeTile (tile) {
        delete this.texts[tile];
    },

    // Override to queue features instead of processing immediately
    addFeature (feature, draw, context) {
        let tile = context.tile;
        if (tile.generation !== this.generation) {
            return;
        }

        // Called here because otherwise it will be delayed until the feature queue is parsed,
        // and we want the preprocessing done before we evaluate text style below
        draw = this.preprocess(draw);
        if (!draw) {
            return;
        }

        // Collect text - default source is feature.properties.name
        let text;
        let source = draw.text_source || 'name';

        if (typeof source === 'string') {
            text = feature.properties[source];
        } else if (typeof source === 'function') {
            text = source(context);
        }

        if (text == null) {
            return; // no text for this feature
        }

        // Compute text style and layout settings for this feature label
        let layout = LayoutSettings.compute(feature, draw, text, context, tile);
        let text_settings = TextSettings.compute(feature, draw, context);
        let text_settings_key = TextSettings.key(text_settings);

        // first label in tile, or with this style?
        this.texts[tile.key] = this.texts[tile.key] || {};
        this.texts[tile.key][text_settings_key] = this.texts[tile.key][text_settings_key] || {};

        // unique text strings, grouped by text drawing style
        if (!this.texts[tile.key][text_settings_key][text]) {
            // first label with this text/style/tile combination, make a new label entry
            this.texts[tile.key][text_settings_key][text] = {
                text_settings,
                ref: 0 // # of times this text/style combo appears in tile
            };
        }

        // Queue the feature for processing
        if (!this.tile_data[tile.key]) {
            this.startData(tile.key);
        }

        if (!this.queues[tile.key]) {
            this.queues[tile.key] = [];
        }

        // this.tile_data[tile.key].queue.push({
        this.queues[tile.key].push({
            feature, draw, context,
            text, text_settings_key, layout
        });
    },

    // Override
    endData (tile) {
        let queue = this.queues[tile];
        this.queues[tile] = [];

        let count = Object.keys(this.texts[tile]||{}).length;

        if (!count) {
            return Promise.resolve();
        }

        // first call to main thread, ask for text pixel sizes
        return WorkerBroker.postMessage(this.main_thread_target+'.calcTextSizes', tile, this.texts[tile]).then(texts => {
            if (!texts) {
                this.freeTile(tile);
                return this.super.endData.apply(this, arguments);
            }
            this.texts[tile] = texts;

            let labels = this.createLabels(tile, queue);

            if (!labels) {
                this.freeTile(tile);
                return this.super.endData.apply(this, arguments);
            }

            labels = this.discardLabels(tile, labels, texts);

            // No labels for this tile
            if (Object.keys(texts).length === 0) {
                this.freeTile(tile);
                return this.super.endData.apply(this, arguments);
            }

            // second call to main thread, for rasterizing the set of texts
            return WorkerBroker.postMessage(this.main_thread_target+'.rasterizeTexts', tile, texts).then(({ texts, texture }) => {
                if (texts) {
                    this.texts[tile] = texts;

                    // Build queued features
                    labels.forEach(q => {
                        let text = q.label.text;
                        let text_settings_key = q.text_settings_key;
                        let text_info = this.texts[tile] && this.texts[tile][text_settings_key] && this.texts[tile][text_settings_key][text];
                        q.label.texcoords = text_info.texcoords;

                        this.super.addFeature.call(this, q.feature, q.draw, q.context, q.label);
                    });
                }

                this.freeTile(tile);

                return this.super.endData.apply(this, arguments).then(tile_data => {
                    // Attach tile-specific label atlas to mesh as a texture uniform
                    if (texture && tile_data) {
                        tile_data.uniforms = { u_texture: texture };
                        tile_data.textures = [texture]; // assign texture ownership to tile
                        return tile_data;
                    }
                });
            });
        });
    },

    createLabels (tile, feature_queue) {
        let priorities = {}; // labels, group by priority

        for (let f=0; f < feature_queue.length; f++) {
            let { feature, draw, context, text, text_settings_key, layout } = feature_queue[f];
            let text_info = this.texts[tile][text_settings_key][text];

            let labels = LabelBuilder.buildFromGeometry(text, text_info.size, feature.geometry, layout);
            for (let i = 0; i < labels.length; ++i) {
                let label = labels[i];
                priorities[layout.priority] = priorities[layout.priority] || [];
                priorities[layout.priority].push({
                    feature, draw, context,
                    text, text_settings_key, layout, label
                });
            }
        }

        return priorities;
    },

    // Test labels for collisions, higher to lower priority
    // When two collide, discard the lower-priority label
    discardLabels (tile, labels, texts) {
        let aabbs = [];
        let keep_labels = [];
        RepeatGroup.clear(tile);

        // Process labels by priority
        let priorities = Object.keys(labels).sort((a, b) => a - b);
        for (let priority of priorities) {
            if (!labels[priority]) { // no labels at this priority, skip to next
                continue;
            }

            for (let i = 0; i < labels[priority].length; i++) {
                let { label, text_settings_key, layout } = labels[priority][i];
                let settings = texts[text_settings_key][label.text];

                // test the label for intersections with other labels in the tile
                if (!layout.collide || !label.discard(aabbs)) {
                    // check for repeats
                    let check = RepeatGroup.check(label, layout, tile);
                    if (check) {
                        log.trace(`discard label '${label.text}', (one_per_group: ${check.one_per_group}), dist ${Math.sqrt(check.dist_sq)/layout.units_per_pixel} < ${Math.sqrt(check.repeat_dist_sq)/layout.units_per_pixel}`);
                        continue;
                    }
                    // register as placed for future repeat culling
                    RepeatGroup.add(label, layout, tile);

                    label.add(aabbs); // add label to currently visible set
                    keep_labels.push(labels[priority][i]);

                    // increment a count of how many times this style is used in the tile
                    settings.ref++;
                }
                else if (layout.collide) {
                    log.trace(`discard label '${label.text}' due to collision`);
                }
            }
        }

        // Remove text/style combinations that have no visible labels
        for (let style in texts) {
            for (let text in texts[style]) {
                // no labels for this text
                if (texts[style][text].ref < 1) {
                    delete texts[style][text];
                }
            }
        }
        for (let style in texts) {
            // no labels for this style
            if (Object.keys(texts[style]).length === 0) {
                delete texts[style];
            }
        }

        return keep_labels;
    },

    // Called on main thread from worker, to compute the size of each text string,
    // were it to be rendered. This info is then used to perform initial label culling, *before*
    // labels are actually rendered.
    calcTextSizes (tile, texts) {
        return this.canvas.textSizes(tile, texts);
    },

    // Called on main thread from worker, to create atlas of labels for a tile
    rasterizeTexts (tile, texts) {
        let canvas = new CanvasText();
        let texture_size = canvas.setTextureTextPositions(texts);
        log.trace(`text summary for tile ${tile}: fits in ${texture_size[0]}x${texture_size[1]}px`);

        // update canvas size & rasterize all the text strings we need
        canvas.resize(...texture_size);
        canvas.rasterize(tile, texts, texture_size);

        // create a texture
        let t = 'labels-' + tile + '-' + (TextStyle.texture_id++);
        let texture = new Texture(this.gl, t);
        texture.setCanvas(canvas.canvas, {
            filtering: 'linear',
            UNPACK_PREMULTIPLY_ALPHA_WEBGL: true
        });

        return { texts, texture: t }; // texture is returned by name (not instance)
    },

    // Sets up caching for draw rule properties
    _preprocess (draw) {
        if (!draw.font) {
            return;
        }

        // Colors
        draw.font.fill = StyleParser.cacheObject(draw.font.fill);
        if (draw.font.stroke) {
            draw.font.stroke.color = StyleParser.cacheObject(draw.font.stroke.color);
        }

        // Convert font and text stroke sizes
        draw.font.px_size = StyleParser.cacheObject(draw.font.size, CanvasText.fontPixelSize);
        if (draw.font.stroke && draw.font.stroke.width != null) {
            draw.font.stroke.width = StyleParser.cacheObject(draw.font.stroke.width, parseFloat);
        }

        return draw;
    },

    // Parse feature is called "late", after all labels have been created
    // The usual parsing done by _parseFeature() is handled by addFeature() above
    // Here we just pass the label through to the build functions below
    _parseFeature (feature, draw, context, label) {
        this.feature_style.label = label;
        return this.feature_style;
    },

    build (style, vertex_data) {
        let vertex_template = this.makeVertexTemplate(style);
        let label = style.label;

        this.texcoord_scale = label.texcoords;

        this.buildQuad(
            [label.position],
            label.size.logical_size,
            Utils.radToDeg(label.angle) || 0, vertex_data,
            vertex_template, label.options.offset
        );
    },

    buildLines (lines, style, vertex_data) {
        this.build(style, vertex_data);
    },

    buildPoints (points, style, vertex_data) {
        this.build(style, vertex_data);
    },

    buildPolygons (points, style, vertex_data) {
        this.build(style, vertex_data);
    }

});

TextStyle.texture_id = 0; // namespaces per-tile label textures
<|endoftext|>


<commit_before>SVG.extend(SVG.PathArray, {
  morph: function(array) {

    var startArr = this.value
      ,  destArr = this.parse(array)

    var startOffsetM = 0
      ,  destOffsetM = 0

    while(true){
      // stop if there is no M anymore
      if(startOffsetM === false && destOffsetM === false) break

      // find the next M in path array
      startOffsetNextM = findNextM(startArr, startOffsetM === false ? false : startOffsetM+1)
       destOffsetNextM = findNextM( destArr,  destOffsetM === false ? false :  destOffsetM+1)

      // We have to add one M to the startArray
      if(startOffsetM === false){
        var bbox = new SVG.PathArray(result.start).bbox()

        // when the last block had no bounding box we simply take the first M we got
        if(bbox.height == 0 || bbox.width == 0){
          startOffsetM =  startArr.push(startArr[0]) - 1
        }else{
          // we take the middle of the bbox instead when we got one
          startOffsetM = startArr.push( ['M', bbox.x + bbox.width/2, bbox.y + bbox.height/2 ] ) - 1
        }
      }

      // We have to add one M to the destArray
      if( destOffsetM === false){
        var bbox = new SVG.PathArray(result.dest).bbox()

        if(bbox.height == 0 || bbox.width == 0){
          destOffsetM =  destArr.push(destArr[0]) - 1
        }else{
          destOffsetM =  destArr.push( ['M', bbox.x + bbox.width/2, bbox.y + bbox.height/2 ] ) - 1
        }
      }

      // handle block from M to next M
      var result = handleBlock(startArr, startOffsetM, startOffsetNextM, destArr, destOffsetM, destOffsetNextM)

      // update the arrays to their new values
      startArr = startArr.slice(0, startOffsetM).concat(result.start, startOffsetNextM === false ? [] : startArr.slice(startOffsetNextM))
       destArr =  destArr.slice(0,  destOffsetM).concat(result.dest ,  destOffsetNextM === false ? [] :  destArr.slice( destOffsetNextM))

      // update offsets
      startOffsetM = startOffsetNextM === false ? false : startOffsetM + result.start.length
       destOffsetM =  destOffsetNextM === false ? false :  destOffsetM + result.dest.length

    }

    // copy back arrays
    this.value = startArr
    this.destination = destArr

    return this
  }
, at: function(pos) {
    // make sure a destination is defined
    if (!this.destination) return this

    // generate morphed path array
    for (var i = 0, il = this.value.length, array = []; i < il; ++i){

      // path array values have different length. so we need another loop
      for (var j = 1, len = this.value[i].length, el = [this.value[i][0]]; j < len; ++j) {

        el.push( this.value[i][j] + (this.destination[i][j] - this.value[i][j]) * pos )

      }

      array.push(el)
    }

    return new SVG.PathArray(array)
  }

})

// sorry for the long declaration
// slices out one block (from M to M) and syncronize it so the types and length match
function handleBlock(startArr, startOffsetM, startOffsetNextM, destArr, destOffsetM, destOffsetNextM, undefined){

  // slice out the block we need
  var startArrTemp = startArr.slice(startOffsetM, startOffsetNextM || undefined)
    ,  destArrTemp =  destArr.slice( destOffsetM,  destOffsetNextM || undefined)

  var i = 0
    , posStart = {pos:[0,0], start:[0,0]}
    , posDest  = {pos:[0,0], start:[0,0]}

  do{

    // convert shorthand types to long form
    startArrTemp[i] = simplyfy.call(posStart, startArrTemp[i])
     destArrTemp[i] = simplyfy.call(posDest ,  destArrTemp[i])

    // check if both shape types match
    if(startArrTemp[i][0] != destArrTemp[i][0] || startArrTemp[i][0] == 'M') {

      // if not, convert shapes to beziere
      startArrTemp[i] = toBeziere.call(posStart, startArrTemp[i])
       destArrTemp[i] = toBeziere.call(posDest ,  destArrTemp[i])

    } else {

      // only update positions otherwise
      startArrTemp[i] = setPosAndReflection.call(posStart, startArrTemp[i])
       destArrTemp[i] = setPosAndReflection.call(posDest ,  destArrTemp[i])

    }

    // we are at the end at both arrays. stop here
    if(++i == startArrTemp.length && i == destArrTemp.length) break

    // destArray is longer. Add one element
    if(i == startArrTemp.length){
      startArrTemp.push([
        'C',
        posStart.pos[0],
        posStart.pos[1],
        posStart.pos[0],
        posStart.pos[1],
        posStart.pos[0],
        posStart.pos[1],
      ])
    }

    // startArr is longer. Add one element
    if(i == destArrTemp.length){
      destArrTemp.push([
        'C',
        posDest.pos[0],
        posDest.pos[1],
        posDest.pos[0],
        posDest.pos[1],
        posDest.pos[0],
        posDest.pos[1]
      ])
    }

  }while(true)

  // return the updated block
  return {start:startArrTemp, dest:destArrTemp}
}

// converts shorthand types to long form
function simplyfy(val){

  switch(val[0]){
    case 'z': // shorthand line to start
    case 'Z':
      val[0] = 'L'
      val[1] = this.start[0]
      val[2] = this.start[1]
      break
    case 'H': // shorthand horizontal line
      val[0] = 'L'
      val[2] = this.pos[1]
      break
    case 'V': // shorthand vertical line
      val[0] = 'L'
      val[2] = val[1]
      val[1] = this.pos[0]
      break
    case 'T': // shorthand quadratic beziere
      val[0] = 'Q'
      val[3] = val[1]
      val[4] = val[2]
      val[1] = this.reflection[1]
      val[2] = this.reflection[0]
      break
    case 'S': // shorthand cubic beziere
      val[0] = 'C'
      val[6] = val[4]
      val[5] = val[3]
      val[4] = val[2]
      val[3] = val[1]
      val[2] = this.reflection[1]
      val[1] = this.reflection[0]
      break
  }

  return val

}

// updates reflection point and current position
function setPosAndReflection(val){

  var len = val.length

  this.pos = [ val[len-2], val[len-1] ]

  if('SCQT'.indexOf(val[0]) != -1)
    this.reflection = [ 2 * this.pos[0] - val[len-4], 2 * this.pos[1] - val[len-3] ]

  return val
}

// converts all types to cubic beziere
function toBeziere(val){

  switch(val[0]){
    case 'M': // special handling for M
      this.pos = this.start = [val[1], val[2]]
      return val
    case 'L':
      val[5] = val[3] = val[1]
      val[6] = val[4] = val[2]
      val[1] = this.pos[0]
      val[2] = this.pos[1]
      break
    case 'Q':
      val[6] = val[4]
      val[5] = val[3]
      val[4] = val[2]
      val[3] = val[1]
      val[2] = this.pos[1]
      val[1] = this.pos[0]
      break
    case 'A':
      throw 'Cant morph arcs to beziere'
      break
  }

  val[0] = 'C'
  this.pos = [val[5], val[6]]
  this.reflection = [2 * val[5] - val[3], 2 * val[6] - val[4]]

  return val

}

// fins the next position of type M
function findNextM(arr, offset){

  if(offset === false) return false

  for(var i = offset, len = arr.length;i < len;++i){

    if(arr[i][0] == 'M') return i

  }

  return false
}<commit_msg>fix conversion quadratic -> cubic bezier<commit_after>SVG.extend(SVG.PathArray, {
  morph: function(array) {

    var startArr = this.value
      ,  destArr = this.parse(array)

    var startOffsetM = 0
      ,  destOffsetM = 0

    while(true){
      // stop if there is no M anymore
      if(startOffsetM === false && destOffsetM === false) break

      // find the next M in path array
      startOffsetNextM = findNextM(startArr, startOffsetM === false ? false : startOffsetM+1)
       destOffsetNextM = findNextM( destArr,  destOffsetM === false ? false :  destOffsetM+1)

      // We have to add one M to the startArray
      if(startOffsetM === false){
        var bbox = new SVG.PathArray(result.start).bbox()

        // when the last block had no bounding box we simply take the first M we got
        if(bbox.height == 0 || bbox.width == 0){
          startOffsetM =  startArr.push(startArr[0]) - 1
        }else{
          // we take the middle of the bbox instead when we got one
          startOffsetM = startArr.push( ['M', bbox.x + bbox.width/2, bbox.y + bbox.height/2 ] ) - 1
        }
      }

      // We have to add one M to the destArray
      if( destOffsetM === false){
        var bbox = new SVG.PathArray(result.dest).bbox()

        if(bbox.height == 0 || bbox.width == 0){
          destOffsetM =  destArr.push(destArr[0]) - 1
        }else{
          destOffsetM =  destArr.push( ['M', bbox.x + bbox.width/2, bbox.y + bbox.height/2 ] ) - 1
        }
      }

      // handle block from M to next M
      var result = handleBlock(startArr, startOffsetM, startOffsetNextM, destArr, destOffsetM, destOffsetNextM)

      // update the arrays to their new values
      startArr = startArr.slice(0, startOffsetM).concat(result.start, startOffsetNextM === false ? [] : startArr.slice(startOffsetNextM))
       destArr =  destArr.slice(0,  destOffsetM).concat(result.dest ,  destOffsetNextM === false ? [] :  destArr.slice( destOffsetNextM))

      // update offsets
      startOffsetM = startOffsetNextM === false ? false : startOffsetM + result.start.length
       destOffsetM =  destOffsetNextM === false ? false :  destOffsetM + result.dest.length

    }

    // copy back arrays
    this.value = startArr
    this.destination = destArr

    return this
  }
, at: function(pos) {
    // make sure a destination is defined
    if (!this.destination) return this

    // generate morphed path array
    for (var i = 0, il = this.value.length, array = []; i < il; ++i){

      // path array values have different length. so we need another loop
      for (var j = 1, len = this.value[i].length, el = [this.value[i][0]]; j < len; ++j) {

        el.push( this.value[i][j] + (this.destination[i][j] - this.value[i][j]) * pos )

      }

      array.push(el)
    }

    return new SVG.PathArray(array)
  }

})

// sorry for the long declaration
// slices out one block (from M to M) and syncronize it so the types and length match
function handleBlock(startArr, startOffsetM, startOffsetNextM, destArr, destOffsetM, destOffsetNextM, undefined){

  // slice out the block we need
  var startArrTemp = startArr.slice(startOffsetM, startOffsetNextM || undefined)
    ,  destArrTemp =  destArr.slice( destOffsetM,  destOffsetNextM || undefined)

  var i = 0
    , posStart = {pos:[0,0], start:[0,0]}
    , posDest  = {pos:[0,0], start:[0,0]}

  do{

    // convert shorthand types to long form
    startArrTemp[i] = simplyfy.call(posStart, startArrTemp[i])
     destArrTemp[i] = simplyfy.call(posDest ,  destArrTemp[i])

    // check if both shape types match
    if(startArrTemp[i][0] != destArrTemp[i][0] || startArrTemp[i][0] == 'M') {

      // if not, convert shapes to beziere
      startArrTemp[i] = toBeziere.call(posStart, startArrTemp[i])
       destArrTemp[i] = toBeziere.call(posDest ,  destArrTemp[i])

    } else {

      // only update positions otherwise
      startArrTemp[i] = setPosAndReflection.call(posStart, startArrTemp[i])
       destArrTemp[i] = setPosAndReflection.call(posDest ,  destArrTemp[i])

    }

    // we are at the end at both arrays. stop here
    if(++i == startArrTemp.length && i == destArrTemp.length) break

    // destArray is longer. Add one element
    if(i == startArrTemp.length){
      startArrTemp.push([
        'C',
        posStart.pos[0],
        posStart.pos[1],
        posStart.pos[0],
        posStart.pos[1],
        posStart.pos[0],
        posStart.pos[1],
      ])
    }

    // startArr is longer. Add one element
    if(i == destArrTemp.length){
      destArrTemp.push([
        'C',
        posDest.pos[0],
        posDest.pos[1],
        posDest.pos[0],
        posDest.pos[1],
        posDest.pos[0],
        posDest.pos[1]
      ])
    }

  }while(true)

  // return the updated block
  return {start:startArrTemp, dest:destArrTemp}
}

// converts shorthand types to long form
function simplyfy(val){

  switch(val[0]){
    case 'z': // shorthand line to start
    case 'Z':
      val[0] = 'L'
      val[1] = this.start[0]
      val[2] = this.start[1]
      break
    case 'H': // shorthand horizontal line
      val[0] = 'L'
      val[2] = this.pos[1]
      break
    case 'V': // shorthand vertical line
      val[0] = 'L'
      val[2] = val[1]
      val[1] = this.pos[0]
      break
    case 'T': // shorthand quadratic beziere
      val[0] = 'Q'
      val[3] = val[1]
      val[4] = val[2]
      val[1] = this.reflection[1]
      val[2] = this.reflection[0]
      break
    case 'S': // shorthand cubic beziere
      val[0] = 'C'
      val[6] = val[4]
      val[5] = val[3]
      val[4] = val[2]
      val[3] = val[1]
      val[2] = this.reflection[1]
      val[1] = this.reflection[0]
      break
  }

  return val

}

// updates reflection point and current position
function setPosAndReflection(val){

  var len = val.length

  this.pos = [ val[len-2], val[len-1] ]

  if('SCQT'.indexOf(val[0]) != -1)
    this.reflection = [ 2 * this.pos[0] - val[len-4], 2 * this.pos[1] - val[len-3] ]

  return val
}

// converts all types to cubic beziere
function toBeziere(val){

  switch(val[0]){
    case 'M': // special handling for M
      this.pos = this.start = [val[1], val[2]]
      return val
    case 'L':
      val[5] = val[3] = val[1]
      val[6] = val[4] = val[2]
      val[1] = this.pos[0]
      val[2] = this.pos[1]
      break
    case 'Q':
      val[6] = val[4]
      val[5] = val[3]
      val[4] = val[2] * 1/3 + val[4] * 2/3
      val[3] = val[1] * 1/3 + val[3] * 2/3
      val[2] = this.pos[1] * 1/3 + val[2] * 2/3
      val[1] = this.pos[0] * 1/3 + val[1] * 2/3
      break
    case 'A':
      throw new Error('Cant morph arcs to beziere')
      break
  }

  val[0] = 'C'
  this.pos = [val[5], val[6]]
  this.reflection = [2 * val[5] - val[3], 2 * val[6] - val[4]]

  return val

}

// finds the next position of type M
function findNextM(arr, offset){

  if(offset === false) return false

  for(var i = offset, len = arr.length;i < len;++i){

    if(arr[i][0] == 'M') return i

  }

  return false
}<|endoftext|>


<commit_before>var SVGContainer = require('./svgcontainer');
var Promise = require('./promise');

function SVGNodeContainer(node, _native) {
    this.src = node;
    this.image = null;
    var self = this;

    this.promise = _native ? new Promise(function(resolve, reject) {
        self.image = new Image();
        self.image.onload = resolve;
        self.image.onerror = reject;
        self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node);
        if (self.image.complete === true) {
            resolve(self.image);
        }
    }) : this.hasFabric().then(function() {
        return new Promise(function(resolve) {
            window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));
        });
    });
}

SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);

module.exports = SVGNodeContainer;
<commit_msg>encode svg data into base64 to prevent some browsers to add html entities<commit_after>var SVGContainer = require('./svgcontainer');
var Promise = require('./promise');

function SVGNodeContainer(node, _native) {
    this.src = node;
    this.image = null;
    var self = this;

    this.promise = _native ? new Promise(function(resolve, reject) {
        self.image = new Image();
        self.image.onload = resolve;
        self.image.onerror = reject;
        self.image.src = "data:image/svg+xml;base64," + btoa((new XMLSerializer()).serializeToString(node));
        if (self.image.complete === true) {
            resolve(self.image);
        }
    }) : this.hasFabric().then(function() {
        return new Promise(function(resolve) {
            window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));
        });
    });
}

SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);

module.exports = SVGNodeContainer;
<|endoftext|>


<commit_before>#!/usr/bin/env node
var _ = require("underscore");
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var conf = require("./conf.json");
var regions = require("us-regions");

var OUTPUT_PATH = __dirname + "/out/";

var loadAllSchools = function() {
  // Load all schools.
  var range = _.range(conf.minSchoolId, conf.maxSchoolId + 1);
  var schools = {};
  for (var i = conf.minSchoolId; i < conf.maxSchoolId + 1; i++) {
    try {
      schools[i] = require(__dirname + "/parsed_schools/" + i + ".json");
    } catch (e) {
      continue;
    }
  }
  return schools;
};

var matchSchool = function(schools, name) {
  // Special cases
  if (name === "Concordia College, Selma") {
    name = "Concordia College Alabama";
  } else if (name === "Florida A&M University") {
    name = "Florida Agricultural and Mechanical University";
  }

  var sname;
  for (var schoolId in schools) {
    sname = schools[schoolId].name;
    if (!sname) {
      continue;
    }
    if (name === "Lincoln University of Missouri" &&
        sname === "Lincoln University" &&
        schools[schoolId].state === "MO") {
      return schoolId;
    }
    if (sname === name) {
      return schoolId;
    }
  }
  return;
};

/**
 * Go through the list of given schools.  Filter out the schools that don't
 * have ``generalAdmissionsData.acceptanceRate.percent``.  Sort the remaining
 * into those that have a value returned by ``getter`` and those who don't.
 * For those that don't, find the closest school by acceptance rate percent
 * that does, and apply setter to it.
 */
function assignScoresByAcceptanceRateNeighbor(schools, getter, setter) {
  var sbar = [];
  var schoolsWithout = [];
  _.each(schools, function(school) {
    var percent = school.generalAdmissionsData.acceptanceRate.percent;
    var type = school.schoolType;
    var military = school.military;
    var historicallyBlack = school.historicallyBlack;
    if (percent) {
      var val = getter(school);
      if (val) {
        sbar.push([percent, school, type, military, historicallyBlack]);
      } else {
        schoolsWithout.push([percent, school, type, military, historicallyBlack]);
      }
    }
  });
  sbar = _.sortBy(sbar, function(s) { return s[0]; });
  schoolsWithout.forEach(function(percentSchool) {
    // limit list of potential neighbors if the schools have similar gender, type, military and HBC values
    newBar = _.filter(sbar, function(s) {
      return s[3] === percentSchool[3] && s[4] === percentSchool[4];
    });
    var pos = _.sortedIndex(newBar, percentSchool, function(s) { return s[0]; });
    var chosen;
    if (pos === 0) {
      chosen = newBar[0];
    } else if (pos === newBar.length - 1) {
      chosen = newBar[newBar.length - 1];
    } else if (pos === newBar.length) {
      chosen = newBar[newBar.length - 1];
    } else if (Math.abs(newBar[pos][0] - percentSchool[0]) <
               Math.abs(newBar[pos + 1][0] - percentSchool[0])) {
      chosen = newBar[pos];
    } else {
      chosen = newBar[pos + 1];
    }
    var chosenPercent = chosen[0];
    var chosenSchool = chosen[1];
    percentSchool[1].calculatedAdmissionsData = percentSchool[1].calculatedAdmissionsData || {};
    setter(percentSchool[1], chosenSchool, chosenPercent);
  });
};

var mergeAll = function() {
  // School types
  var schoolTypes = require(__dirname + "/data/schoolTypes.json");
  var schools = loadAllSchools();
  _.each(schools, function(school) {
    school.military = false;
    school.historicallyBlack = false;
    school.partner = false;
  });

  // Military
  _.each(schoolTypes.military, function(name) {
    var schoolId = matchSchool(schools, name);
    if (schoolId) {
      schools[schoolId].military = true;
    }
  });
  // Historically black
  _.each(schoolTypes.historicallyBlack, function(name) {
    var schoolId = matchSchool(schools, name);
    if (schoolId) {
      schools[schoolId].historicallyBlack = true;
    }
  });

  // Partner colleges
  _.each(schoolTypes.partnerColleges, function(partnerIdNumber) {
    schools[partnerIdNumber].partner = true;
  });

  // Images
  var images = fs.readdirSync(__dirname + "/images/");
  images.forEach(function(image) {
    var match = /(\d+)-(logo|cover)\.(png|jpg|gif)/.exec(image);
    if (match) {
      var url = conf.awsRoot + image;
      if (match[2] === "logo") {
        schools[match[1]].schoolLogo = url;
      } else if (match[2] === "cover") {
        schools[match[1]].coverPhoto = url;
      }
    }
  });

  // SAT Composites. Calculate an SAT composite for the averages for each
  // school that has them.
  var satSum = function(scores) {
    scores = _.without(scores, null);
    var sum = _.reduce(scores, function(m, n) { return m + n }, 0);
    switch (scores.length) {
      case 0: return null;
      case 1: return sum * 3;
      case 2: return sum * 1.5;
      case 3: return sum;
      default: throw new Error("More than 3 SAT scores to average.");
    }
  };
  _.each(schools, function(school) {
    school.generalAdmissionsData.SATComposite = {
      average: satSum([
        school.generalAdmissionsData.SATMath.average,
        school.generalAdmissionsData.SATReading.average,
        school.generalAdmissionsData.SATWriting.average
      ]),
      halfClassRange: {
        low: satSum([
          school.generalAdmissionsData.SATMath.halfClassRange.low,
          school.generalAdmissionsData.SATReading.halfClassRange.low,
          school.generalAdmissionsData.SATWriting.halfClassRange.low,
        ]),
        high: satSum([
          school.generalAdmissionsData.SATMath.halfClassRange.high,
          school.generalAdmissionsData.SATReading.halfClassRange.high,
          school.generalAdmissionsData.SATWriting.halfClassRange.high,
        ]),
      }
    };
  });

  // Neighbor GPA.
  assignScoresByAcceptanceRateNeighbor(schools, function(school) {
    return school.specificAdmissionsData.GPA.average;
  }, function(schoolWithout, chosen, percent) {
    schoolWithout.calculatedAdmissionsData.GPA = {
      school: chosen.name,
      acceptanceRatePercent: percent,
      scores: chosen.specificAdmissionsData.GPA
    };
  });

  // Neighbor SAT / ACT.
  _.each(["SATComposite", "SATMath", "SATReading", "SATWriting", "ACTComposite"], function(key) {
    assignScoresByAcceptanceRateNeighbor(schools, function(school) {
      return school.generalAdmissionsData[key].average;
    }, function(schoolWithout, chosen, percent) {
      schoolWithout.calculatedAdmissionsData[key] = {
        school: chosen.name,
        acceptanceRatePercent: percent,
        scores: chosen.generalAdmissionsData[key]
      }
    });
  });

  // Slugs
  var usedSlugs = {};
  _.each(schools, function(school) {
    if (school.name) {
      var name = [school.name, school.city, school.state].join(" ");
      var slug = name.toLowerCase().replace(/[^-a-z0-9]+/g, "-").replace(/-*$/, "");
      if (usedSlugs[slug]) {
        throw new Error("Duplicate slug " + slug + " " + usedSlugs[slug] + ", " + school.idNumber);
      }
      usedSlugs[slug] = school.idNumber;
      school.slug = slug;
    }
  });

  // Regions
  _.each(schools, function(school) {
    if (school.state) {
      var region = regions.region(school.state);
      school.region = region ? region.toLowerCase() : null;
    }
  });

  // Weather
  _.each(schools, function(school) {
    if (school.campusSetting) {
      var low = school.campusSetting.averageLowTempJanuary;
      var high = school.campusSetting.averageHighTempSeptember;
      var rainyDays = school.campusSetting.rainyDaysPerYear;
      if (low && high && rainyDays) {
        if (low > 45 && rainyDays < 150) {
          school.weather = "flipFlops";
        } else if (high < 80) {
          school.weather = "frozenTundra";
        } else {
          school.weather = "allSeasons";
        }
      }
    }
  });

  // Distances. Construct an object that quantifies relationships between
  // between various semantic fields, for use in filtering. The keys in the
  // distance object should accord with user profile fields, and the value
  // should be a score which we will sort to minimize.
  _.each(schools, function(school) {
    var distances = {};
    school._distances = distances;
    if (school.weather) {
      distances.weather = {
        frozenTundra: {frozenTundra: 0,   allSeasons: 0.5, flipFlops: 1  }[school.weather],
        allSeasons:   {frozenTundra: 0.5, allSeasons: 0,   flipFlops: 0.5}[school.weather],
        flipFlops:    {frozenTundra: 1,   allSeasons: 0.5, flipFlops: 0  }[school.weather]
      };
    }
    if (school.region) {
      distances.region = {
        northeast: {northeast: 0,   midwest: 0.5, south: 1,   west: 1  }[school.region],
        midwest:   {northeast: 0.5, midwest: 0,   south: 0.5, west: 0.5}[school.region],
        south:     {northeast: 1,   midwest: 0.5, south: 0,   west: 0.5}[school.region],
        west:      {northeast: 1,   midwest: 0.5, south: 0.5, west: 0  }[school.region]
      };
    }
    if (school.population) {
      var p = school.population;
      distances.size = {
        small:  p < 3000 ? 0   : p < 10000 ? 0.5 : 1,
        medium: p < 3000 ? 0.5 : p < 10000 ? 0   : 0.5,
        large:  p < 3000 ? 1   : p < 10000 ? 0.5 : 0
      };
    }
    if (school.schoolType) {
      distances.schoolType = {
        "public":  {"Private": 1, "Private for-profit": 1, "Public": 0}[school.schoolType],
        "private": {"Private": 0, "Private for-profit": 0, "Public": 1}[school.schoolType],
      };
    }
    if (school.gender) {
      distances.gender = {
        "Male"  : {"Men": 0,   "Women": 1,   "coed": 0.5}[school.gender],
        "Female": {"Men": 1,   "Women": 0,   "coed": 0.5}[school.gender],
        "coed"  : {"Men": 0.5, "Women": 0.5, "coed": 0  }[school.gender]
      };
    }
    if (school.campusSetting && school.campusSetting.environment) {
      var citySizeMap = function(sizes) {
        switch (school.campusSetting.environment) {
          case "Very large city": return sizes[0];
          case "Large city":      return sizes[1];
          case "Small city":      return sizes[2];
          case "Large town":      return sizes[3];
          case "Small town":      return sizes[4];
          case "Rural community": return sizes[5];
        }
      }
      distances.city = {
        big:    citySizeMap([0,    0,    0.33, 0.33, 0.66, 0.66, 1   ]),
        medium: citySizeMap([0.33, 0.33, 0,    0,    0.33, 0.33, 0.66]),
        small:  citySizeMap([0.66, 0.66, 0.33, 0.33, 0,    0,    0.33]),
        sticks: citySizeMap([1,    1,    0.66, 0.66, 0.33, 0.33, 0   ])
      };
    }
    if (school.partner) {
      distances.partner = {true: 0.5, false: 1}[school.partner];
    }
  });

  // Create output directory if it doesn't exist yet.
  try { fs.mkdirSync(OUTPUT_PATH) } catch (e) {}

  return Promise.map(_.values(schools), function(school) {
    return fs.writeFileAsync(
      OUTPUT_PATH + school.idNumber + ".json",
      JSON.stringify(school, null, 2)
    );
  });
};

if (require.main === module) {
  mergeAll();
}
<commit_msg>intermediate commit<commit_after>#!/usr/bin/env node
var _ = require("underscore");
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var conf = require("./conf.json");
var regions = require("us-regions");

var OUTPUT_PATH = __dirname + "/out/";

var loadAllSchools = function() {
  // Load all schools.
  var range = _.range(conf.minSchoolId, conf.maxSchoolId + 1);
  var schools = {};
  for (var i = conf.minSchoolId; i < conf.maxSchoolId + 1; i++) {
    try {
      schools[i] = require(__dirname + "/parsed_schools/" + i + ".json");
    } catch (e) {
      continue;
    }
  }
  return schools;
};

var matchSchool = function(schools, name) {
  // Special cases
  if (name === "Concordia College, Selma") {
    name = "Concordia College Alabama";
  } else if (name === "Florida A&M University") {
    name = "Florida Agricultural and Mechanical University";
  }

  var sname;
  for (var schoolId in schools) {
    sname = schools[schoolId].name;
    if (!sname) {
      continue;
    }
    if (name === "Lincoln University of Missouri" &&
        sname === "Lincoln University" &&
        schools[schoolId].state === "MO") {
      return schoolId;
    }
    if (sname === name) {
      return schoolId;
    }
  }
  return;
};

/**
 * Go through the list of given schools.  Filter out the schools that don't
 * have ``generalAdmissionsData.acceptanceRate.percent``.  Sort the remaining
 * into those that have a value returned by ``getter`` and those who don't.
 * For those that don't, find the closest school by acceptance rate percent
 * that does, and apply setter to it.
 */
function assignScoresByAcceptanceRateNeighbor(schools, getter, setter) {
  var sbar = [];
  var schoolsWithout = [];
  _.each(schools, function(school) {
    var percent = school.generalAdmissionsData.acceptanceRate.percent;
    var type = school.schoolType;
    var military = school.military;
    var historicallyBlack = school.historicallyBlack;
    if (percent) {
      var val = getter(school);
      if (val) {
        sbar.push([percent, school, type, military, historicallyBlack]);
      } else {
        schoolsWithout.push([percent, school, type, military, historicallyBlack]);
      }
    }
  });
  sbar = _.sortBy(sbar, function(s) { return s[0]; });
  schoolsWithout.forEach(function(percentSchool) {
    // limit list of potential neighbors if the schools have similar gender, type, military and HBC values
    newBar = _.filter(sbar, function(s) {
      return s[2] === percentSchool[2] && s[3] === percentSchool[3] && s[4] === percentSchool[4];
    });
    var pos = _.sortedIndex(newBar, percentSchool, function(s) { return s[0]; });
    var chosen;
    if (pos === 0) {
      chosen = newBar[0];
    } else if (pos === newBar.length - 1) {
      chosen = newBar[newBar.length - 1];
    } else if (pos === newBar.length) {
      chosen = newBar[newBar.length - 1];
    } else if (Math.abs(newBar[pos][0] - percentSchool[0]) <
               Math.abs(newBar[pos + 1][0] - percentSchool[0])) {
      chosen = newBar[pos];
    } else {
      chosen = newBar[pos + 1];
    }
    if (chosen) {
      var chosenPercent = chosen[0];
      var chosenSchool = chosen[1];
      percentSchool[1].calculatedAdmissionsData = percentSchool[1].calculatedAdmissionsData || {};
      setter(percentSchool[1], chosenSchool, chosenPercent);
    }
  });
};

var mergeAll = function() {
  // School types
  var schoolTypes = require(__dirname + "/data/schoolTypes.json");
  var schools = loadAllSchools();
  _.each(schools, function(school) {
    school.military = false;
    school.historicallyBlack = false;
    school.partner = false;
  });

  // Military
  _.each(schoolTypes.military, function(name) {
    var schoolId = matchSchool(schools, name);
    if (schoolId) {
      schools[schoolId].military = true;
    }
  });
  // Historically black
  _.each(schoolTypes.historicallyBlack, function(name) {
    var schoolId = matchSchool(schools, name);
    if (schoolId) {
      schools[schoolId].historicallyBlack = true;
    }
  });

  // Partner colleges
  _.each(schoolTypes.partnerColleges, function(partnerIdNumber) {
    schools[partnerIdNumber].partner = true;
  });

  // Images
  var images = fs.readdirSync(__dirname + "/images/");
  images.forEach(function(image) {
    var match = /(\d+)-(logo|cover)\.(png|jpg|gif)/.exec(image);
    if (match) {
      var url = conf.awsRoot + image;
      if (match[2] === "logo") {
        schools[match[1]].schoolLogo = url;
      } else if (match[2] === "cover") {
        schools[match[1]].coverPhoto = url;
      }
    }
  });

  // SAT Composites. Calculate an SAT composite for the averages for each
  // school that has them.
  var satSum = function(scores) {
    scores = _.without(scores, null);
    var sum = _.reduce(scores, function(m, n) { return m + n }, 0);
    switch (scores.length) {
      case 0: return null;
      case 1: return sum * 3;
      case 2: return sum * 1.5;
      case 3: return sum;
      default: throw new Error("More than 3 SAT scores to average.");
    }
  };
  _.each(schools, function(school) {
    school.generalAdmissionsData.SATComposite = {
      average: satSum([
        school.generalAdmissionsData.SATMath.average,
        school.generalAdmissionsData.SATReading.average,
        school.generalAdmissionsData.SATWriting.average
      ]),
      halfClassRange: {
        low: satSum([
          school.generalAdmissionsData.SATMath.halfClassRange.low,
          school.generalAdmissionsData.SATReading.halfClassRange.low,
          school.generalAdmissionsData.SATWriting.halfClassRange.low,
        ]),
        high: satSum([
          school.generalAdmissionsData.SATMath.halfClassRange.high,
          school.generalAdmissionsData.SATReading.halfClassRange.high,
          school.generalAdmissionsData.SATWriting.halfClassRange.high,
        ]),
      }
    };
  });

  // Neighbor GPA.
  assignScoresByAcceptanceRateNeighbor(schools, function(school) {
    return school.specificAdmissionsData.GPA.average;
  }, function(schoolWithout, chosen, percent) {
    schoolWithout.calculatedAdmissionsData.GPA = {
      school: chosen.name,
      acceptanceRatePercent: percent,
      scores: chosen.specificAdmissionsData.GPA
    };
    console.log(chosen.name, schoolWithout.calculatedAdmissionsData);
  });

  // Neighbor SAT / ACT.
  _.each(["SATComposite", "SATMath", "SATReading", "SATWriting", "ACTComposite"], function(key) {
    assignScoresByAcceptanceRateNeighbor(schools, function(school) {
      return school.generalAdmissionsData[key].average;
    }, function(schoolWithout, chosen, percent) {
      schoolWithout.calculatedAdmissionsData[key] = {
        school: chosen.name,
        acceptanceRatePercent: percent,
        scores: chosen.generalAdmissionsData[key]
      }
    });
  });

  // Slugs
  var usedSlugs = {};
  _.each(schools, function(school) {
    if (school.name) {
      var name = [school.name, school.city, school.state].join(" ");
      var slug = name.toLowerCase().replace(/[^-a-z0-9]+/g, "-").replace(/-*$/, "");
      if (usedSlugs[slug]) {
        throw new Error("Duplicate slug " + slug + " " + usedSlugs[slug] + ", " + school.idNumber);
      }
      usedSlugs[slug] = school.idNumber;
      school.slug = slug;
    }
  });

  // Regions
  _.each(schools, function(school) {
    if (school.state) {
      var region = regions.region(school.state);
      school.region = region ? region.toLowerCase() : null;
    }
  });

  // Weather
  _.each(schools, function(school) {
    if (school.campusSetting) {
      var low = school.campusSetting.averageLowTempJanuary;
      var high = school.campusSetting.averageHighTempSeptember;
      var rainyDays = school.campusSetting.rainyDaysPerYear;
      if (low && high && rainyDays) {
        if (low > 45 && rainyDays < 150) {
          school.weather = "flipFlops";
        } else if (high < 80) {
          school.weather = "frozenTundra";
        } else {
          school.weather = "allSeasons";
        }
      }
    }
  });

  // Distances. Construct an object that quantifies relationships between
  // between various semantic fields, for use in filtering. The keys in the
  // distance object should accord with user profile fields, and the value
  // should be a score which we will sort to minimize.
  _.each(schools, function(school) {
    var distances = {};
    school._distances = distances;
    if (school.weather) {
      distances.weather = {
        frozenTundra: {frozenTundra: 0,   allSeasons: 0.5, flipFlops: 1  }[school.weather],
        allSeasons:   {frozenTundra: 0.5, allSeasons: 0,   flipFlops: 0.5}[school.weather],
        flipFlops:    {frozenTundra: 1,   allSeasons: 0.5, flipFlops: 0  }[school.weather]
      };
    }
    if (school.region) {
      distances.region = {
        northeast: {northeast: 0,   midwest: 0.5, south: 1,   west: 1  }[school.region],
        midwest:   {northeast: 0.5, midwest: 0,   south: 0.5, west: 0.5}[school.region],
        south:     {northeast: 1,   midwest: 0.5, south: 0,   west: 0.5}[school.region],
        west:      {northeast: 1,   midwest: 0.5, south: 0.5, west: 0  }[school.region]
      };
    }
    if (school.population) {
      var p = school.population;
      distances.size = {
        small:  p < 3000 ? 0   : p < 10000 ? 0.5 : 1,
        medium: p < 3000 ? 0.5 : p < 10000 ? 0   : 0.5,
        large:  p < 3000 ? 1   : p < 10000 ? 0.5 : 0
      };
    }
    if (school.schoolType) {
      distances.schoolType = {
        "public":  {"Private": 1, "Private for-profit": 1, "Public": 0}[school.schoolType],
        "private": {"Private": 0, "Private for-profit": 0, "Public": 1}[school.schoolType],
      };
    }
    if (school.gender) {
      distances.gender = {
        "Male"  : {"Men": 0,   "Women": 1,   "coed": 0.5}[school.gender],
        "Female": {"Men": 1,   "Women": 0,   "coed": 0.5}[school.gender],
        "coed"  : {"Men": 0.5, "Women": 0.5, "coed": 0  }[school.gender]
      };
    }
    if (school.campusSetting && school.campusSetting.environment) {
      var citySizeMap = function(sizes) {
        switch (school.campusSetting.environment) {
          case "Very large city": return sizes[0];
          case "Large city":      return sizes[1];
          case "Small city":      return sizes[2];
          case "Large town":      return sizes[3];
          case "Small town":      return sizes[4];
          case "Rural community": return sizes[5];
        }
      }
      distances.city = {
        big:    citySizeMap([0,    0,    0.33, 0.33, 0.66, 0.66, 1   ]),
        medium: citySizeMap([0.33, 0.33, 0,    0,    0.33, 0.33, 0.66]),
        small:  citySizeMap([0.66, 0.66, 0.33, 0.33, 0,    0,    0.33]),
        sticks: citySizeMap([1,    1,    0.66, 0.66, 0.33, 0.33, 0   ])
      };
    }

    distances.partner = {true: 0.5, false: 1}[school.partner];

  });

  // Create output directory if it doesn't exist yet.
  try { fs.mkdirSync(OUTPUT_PATH) } catch (e) {}

  return Promise.map(_.values(schools), function(school) {
    return fs.writeFileAsync(
      OUTPUT_PATH + school.idNumber + ".json",
      JSON.stringify(school, null, 2)
    );
  });
};

if (require.main === module) {
  mergeAll();
}
<|endoftext|>


<commit_before>import {
  tree,
  treeAlt,
  avenger,
  cloud,
  mountain,
} from './emoji';

export const createSprite = (emoji) => ([x, y], offset = 0) => ([x, y, emoji, offset]);
export const mkTree = createSprite(tree);
export const mkTreeAlt = createSprite(treeAlt);
export const mkAvenger = createSprite(avenger);
export const mkCloud = createSprite(cloud);
export const mkMountain = createSprite(mountain);
<commit_msg>Add sprite<commit_after>import {
  tree,
  treeAlt,
  avenger,
  cloud,
  mountain,
  ghost,
} from './emoji';

export const createSprite = (emoji) => ([x, y], offset = 0) => ([x, y, emoji, offset]);
export const mkTree = createSprite(tree);
export const mkTreeAlt = createSprite(treeAlt);
export const mkAvenger = createSprite(avenger);
export const mkCloud = createSprite(cloud);
export const mkMountain = createSprite(mountain);
export const mkGhost = createSprite(ghost);
<|endoftext|>


<commit_before>"use strict";

const config = require('./config/database');

const mongoose = require('mongoose');
mongoose.connect(config.db.uri);

const path = require('path');

const Torrent = require('./models/Torrent.js');

const bunyan = require("bunyan");
const logger = bunyan.createLogger({name: "categorize"});

const filter = { 'category' : /Unknown/ };

const specialIgnores = () => {
  const pad = (n, width, z) => {
    z = z || '0';
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
  }
  const result = [];
  // need to refactor this shit
  for(var i=2; i<100; i++) {
    result.push('.s'+pad(i,2));
    result.push('.r'+pad(i,2));
    result.push('.z'+pad(i,2));
    result.push('.'+pad(i,2));
  }
  for(var i=2; i<1000; i++) {
    result.push('.s'+pad(i,3));
    result.push('.r'+pad(i,3));
    result.push('.z'+pad(i,3));
    result.push('.'+pad(i,3));
  }
  return result;
}

const cursor = Torrent.find(filter).sort({'imported': -1}).limit(100).cursor();
cursor.eachAsync(torrent => {
  logger.info(`Treating ${torrent._id} categorization`);
  if(!torrent.files) {
    logger.info(`Torrent ${torrent._id} has no files!`);
    return Promise.resolve(torrent);
  }
  const exts = torrent.files
    .map(file => path.extname(file).toLowerCase())
    .filter(ext => ext.length > 0) // no empty
    .filter(ext => !config.extToIgnore.includes(ext)) // no ignored
    .filter(ext => !specialIgnores().includes(ext)) // no special ignored
    .filter(ext => ext.length < config.limitExt) // with min length
    .slice() // shallow copy
    .sort() // sort
    .reduce((p, c) => {
      if(p[0] !== c) return p.concat(c);
      return p;
    }, []) // deduplicate

  if(exts.length > 5) {
    return Promise.reject(`Torrent ${torrent._id} has no too many extensions!`);
  }

  const category = Object.keys(config.extToCateg)
    .map(categ => { // browse category extensions
      if ( config.extToCateg[categ].some(c => exts.includes(c)) )
      return categ;
    })
    .find((c) => c !== undefined); // find the first category

  torrent.category = category || "Unknown";
  return torrent.save();
})
.then(() => logger.info(`All torrents treated`))
.then(() => process.exit(0))<commit_msg>functional category finding<commit_after>"use strict";

const config = require('./config/database');

const mongoose = require('mongoose');
mongoose.connect(config.db.uri);

const path = require('path');

const Torrent = require('./models/Torrent.js');

const bunyan = require("bunyan");
const logger = bunyan.createLogger({name: "categorize"});

const filter = { 'category' : /Unknown/ };

const dynamicToIgnore = () => {
  const pad = (n, width, z) => {
    z = z || '0';
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
  }
  const result = [];
  // need to refactor this shit
  for(var i=2; i<100; i++) {
    result.push('.s'+pad(i,2));
    result.push('.r'+pad(i,2));
    result.push('.z'+pad(i,2));
    result.push('.'+pad(i,2));
  }
  for(var i=2; i<1000; i++) {
    result.push('.s'+pad(i,3));
    result.push('.r'+pad(i,3));
    result.push('.z'+pad(i,3));
    result.push('.'+pad(i,3));
  }
  return result;
}

function findCategoryBasedOnExtensions(exts) {
  return Object.keys(config.extToCateg)
    .map(categ => { // browse category extensions
      if ( config.extToCateg[categ].some(c => exts.includes(c)) )
      return categ;
    })
    .find((c) => c !== undefined); // find the first category
}

const cursor = Torrent.find(filter).sort({'imported': -1}).limit(100).cursor();
cursor.eachAsync(torrent => {
  logger.info(`Treating ${torrent._id} categorization`);
  if(!torrent.files) {
    logger.info(`Torrent ${torrent._id} has no files!`);
    return Promise.resolve(torrent);
  }
  const exts = torrent.files
    .map(file => path.extname(file).toLowerCase())
    .filter(ext => ext.length > 0) // no empty
    .filter(ext => !config.extToIgnore.includes(ext)) // no ignored
    .filter(ext => !dynamicToIgnore().includes(ext)) // no special ignored
    .filter(ext => ext.length < config.limitExt) // with min length
    .slice() // shallow copy
    .sort() // sort
    .reduce((p, c) => {
      if(p[0] !== c) return p.concat(c);
      return p;
    }, []) // deduplicate

  if(exts.length > 5) {
    return Promise.reject(`Torrent ${torrent._id} has no too many extensions!`);
  }

  const category = findCategoryBasedOnExtensions(exts);
  torrent.category = category || "Unknown";
  return torrent.save();
})
.then(() => logger.info(`All torrents treated`))
.then(() => process.exit(0))<|endoftext|>


<commit_before>(function() {
    gf.HudItem = Class.extend({
        init: function(x, y, settings) {
            //can be dragged
            this.draggable = false;

            //is this item visible?
            this.visible = true;

            //value of the item
            this.value = 0;

            this.name = '';

            //use the passed settings object ot override the default values above
            gf.utils.setValues(this, settings);

            this.default = this.value;

            //create the base div of this element
            this._createElement(x, y);

            //some private stuffs
            this.dirty = true;

            //dragging stuff
            this.dragging = false;
        },
        reset: function() {
            this.set(this.default);
            return this;
        },
        setValue: function(val) {
            this.value = val;
            this.dirty = true;
            return this;
        },
        getValue: function() {
            return this.value;
        },
        update: function() {
            if(!this.dirty) return;

            this.$elm.text(this.value);
            return this;
        },
        //virtual events
        onClick: function(e) {},
        onMouseDown: function(e) {
            if(!this.draggable) return;

            this.dragging = {
                x: e.clientX,
                y: e.clientY
            }
        },
        onMouseUp: function(e) {
            this.dragging = false;
        },
        onMouseMove: function(e) {
            if(!this.draggable || !this.dragging) return;

            var diffX = e.clientX - this.dragging.x,
                diffY = e.clientY - this.dragging.y,
                pos = this.$elm.position();

            this.dragging.x = e.clientX;
            this.dragging.y = e.clientY;

            this.$elm.css({
                top: pos.top += diffY,
                left: pos.left += diffX
            });
        },
        //private functions
        _createElement: function(x, y) {
            this.$elm = $('<div/>', {
                'class': 'gf-hud-item ' + this.name
            }).css({
                position: 'absolute',
                top: y,
                left: x
            });

            this._bindEvents();
        },
        _bindEvents: function() {
            this.$elm.on({
                click: this.onClick.bind(this),
                mousedown: this.onMouseDown.bind(this),
                mouseup: this.onMouseUp.bind(this),
                mousemove: this.onMouseMove.bind(this)
            });
        }
    });
})();<commit_msg>fix an issue when you want value to be a string<commit_after>(function() {
    gf.HudItem = Class.extend({
        init: function(x, y, settings) {
            //can be dragged
            this.draggable = false;

            //is this item visible?
            this.visible = true;

            //value of the item
            this.value = typeof settings.value == 'string' ? '' : 0;

            this.name = '';

            //use the passed settings object ot override the default values above
            gf.utils.setValues(this, settings);

            this.default = this.value;

            //create the base div of this element
            this._createElement(x, y);

            //some private stuffs
            this.dirty = true;

            //dragging stuff
            this.dragging = false;
        },
        reset: function() {
            this.set(this.default);
            return this;
        },
        setValue: function(val) {
            this.value = val;
            this.dirty = true;
            return this;
        },
        getValue: function() {
            return this.value;
        },
        update: function() {
            if(!this.dirty) return;

            this.$elm.text(this.value);
            return this;
        },
        //virtual events
        onClick: function(e) {},
        onMouseDown: function(e) {
            if(!this.draggable) return;

            this.dragging = {
                x: e.clientX,
                y: e.clientY
            }
        },
        onMouseUp: function(e) {
            this.dragging = false;
        },
        onMouseMove: function(e) {
            if(!this.draggable || !this.dragging) return;

            var diffX = e.clientX - this.dragging.x,
                diffY = e.clientY - this.dragging.y,
                pos = this.$elm.position();

            this.dragging.x = e.clientX;
            this.dragging.y = e.clientY;

            this.$elm.css({
                top: pos.top += diffY,
                left: pos.left += diffX
            });
        },
        //private functions
        _createElement: function(x, y) {
            this.$elm = $('<div/>', {
                'class': 'gf-hud-item ' + this.name
            }).css({
                position: 'absolute',
                top: y,
                left: x
            });

            this._bindEvents();
        },
        _bindEvents: function() {
            this.$elm.on({
                click: this.onClick.bind(this),
                mousedown: this.onMouseDown.bind(this),
                mouseup: this.onMouseUp.bind(this),
                mousemove: this.onMouseMove.bind(this)
            });
        }
    });
})();<|endoftext|>


<commit_before>if (typeof module !== 'undefined' && module.exports) {
    var _ = require('underscore');
    var Backbone = require('backbone');
}

/**
 * Backbone.ModelExtensions
 *
 * Adds extensions into Backbone's base classes
 *
 * @author Magnus Bjuvensjö, Anders Bälter
 */
(function (Backbone, _) {
    'use strict';

    var ModelExtensions = Backbone.ModelExtensions = Backbone.ModelExtensions || {};

    var addEventProxy = function (parent, child, options) {
        if (options.bubbleEvents) {
            child.on("all", function (eventName) {
                parent.trigger.apply(parent, arguments);
            });
        }
    };

    var handleObject = function (value, scheme, parent, options) {
        var result;
        if (_.isArray(value)) {
            result = ModelExtensions.toBackboneCollection({
                array: value,
                scheme: scheme,
                bubbleEvents: options.bubbleEvents
            });
            addEventProxy(parent, result, options);
        } else if (_.isObject(value)) {
            result = ModelExtensions.toBackboneModel({
                object: value,
                scheme: scheme,
                bubbleEvents: options.bubbleEvents
            });
            addEventProxy(parent, result, options);
        } else {
            result = value;
        }
        return result;
    };

    /**
     * Creates nested Backbone models and collections from object.
     *
     * Usage example:
     *
     * var myObject = {
     *     name: 'Name',
     *     addresses: [
     *         {
     *             street: 'street 1',
     *             phone: {
     *                 type: 'mobile',
     *                 number: '1'
     *             }
     *         },
     *         {
     *             street: 'street 2',
     *             phone: {
     *                 type: 'mobile',
     *                 number: '2'
     *             }
     *         }
     *     ]
     * };

     * var myObjectScheme = {
     *     model: Backbone.Model.extend({}),
     *     addresses: {
     *         //collection: Backbone.Collection.extend({}),
     *         //model: Backbone.Model.extend({})
     *     }
     * };

     * var myBackboneModelObject = Backbone.ModelExtensions.toBackboneModel({
     *     object: myObject,
     *     scheme: myObjectScheme,
     *     bubbleEvents: true
     * });
     *
     * myBackboneModelObject.on('all', function (eventName) {
     *     console.dir(arguments);
     * });
     *
     * myBackboneModelObject.get('addresses').at(0).set('street', 'new street');
     */
    ModelExtensions.toBackboneModel = function (options) {
        var createModel = function (scheme) {
            if (scheme && scheme.model) {
                return new scheme.model();
            }
            return new Backbone.Model();
        };

        return _.reduce(options.object, function (model, value, key) {
            var result;
            var scheme = {};

            if (options.scheme && options.scheme[key]) {
                scheme = options.scheme[key];
            }

            result = handleObject(value, scheme, model, options);

            model.set(key, result);

            return model;
        }, createModel(options.scheme));

    };

    ModelExtensions.toBackboneCollection = function (options) {
        var createCollection = function (scheme) {
            var collection;
            if (scheme && scheme.collection) {
                collection = new scheme.collection();
            } else {
                collection = new Backbone.Collection();
            }
            if (scheme && scheme.model) {
                collection.model = scheme.model.model;
            }
            return collection;
        };

        return _.reduce(options.array, function (collection, value) {
            var result;
            var scheme = {};

            if (options.scheme && options.scheme.collection) {
                scheme = options.scheme.model || options.scheme;
            }

            result = handleObject(value, scheme, collection, options);
            collection.add(result);

            return collection;
        }, createCollection(options.scheme));

    };
}(Backbone, _));
<commit_msg>check arguments<commit_after>if (typeof module !== 'undefined' && module.exports) {
    var _ = require('underscore');
    var Backbone = require('backbone');
}

/**
 * Backbone.ModelExtensions
 *
 * Adds extensions into Backbone's base classes
 *
 * @author Magnus Bjuvensjö, Anders Bälter
 */
(function (Backbone, _) {
    'use strict';

    var ModelExtensions = Backbone.ModelExtensions = Backbone.ModelExtensions || {};

    var addEventProxy = function (parent, child, options) {
        if (options.bubbleEvents) {
            child.on("all", function (eventName) {
                parent.trigger.apply(parent, arguments);
            });
        }
    };

    var handleObject = function (value, scheme, parent, options) {
        var result;
        if (_.isArray(value)) {
            result = ModelExtensions.toBackboneCollection({
                array: value,
                scheme: scheme,
                bubbleEvents: options.bubbleEvents
            });
            addEventProxy(parent, result, options);
        } else if (_.isObject(value)) {
            result = ModelExtensions.toBackboneModel({
                object: value,
                scheme: scheme,
                bubbleEvents: options.bubbleEvents
            });
            addEventProxy(parent, result, options);
        } else {
            result = value;
        }
        return result;
    };

    /**
     * Creates nested Backbone models and collections from object.
     *
     * Usage example:
     *
     * var myObject = {
     *     name: 'Name',
     *     addresses: [
     *         {
     *             street: 'street 1',
     *             phone: {
     *                 type: 'mobile',
     *                 number: '1'
     *             }
     *         },
     *         {
     *             street: 'street 2',
     *             phone: {
     *                 type: 'mobile',
     *                 number: '2'
     *             }
     *         }
     *     ]
     * };

     * var myObjectScheme = {
     *     model: Backbone.Model.extend({}),
     *     addresses: {
     *         //collection: Backbone.Collection.extend({}),
     *         //model: Backbone.Model.extend({})
     *     }
     * };

     * var myBackboneModelObject = Backbone.ModelExtensions.toBackboneModel({
     *     object: myObject,
     *     scheme: myObjectScheme,
     *     bubbleEvents: true
     * });
     *
     * myBackboneModelObject.on('all', function (eventName) {
     *     console.dir(arguments);
     * });
     *
     * myBackboneModelObject.get('addresses').at(0).set('street', 'new street');
     */
    ModelExtensions.toBackboneModel = function (options) {
        if (!options.object) {
            throw 'Illegal arguments, options.object must be set';
        }
        var createModel = function (scheme) {
            if (scheme && scheme.model) {
                return new scheme.model();
            }
            return new Backbone.Model();
        };

        return _.reduce(options.object, function (model, value, key) {
            var result;
            var scheme = {};

            if (options.scheme && options.scheme[key]) {
                scheme = options.scheme[key];
            }

            result = handleObject(value, scheme, model, options);

            model.set(key, result);

            return model;
        }, createModel(options.scheme));

    };

    ModelExtensions.toBackboneCollection = function (options) {
        if (!options.array) {
            throw 'Illegal arguments, options.array must be set';
        }
        var createCollection = function (scheme) {
            var collection;
            if (scheme && scheme.collection) {
                collection = new scheme.collection();
            } else {
                collection = new Backbone.Collection();
            }
            if (scheme && scheme.model) {
                collection.model = scheme.model.model;
            }
            return collection;
        };

        return _.reduce(options.array, function (collection, value) {
            var result;
            var scheme = {};

            if (options.scheme && options.scheme.collection) {
                scheme = options.scheme.model || options.scheme;
            }

            result = handleObject(value, scheme, collection, options);
            collection.add(result);

            return collection;
        }, createCollection(options.scheme));

    };
}(Backbone, _));
<|endoftext|>