<commit_before>"use strict";

const Endpoints = require("./Endpoints");
const HTTPS = require("https");
const MultipartData = require("../util/MultipartData");
const SequentialBucket = require("../util/SequentialBucket");

/**
* Handles APi requests
*/
class RequestHandler {
    constructor(client, forceQueueing) {
        this._client = client;
        this.baseURL = Endpoints.BASE_URL;
        this.userAgent = `DiscordBot (https://github.com/abalabahaha/eris, ${require("../../package.json").version})`;
        this.ratelimits = {};
        this.latencyRef = {
            latency: 500,
            raw: [500, 500, 500, 500, 500, 500, 500, 500, 500, 500],
            total: 5000
        };
        this.globalBlock = false;
        if(forceQueueing || (this._client.bot === undefined && forceQueueing === undefined)) {
            this.globalBlock = true;
            this.readyQueue = [];
            this._client.once("shardPreReady", () => this.globalUnblock());
        }
    }

    globalUnblock() {
        this.globalBlock = false;
        while(this.readyQueue.length > 0) {
            this.readyQueue.shift()();
        }
    }

    routefy(url) {
        return url.replace(/\/([a-z-])\/[0-9]{17,}/g, function(match, p) {
            return p === "channel" || p === "guild" ? match : `/${p}/:id`;
        });
    }

    /**
    * Make an API request
    * @arg {String} method Uppercase HTTP method
    * @arg {String} url URL of the endpoint
    * @arg {Boolean} auth Whether to add the Authorization header and token or not
    * @arg {Object} [body] Request payload
    * @arg {Object} [file] File object
    * @arg {String} file.file A readable stream or buffer
    * @arg {String} file.name What to name the file
    * @returns {Promise<Object>} Resolves with the returned JSON data
    */
    request(method, url, auth, body, file, _route) {
        var route = _route || this.routefy(url);

        return new Promise((resolve, reject) => {
            var attempts = 0;

            var actualCall = (cbs) => {
                var headers = {
                    "User-Agent": this.userAgent
                };
                var data;

                if(auth) {
                    headers.Authorization = this._client.token;
                }
                if(file && file.file) {
                    data = new MultipartData();
                    headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
                    data.attach("file", file.file, file.name);
                    if(body) {
                        Object.keys(body).forEach((key) => data.attach(key, body[key]));
                    }
                    data = data.finish();
                } else if(body) {
                    if(method === "GET" || (method === "PUT" && url.includes("/bans/"))) { // TODO remove PUT case when devs fix
                        var qs = "";
                        Object.keys(body).forEach(function(key) {
                            if(body[key] != undefined) {
                                 qs += `&${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`;
                            }
                        });
                        url += "?" + qs.substring(1);
                    } else {
                        data = JSON.stringify(body);
                        headers["Content-Type"] = "application/json";
                    }
                }

                var req = HTTPS.request({
                    method: method,
                    host: "discordapp.com",
                    path: this.baseURL + url,
                    headers: headers
                });

                var reqError;

                req.once("abort", () => {
                    cbs.forEach((cb) => cb());
                    reqError = reqError || new Error("Request aborted by client");
                    reqError.req = req;
                    reject(reqError);
                });

                req.once("aborted", () => {
                    cbs.forEach((cb) => cb());
                    reqError = reqError || new Error("Request aborted by server");
                    reqError.req = req;
                    reject(reqError);
                });

                req.once("error", (err) => {
                    reqError = err;
                    req.abort();
                });

                var latency = Date.now();

                req.once("response", (resp) => {
                    var response = "";

                    resp.on("data", (chunk) => {
                        response += chunk;
                    });

                    resp.once("end", () => {
                        latency = Date.now() - latency;
                        this.latencyRef.total = this.latencyRef.total - this.latencyRef.raw.shift() + latency;
                        this.latencyRef.latency = ~~(this.latencyRef.total / this.latencyRef.raw.push(latency));

                        if(resp.headers["x-ratelimit-limit"]) {
                            this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"];
                        }

                        this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0;

                        if(resp.headers["retry-after"]) {
                            if(resp.headers["x-ratelimit-global"]) {
                                this.globalBlock = true;
                                setTimeout(() => this.globalUnblock(), +resp.headers["retry-after"]);
                            } else {
                                this.ratelimits[route].reset = (+resp.headers["retry-after"]) + Date.now();
                            }
                        } else if(resp.headers["x-ratelimit-reset"]) {
                            this.ratelimits[route].reset = 1000 * resp.headers["x-ratelimit-reset"];
                        }

                        this._client.emit("debug", `${body && body.content} ${Date.now()} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - Date.now()}ms left)`);

                        cbs.forEach((cb) => cb());

                        if(resp.statusCode >= 300) {
                            if(resp.statusCode === 429) {
                                this._client.emit("warn", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°）╯︵ ┻━┻: ${response}`);
                                if(resp.headers["retry-after"]) {
                                    setTimeout(() => {
                                        this.request(method, url, auth, body, file, route).then(resolve).catch(reject);
                                    }, +resp.headers["retry-after"]);
                                    return;
                                } else {
                                    this.request(method, url, auth, body, file, route).then(resolve).catch(reject);
                                    return;
                                }
                            } else if(resp.statusCode === 502 && ++attempts < 4) {
                                this._client.emit("warn", "A wild 502 appeared! Thanks CloudFlare!");
                                setTimeout(() => {
                                    this.request(method, url, auth, body, file, route).then(resolve).catch(reject);
                                }, Math.floor(Math.random() * 1900 + 100));
                                return;
                            }
                            var err = new Error(`${resp.statusCode} ${resp.statusMessage}`);
                            err.resp = resp;
                            err.response = response;
                            err.req = req;
                            reject(err);
                            return;
                        }
                        if(response.length > 0) {
                            if(resp.headers["content-type"] === "application/json") {
                                try {
                                    response = JSON.parse(response);
                                } catch(err) {
                                    reject(err);
                                    return;
                                }
                            }
                        }
                        resolve(response);
                    });
                });

                req.setTimeout(10000, function() {
                    reqError = new Error("Request timed out (>10000ms)");
                    req.abort();
                });

                req.end(data);
            };

            var waitFor = 0;
            var i = 0;
            var cbs = [];
            var done = (cb) => {
                if(cb) {
                    cbs.push(cb);
                }
                if(++i >= waitFor) {
                    actualCall(cbs);
                }
            };
            if(this.globalBlock && auth) {
                this.readyQueue.push(() => {
                    if(!this.ratelimits[route]) {
                        this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                    }
                    ++waitFor;
                    this.ratelimits[route].queue(done);
                });
            } else {
                if(!this.ratelimits[route]) {
                    this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                }
                ++waitFor;
                this.ratelimits[route].queue(done);
            }
        });
    }
}

module.exports = RequestHandler;
<commit_msg>Eventually Consistent ratelimit time inconsistency compensation<commit_after>"use strict";

const Endpoints = require("./Endpoints");
const HTTPS = require("https");
const MultipartData = require("../util/MultipartData");
const SequentialBucket = require("../util/SequentialBucket");

/**
* Handles APi requests
*/
class RequestHandler {
    constructor(client, forceQueueing) {
        this._client = client;
        this.baseURL = Endpoints.BASE_URL;
        this.userAgent = `DiscordBot (https://github.com/abalabahaha/eris, ${require("../../package.json").version})`;
        this.ratelimits = {};
        this.latencyRef = {
            latency: 500,
            raw: [500, 500, 500, 500, 500, 500, 500, 500, 500, 500],
            total: 5000
        };
        this.globalBlock = false;
        if(forceQueueing || (this._client.bot === undefined && forceQueueing === undefined)) {
            this.globalBlock = true;
            this.readyQueue = [];
            this._client.once("shardPreReady", () => this.globalUnblock());
        }
    }

    globalUnblock() {
        this.globalBlock = false;
        while(this.readyQueue.length > 0) {
            this.readyQueue.shift()();
        }
    }

    routefy(url) {
        return url.replace(/\/([a-z-])\/[0-9]{17,}/g, function(match, p) {
            return p === "channel" || p === "guild" ? match : `/${p}/:id`;
        });
    }

    /**
    * Make an API request
    * @arg {String} method Uppercase HTTP method
    * @arg {String} url URL of the endpoint
    * @arg {Boolean} auth Whether to add the Authorization header and token or not
    * @arg {Object} [body] Request payload
    * @arg {Object} [file] File object
    * @arg {String} file.file A readable stream or buffer
    * @arg {String} file.name What to name the file
    * @returns {Promise<Object>} Resolves with the returned JSON data
    */
    request(method, url, auth, body, file, _route) {
        var route = _route || this.routefy(url);

        return new Promise((resolve, reject) => {
            var attempts = 0;

            var actualCall = (cbs) => {
                var headers = {
                    "User-Agent": this.userAgent
                };
                var data;

                if(auth) {
                    headers.Authorization = this._client.token;
                }
                if(file && file.file) {
                    data = new MultipartData();
                    headers["Content-Type"] = "multipart/form-data; boundary=" + data.boundary;
                    data.attach("file", file.file, file.name);
                    if(body) {
                        Object.keys(body).forEach((key) => data.attach(key, body[key]));
                    }
                    data = data.finish();
                } else if(body) {
                    if(method === "GET" || (method === "PUT" && url.includes("/bans/"))) { // TODO remove PUT case when devs fix
                        var qs = "";
                        Object.keys(body).forEach(function(key) {
                            if(body[key] != undefined) {
                                 qs += `&${encodeURIComponent(key)}=${encodeURIComponent(body[key])}`;
                            }
                        });
                        url += "?" + qs.substring(1);
                    } else {
                        data = JSON.stringify(body);
                        headers["Content-Type"] = "application/json";
                    }
                }

                var req = HTTPS.request({
                    method: method,
                    host: "discordapp.com",
                    path: this.baseURL + url,
                    headers: headers
                });

                var reqError;

                req.once("abort", () => {
                    cbs.forEach((cb) => cb());
                    reqError = reqError || new Error("Request aborted by client");
                    reqError.req = req;
                    reject(reqError);
                });

                req.once("aborted", () => {
                    cbs.forEach((cb) => cb());
                    reqError = reqError || new Error("Request aborted by server");
                    reqError.req = req;
                    reject(reqError);
                });

                req.once("error", (err) => {
                    reqError = err;
                    req.abort();
                });

                var latency = Date.now();

                req.once("response", (resp) => {
                    var response = "";

                    resp.on("data", (chunk) => {
                        response += chunk;
                    });

                    resp.once("end", () => {
                        latency = Date.now() - Math.min((+resp.headers["x-ratelimit-reset"] * 1000) || Date.now(), latency);
                        this.latencyRef.total = this.latencyRef.total - this.latencyRef.raw.shift() + latency;
                        this.latencyRef.latency = ~~(this.latencyRef.total / this.latencyRef.raw.push(latency));

                        if(resp.headers["x-ratelimit-limit"]) {
                            this.ratelimits[route].limit = +resp.headers["x-ratelimit-limit"];
                        }

                        this.ratelimits[route].remaining = resp.headers["x-ratelimit-remaining"] === undefined ? 1 : +resp.headers["x-ratelimit-remaining"] || 0;

                        if(resp.headers["retry-after"]) {
                            if(resp.headers["x-ratelimit-global"]) {
                                this.globalBlock = true;
                                setTimeout(() => this.globalUnblock(), +resp.headers["retry-after"]);
                            } else {
                                this.ratelimits[route].reset = (+resp.headers["retry-after"]) + Date.now();
                            }
                        } else if(resp.headers["x-ratelimit-reset"]) {
                            this.ratelimits[route].reset = 1000 * resp.headers["x-ratelimit-reset"];
                        }

                        this._client.emit("debug", `${body && body.content} ${Date.now()} ${resp.statusCode}: ${latency}ms (${this.latencyRef.latency}ms avg) | ${this.ratelimits[route].remaining}/${this.ratelimits[route].limit} left | Reset ${this.ratelimits[route].reset} (${this.ratelimits[route].reset - Date.now()}ms left)`);

                        cbs.forEach((cb) => cb());

                        if(resp.statusCode >= 300) {
                            if(resp.statusCode === 429) {
                                this._client.emit("warn", `${resp.headers["x-ratelimit-global"] ? "Global" : "Unexpected"} 429 (╯°□°）╯︵ ┻━┻: ${response}`);
                                if(resp.headers["retry-after"]) {
                                    setTimeout(() => {
                                        this.request(method, url, auth, body, file, route).then(resolve).catch(reject);
                                    }, +resp.headers["retry-after"]);
                                    return;
                                } else {
                                    this.request(method, url, auth, body, file, route).then(resolve).catch(reject);
                                    return;
                                }
                            } else if(resp.statusCode === 502 && ++attempts < 4) {
                                this._client.emit("warn", "A wild 502 appeared! Thanks CloudFlare!");
                                setTimeout(() => {
                                    this.request(method, url, auth, body, file, route).then(resolve).catch(reject);
                                }, Math.floor(Math.random() * 1900 + 100));
                                return;
                            }
                            var err = new Error(`${resp.statusCode} ${resp.statusMessage}`);
                            err.resp = resp;
                            err.response = response;
                            err.req = req;
                            reject(err);
                            return;
                        }
                        if(response.length > 0) {
                            if(resp.headers["content-type"] === "application/json") {
                                try {
                                    response = JSON.parse(response);
                                } catch(err) {
                                    reject(err);
                                    return;
                                }
                            }
                        }
                        resolve(response);
                    });
                });

                req.setTimeout(10000, function() {
                    reqError = new Error("Request timed out (>10000ms)");
                    req.abort();
                });

                req.end(data);
            };

            var waitFor = 0;
            var i = 0;
            var cbs = [];
            var done = (cb) => {
                if(cb) {
                    cbs.push(cb);
                }
                if(++i >= waitFor) {
                    actualCall(cbs);
                }
            };
            if(this.globalBlock && auth) {
                this.readyQueue.push(() => {
                    if(!this.ratelimits[route]) {
                        this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                    }
                    ++waitFor;
                    this.ratelimits[route].queue(done);
                });
            } else {
                if(!this.ratelimits[route]) {
                    this.ratelimits[route] = new SequentialBucket(1, this.latencyRef);
                }
                ++waitFor;
                this.ratelimits[route].queue(done);
            }
        });
    }
}

module.exports = RequestHandler;
<|endoftext|>


<commit_before>import generateContentHash from './utils/generateContentHash'
import sortedStringify from './utils/sortedStringify'
import getFontFormat from './utils/getFontFormat'

import cssifyKeyframe from './utils/cssifyKeyframe'
import cssifyObject from './utils/cssifyObject'

export default function Renderer(config = { }) {
  const r = {
    listeners: [],
    keyframePrefixes: config.keyframePrefixes || [ '-webkit-', '-moz-' ],
    plugins: config.plugins || [ ],

    /**
     * clears the sheet's cache but keeps all listeners
     */
    clear() {
      r.fontFaces = ''
      r.keyframes = ''
      r.statics = ''
      r.rules = ''
      r.mediaRules = { }
      r.rendered = { }
      r.base = { }
      r.ids = [ ]

      // emit changes to notify subscribers
      r._emitChange()
    },

    /**
     * renders a new rule variation and caches the result
     *
     * @param {Function} rule - rule which gets rendered
     * @param {Object?} props - properties used to render
     * @return {string} className to reference the rendered rule
     */
    renderRule(rule, props = { }) {
      // rendering a rule for the first time
      // will create an ID reference
      if (r.ids.indexOf(rule) < 0) {
        r.ids.push(rule)

        // directly render the static base style to be able
        // to diff future dynamic style with those
        r.renderRule(rule, { })
      }

      // uses the reference ID and the props to generate an unique className
      const ruleId = r.ids.indexOf(rule)
      const className = 'c' + ruleId + r._generatePropsReference(props)

      // only if the cached rule has not already been rendered
      // with a specific set of properties it actually renders
      if (!r.rendered.hasOwnProperty(className)) {
        const style = r._processStyle(rule(props))
        r._renderStyle(className, style, r.base[ruleId])

        r.rendered[className] = r._didChange

        if (r._didChange) {
          r._didChange = false
          r._emitChange()
        }

        // keep static style to diff dynamic onces later on
        if (className === 'c' + ruleId) {
          r.base[ruleId] = style
        }
      }

      const baseClassName = 'c' + ruleId
      if (!r.rendered[className]) {
        return baseClassName
      }

      // returns either the base className or both the base and the dynamic part
      return className !== baseClassName ? baseClassName + ' ' + className : className
    },

    /**
     * renders a new keyframe variation and caches the result
     *
     * @param {Keyframe} keyframe - Keyframe which gets rendered
     * @param {Object?} props - properties used to render
     * @return {string} animationName to reference the rendered keyframe
     */
    renderKeyframe(keyframe, props = { }) {
      // rendering a Keyframe for the first time
      // will create cache entries and an ID reference
      if (r.ids.indexOf(keyframe) < 0) {
        r.ids.push(keyframe)
      }

      const propsReference = r._generatePropsReference(props)
      const animationName = 'k' + r.ids.indexOf(keyframe) + propsReference

      // only if the cached keyframe has not already been rendered
      // with a specific set of properties it actually renders
      if (!r.rendered.hasOwnProperty(animationName)) {
        const processedKeyframe = r._processStyle(keyframe(props))
        const css = cssifyKeyframe(processedKeyframe, animationName, r.keyframePrefixes)
        r.rendered[animationName] = true
        r.keyframes += css
        r._emitChange()
      }

      return animationName
    },

    /**
     * renders a new font-face and caches it
     *
     * @param {FontFace} fontFace - fontFace which gets rendered
     * @return {string} fontFamily reference
     */
    renderFont(family, files, properties = { }) {
      if (!r.rendered.hasOwnProperty(family)) {
        const fontFace = {
          fontFamily: '\'' + family + '\'',
          src: files.map(src => 'url(\'' + src + '\') format(\'' + getFontFormat(src) + '\')').join(',')
        }

        const fontProperties = [ 'fontVariant', 'fontWeight', 'fontStretch', 'fontStyle', 'unicodeRange' ]
        Object.keys(properties).filter(prop => fontProperties.indexOf(prop) > -1).forEach(fontProp => fontFace[fontProp] = properties[fontProp])

        const css = '@font-face{' + cssifyObject(fontFace) + '}'
        r.rendered[family] = true
        r.fontFaces += css
        r._emitChange()
      }

      return family
    },

    /**
     * renders static style and caches them
     *
     * @param {string|Object} style - static style to be rendered
     * @param {string?} selector - selector used to render the styles
     * @return {string} rendered CSS output
     */
    renderStatic(style, selector) {
      const reference = typeof style === 'string' ? style : selector

      if (!r.rendered.hasOwnProperty(reference)) {
        if (typeof style === 'string') {
          // remove new lines from template strings
          r.statics += style.replace(/\s{2,}/g, '')
        } else {
          r.statics += selector + '{' + cssifyObject(r._processStyle(style)) + '}'
        }

        r.rendered[reference] = true
        r._emitChange()
      }
    },

    /**
     * renders all cached styles into a single valid CSS string
     * clusters media query styles into groups to reduce output size

     * @return single concatenated CSS string
     */
    renderToString() {
      let css = r.fontFaces + r.statics + r.rules

      for (let media in r.mediaRules) {
        css += '@media ' + media + '{' + r.mediaRules[media] + '}'
      }

      return css + r.keyframes
    },

    /**
     * Adds a new subscription to get notified on every rerender
     *
     * @param {Function} callback - callback function which will be executed
     * @return {Object} equivalent unsubscribe method
     */
    subscribe(callback) {
      r.listeners.push(callback)
      return {
        unsubscribe: () => r.listeners.splice(r.listeners.indexOf(callback), 1)
      }
    },

    /**
     * calls each listener with the current CSS markup of all caches
     * gets only called if the markup actually changes
     *
     * @param {Function} callback - callback function which will be executed
     * @return {Object} equivalent unsubscribe method
     */
    _emitChange() {
      const css = r.renderToString()
      r.listeners.forEach(listener => listener(css))
    },

    /**
     * generates an unique reference id by content hashing props
     *
     * @param {Object} props - props that get hashed
     * @return {string} reference - unique props reference
     */
    _generatePropsReference(props) {
      return generateContentHash(sortedStringify(props))
    },

    /**
     * pipes a style object through a list of plugins
     *
     * @param {Object} style - style object to process
     * @return {Object} processed style
     */
    _processStyle(style) {
      return r.plugins.reduce((processedStyle, plugin) => plugin(processedStyle), style)
    },

    /**
     * iterates a style object and renders each rule to the cache
     *
     * @param {string} className - className reference to be rendered to
     * @param {Object} style - style object which is rendered
     * @param {Object`} base - base style subset for diffing
     */
    _renderStyle(className, style, base = { }, pseudo = '', media = '') {
      const ruleset = Object.keys(style).reduce((ruleset, property) => {
        const value = style[property]
        // recursive object iteration in order to render
        // pseudo class and media class declarations
        if (value instanceof Object && !Array.isArray(value)) {
          if (property.charAt(0) === ':') {
            r._renderStyle(className, value, base[property], pseudo + property, media)
          } else if (property.substr(0, 6) === '@media') {
            // combine media query rules with an `and`
            const query = property.slice(6).trim()
            const combinedMedia = media.length > 0 ? media + ' and ' + query : query
            r._renderStyle(className, value, base[property], pseudo, combinedMedia)
          }
        } else {
          // diff styles with the base styles to only extract dynamic styles
          if (value !== undefined && !base.hasOwnProperty(property) || base[property] !== value) {
            // remove concatenated string values including `undefined`
            if (typeof value === 'string' && value.indexOf('undefined') > -1) {
              return ruleset
            }
            ruleset[property] = value
          }
        }
        return ruleset
      }, { })

      // add styles to the cache
      if (Object.keys(ruleset).length > 0) {
        const css = '.' + className + pseudo + '{' + cssifyObject(ruleset) + '}'
        r._didChange = true

        if (media.length > 0) {
          if (!r.mediaRules.hasOwnProperty(media)) {
            r.mediaRules[media] = ''
          }

          r.mediaRules[media] += css
        } else {
          r.rules += css
        }
      }
    }
  }

  // initial setup
  renderer.keyframePrefixes.push('')
  renderer.clear()

  return renderer
}
<commit_msg>`properties` -> `props` for more consistency<commit_after>import generateContentHash from './utils/generateContentHash'
import sortedStringify from './utils/sortedStringify'
import getFontFormat from './utils/getFontFormat'

import cssifyKeyframe from './utils/cssifyKeyframe'
import cssifyObject from './utils/cssifyObject'

export default function Renderer(config = { }) {
  const r = {
    listeners: [],
    keyframePrefixes: config.keyframePrefixes || [ '-webkit-', '-moz-' ],
    plugins: config.plugins || [ ],

    /**
     * clears the sheet's cache but keeps all listeners
     */
    clear() {
      r.fontFaces = ''
      r.keyframes = ''
      r.statics = ''
      r.rules = ''
      r.mediaRules = { }
      r.rendered = { }
      r.base = { }
      r.ids = [ ]

      // emit changes to notify subscribers
      r._emitChange()
    },

    /**
     * renders a new rule variation and caches the result
     *
     * @param {Function} rule - rule which gets rendered
     * @param {Object?} props - properties used to render
     * @return {string} className to reference the rendered rule
     */
    renderRule(rule, props = { }) {
      // rendering a rule for the first time
      // will create an ID reference
      if (r.ids.indexOf(rule) < 0) {
        r.ids.push(rule)

        // directly render the static base style to be able
        // to diff future dynamic style with those
        r.renderRule(rule, { })
      }

      // uses the reference ID and the props to generate an unique className
      const ruleId = r.ids.indexOf(rule)
      const className = 'c' + ruleId + r._generatePropsReference(props)

      // only if the cached rule has not already been rendered
      // with a specific set of properties it actually renders
      if (!r.rendered.hasOwnProperty(className)) {
        const style = r._processStyle(rule(props))
        r._renderStyle(className, style, r.base[ruleId])

        r.rendered[className] = r._didChange

        if (r._didChange) {
          r._didChange = false
          r._emitChange()
        }

        // keep static style to diff dynamic onces later on
        if (className === 'c' + ruleId) {
          r.base[ruleId] = style
        }
      }

      const baseClassName = 'c' + ruleId
      if (!r.rendered[className]) {
        return baseClassName
      }

      // returns either the base className or both the base and the dynamic part
      return className !== baseClassName ? baseClassName + ' ' + className : className
    },

    /**
     * renders a new keyframe variation and caches the result
     *
     * @param {Keyframe} keyframe - Keyframe which gets rendered
     * @param {Object?} props - properties used to render
     * @return {string} animationName to reference the rendered keyframe
     */
    renderKeyframe(keyframe, props = { }) {
      // rendering a Keyframe for the first time
      // will create cache entries and an ID reference
      if (r.ids.indexOf(keyframe) < 0) {
        r.ids.push(keyframe)
      }

      const propsReference = r._generatePropsReference(props)
      const animationName = 'k' + r.ids.indexOf(keyframe) + propsReference

      // only if the cached keyframe has not already been rendered
      // with a specific set of properties it actually renders
      if (!r.rendered.hasOwnProperty(animationName)) {
        const processedKeyframe = r._processStyle(keyframe(props))
        const css = cssifyKeyframe(processedKeyframe, animationName, r.keyframePrefixes)
        r.rendered[animationName] = true
        r.keyframes += css
        r._emitChange()
      }

      return animationName
    },

    /**
     * renders a new font-face and caches it
     *
     * @param {FontFace} fontFace - fontFace which gets rendered
     * @return {string} fontFamily reference
     */
    renderFont(family, files, props = { }) {
      if (!r.rendered.hasOwnProperty(family)) {
        const fontFace = {
          fontFamily: '\'' + family + '\'',
          src: files.map(src => 'url(\'' + src + '\') format(\'' + getFontFormat(src) + '\')').join(',')
        }

        const fontProperties = [ 'fontVariant', 'fontWeight', 'fontStretch', 'fontStyle', 'unicodeRange' ]
        Object.keys(props).filter(prop => fontProperties.indexOf(prop) > -1).forEach(fontProp => fontFace[fontProp] = props[fontProp])

        const css = '@font-face{' + cssifyObject(fontFace) + '}'
        r.rendered[family] = true
        r.fontFaces += css
        r._emitChange()
      }

      return family
    },

    /**
     * renders static style and caches them
     *
     * @param {string|Object} style - static style to be rendered
     * @param {string?} selector - selector used to render the styles
     * @return {string} rendered CSS output
     */
    renderStatic(style, selector) {
      const reference = typeof style === 'string' ? style : selector

      if (!r.rendered.hasOwnProperty(reference)) {
        if (typeof style === 'string') {
          // remove new lines from template strings
          r.statics += style.replace(/\s{2,}/g, '')
        } else {
          r.statics += selector + '{' + cssifyObject(r._processStyle(style)) + '}'
        }

        r.rendered[reference] = true
        r._emitChange()
      }
    },

    /**
     * renders all cached styles into a single valid CSS string
     * clusters media query styles into groups to reduce output size

     * @return single concatenated CSS string
     */
    renderToString() {
      let css = r.fontFaces + r.statics + r.rules

      for (let media in r.mediaRules) {
        css += '@media ' + media + '{' + r.mediaRules[media] + '}'
      }

      return css + r.keyframes
    },

    /**
     * Adds a new subscription to get notified on every rerender
     *
     * @param {Function} callback - callback function which will be executed
     * @return {Object} equivalent unsubscribe method
     */
    subscribe(callback) {
      r.listeners.push(callback)
      return {
        unsubscribe: () => r.listeners.splice(r.listeners.indexOf(callback), 1)
      }
    },

    /**
     * calls each listener with the current CSS markup of all caches
     * gets only called if the markup actually changes
     *
     * @param {Function} callback - callback function which will be executed
     * @return {Object} equivalent unsubscribe method
     */
    _emitChange() {
      const css = r.renderToString()
      r.listeners.forEach(listener => listener(css))
    },

    /**
     * generates an unique reference id by content hashing props
     *
     * @param {Object} props - props that get hashed
     * @return {string} reference - unique props reference
     */
    _generatePropsReference(props) {
      return generateContentHash(sortedStringify(props))
    },

    /**
     * pipes a style object through a list of plugins
     *
     * @param {Object} style - style object to process
     * @return {Object} processed style
     */
    _processStyle(style) {
      return r.plugins.reduce((processedStyle, plugin) => plugin(processedStyle), style)
    },

    /**
     * iterates a style object and renders each rule to the cache
     *
     * @param {string} className - className reference to be rendered to
     * @param {Object} style - style object which is rendered
     * @param {Object`} base - base style subset for diffing
     */
    _renderStyle(className, style, base = { }, pseudo = '', media = '') {
      const ruleset = Object.keys(style).reduce((ruleset, property) => {
        const value = style[property]
        // recursive object iteration in order to render
        // pseudo class and media class declarations
        if (value instanceof Object && !Array.isArray(value)) {
          if (property.charAt(0) === ':') {
            r._renderStyle(className, value, base[property], pseudo + property, media)
          } else if (property.substr(0, 6) === '@media') {
            // combine media query rules with an `and`
            const query = property.slice(6).trim()
            const combinedMedia = media.length > 0 ? media + ' and ' + query : query
            r._renderStyle(className, value, base[property], pseudo, combinedMedia)
          }
        } else {
          // diff styles with the base styles to only extract dynamic styles
          if (value !== undefined && !base.hasOwnProperty(property) || base[property] !== value) {
            // remove concatenated string values including `undefined`
            if (typeof value === 'string' && value.indexOf('undefined') > -1) {
              return ruleset
            }
            ruleset[property] = value
          }
        }
        return ruleset
      }, { })

      // add styles to the cache
      if (Object.keys(ruleset).length > 0) {
        const css = '.' + className + pseudo + '{' + cssifyObject(ruleset) + '}'
        r._didChange = true

        if (media.length > 0) {
          if (!r.mediaRules.hasOwnProperty(media)) {
            r.mediaRules[media] = ''
          }

          r.mediaRules[media] += css
        } else {
          r.rules += css
        }
      }
    }
  }

  // initial setup
  renderer.keyframePrefixes.push('')
  renderer.clear()

  return renderer
}
<|endoftext|>


<commit_before>const DrawCard = require('../../../drawcard.js');

class NewlyMadeLord extends DrawCard {
    setupCardAbilities() {
        this.reaction({
            when: {
                onCardEntersPlay: (e, card) => card === this
            },
            handler: () => {
                this.game.promptForSelect(this.controller, {
                    cardCondition: card => this.cardCondition(card),
                    activePromptTitle: 'Select location',
                    waitingPromptTitle: 'Waiting for opponent to use ' + this.name,
                    onSelect: (player, card) => this.onCardSelected(player, card)
                });
            }
        });
    }

    cardCondition(card) {
        return !card.kneeled && card.getType() === 'location' && !card.isLimited() && card.getCost() <= 3;
    }

    onCardSelected(player, card) {
        card.controller.discardCard(card);

        this.game.addMessage('{0} uses {1} to discard {2}', player, this, card);

        return true;
    }
}

NewlyMadeLord.code = '02051';

module.exports = NewlyMadeLord;
<commit_msg>make Newly Made Lord activable on kneeled location (#461)<commit_after>const DrawCard = require('../../../drawcard.js');

class NewlyMadeLord extends DrawCard {
    setupCardAbilities() {
        this.reaction({
            when: {
                onCardEntersPlay: (e, card) => card === this
            },
            handler: () => {
                this.game.promptForSelect(this.controller, {
                    cardCondition: card => this.cardCondition(card),
                    activePromptTitle: 'Select location',
                    waitingPromptTitle: 'Waiting for opponent to use ' + this.name,
                    onSelect: (player, card) => this.onCardSelected(player, card)
                });
            }
        });
    }

    cardCondition(card) {
        return card.getType() === 'location' && !card.isLimited() && card.getCost() <= 3;
    }

    onCardSelected(player, card) {
        card.controller.discardCard(card);

        this.game.addMessage('{0} uses {1} to discard {2}', player, this, card);

        return true;
    }
}

NewlyMadeLord.code = '02051';

module.exports = NewlyMadeLord;
<|endoftext|>


<commit_before>var socket = io();
var waitingDblclick = false;
if (screenfull) {
  $("body").click(function() {
    waitingDblclick = true;
    window.setTimeout(function() {
      if (waitingDblclick) {
        screenfull.toggle();
      }
    }, 300);
    // e.preventDefault();
  });
}

$("body").dblclick(function(e) {
  waitingDblclick = false;
  $("#modal").modal("show");

});

// preload images that will be used as background
var queue = new createjs.LoadQueue(useXHR = false);
//The queue emits fileload event for
// files queued with loadFile
queue.on("fileload", function(event) {
  flipit(event.item.src);
}, this);

socket.on("photo", function(photo) {
  // queue the photo till it fires the fileload event
  queue.loadFile(photo.url_l);
})
// send new search tags
$("#options").submit(function(e) {
  var tags = $("#tags").val()
  delay = $("#delay").val();
  socket.emit("newoptions", {
    tags: tags,
    delay: delay
  });
  e.preventDefault();
  $("#modal").modal("hide")
});
// prevent bubling of the clicks

$("#modal").click(function(e) {
  e.stopPropagation();
});

function flipit(url) {
  $("html").css({
    "background": "url(" + url + ") no-repeat center center fixed",
    "-webkit-background-size": "cover",
    "-moz-background-size": "cover",
    "-o-background-size": "cover",
    "background-size": "cover"
  });

}<commit_msg>clean the client images queue on flip because images got loaded in spite of new photo arriving. This was delaying playback<commit_after>var socket = io();
var waitingDblclick = false;
if (screenfull) {
  $("body").click(function() {
    waitingDblclick = true;
    window.setTimeout(function() {
      if (waitingDblclick) {
        screenfull.toggle();
      }
    }, 300);
    // e.preventDefault();
  });
}

$("body").dblclick(function(e) {
  waitingDblclick = false;
  $("#modal").modal("show");

});

// preload images that will be used as background
var queue = new createjs.LoadQueue(useXHR = false);
//The queue emits fileload event for
// files queued with loadFile
queue.on("fileload", function(event) {
  flipit(event.item.src);
  // Clean the queue because
  // queued images get loaded in spite
  // of a new photo arriving.
  queue.removeAll();
}, this);

socket.on("photo", function(photo) {
  // queue the photo till it fires the fileload event
  queue.loadFile(photo.url_l);
})
// send new search tags
$("#options").submit(function(e) {
  var tags = $("#tags").val()
  delay = $("#delay").val();
  socket.emit("newoptions", {
    tags: tags,
    delay: delay
  });
  e.preventDefault();
  $("#modal").modal("hide")
});
// prevent bubling of the clicks

$("#modal").click(function(e) {
  e.stopPropagation();
});

function flipit(url) {
  $("html").css({
    "background": "url(" + url + ") no-repeat center center fixed",
    "-webkit-background-size": "cover",
    "-moz-background-size": "cover",
    "-o-background-size": "cover",
    "background-size": "cover"
  });

}<|endoftext|>


<commit_before>/*
Leaflet.markercluster building, testing and linting scripts.

To use, install Node, then run the following commands in the project root:

    npm install -g jake
    npm install

To check the code for errors and build Leaflet from source, run "jake".
To run the tests, run "jake test".

For a custom build, open build/build.html in the browser and follow the instructions.
*/

var path = require('path');

desc('Check Leaflet.markercluster source for errors with JSHint');
task('lint', function(){
		jake.exec('node node_modules/.bin/jshint ', {
			printStdout: true
		}, function () {
			console.log('\tCheck passed.\n');
			complete();
		});
});

desc('Combine Leaflet.markercluster source files');
task('build', ['lint'], function(){
	jake.exec('npm run-script rollup', function() { console.log('Rolled up.'); });
});

desc('Compress bundled files');
task('uglify', ['build'], function(){
	jake.exec('npm run-script uglify', function() { console.log('Uglyfied.'); });
});

desc('Run PhantomJS tests');
task('test', ['lint'], function() {

	var karma = require('karma'),
	testConfig = {configFile : path.join(__dirname, './spec/karma.conf.js')};

	testConfig.browsers = ['PhantomJS'];

	function isArgv(optName) {
		 return process.argv.indexOf(optName) !== -1;
	}

	if (isArgv('--chrome')) {
		testConfig.browsers.push('Chrome');
	}
	if (isArgv('--safari')) {
		testConfig.browsers.push('Safari');
	}
	if (isArgv('--ff')) {
		testConfig.browsers.push('Firefox');
	}
	if (isArgv('--ie')) {
		testConfig.browsers.push('IE');
	}

	if (isArgv('--cov')) {
		testConfig.preprocessors = {
			'src/**/*.js': 'coverage'
		};
		testConfig.coverageReporter = {
			type : 'html',
			dir : 'coverage/'
		};
		testConfig.reporters = ['coverage'];
	}

	console.log('Running tests...');

	var server = new karma.Server(testConfig, function(exitCode) {
		if (!exitCode) {
			console.log('\tTests ran successfully.\n');
			complete();
		} else {
			process.exit(exitCode);
		}
	});
	server.start();
});

task('default', ['build', 'uglify']);
<commit_msg>Get jshint to run on windows<commit_after>/*
Leaflet.markercluster building, testing and linting scripts.

To use, install Node, then run the following commands in the project root:

    npm install -g jake
    npm install

To check the code for errors and build Leaflet from source, run "jake".
To run the tests, run "jake test".

For a custom build, open build/build.html in the browser and follow the instructions.
*/

var path = require('path');

desc('Check Leaflet.markercluster source for errors with JSHint');
task('lint', function(){
		jake.exec('jshint', {
			printStdout: true
		}, function () {
			console.log('\tCheck passed.\n');
			complete();
		});
});

desc('Combine Leaflet.markercluster source files');
task('build', ['lint'], function(){
	jake.exec('npm run-script rollup', function() { console.log('Rolled up.'); });
});

desc('Compress bundled files');
task('uglify', ['build'], function(){
	jake.exec('npm run-script uglify', function() { console.log('Uglyfied.'); });
});

desc('Run PhantomJS tests');
task('test', ['lint'], function() {

	var karma = require('karma'),
	testConfig = {configFile : path.join(__dirname, './spec/karma.conf.js')};

	testConfig.browsers = ['PhantomJS'];

	function isArgv(optName) {
		 return process.argv.indexOf(optName) !== -1;
	}

	if (isArgv('--chrome')) {
		testConfig.browsers.push('Chrome');
	}
	if (isArgv('--safari')) {
		testConfig.browsers.push('Safari');
	}
	if (isArgv('--ff')) {
		testConfig.browsers.push('Firefox');
	}
	if (isArgv('--ie')) {
		testConfig.browsers.push('IE');
	}

	if (isArgv('--cov')) {
		testConfig.preprocessors = {
			'src/**/*.js': 'coverage'
		};
		testConfig.coverageReporter = {
			type : 'html',
			dir : 'coverage/'
		};
		testConfig.reporters = ['coverage'];
	}

	console.log('Running tests...');

	var server = new karma.Server(testConfig, function(exitCode) {
		if (!exitCode) {
			console.log('\tTests ran successfully.\n');
			complete();
		} else {
			process.exit(exitCode);
		}
	});
	server.start();
});

task('default', ['build', 'uglify']);
<|endoftext|>


<commit_before>"use strict";
if (process.env.NODE_ENV=="test") {
  exports.osmbc_version = "1.8.4";
} else {
  exports.osmbc_version = "1.8.6b";
}

<commit_msg>Version.<commit_after>"use strict";
if (process.env.NODE_ENV=="test") {
  exports.osmbc_version = "1.8.4";
} else {
  exports.osmbc_version = "1.8.6c";
}

<|endoftext|>


<commit_before>define(['mathlive/core/mathAtom'],
    function(MathAtom) {

/**
 * Give an atom or an array of atoms, return a LaTeX string representation
 * @return {string}
 * @param {string|MathAtom|MathAtom[]} value
 * @private
 */
function latexify(value) {
    let result = '';
    if (Array.isArray(value)) {
        for (const child of value) {
            result += latexify(child);
        }
    } else if (typeof value === 'number' || typeof value === 'boolean') {
        result = value.toString();
    } else if (typeof value === 'string') {
        result = value.replace(/\s/g, '~');
    } else if (value && typeof value.toLatex === 'function') {
        result = value.toLatex();
    }
    return result;
}

/**
 * Return a LaTeX representation of the atom
 *
 * @return {string}
 */
MathAtom.MathAtom.prototype.toLatex = function() {
    let result = '';
    let col, row = 0;
    let i = 0;
    const command = this.latex ? this.latex.trim() : null;
    switch(this.type) {
        case 'group':
            result += this.latexOpen || '';
            result += latexify(this.body);
            result += this.latexClose || '';
            break;

        case 'array':
            result += '\\begin{' + this.env.name + '}';
            if (this.env.name === 'array') {
                result += '{';
                if (this.colFormat) {
                    for (i = 0; i < this.colFormat.length; i++) {
                        if (this.colFormat[i].align) {
                            result += this.colFormat[i].align;
                        } else if (this.colFormat[i].rule) {
                            result += '|';
                        }
                    }
                }
                result += '}';
            }
            for (row = 0; row < this.array.length; row++) {
                for (col = 0; col < this.array[row].length; col++) {
                    if (col > 0) result += ' & ';
                    result += latexify(this.array[row][col]);
                }
                // Adds a separator between rows (but not after the last row)
                if (row < this.array.length - 1) {
                    result += ' \\\\ ';
                }
            }
            result += '\\end{' + this.env.name + '}';
            break;

        case 'root':
            result = latexify(this.body);
            break;

        case 'genfrac':
            if (this.body === 'choose' || this.body === 'atop') {
                // Infix commands.
                result += '{';
                result += latexify(this.numer)
                result += '\\' + this.body + ' ';
                result += latexify(this.denom);
                result += '}';
            } else {
                // @todo: deal with fracs delimiters
                result += this.latex;
                result += `{${latexify(this.numer)}}{${latexify(this.denom)}}`;
            }
            break;

        case 'surd':
            result += '\\sqrt';
            if (this.index) {
                result += '[';
                result += latexify(this.index);
                result += ']';
            }
            result += `{${latexify(this.body)}}`;
            break;

        case 'leftright':
            result += '\\left' + this.leftDelim + ' ';
            result += latexify(this.body);
            result += '\\right' + this.rightDelim + ' ';
            break;

        case 'delim':
        case 'sizeddelim':
            result += command + '{' + this.delim + '}';
            break;

        case 'rule':
            result += command;
            if (this.shift) {
                result += `[${latexify(this.shift)}em]`;
            }
            result += `{${latexify(this.width)}em}{${latexify(this.height)}em}`;
            break;

        case 'line':
        case 'overlap':
        case 'font':
        case 'accent':
            result += `${command}{${latexify(this.body)}}`;
            break;

        case 'overunder':
            result += `${command}{${latexify(this.overscript || this.underscript)}}{${latexify(this.body)}}`;
            break;

        case 'mord':
        case 'minner':
        case 'mbin':
        case 'mrel':
        case 'mpunct':
        case 'mopen':
        case 'mclose':
        case 'textord':
            if (command === '\\mathbin' || command === '\\mathrel' ||
                command === '\\mathopen' || command === '\\mathclose' ||
                command === '\\mathpunct' || command === '\\mathord' ||
                command === '\\mathinner') {
                result += command + '{' + latexify(this.body) + '}';
            } else if (this.latex || typeof this.body === 'string') {
                // Not ZERO-WIDTH
                if (this.latex && this.latex[0] === '\\') {
                    result += this.latex + ' ';
                } else {
                    result += this.body !== '\u200b' ? this.body : '';
                }
            }
            break;

        case 'mop':
            if (this.body !== '\u200b') {
                // Not ZERO-WIDTH
                if (command === '\\mathop' || command === '\\operatorname') {
                    result += command + '{' + latexify(this.body) + '}';
                } else {
                    result += this.latex || this.body;
                }
            }
            if (this.explicitLimits) {
                if (this.limits === 'limits') result += '\\limits ';
                if (this.limits === 'nolimits') result += '\\nolimits ';
            }
            break;

        case 'color':
            result += command;
            if (this.color) {
                result += `{${this.color}}`;
            } else if (this.textcolor) {
                result += '{' + this.textcolor + '}{' + latexify(this.body) + '}';
            }
            break;

        case 'box':
            if (command === '\\bbox') {
                result += command;
                if (this.padding || this.border || this.backgroundcolor) {
                    let bboxParams = latexify(this.padding);
                    if (this.border) {
                        if (bboxParams) bboxParams += ',';
                        bboxParams += 'border:' + latexify(this.border);
                    }
                    if (this.backgroundcolor) {
                        if (bboxParams) bboxParams += ',';
                        bboxParams += latexify(this.backgroundcolor);
                    }
                    result += `[${bboxParams}]`;
                }
                result += `{${latexify(this.body)}}`;
            } else if (command === '\\boxed') {
                result += `\\boxed{${latexify(this.body)}}`;
            } else {
                // \\colorbox, \\fcolorbox
                result += command;
                if (this.framecolor) {
                    result += `{${latexify(this.framecolor)}}`;
                }
                if (this.backgroundcolor) {
                    result += `{${latexify(this.backgroundcolor)}}`;
                }
                result += `{${latexify(this.body)}}`;
            }
            break;

        case 'spacing':
            result += command;
            if (this.width) {
                result += `{${latexify(this.width)}}`;
            } else {
                result += ' ';
            }
            break;

        case 'enclose':
            result += command;
            if (command === '\\enclose') {
                result += '{';
                let sep = '';
                for (const notation in this.notation) {
                    if (this.notation.hasOwnProperty(notation) &&
                        this.notation[notation]) {
                        result += sep + notation;
                        sep = ' ';
                    }
                }
                result += '}';

                // \enclose can have optional parameters...
                let style = '';
                sep = '';
                if (this.backgroundcolor && this.backgroundcolor !== 'transparent') {
                    style += sep + 'mathbackground="' + this.backgroundcolor + '"';
                    sep = ',';
                }
                if (this.shadow && this.shadow !== 'auto') {
                    style += sep + 'shadow="' + this.shadow + '"';
                    sep = ',';
                }
                if (this.strokeWidth !== 1 || this.strokeStyle !== 'solid') {
                    style += sep + this.borderStyle;
                    sep = ',';
                } else if (this.strokeColor && this.strokeColor !== 'currentColor') {
                    style += sep + 'mathcolor="' + this.strokeColor + '"';
                    sep = ',';
                }

                if (style) {
                    result += `[${style}]`;
                }
            }
            result += `{${latexify(this.body)}}`;
            break;

        case 'mathstyle':
            result += '\\' + this.mathstyle + ' ';
            break;

        case 'sizing':
            result =  {'size1': '\\tiny ',
                'size2': '\\scriptsize ',
                'size3': '\\footnotesize ',
                'size4': '\\small ',
                'size5': '\\normalsize ',
                'size6': '\\large ',
                'size7': '\\Large ',
                'size8': '\\LARGE ',
                'size9': '\\huge ',
                'size10': '\\Huge '}[this.size] || '';
            break;

        case 'space':
            result += this.latex;
            break;

        case 'first':
        case 'placeholder':
        case 'command':
            break;

        default:
            console.log('unknown atom type "' + this.type + '"');
            break;

    }
    if (this.superscript) {
        const sup = latexify(this.superscript);
        if (sup.length === 1) {
            result += '^' + sup;
        } else {
            result += '^{' + sup + '}';
        }
    }
    if (this.subscript) {
        const sub = latexify(this.subscript);
        if (sub.length === 1) {
            result += '_' + sub;
        } else {
            result += '_{' + sub + '}';
        }
    }
    return result;
}

// Export the public interface for this module
return {
}

})
<commit_msg>Correctly output the 'minus' sign to Latex<commit_after>define(['mathlive/core/mathAtom'],
    function(MathAtom) {

/**
 * Give an atom or an array of atoms, return a LaTeX string representation
 * @return {string}
 * @param {string|MathAtom|MathAtom[]} value
 * @private
 */
function latexify(value) {
    let result = '';
    if (Array.isArray(value)) {
        for (const child of value) {
            result += latexify(child);
        }
    } else if (typeof value === 'number' || typeof value === 'boolean') {
        result = value.toString();
    } else if (typeof value === 'string') {
        result = value.replace(/\s/g, '~');
    } else if (value && typeof value.toLatex === 'function') {
        result = value.toLatex();
    }
    return result;
}

/**
 * Return a LaTeX representation of the atom
 *
 * @return {string}
 */
MathAtom.MathAtom.prototype.toLatex = function() {
    let result = '';
    let col, row = 0;
    let i = 0;
    const command = this.latex ? this.latex.trim() : null;
    switch(this.type) {
        case 'group':
            result += this.latexOpen || '';
            result += latexify(this.body);
            result += this.latexClose || '';
            break;

        case 'array':
            result += '\\begin{' + this.env.name + '}';
            if (this.env.name === 'array') {
                result += '{';
                if (this.colFormat) {
                    for (i = 0; i < this.colFormat.length; i++) {
                        if (this.colFormat[i].align) {
                            result += this.colFormat[i].align;
                        } else if (this.colFormat[i].rule) {
                            result += '|';
                        }
                    }
                }
                result += '}';
            }
            for (row = 0; row < this.array.length; row++) {
                for (col = 0; col < this.array[row].length; col++) {
                    if (col > 0) result += ' & ';
                    result += latexify(this.array[row][col]);
                }
                // Adds a separator between rows (but not after the last row)
                if (row < this.array.length - 1) {
                    result += ' \\\\ ';
                }
            }
            result += '\\end{' + this.env.name + '}';
            break;

        case 'root':
            result = latexify(this.body);
            break;

        case 'genfrac':
            if (this.body === 'choose' || this.body === 'atop') {
                // Infix commands.
                result += '{';
                result += latexify(this.numer)
                result += '\\' + this.body + ' ';
                result += latexify(this.denom);
                result += '}';
            } else {
                // @todo: deal with fracs delimiters
                result += this.latex;
                result += `{${latexify(this.numer)}}{${latexify(this.denom)}}`;
            }
            break;

        case 'surd':
            result += '\\sqrt';
            if (this.index) {
                result += '[';
                result += latexify(this.index);
                result += ']';
            }
            result += `{${latexify(this.body)}}`;
            break;

        case 'leftright':
            result += '\\left' + this.leftDelim + ' ';
            result += latexify(this.body);
            result += '\\right' + this.rightDelim + ' ';
            break;

        case 'delim':
        case 'sizeddelim':
            result += command + '{' + this.delim + '}';
            break;

        case 'rule':
            result += command;
            if (this.shift) {
                result += `[${latexify(this.shift)}em]`;
            }
            result += `{${latexify(this.width)}em}{${latexify(this.height)}em}`;
            break;

        case 'line':
        case 'overlap':
        case 'font':
        case 'accent':
            result += `${command}{${latexify(this.body)}}`;
            break;

        case 'overunder':
            result += `${command}{${latexify(this.overscript || this.underscript)}}{${latexify(this.body)}}`;
            break;

        case 'mord':
        case 'minner':
        case 'mbin':
        case 'mrel':
        case 'mpunct':
        case 'mopen':
        case 'mclose':
        case 'textord':
            if (command === '\\mathbin' || command === '\\mathrel' ||
                command === '\\mathopen' || command === '\\mathclose' ||
                command === '\\mathpunct' || command === '\\mathord' ||
                command === '\\mathinner') {
                result += command + '{' + latexify(this.body) + '}';
            } else if (this.latex || typeof this.body === 'string') {
                // Not ZERO-WIDTH
                if (this.latex && this.latex[0] === '\\') {
                    result += this.latex + ' ';
                } else if (this.latex) {
                    result += this.latex;
                } else {
                    result += this.body !== '\u200b' ? this.body : '';
                }
            }
            break;

        case 'mop':
            if (this.body !== '\u200b') {
                // Not ZERO-WIDTH
                if (command === '\\mathop' || command === '\\operatorname') {
                    result += command + '{' + latexify(this.body) + '}';
                } else {
                    result += this.latex || this.body;
                }
            }
            if (this.explicitLimits) {
                if (this.limits === 'limits') result += '\\limits ';
                if (this.limits === 'nolimits') result += '\\nolimits ';
            }
            break;

        case 'color':
            result += command;
            if (this.color) {
                result += `{${this.color}}`;
            } else if (this.textcolor) {
                result += '{' + this.textcolor + '}{' + latexify(this.body) + '}';
            }
            break;

        case 'box':
            if (command === '\\bbox') {
                result += command;
                if (this.padding || this.border || this.backgroundcolor) {
                    let bboxParams = latexify(this.padding);
                    if (this.border) {
                        if (bboxParams) bboxParams += ',';
                        bboxParams += 'border:' + latexify(this.border);
                    }
                    if (this.backgroundcolor) {
                        if (bboxParams) bboxParams += ',';
                        bboxParams += latexify(this.backgroundcolor);
                    }
                    result += `[${bboxParams}]`;
                }
                result += `{${latexify(this.body)}}`;
            } else if (command === '\\boxed') {
                result += `\\boxed{${latexify(this.body)}}`;
            } else {
                // \\colorbox, \\fcolorbox
                result += command;
                if (this.framecolor) {
                    result += `{${latexify(this.framecolor)}}`;
                }
                if (this.backgroundcolor) {
                    result += `{${latexify(this.backgroundcolor)}}`;
                }
                result += `{${latexify(this.body)}}`;
            }
            break;

        case 'spacing':
            result += command;
            if (this.width) {
                result += `{${latexify(this.width)}}`;
            } else {
                result += ' ';
            }
            break;

        case 'enclose':
            result += command;
            if (command === '\\enclose') {
                result += '{';
                let sep = '';
                for (const notation in this.notation) {
                    if (this.notation.hasOwnProperty(notation) &&
                        this.notation[notation]) {
                        result += sep + notation;
                        sep = ' ';
                    }
                }
                result += '}';

                // \enclose can have optional parameters...
                let style = '';
                sep = '';
                if (this.backgroundcolor && this.backgroundcolor !== 'transparent') {
                    style += sep + 'mathbackground="' + this.backgroundcolor + '"';
                    sep = ',';
                }
                if (this.shadow && this.shadow !== 'auto') {
                    style += sep + 'shadow="' + this.shadow + '"';
                    sep = ',';
                }
                if (this.strokeWidth !== 1 || this.strokeStyle !== 'solid') {
                    style += sep + this.borderStyle;
                    sep = ',';
                } else if (this.strokeColor && this.strokeColor !== 'currentColor') {
                    style += sep + 'mathcolor="' + this.strokeColor + '"';
                    sep = ',';
                }

                if (style) {
                    result += `[${style}]`;
                }
            }
            result += `{${latexify(this.body)}}`;
            break;

        case 'mathstyle':
            result += '\\' + this.mathstyle + ' ';
            break;

        case 'sizing':
            result =  {'size1': '\\tiny ',
                'size2': '\\scriptsize ',
                'size3': '\\footnotesize ',
                'size4': '\\small ',
                'size5': '\\normalsize ',
                'size6': '\\large ',
                'size7': '\\Large ',
                'size8': '\\LARGE ',
                'size9': '\\huge ',
                'size10': '\\Huge '}[this.size] || '';
            break;

        case 'space':
            result += this.latex;
            break;

        case 'first':
        case 'placeholder':
        case 'command':
            break;

        default:
            console.log('unknown atom type "' + this.type + '"');
            break;

    }
    if (this.superscript) {
        const sup = latexify(this.superscript);
        if (sup.length === 1) {
            result += '^' + sup;
        } else {
            result += '^{' + sup + '}';
        }
    }
    if (this.subscript) {
        const sub = latexify(this.subscript);
        if (sub.length === 1) {
            result += '_' + sub;
        } else {
            result += '_{' + sub + '}';
        }
    }
    return result;
}

// Export the public interface for this module
return {
}

})
<|endoftext|>


<commit_before>const DrawCard = require('../../../drawcard.js');

class BreienneOfTarth extends DrawCard {
    setupCardAbilities(ability) {
        this.persistentEffect({
            condition: () => this.controller.cardsInPlay.any(card => card.hasTrait('King') || card.name === 'Catelyn Stark'),
            match: this,
            effect: ability.effects.doesNotKneelAsDefender()
        });
    }
}

BreienneOfTarth.code = '04083';

module.exports = BreienneOfTarth;
<commit_msg>fix typo in Brienne's class name (#463)<commit_after>const DrawCard = require('../../../drawcard.js');

class BrienneOfTarth extends DrawCard {
    setupCardAbilities(ability) {
        this.persistentEffect({
            condition: () => this.controller.cardsInPlay.any(card => card.hasTrait('King') || card.name === 'Catelyn Stark'),
            match: this,
            effect: ability.effects.doesNotKneelAsDefender()
        });
    }
}

BrienneOfTarth.code = '04083';

module.exports = BrienneOfTarth;
<|endoftext|>


<commit_before>'use strict';

const bodyParser = require('koa-bodyparser');
const Joi = require('joi');
const validateNpmPackageName = require('validate-npm-package-name');
const keyBy = require('lodash/keyBy');
const uniq = require('lodash/uniq');
const joiHelper = require('../../util/joiHelper');

function validateNames(names) {
    names.forEach((name) => {
        const validation = validateNpmPackageName(name);

        if (validation.errors) {
            throw Object.assign(new Error(`${validation.errors[0]} ("${name}")`),
                { code: 'INVALID_PARAMETER', status: 400, expose: true });
        }
    });

    return names;
}

function fetchFromCouchdb(names, npmsNano) {
    return npmsNano.fetchAsync({ keys: names.map((name) => `package!${name}`) })
    .get('rows')
    .map((row) => {
        if (row.doc) {
            return {
                analyzedAt: row.doc.finishedAt,
                collected: row.doc.collected,
                evaluation: row.doc.evaluation,
                error: row.doc.error,
            };
        }

        if (row.error === 'not_found') {
            return null;
        }

        throw Object.assign(new Error(`Unable to retrieve ${row.key} from CouchDB`), { code: row.error });
    });
}

function fetchFromElasticsearch(names, esClient) {
    return Promise.resolve(esClient.mget({
        index: 'npms-current',
        type: 'score',
        body: { ids: names },
        _source: ['score'],
    }))
    .get('docs')
    .map((doc) => doc._source);
}

// ----------------------------------------------------------

module.exports = (router, npmsNano, esClient) => {
    /**
     * @api {get} /package/:name Get package info
     * @apiName GetPackageInfo
     * @apiGroup Package
     * @apiVersion 2.0.0
     *
     * @apiParam {string} name The package name
     *
     * @apiSuccess {string} analyzedAt  The date in which the package was last analyzed
     * @apiSuccess {object} collected   The collected information from all sources
     * @apiSuccess {object} evaluation  The package evaluation
     * @apiSuccess {object} score       The package score
     * @apiSuccess {object} [error]     The error associated with the last analyze attempt
     *
     * @apiSuccessExample {json} Response
     *     {
     *       "analyzedAt": "2016-07-04T23:04:54.646Z",
     *       "collected": { "metadata": { ... }, "npm": { ... }, ... },
     *       "evaluation": { "quality": { ... }, "popularity": { ... }, ... }
     *       "score": { "final": 0.89, "detail": { "quality": 0.99, "popularity": 0.77, ... } },
     *       "error": { ... }
     *     }
     *
     * @apiExample {curl} Usage
     *     curl "https://api.npms.io/v2/package/react"
     */
    /* eslint-disable newline-per-chained-call */
    const infoParamsSchema = Joi.object({
        name: Joi.string().trim().min(1).required(),
    }).required();
    /* eslint-enable newline-per-chained-call */

    router.get('/package/:name',
        function* (next) {
            this.validated = joiHelper.validate(infoParamsSchema, this.params);
            validateNames([this.validated.name]);
            yield next;
        },
        function* () {
            const names = [this.validated.name];

            this.log.debug({ names }, 'Will fetch info');

            // Fetch info
            const info = yield Promise.props({
                couchdb: fetchFromCouchdb(names, npmsNano).get(0),
                elasticsearch: fetchFromElasticsearch(names, esClient).get(0),
            });

            this.log.debug({ names, info }, 'Got info');

            // Check if the package does not exist
            if (!info.couchdb || !info.elasticsearch) {
                throw Object.assign(new Error('Module not found'), { code: 'NOT_FOUND', status: 404, expose: true });
            }

            this.body = Object.assign({}, info.couchdb, info.elasticsearch);
        });

    /**
     * @api {post} /package/mget Get various packages info
     * @apiName GetMultiPackageInfo
     * @apiGroup Package
     * @apiVersion 2.0.0
     *
     * @apiParam {array} names The package names, specified as the request body
     *
     * @apiSuccess (Each info has) {string} analyzedAt  The date in which the package was last analyzed
     * @apiSuccess (Each info has) {object} collected   The collected information from all sources
     * @apiSuccess (Each info has) {object} evaluation  The package evaluation
     * @apiSuccess (Each info has) {object} score       The package score
     * @apiSuccess (Each info has) {object} [error]     The error associated with the last analyze attempt
     *
     * @apiSuccessExample {json} Response
     *     {
     *       "cross-spawn": {
     *         "analyzedAt": "2016-07-04T23:04:54.646Z",
     *         "collected": { "metadata": { ... }, "npm": { ... }, ... },
     *         "evaluation": { "quality": { ... }, "popularity": { ... }, ... }
     *         "score": { "final": 0.89, "detail": { "quality": 0.99, "popularity": 0.77, ... } }
     *         "error": { ... }
     *       },
     *       "react": {
     *         "analyzedAt": "2016-07-03T11:23:04.553Z",
     *         "collected": { "metadata": { ... }, "npm": { ... }, ... },
     *         "evaluation": { "quality": { ... }, "popularity": { ... }, ... }
     *         "score": { "final": 0.95, "detail": { "quality": 0.99, "popularity": 0.93, ... } }
     *         "error": { ... }
     *       }
     *     }
     *
     * @apiExample {curl} Example usage
     *     curl -X POST "https://api.npms.io/v2/package/mget" \
     *          -H "Accept: application/json" \
     *          -H "Content-Type: application/json" \
     *          -d '["cross-spawn", "react"]'
     */
    /* eslint-disable newline-per-chained-call */
    const infoMgetBodySchema = Joi.array().label('names').min(1).max(250).items(Joi.string().trim().min(1)).required();
    /* eslint-enable newline-per-chained-call */

    router.post('/package/mget',
        bodyParser({ enableTypes: ['json'] }),
        function* (next) {
            this.validated = validateNames(joiHelper.validate(infoMgetBodySchema, uniq(this.request.body)));
            yield next;
        },
        function* () {
            const names = this.validated;

            this.log.debug({ names }, 'Will fetch info');

            // Fetch info
            const info = yield Promise.props({
                couchdb: fetchFromCouchdb(names, npmsNano),
                elasticsearch: fetchFromElasticsearch(names, esClient),
            });

            this.log.debug({ names, info }, 'Got info');

            // Build the packages array, clearing the ones that do not exist
            const infos = info.couchdb
            .map((infoCouchdb, index) => {
                const infoElasticsearch = info.elasticsearch[index];

                return !infoCouchdb || !infoElasticsearch ? null :
                    Object.assign({}, infoCouchdb, infoElasticsearch);
            })
            .filter((info) => !!info);

            // Return an object indexed by name
            this.body = keyBy(infos, (info) => info.collected.metadata.name);
        });
};
<commit_msg>fix: fix 404 not found in deleted docs<commit_after>'use strict';

const bodyParser = require('koa-bodyparser');
const Joi = require('joi');
const validateNpmPackageName = require('validate-npm-package-name');
const keyBy = require('lodash/keyBy');
const uniq = require('lodash/uniq');
const joiHelper = require('../../util/joiHelper');

function validateNames(names) {
    names.forEach((name) => {
        const validation = validateNpmPackageName(name);

        if (validation.errors) {
            throw Object.assign(new Error(`${validation.errors[0]} ("${name}")`),
                { code: 'INVALID_PARAMETER', status: 400, expose: true });
        }
    });

    return names;
}

function fetchFromCouchdb(names, npmsNano) {
    return npmsNano.fetchAsync({ keys: names.map((name) => `package!${name}`) })
    .get('rows')
    .map((row) => {
        if (row.doc) {
            return {
                analyzedAt: row.doc.finishedAt,
                collected: row.doc.collected,
                evaluation: row.doc.evaluation,
                error: row.doc.error,
            };
        }

        if (row.error === 'not_found' || !row.doc) {
            return null;
        }

        throw Object.assign(new Error(`Unable to retrieve ${row.key} from CouchDB`), { code: row.error });
    });
}

function fetchFromElasticsearch(names, esClient) {
    return Promise.resolve(esClient.mget({
        index: 'npms-current',
        type: 'score',
        body: { ids: names },
        _source: ['score'],
    }))
    .get('docs')
    .map((doc) => doc._source);
}

// ----------------------------------------------------------

module.exports = (router, npmsNano, esClient) => {
    /**
     * @api {get} /package/:name Get package info
     * @apiName GetPackageInfo
     * @apiGroup Package
     * @apiVersion 2.0.0
     *
     * @apiParam {string} name The package name
     *
     * @apiSuccess {string} analyzedAt  The date in which the package was last analyzed
     * @apiSuccess {object} collected   The collected information from all sources
     * @apiSuccess {object} evaluation  The package evaluation
     * @apiSuccess {object} score       The package score
     * @apiSuccess {object} [error]     The error associated with the last analyze attempt
     *
     * @apiSuccessExample {json} Response
     *     {
     *       "analyzedAt": "2016-07-04T23:04:54.646Z",
     *       "collected": { "metadata": { ... }, "npm": { ... }, ... },
     *       "evaluation": { "quality": { ... }, "popularity": { ... }, ... }
     *       "score": { "final": 0.89, "detail": { "quality": 0.99, "popularity": 0.77, ... } },
     *       "error": { ... }
     *     }
     *
     * @apiExample {curl} Usage
     *     curl "https://api.npms.io/v2/package/react"
     */
    /* eslint-disable newline-per-chained-call */
    const infoParamsSchema = Joi.object({
        name: Joi.string().trim().min(1).required(),
    }).required();
    /* eslint-enable newline-per-chained-call */

    router.get('/package/:name',
        function* (next) {
            this.validated = joiHelper.validate(infoParamsSchema, this.params);
            validateNames([this.validated.name]);
            yield next;
        },
        function* () {
            const names = [this.validated.name];

            this.log.debug({ names }, 'Will fetch info');

            // Fetch info
            const info = yield Promise.props({
                couchdb: fetchFromCouchdb(names, npmsNano).get(0),
                elasticsearch: fetchFromElasticsearch(names, esClient).get(0),
            });

            this.log.debug({ names, info }, 'Got info');

            // Check if the package does not exist
            if (!info.couchdb || !info.elasticsearch) {
                throw Object.assign(new Error('Module not found'), { code: 'NOT_FOUND', status: 404, expose: true });
            }

            this.body = Object.assign({}, info.couchdb, info.elasticsearch);
        });

    /**
     * @api {post} /package/mget Get various packages info
     * @apiName GetMultiPackageInfo
     * @apiGroup Package
     * @apiVersion 2.0.0
     *
     * @apiParam {array} names The package names, specified as the request body
     *
     * @apiSuccess (Each info has) {string} analyzedAt  The date in which the package was last analyzed
     * @apiSuccess (Each info has) {object} collected   The collected information from all sources
     * @apiSuccess (Each info has) {object} evaluation  The package evaluation
     * @apiSuccess (Each info has) {object} score       The package score
     * @apiSuccess (Each info has) {object} [error]     The error associated with the last analyze attempt
     *
     * @apiSuccessExample {json} Response
     *     {
     *       "cross-spawn": {
     *         "analyzedAt": "2016-07-04T23:04:54.646Z",
     *         "collected": { "metadata": { ... }, "npm": { ... }, ... },
     *         "evaluation": { "quality": { ... }, "popularity": { ... }, ... }
     *         "score": { "final": 0.89, "detail": { "quality": 0.99, "popularity": 0.77, ... } }
     *         "error": { ... }
     *       },
     *       "react": {
     *         "analyzedAt": "2016-07-03T11:23:04.553Z",
     *         "collected": { "metadata": { ... }, "npm": { ... }, ... },
     *         "evaluation": { "quality": { ... }, "popularity": { ... }, ... }
     *         "score": { "final": 0.95, "detail": { "quality": 0.99, "popularity": 0.93, ... } }
     *         "error": { ... }
     *       }
     *     }
     *
     * @apiExample {curl} Example usage
     *     curl -X POST "https://api.npms.io/v2/package/mget" \
     *          -H "Accept: application/json" \
     *          -H "Content-Type: application/json" \
     *          -d '["cross-spawn", "react"]'
     */
    /* eslint-disable newline-per-chained-call */
    const infoMgetBodySchema = Joi.array().label('names').min(1).max(250).items(Joi.string().trim().min(1)).required();
    /* eslint-enable newline-per-chained-call */

    router.post('/package/mget',
        bodyParser({ enableTypes: ['json'] }),
        function* (next) {
            this.validated = validateNames(joiHelper.validate(infoMgetBodySchema, uniq(this.request.body)));
            yield next;
        },
        function* () {
            const names = this.validated;

            this.log.debug({ names }, 'Will fetch info');

            // Fetch info
            const info = yield Promise.props({
                couchdb: fetchFromCouchdb(names, npmsNano),
                elasticsearch: fetchFromElasticsearch(names, esClient),
            });

            this.log.debug({ names, info }, 'Got info');

            // Build the packages array, clearing the ones that do not exist
            const infos = info.couchdb
            .map((infoCouchdb, index) => {
                const infoElasticsearch = info.elasticsearch[index];

                return !infoCouchdb || !infoElasticsearch ? null :
                    Object.assign({}, infoCouchdb, infoElasticsearch);
            })
            .filter((info) => !!info);

            // Return an object indexed by name
            this.body = keyBy(infos, (info) => info.collected.metadata.name);
        });
};
<|endoftext|>