<commit_before>
exports.seed = function(knex, Promise) {
  // Deletes ALL existing entries
  return knex('blogs').del()
    .then(function () {
      return Promise.all([
        // Inserts seed entries
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C1 - First Reflections',
          content: {
            "title": "I'm taking the opportunity, on this fine, sunny Friday morning to stay inside and write this, my first ever blog post. These are just a few thoughts I have about EDA so far at the start of phase-0, with the lovely Nikau cohort.",
            "questions": "What's your take on the DBC/EDA experience?",
            "answers": "To sum it up very simply, it seems to be a very community-centric type of system, not unlike a good (non-toxic) gaming community. Everyone will have their own skills and experiences to bring to the table and their own goals and motivations to work towards. However, I get the sense that, at least from what I got from my own cohort so far, we're all in it for the same reason at the end of the day. We're here to learn and we can lean on each other when it comes down to building our skills."
          }
        }),
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C1 - Time and Habits',
          content: {
            "title": "Here's my second post. Hope this looks okay. @.@",
            "questions": "Which timebox and habits techniques did you try?",
            "answers": "I've managed to stumble into the Growth Mindset on accident (or maybe not?). I've had success jumping into phase-0 with this in mind and have been playing around with resources and tools ever since I applied. (it's really just treating study like play time) I'm also planning out the future in increments.  The Toggl timer in conjunction with the Pomodoro technique was another one of those techniques that I tried out."
          }
        }),
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C2 - Emotional Intelligence',
          content: {
            "title": "EQ > IQ? It's not really news to me now.",
            "questions": "What learnings did you take from the chapter?",
            "answers": "That we really need to rethink how we define intelligence to be inclusive of factors beyond intellect and that the world seems to be getting less monochrome, so to speak.
There's also a lot to be said about the value of taking time away from task and objective oriented thinking to just focusing on awareness. To knowing all the intimate little details that your brain can process that normally get filtered out when we work just to accomplish a goal."
          }
        }),
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C3 - The Process of Meditation',
          content: {
            "title": "Here's one about meditation",
            "questions": "How did the 'process over product' concept affect the way you tackled the site redesign and rebuild?",
            "answers": "My site looks like a butt at the moment. :/ If I wasn't focusing my attention on the actual process of making the site work, I think I'd get a lot more caught up on random little things; Like the colour doesn't look so great, or that post isn't quite centered. I could have wasted a lot of time that could be spent building the site."
          }
        })
      ])
    })
}
<commit_msg>updated seeds<commit_after>
exports.seed = function(knex, Promise) {
  // Deletes ALL existing entries
  return knex('blogs').del()
    .then(function () {
      return Promise.all([
        // Inserts seed entries
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C1 - First Reflections',
          content: {
            "title": "I'm taking the opportunity, on this fine, sunny Friday morning to stay inside and write this, my first ever blog post. These are just a few thoughts I have about EDA so far at the start of phase-0, with the lovely Nikau cohort.",
            "questions": ["What's your take on the DBC/EDA (Dev Bootcamp/Enspiral Dev Academy) experience?", "What are your impressions?"],
            "answers": ["To sum it up very simply, it seems to be a very community-centric type of system, not unlike a good (non-toxic) gaming community. Everyone will have their own skills and experiences to bring to the table and their own goals and motivations to work towards. However, I get the sense that, at least from what I got from my own cohort so far, we're all in it for the same reason at the end of the day. We're here to learn and we can lean on each other when it comes down to building our skills.", "I'm in love with the system already. It actually hits very close to home, I think. As a gamer, having been part of several gaming communities, I've drawn a few comparisons between EDA and the communities I've experienced. While the goal in gaming is mainly to have fun (unless you're that type of person... XD), part of the experience is learning the rules of a game, testing out new things and developing skills that are useful within the game world. EDA, I feel, seems to build a community where fun, experimentation and skill development are simply applied to the real world, rather than a virtual one."]
          }
        }),
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C1 - Time and Habits',
          content: {
            "title": "Here's my second post. Hope this looks okay. @.@",
            "questions": ["Which timebox and habits techniques did you try?"],
            "answers": ["I've managed to stumble into the Growth Mindset on accident (or maybe not?). I've had success jumping into phase-0 with this in mind and have been playing around with resources and tools ever since I applied. (it's really just treating study like play time) I'm also planning out the future in increments.  The Toggl timer in conjunction with the Pomodoro technique was another one of those techniques that I tried out."]
          }
        }),
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C2 - Emotional Intelligence',
          content: {
            "title": "EQ > IQ? It's not really news to me now.",
            "questions": ["What learnings did you take from the chapter?"],
            "answers": ["That we really need to rethink how we define intelligence to be inclusive of factors beyond intellect and that the world seems to be getting less monochrome, so to speak.
There's also a lot to be said about the value of taking time away from task and objective oriented thinking to just focusing on awareness. To knowing all the intimate little details that your brain can process that normally get filtered out when we work just to accomplish a goal."]
          }
        }),
        knex('blogs').insert({
          category: 'EDA culture',
          title: 'C3 - The Process of Meditation',
          content: {
            "title": "Here's one about meditation",
            "questions": ["How did the 'process over product' concept affect the way you tackled the site redesign and rebuild?"],
            "answers": ["My site looks like a butt at the moment. :/ If I wasn't focusing my attention on the actual process of making the site work, I think I'd get a lot more caught up on random little things; Like the colour doesn't look so great, or that post isn't quite centered. I could have wasted a lot of time that could be spent building the site."]
          }
        })
      ])
    })
}
<|endoftext|>


<commit_before>/*
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import request from 'browser-request';
import counterpart from 'counterpart';
import Promise from 'bluebird';
import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";

const i18nFolder = 'i18n/';

// Control whether to also return original, untranslated strings
// Useful for debugging and testing
const ANNOTATE_STRINGS = false;

// We use english strings as keys, some of which contain full stops
counterpart.setSeparator('|');
// Fall back to English
counterpart.setFallbackLocale('en');

// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
export function _td(s) {
    return s;
}

// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text, options) {
    // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
    // The interpolation library that counterpart uses does not support undefined/null
    // values and instead will throw an error. This is a problem since everywhere else
    // in JS land passing undefined/null will simply stringify instead, and when converting
    // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
    // if there are no existing null guards. To avoid this making the app completely inoperable,
    // we'll check all the values for undefined/null and stringify them here.
    let count;

    if (options && typeof options === 'object') {
        count = options['count'];
        Object.keys(options).forEach((k) => {
            if (options[k] === undefined) {
                console.warn("safeCounterpartTranslate called with undefined interpolation name: " + k);
                options[k] = 'undefined';
            }
            if (options[k] === null) {
                console.warn("safeCounterpartTranslate called with null interpolation name: " + k);
                options[k] = 'null';
            }
        });
    }
    let translated = counterpart.translate(text, options);
    if (translated === undefined && count !== undefined) {
        // counterpart does not do fallback if no pluralisation exists
        // in the preferred language, so do it here
        translated = counterpart.translate(text, Object.assign({}, options, {locale: 'en'}));
    }
    return translated;
}

/*
 * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
 * @param {string} text The untranslated text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * In both variables and tags, the values to substitute with can be either simple strings, React components,
 * or functions that return the value to use in the substitution (e.g. return a React component). In case of
 * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag.
 *
 * Use tag substitutions if you need to translate text between tags (e.g. "Click here!"), otherwise
 * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable
 * substitution to insert React components, but you can't use it to translate text between tags.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function _t(text, variables, tags) {
    // Don't do subsitutions in counterpart. We handle it ourselves so we can replace with React components
    // However, still pass the variables to counterpart so that it can choose the correct plural if count is given
    // It is enough to pass the count variable, but in the future counterpart might make use of other information too
    const args = Object.assign({ interpolate: false }, variables);

    // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
    const translated = safeCounterpartTranslate(text, args);

    const substituted = substitute(translated, variables, tags);

    // For development/testing purposes it is useful to also output the original string
    // Don't do that for release versions
    if (ANNOTATE_STRINGS) {
        if (typeof substituted === 'string') {
            return `@@${text}##${substituted}@@`;
        } else {
            return {substituted};
        }
    } else {
        return substituted;
    }
}

/*
 * Similar to _t(), except only does substitutions, and no translation
 * @param {string} text The text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * The values to substitute with can be either simple strings, or functions that return the value to use in
 * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
 * the argument the text inside the element corresponding to the tag.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function substitute(text, variables, tags) {
    const regexpMapping = {};

    if (variables !== undefined) {
        for (const variable in variables) {
            regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
        }
    }

    if (tags !== undefined) {
        for (const tag in tags) {
            regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
        }
    }
    return replaceByRegexes(text, regexpMapping);
}

/*
 * Replace parts of a text using regular expressions
 * @param {string} text The text on which to perform substitutions
 * @param {object} mapping A mapping from regular expressions in string form to replacement string or a
 * function which will receive as the argument the capture groups defined in the regexp. E.g.
 * { 'Hello (.?) World': (sub) => sub.toUpperCase() }
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function replaceByRegexes(text, mapping) {
    // We initially store our output as an array of strings and objects (e.g. React components).
    // This will then be converted to a string or a  at the end
    const output = [text];

    // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
    let shouldWrapInSpan = false;

    for (const regexpString in mapping) {
        // TODO: Cache regexps
        const regexp = new RegExp(regexpString);

        // Loop over what output we have so far and perform replacements
        // We look for matches: if we find one, we get three parts: everything before the match, the replaced part,
        // and everything after the match. Insert all three into the output. We need to do this because we can insert objects.
        // Otherwise there would be no need for the splitting and we could do simple replcement.
        let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it
        for (const outputIndex in output) {
            const inputText = output[outputIndex];
            if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them
                continue;
            }

            const match = inputText.match(regexp);
            if (!match) {
                continue;
            }
            matchFoundSomewhere = true;

            const capturedGroups = match.slice(2);

            // The textual part before the match
            const head = inputText.substr(0, match.index);

            // The textual part after the match
            const tail = inputText.substr(match.index + match[0].length);

            let replaced;
            // If substitution is a function, call it
            if (mapping[regexpString] instanceof Function) {
                replaced = mapping[regexpString].apply(null, capturedGroups);
            } else {
                replaced = mapping[regexpString];
            }

            if (typeof replaced === 'object') {
                shouldWrapInSpan = true;
            }

            output.splice(outputIndex, 1); // Remove old element

            // Insert in reverse order as splice does insert-before and this way we get the final order correct
            if (tail !== '') {
                output.splice(outputIndex, 0, tail);
            }

            // Here we also need to check that it actually is a string before comparing against one
            // The head and tail are always strings
            if (typeof replaced !== 'string' || replaced !== '') {
                output.splice(outputIndex, 0, replaced);
            }

            if (head !== '') { // Don't push empty nodes, they are of no use
                output.splice(outputIndex, 0, head);
            }
        }
        if (!matchFoundSomewhere) { // The current regexp did not match anything in the input
            // Missing matches is entirely possible because you might choose to show some variables only in the case
            // of e.g. plurals. It's still a bit suspicious, and could be due to an error, so log it.
            // However, not showing count is so common that it's not worth logging. And other commonly unused variables
            // here, if there are any.
            if (regexpString !== '%\\(count\\)s') {
                console.log(`Could not find ${regexp} in ${text}`);
            }
        }
    }

    if (shouldWrapInSpan) {
        return React.createElement('span', null, ...output);
    } else {
        return output.join('');
    }
}

// Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load
// the translations in riot-web
export function setMissingEntryGenerator(f) {
    counterpart.setMissingEntryGenerator(f);
}

export function setLanguage(preferredLangs) {
    if (!Array.isArray(preferredLangs)) {
        preferredLangs = [preferredLangs];
    }

    let langToUse;
    let availLangs;
    return getLangsJson().then((result) => {
        availLangs = result;

        for (let i = 0; i < preferredLangs.length; ++i) {
            if (availLangs.hasOwnProperty(preferredLangs[i])) {
                langToUse = preferredLangs[i];
                break;
            }
        }
        if (!langToUse) {
            // Fallback to en_EN if none is found
            langToUse = 'en';
            console.error("Unable to find an appropriate language");
        }

        return getLanguage(i18nFolder + availLangs[langToUse].fileName);
    }).then((langData) => {
        counterpart.registerTranslations(langToUse, langData);
        counterpart.setLocale(langToUse);
        SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse);
        console.log("set language to " + langToUse);

        // Set 'en' as fallback language:
        if (langToUse != "en") {
            return getLanguage(i18nFolder + availLangs['en'].fileName);
        }
    }).then((langData) => {
        if (langData) counterpart.registerTranslations('en', langData);
    });
}

export function getAllLanguagesFromJson() {
    return getLangsJson().then((langsObject) => {
        const langs = [];
        for (const langKey in langsObject) {
            if (langsObject.hasOwnProperty(langKey)) {
                langs.push({
                    'value': langKey,
                    'label': langsObject[langKey].label,
                });
            }
        }
        return langs;
    });
}

export function getLanguagesFromBrowser() {
    if (navigator.languages && navigator.languages.length) return navigator.languages;
    if (navigator.language) return [navigator.language];
    return [navigator.userLanguage || "en"];
}

/**
 * Turns a language string, normalises it,
 * (see normalizeLanguageKey) into an array of language strings
 * with fallback to generic languages
 * (eg. 'pt-BR' => ['pt-br', 'pt'])
 *
 * @param {string} language The input language string
 * @return {string[]} List of normalised languages
 */
export function getNormalizedLanguageKeys(language) {
    const languageKeys = [];
    const normalizedLanguage = this.normalizeLanguageKey(language);
    const languageParts = normalizedLanguage.split('-');
    if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
        languageKeys.push(languageParts[0]);
    } else {
        languageKeys.push(normalizedLanguage);
        if (languageParts.length == 2) {
            languageKeys.push(languageParts[0]);
        }
    }
    return languageKeys;
}

/**
 * Returns a language string with underscores replaced with
 * hyphens, and lowercased.
 */
export function normalizeLanguageKey(language) {
    return language.toLowerCase().replace("_", "-");
}

export function getCurrentLanguage() {
    return counterpart.getLocale();
}

function getLangsJson() {
    return new Promise((resolve, reject) => {
        // LANGUAGES_FILE is a webpack compile-time define, see webpack config
        request(
            { method: "GET", url: require(LANGUAGES_FILE) },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(JSON.parse(body));
            },
        );
    });
}

function weblateToCounterpart(inTrs) {
    const outTrs = {};

    for (const key of Object.keys(inTrs)) {
        const keyParts = key.split('|', 2);
        if (keyParts.length === 2) {
            let obj = outTrs[keyParts[0]];
            if (obj === undefined) {
                obj = {};
                outTrs[keyParts[0]] = obj;
            }
            obj[keyParts[1]] = inTrs[key];
        } else {
            outTrs[key] = inTrs[key];
        }
    }

    return outTrs;
}

function getLanguage(langPath) {
    return new Promise((resolve, reject) => {
        request(
            { method: "GET", url: langPath },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(weblateToCounterpart(JSON.parse(body)));
            },
        );
    });
}
<commit_msg>fix unit tests without breaking the build<commit_after>/*
Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import request from 'browser-request';
import counterpart from 'counterpart';
import Promise from 'bluebird';
import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";

const i18nFolder = 'i18n/';

// Control whether to also return original, untranslated strings
// Useful for debugging and testing
const ANNOTATE_STRINGS = false;

// We use english strings as keys, some of which contain full stops
counterpart.setSeparator('|');
// Fall back to English
counterpart.setFallbackLocale('en');

// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
export function _td(s) {
    return s;
}

// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text, options) {
    // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
    // The interpolation library that counterpart uses does not support undefined/null
    // values and instead will throw an error. This is a problem since everywhere else
    // in JS land passing undefined/null will simply stringify instead, and when converting
    // valid ES6 template strings to i18n strings it's extremely easy to pass undefined/null
    // if there are no existing null guards. To avoid this making the app completely inoperable,
    // we'll check all the values for undefined/null and stringify them here.
    let count;

    if (options && typeof options === 'object') {
        count = options['count'];
        Object.keys(options).forEach((k) => {
            if (options[k] === undefined) {
                console.warn("safeCounterpartTranslate called with undefined interpolation name: " + k);
                options[k] = 'undefined';
            }
            if (options[k] === null) {
                console.warn("safeCounterpartTranslate called with null interpolation name: " + k);
                options[k] = 'null';
            }
        });
    }
    let translated = counterpart.translate(text, options);
    if (translated === undefined && count !== undefined) {
        // counterpart does not do fallback if no pluralisation exists
        // in the preferred language, so do it here
        translated = counterpart.translate(text, Object.assign({}, options, {locale: 'en'}));
    }
    return translated;
}

/*
 * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
 * @param {string} text The untranslated text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * In both variables and tags, the values to substitute with can be either simple strings, React components,
 * or functions that return the value to use in the substitution (e.g. return a React component). In case of
 * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag.
 *
 * Use tag substitutions if you need to translate text between tags (e.g. "Click here!"), otherwise
 * you will end up with literal "" in your output, rather than HTML. Note that you can also use variable
 * substitution to insert React components, but you can't use it to translate text between tags.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function _t(text, variables, tags) {
    // Don't do subsitutions in counterpart. We handle it ourselves so we can replace with React components
    // However, still pass the variables to counterpart so that it can choose the correct plural if count is given
    // It is enough to pass the count variable, but in the future counterpart might make use of other information too
    const args = Object.assign({ interpolate: false }, variables);

    // The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
    const translated = safeCounterpartTranslate(text, args);

    const substituted = substitute(translated, variables, tags);

    // For development/testing purposes it is useful to also output the original string
    // Don't do that for release versions
    if (ANNOTATE_STRINGS) {
        if (typeof substituted === 'string') {
            return `@@${text}##${substituted}@@`;
        } else {
            return {substituted};
        }
    } else {
        return substituted;
    }
}

/*
 * Similar to _t(), except only does substitutions, and no translation
 * @param {string} text The text, e.g "click here now to %(foo)s".
 * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
 * @param {object} tags Tag substitutions e.g. { 'a': (sub) => {sub} }
 *
 * The values to substitute with can be either simple strings, or functions that return the value to use in
 * the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
 * the argument the text inside the element corresponding to the tag.
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function substitute(text, variables, tags) {
    const regexpMapping = {};

    if (variables !== undefined) {
        for (const variable in variables) {
            regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
        }
    }

    if (tags !== undefined) {
        for (const tag in tags) {
            regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
        }
    }
    return replaceByRegexes(text, regexpMapping);
}

/*
 * Replace parts of a text using regular expressions
 * @param {string} text The text on which to perform substitutions
 * @param {object} mapping A mapping from regular expressions in string form to replacement string or a
 * function which will receive as the argument the capture groups defined in the regexp. E.g.
 * { 'Hello (.?) World': (sub) => sub.toUpperCase() }
 *
 * @return a React  component if any non-strings were used in substitutions, otherwise a string
 */
export function replaceByRegexes(text, mapping) {
    // We initially store our output as an array of strings and objects (e.g. React components).
    // This will then be converted to a string or a  at the end
    const output = [text];

    // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
    let shouldWrapInSpan = false;

    for (const regexpString in mapping) {
        // TODO: Cache regexps
        const regexp = new RegExp(regexpString);

        // Loop over what output we have so far and perform replacements
        // We look for matches: if we find one, we get three parts: everything before the match, the replaced part,
        // and everything after the match. Insert all three into the output. We need to do this because we can insert objects.
        // Otherwise there would be no need for the splitting and we could do simple replcement.
        let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it
        for (const outputIndex in output) {
            const inputText = output[outputIndex];
            if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them
                continue;
            }

            const match = inputText.match(regexp);
            if (!match) {
                continue;
            }
            matchFoundSomewhere = true;

            const capturedGroups = match.slice(2);

            // The textual part before the match
            const head = inputText.substr(0, match.index);

            // The textual part after the match
            const tail = inputText.substr(match.index + match[0].length);

            let replaced;
            // If substitution is a function, call it
            if (mapping[regexpString] instanceof Function) {
                replaced = mapping[regexpString].apply(null, capturedGroups);
            } else {
                replaced = mapping[regexpString];
            }

            if (typeof replaced === 'object') {
                shouldWrapInSpan = true;
            }

            output.splice(outputIndex, 1); // Remove old element

            // Insert in reverse order as splice does insert-before and this way we get the final order correct
            if (tail !== '') {
                output.splice(outputIndex, 0, tail);
            }

            // Here we also need to check that it actually is a string before comparing against one
            // The head and tail are always strings
            if (typeof replaced !== 'string' || replaced !== '') {
                output.splice(outputIndex, 0, replaced);
            }

            if (head !== '') { // Don't push empty nodes, they are of no use
                output.splice(outputIndex, 0, head);
            }
        }
        if (!matchFoundSomewhere) { // The current regexp did not match anything in the input
            // Missing matches is entirely possible because you might choose to show some variables only in the case
            // of e.g. plurals. It's still a bit suspicious, and could be due to an error, so log it.
            // However, not showing count is so common that it's not worth logging. And other commonly unused variables
            // here, if there are any.
            if (regexpString !== '%\\(count\\)s') {
                console.log(`Could not find ${regexp} in ${text}`);
            }
        }
    }

    if (shouldWrapInSpan) {
        return React.createElement('span', null, ...output);
    } else {
        return output.join('');
    }
}

// Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load
// the translations in riot-web
export function setMissingEntryGenerator(f) {
    counterpart.setMissingEntryGenerator(f);
}

export function setLanguage(preferredLangs) {
    if (!Array.isArray(preferredLangs)) {
        preferredLangs = [preferredLangs];
    }

    let langToUse;
    let availLangs;
    return getLangsJson().then((result) => {
        availLangs = result;

        for (let i = 0; i < preferredLangs.length; ++i) {
            if (availLangs.hasOwnProperty(preferredLangs[i])) {
                langToUse = preferredLangs[i];
                break;
            }
        }
        if (!langToUse) {
            // Fallback to en_EN if none is found
            langToUse = 'en';
            console.error("Unable to find an appropriate language");
        }

        return getLanguage(i18nFolder + availLangs[langToUse].fileName);
    }).then((langData) => {
        counterpart.registerTranslations(langToUse, langData);
        counterpart.setLocale(langToUse);
        SettingsStore.setValue("language", null, SettingLevel.DEVICE, langToUse);
        console.log("set language to " + langToUse);

        // Set 'en' as fallback language:
        if (langToUse != "en") {
            return getLanguage(i18nFolder + availLangs['en'].fileName);
        }
    }).then((langData) => {
        if (langData) counterpart.registerTranslations('en', langData);
    });
}

export function getAllLanguagesFromJson() {
    return getLangsJson().then((langsObject) => {
        const langs = [];
        for (const langKey in langsObject) {
            if (langsObject.hasOwnProperty(langKey)) {
                langs.push({
                    'value': langKey,
                    'label': langsObject[langKey].label,
                });
            }
        }
        return langs;
    });
}

export function getLanguagesFromBrowser() {
    if (navigator.languages && navigator.languages.length) return navigator.languages;
    if (navigator.language) return [navigator.language];
    return [navigator.userLanguage || "en"];
}

/**
 * Turns a language string, normalises it,
 * (see normalizeLanguageKey) into an array of language strings
 * with fallback to generic languages
 * (eg. 'pt-BR' => ['pt-br', 'pt'])
 *
 * @param {string} language The input language string
 * @return {string[]} List of normalised languages
 */
export function getNormalizedLanguageKeys(language) {
    const languageKeys = [];
    const normalizedLanguage = this.normalizeLanguageKey(language);
    const languageParts = normalizedLanguage.split('-');
    if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
        languageKeys.push(languageParts[0]);
    } else {
        languageKeys.push(normalizedLanguage);
        if (languageParts.length == 2) {
            languageKeys.push(languageParts[0]);
        }
    }
    return languageKeys;
}

/**
 * Returns a language string with underscores replaced with
 * hyphens, and lowercased.
 */
export function normalizeLanguageKey(language) {
    return language.toLowerCase().replace("_", "-");
}

export function getCurrentLanguage() {
    return counterpart.getLocale();
}

function getLangsJson() {
    return new Promise((resolve, reject) => {
        // LANGUAGES_FILE is a webpack compile-time define, see webpack config
        const url = (typeof LANGUAGES_FILE === "string") ? require(LANGUAGES_FILE) : (i18nFolder + 'languages.json');
        request(
            { method: "GET", url },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(JSON.parse(body));
            },
        );
    });
}

function weblateToCounterpart(inTrs) {
    const outTrs = {};

    for (const key of Object.keys(inTrs)) {
        const keyParts = key.split('|', 2);
        if (keyParts.length === 2) {
            let obj = outTrs[keyParts[0]];
            if (obj === undefined) {
                obj = {};
                outTrs[keyParts[0]] = obj;
            }
            obj[keyParts[1]] = inTrs[key];
        } else {
            outTrs[key] = inTrs[key];
        }
    }

    return outTrs;
}

function getLanguage(langPath) {
    return new Promise((resolve, reject) => {
        request(
            { method: "GET", url: langPath },
            (err, response, body) => {
                if (err || response.status < 200 || response.status >= 300) {
                    reject({err: err, response: response});
                    return;
                }
                resolve(weblateToCounterpart(JSON.parse(body)));
            },
        );
    });
}
<|endoftext|>


<commit_before>/*
Language: Julia
Author: Kenta Sato <bicycle1885@gmail.com>
*/

function(hljs) {
  // Since there are numerous special names in Julia, it is too much trouble
  // to maintain them by hand. Hence these names (i.e. keywords, literals and
  // built-ins) are automatically generated from Julia (v0.3.0 and v0.4.1)
  // itself through following scripts for each.

  var KEYWORDS = {
    // # keyword generator
    // println("in")
    // for kw in Base.REPLCompletions.complete_keyword("")
    //     println(kw)
    // end
    keyword:
      'in abstract baremodule begin bitstype break catch ccall const continue do else elseif end export ' +
      'finally for function global if immutable import importall let local macro module quote return try type ' +
      'typealias using while',

    // # literal generator
    // println("true")
    // println("false")
    // for name in Base.REPLCompletions.completions("", 0)[1]
    //     try
    //         s = symbol(name)
    //         v = eval(s)
    //         if !isa(v, Function) &&
    //            !isa(v, DataType) &&
    //            !isa(v, IntrinsicFunction) &&
    //            !issubtype(typeof(v), Tuple) &&
    //            !isa(v, Union) &&
    //            !isa(v, Module) &&
    //            !isa(v, TypeConstructor) &&
    //            !isa(v, TypeVar) &&
    //            !isa(v, Colon)
    //             println(name)
    //         end
    //     end
    // end
    literal:
      // v0.3
      'true false ARGS CPU_CORES C_NULL DL_LOAD_PATH DevNull ENDIAN_BOM ENV I|0 Inf Inf16 Inf32 ' +
      'InsertionSort JULIA_HOME LOAD_PATH MS_ASYNC MS_INVALIDATE MS_SYNC MergeSort NaN NaN16 NaN32 OS_NAME QuickSort ' +
      'RTLD_DEEPBIND RTLD_FIRST RTLD_GLOBAL RTLD_LAZY RTLD_LOCAL RTLD_NODELETE RTLD_NOLOAD RTLD_NOW RoundDown ' +
      'RoundFromZero RoundNearest RoundToZero RoundUp STDERR STDIN STDOUT VERSION WORD_SIZE catalan cglobal e|0 eu|0 ' +
      'eulergamma golden im nothing pi γ π φ ' +
      // v0.4 (diff)
      'Inf64 NaN64 RoundNearestTiesAway RoundNearestTiesUp ',

    // # built_in generator:
    // for name in Base.REPLCompletions.completions("", 0)[1]
    //     try
    //         v = eval(symbol(name))
    //         if isa(v, DataType) || isa(v, TypeVar)
    //             println(name)
    //         end
    //     end
    // end
    built_in:
      // v0.3
      'ANY ASCIIString AbstractArray AbstractRNG AbstractSparseArray Any ArgumentError Array Associative Base64Pipe ' +
      'Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError Box CFILE Cchar Cdouble Cfloat Char ' +
      'CharString Cint Clong Clonglong ClusterManager Cmd Coff_t Colon Complex Complex128 Complex32 Complex64 ' +
      'Condition Cptrdiff_t Cshort Csize_t Cssize_t Cuchar Cuint Culong Culonglong Cushort Cwchar_t DArray DataType ' +
      'DenseArray Diagonal Dict DimensionMismatch DirectIndexString Display DivideError DomainError EOFError ' +
      'EachLine Enumerate ErrorException Exception Expr Factorization FileMonitor FileOffset Filter Float16 Float32 ' +
      'Float64 FloatRange FloatingPoint Function GetfieldNode GotoNode Hermitian IO IOBuffer IOStream IPv4 IPv6 ' +
      'InexactError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException IntrinsicFunction KeyError ' +
      'LabelNode LambdaStaticData LineNumberNode LoadError LocalProcess MIME MathConst MemoryError MersenneTwister ' +
      'Method MethodError MethodTable Module NTuple NewvarNode Nothing Number ObjectIdDict OrdinalRange ' +
      'OverflowError ParseError PollingFileWatcher ProcessExitedException ProcessGroup Ptr QuoteNode Range Range1 ' +
      'Ranges Rational RawFD Real Regex RegexMatch RemoteRef RepString RevString RopeString RoundingMode Set ' +
      'SharedArray Signed SparseMatrixCSC StackOverflowError Stat StatStruct StepRange String SubArray SubString ' +
      'SymTridiagonal Symbol SymbolNode Symmetric SystemError Task TextDisplay Timer TmStruct TopNode Triangular ' +
      'Tridiagonal Type TypeConstructor TypeError TypeName TypeVar UTF16String UTF32String UTF8String UdpSocket ' +
      'Uint Uint128 Uint16 Uint32 Uint64 Uint8 UndefRefError UndefVarError UniformScaling UnionType UnitRange ' +
      'Unsigned Vararg VersionNumber WString WeakKeyDict WeakRef Woodbury Zip ' +
      // v0.4 (diff)
      'AbstractChannel AbstractFloat AbstractString AssertionError Base64DecodePipe Base64EncodePipe BufferStream ' +
      'CapturedException CartesianIndex CartesianRange Channel Cintmax_t CompositeException Cstring Cuintmax_t ' +
      'Cwstring Date DateTime Dims Enum GenSym GlobalRef HTML InitError InvalidStateException Irrational LinSpace ' +
      'LowerTriangular NullException Nullable OutOfMemoryError Pair PartialQuickSort Pipe RandomDevice ' +
      'ReadOnlyMemoryError ReentrantLock Ref RemoteException SegmentationFault SerializationState SimpleVector ' +
      'TCPSocket Text Tuple UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UnicodeError Union UpperTriangular ' +
      'Val Void WorkerConfig '
  };

  // ref: http://julia.readthedocs.org/en/latest/manual/variables/#allowed-variable-names
  var VARIABLE_NAME_RE = '[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*';

  // placeholder for recursive self-reference
  var DEFAULT = { lexemes: VARIABLE_NAME_RE, keywords: KEYWORDS };

  var TYPE_ANNOTATION = {
    className: 'type',
    begin: /::/
  };

  var SUBTYPE = {
    className: 'type',
    begin: /<:/
  };

  // ref: http://julia.readthedocs.org/en/latest/manual/integers-and-floating-point-numbers/
  var NUMBER = {
    className: 'number',
    // supported numeric literals:
    //  * binary literal (e.g. 0x10)
    //  * octal literal (e.g. 0o76543210)
    //  * hexadecimal literal (e.g. 0xfedcba876543210)
    //  * hexadecimal floating point literal (e.g. 0x1p0, 0x1.2p2)
    //  * decimal literal (e.g. 9876543210, 100_000_000)
    //  * floating pointe literal (e.g. 1.2, 1.2f, .2, 1., 1.2e10, 1.2e-10)
    begin: /(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,
    relevance: 0
  };

  var CHAR = {
    className: 'string',
    begin: /'(.|\\[xXuU][a-zA-Z0-9]+)'/
  };

  var INTERPOLATION = {
    className: 'subst',
    begin: /\$\(/, end: /\)/,
    keywords: KEYWORDS
  };

  var INTERPOLATED_VARIABLE = {
    className: 'variable',
    begin: '\\$' + VARIABLE_NAME_RE
  };

  // TODO: neatly escape normal code in string literal
  var STRING = {
    className: 'string',
    contains: [hljs.BACKSLASH_ESCAPE, INTERPOLATION, INTERPOLATED_VARIABLE],
    variants: [
      { begin: /\w*"/, end: /"\w*/ },
      { begin: /\w*"""/, end: /"""\w*/ }
    ]
  };

  var COMMAND = {
    className: 'string',
    contains: [hljs.BACKSLASH_ESCAPE, INTERPOLATION, INTERPOLATED_VARIABLE],
    begin: '`', end: '`'
  };

  var MACROCALL = {
    className: 'meta',
    begin: '@' + VARIABLE_NAME_RE
  };

  var COMMENT = {
    className: 'comment',
    variants: [
      { begin: '#=', end: '=#', relevance: 10 },
      { begin: '#', end: '$' }
    ]
  };

  DEFAULT.contains = [
    NUMBER,
    CHAR,
    TYPE_ANNOTATION,
    SUBTYPE,
    STRING,
    COMMAND,
    MACROCALL,
    COMMENT,
    hljs.HASH_COMMENT_MODE
  ];
  INTERPOLATION.contains = DEFAULT.contains;

  return DEFAULT;
}
<commit_msg>Add more build-in names for Julia<commit_after>/*
Language: Julia
Author: Kenta Sato <bicycle1885@gmail.com>
*/

function(hljs) {
  // Since there are numerous special names in Julia, it is too much trouble
  // to maintain them by hand. Hence these names (i.e. keywords, literals and
  // built-ins) are automatically generated from Julia (v0.3.0 and v0.4.1)
  // itself through following scripts for each.

  var KEYWORDS = {
    // # keyword generator
    // println("in")
    // for kw in Base.REPLCompletions.complete_keyword("")
    //     println(kw)
    // end
    keyword:
      'in abstract baremodule begin bitstype break catch ccall const continue do else elseif end export ' +
      'finally for function global if immutable import importall let local macro module quote return try type ' +
      'typealias using while',

    // # literal generator
    // println("true")
    // println("false")
    // for name in Base.REPLCompletions.completions("", 0)[1]
    //     try
    //         s = symbol(name)
    //         v = eval(s)
    //         if !isa(v, Function) &&
    //            !isa(v, DataType) &&
    //            !isa(v, IntrinsicFunction) &&
    //            !issubtype(typeof(v), Tuple) &&
    //            !isa(v, Union) &&
    //            !isa(v, Module) &&
    //            !isa(v, TypeConstructor) &&
    //            !isa(v, TypeVar) &&
    //            !isa(v, Colon)
    //             println(name)
    //         end
    //     end
    // end
    literal:
      // v0.3
      'true false ARGS CPU_CORES C_NULL DL_LOAD_PATH DevNull ENDIAN_BOM ENV I|0 Inf Inf16 Inf32 ' +
      'InsertionSort JULIA_HOME LOAD_PATH MS_ASYNC MS_INVALIDATE MS_SYNC MergeSort NaN NaN16 NaN32 OS_NAME QuickSort ' +
      'RTLD_DEEPBIND RTLD_FIRST RTLD_GLOBAL RTLD_LAZY RTLD_LOCAL RTLD_NODELETE RTLD_NOLOAD RTLD_NOW RoundDown ' +
      'RoundFromZero RoundNearest RoundToZero RoundUp STDERR STDIN STDOUT VERSION WORD_SIZE catalan cglobal e|0 eu|0 ' +
      'eulergamma golden im nothing pi γ π φ ' +
      // v0.4 (diff)
      'Inf64 NaN64 RoundNearestTiesAway RoundNearestTiesUp ',

    // # built_in generator:
    // for name in Base.REPLCompletions.completions("", 0)[1]
    //     try
    //         v = eval(symbol(name))
    //         if isa(v, DataType) || isa(v, TypeConstructor) || isa(v, TypeVar)
    //             println(name)
    //         end
    //     end
    // end
    built_in:
      // v0.3
      'ANY ASCIIString AbstractArray AbstractRNG AbstractSparseArray Any ArgumentError Array Associative Base64Pipe ' +
      'Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError Box CFILE Cchar Cdouble Cfloat Char ' +
      'CharString Cint Clong Clonglong ClusterManager Cmd Coff_t Colon Complex Complex128 Complex32 Complex64 ' +
      'Condition Cptrdiff_t Cshort Csize_t Cssize_t Cuchar Cuint Culong Culonglong Cushort Cwchar_t DArray DataType ' +
      'DenseArray Diagonal Dict DimensionMismatch DirectIndexString Display DivideError DomainError EOFError ' +
      'EachLine Enumerate ErrorException Exception Expr Factorization FileMonitor FileOffset Filter Float16 Float32 ' +
      'Float64 FloatRange FloatingPoint Function GetfieldNode GotoNode Hermitian IO IOBuffer IOStream IPv4 IPv6 ' +
      'InexactError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException IntrinsicFunction KeyError ' +
      'LabelNode LambdaStaticData LineNumberNode LoadError LocalProcess MIME MathConst MemoryError MersenneTwister ' +
      'Method MethodError MethodTable Module NTuple NewvarNode Nothing Number ObjectIdDict OrdinalRange ' +
      'OverflowError ParseError PollingFileWatcher ProcessExitedException ProcessGroup Ptr QuoteNode Range Range1 ' +
      'Ranges Rational RawFD Real Regex RegexMatch RemoteRef RepString RevString RopeString RoundingMode Set ' +
      'SharedArray Signed SparseMatrixCSC StackOverflowError Stat StatStruct StepRange String SubArray SubString ' +
      'SymTridiagonal Symbol SymbolNode Symmetric SystemError Task TextDisplay Timer TmStruct TopNode Triangular ' +
      'Tridiagonal Type TypeConstructor TypeError TypeName TypeVar UTF16String UTF32String UTF8String UdpSocket ' +
      'Uint Uint128 Uint16 Uint32 Uint64 Uint8 UndefRefError UndefVarError UniformScaling UnionType UnitRange ' +
      'Unsigned Vararg VersionNumber WString WeakKeyDict WeakRef Woodbury Zip ' +
      // v0.4 (diff)
      'AbstractChannel AbstractFloat AbstractString AssertionError Base64DecodePipe Base64EncodePipe BufferStream ' +
      'CapturedException CartesianIndex CartesianRange Channel Cintmax_t CompositeException Cstring Cuintmax_t ' +
      'Cwstring Date DateTime Dims Enum GenSym GlobalRef HTML InitError InvalidStateException Irrational LinSpace ' +
      'LowerTriangular NullException Nullable OutOfMemoryError Pair PartialQuickSort Pipe RandomDevice ' +
      'ReadOnlyMemoryError ReentrantLock Ref RemoteException SegmentationFault SerializationState SimpleVector ' +
      'TCPSocket Text Tuple UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UnicodeError Union UpperTriangular ' +
      'Val Void WorkerConfig AbstractMatrix AbstractSparseMatrix AbstractSparseVector AbstractVecOrMat AbstractVector ' +
      'DenseMatrix DenseVecOrMat DenseVector Matrix SharedMatrix SharedVector StridedArray StridedMatrix ' +
      'StridedVecOrMat StridedVector VecOrMat Vector '
  };

  // ref: http://julia.readthedocs.org/en/latest/manual/variables/#allowed-variable-names
  var VARIABLE_NAME_RE = '[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*';

  // placeholder for recursive self-reference
  var DEFAULT = { lexemes: VARIABLE_NAME_RE, keywords: KEYWORDS };

  var TYPE_ANNOTATION = {
    className: 'type',
    begin: /::/
  };

  var SUBTYPE = {
    className: 'type',
    begin: /<:/
  };

  // ref: http://julia.readthedocs.org/en/latest/manual/integers-and-floating-point-numbers/
  var NUMBER = {
    className: 'number',
    // supported numeric literals:
    //  * binary literal (e.g. 0x10)
    //  * octal literal (e.g. 0o76543210)
    //  * hexadecimal literal (e.g. 0xfedcba876543210)
    //  * hexadecimal floating point literal (e.g. 0x1p0, 0x1.2p2)
    //  * decimal literal (e.g. 9876543210, 100_000_000)
    //  * floating pointe literal (e.g. 1.2, 1.2f, .2, 1., 1.2e10, 1.2e-10)
    begin: /(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,
    relevance: 0
  };

  var CHAR = {
    className: 'string',
    begin: /'(.|\\[xXuU][a-zA-Z0-9]+)'/
  };

  var INTERPOLATION = {
    className: 'subst',
    begin: /\$\(/, end: /\)/,
    keywords: KEYWORDS
  };

  var INTERPOLATED_VARIABLE = {
    className: 'variable',
    begin: '\\$' + VARIABLE_NAME_RE
  };

  // TODO: neatly escape normal code in string literal
  var STRING = {
    className: 'string',
    contains: [hljs.BACKSLASH_ESCAPE, INTERPOLATION, INTERPOLATED_VARIABLE],
    variants: [
      { begin: /\w*"/, end: /"\w*/ },
      { begin: /\w*"""/, end: /"""\w*/ }
    ]
  };

  var COMMAND = {
    className: 'string',
    contains: [hljs.BACKSLASH_ESCAPE, INTERPOLATION, INTERPOLATED_VARIABLE],
    begin: '`', end: '`'
  };

  var MACROCALL = {
    className: 'meta',
    begin: '@' + VARIABLE_NAME_RE
  };

  var COMMENT = {
    className: 'comment',
    variants: [
      { begin: '#=', end: '=#', relevance: 10 },
      { begin: '#', end: '$' }
    ]
  };

  DEFAULT.contains = [
    NUMBER,
    CHAR,
    TYPE_ANNOTATION,
    SUBTYPE,
    STRING,
    COMMAND,
    MACROCALL,
    COMMENT,
    hljs.HASH_COMMENT_MODE
  ];
  INTERPOLATION.contains = DEFAULT.contains;

  return DEFAULT;
}
<|endoftext|>


<commit_before>"use strict";

var katex = require('katex');

export function LaTeXTransform(mimetype, latex, document) {
    var el = document.createElement('div');
    el.innerHTML = katex.renderToString(latex);
    return el;
}

LaTeXTransform.mimetype = 'text/latex';
<commit_msg>Prefer MathJax if it is available<commit_after>"use strict";

var katex = require('katex');

export function LaTeXTransform(mimetype, latex, document) {
    var el = document.createElement('div');
    if (typeof MathJaX !== "undefined") {
        el.textContent = latex;
        MathJax.Hub.Queue(["Typeset", MathJax.Hub, node]);
    } else {
        el.innerHTML = katex.renderToString(latex);
    }
    return el;
}

LaTeXTransform.mimetype = 'text/latex';
<|endoftext|>


<commit_before>// nodejs base
var fs = require("fs");
var util = require('util');
var path = require('path');
var url = require("url");
var childProcess = require('child_process');

// requires npm install
var express = require('express');
var uuid = require('node-uuid');
var bodyParser = require('body-parser');

const MAX_FOLDER_DELETE_RETRIES = 20;

module.exports = {
    start: function (port, browserPath, browserUserDataFolder, serverData, callback) {
        var app = express();

        const REQUEST_TIMEOUT_IN_MS = 600000; // 10mins
        const server = app.listen(port, function () {
            console.log("restbot running at http://localhost:" + port + "/\n");
            callback(this);
        });
        server.timeout = REQUEST_TIMEOUT_IN_MS;

        app.use(bodyParser.json()); // for parsing application/json
        app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

        app.use(function (req, res, next) {
            // cross origin
            res.header("Access-Control-Allow-Origin", "*");
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, x-timeout-in-sec", "x-throttle-requests-in-ms");
            res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE");

            // no cache
            res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
            res.header('Expires', '-1');
            res.header('Pragma', 'no-cache');

            var throttleRequestInSecs = req.headers["x-throttle-requests-in-ms"];
            if (throttleRequestInSecs) {
                setTimeout(function () {
                    next();
                }, throttleRequestInSecs);
            }
            else
                next();
        });

        function getOpenedBrowsersList() {
            var result = [];
            for (let [browserId, socket] of serverData.socketsByBrowser)
                result.push(browserId);
            return result;
        }

        app.get('/', function (req, res) {
            // returns array of active browserIds
            var allBrowsers = getOpenedBrowsersList();
            res.send(allBrowsers);
            console.log('[*] list');
        });

        app.delete('/', function (req, res) {
            // close/delete all opened browser
            var allBrowsers = getOpenedBrowsersList();
            for (let browserId of allBrowsers)
                deleteBrowser(browserId)

            attemptToDeleteFolderRecursive(browserUserDataFolder, MAX_FOLDER_DELETE_RETRIES, (httpCode) => {
                res.sendStatus(httpCode);
            });
        })

        app.use('/:id*', function (req, res, next) {
            // browserId middleware:
            // 1. gets and stores: browserId and its associated socket,
            // 2. creates the socketData object,
            // 3. stores res object to be later used by (ie responded) the websocket server
            // 4. ensure that the browser socket exists

            if (req.method == "OPTIONS") { // deals with firefox
                res.json('verb not supported')
                return; /* breaks the chain: do not call next() */
            }

            req.browserId = req.params.id;
            req.socket = serverData.getSocket(req.browserId);

            var socketData = req.body; /* this will copy any extra field into the socket data - including body.value */
            req.socketData = socketData;
            socketData.requestId = uuid.v1();
            serverData.setPendingRequest(socketData.requestId, res);
            socketData.params = [];
            var decodedUri = url.parse(decodeURI(req.originalUrl));
            if (decodedUri.query) {
                var queryArgs = decodedUri.query.split('&');
                socketData.cmd = queryArgs[0];
                socketData.params = queryArgs.slice(1);
            }

            var isBrowserCreation = req.method === 'PUT' && (!req.params[0]);

            if (!isBrowserCreation) {
                // then browser needs to exist with a connected socket
                var message;
                if (!req.socket)
                    message = "Browser not found: " + req.browserId;;

                if (req.socket && !req.socket.connected)
                    message = "Browser disconnected: " + req.browserId;

                if (message) {
                    handleRequestError(req, 404, message);
                    return;
                }
            }

            next();
        });

        app.put('/:id', function (req) {
            // creates a new browser instance, defaults to chrome
            req.socketData.type = req.socketData.type || 'chrome';
            if (req.socketData.type !== 'chrome') {
                var message = "unsupported browser type: " + req.socketData.type;
                handleRequestError(req, 415, message);
                return;
            }

            req.socketData.autoOpenDeveloperTools = req.socketData.autoOpenDeveloperTools || false;

            if (serverData.hasBrowserSocket(req.browserId)) {
                var message = "browser " + req.browserId + " already running";
                handleRequestError(req, 409, message);
                return;
            }

            var sessionDataPath = path.resolve(browserUserDataFolder, req.browserId);
            var extensionPath = path.resolve(__dirname, 'chrome_extension');
            var titleExtensionPath = path.resolve(__dirname, '../', 'chromeTitle/chrome_extension');

            if (fs.existsSync(titleExtensionPath))
                extensionPath = extensionPath + "," + titleExtensionPath

            var spawn = childProcess.spawn;
            var startupArgs = ["--no-default-browser-check", "--no-first-run", "--test-type", "--ignore-certificate-errors",
                "--disable-popup-blocking", "--extensions-on-chrome-urls", "--user-data-dir=" + sessionDataPath,
                "--load-extension=" + extensionPath, "about:blank"];

            if (req.socketData.autoOpenDeveloperTools) {
                startupArgs.push("--auto-open-devtools-for-tabs");
            }

            var browser = spawn(browserPath, startupArgs);
            serverData.setBrowser(req.browserId, browser);

            logCommand(req.browserId, { cmd: 'start' })
        });

        function deleteBrowser(browserId, socketData) {
            var browser = serverData.getBrowser(browserId);

            if (socketData) {
                var requestId = socketData.requestId;
                var res = serverData.getPendingRequest(requestId);
                // delete from the maps first, because we capture un-expected socket disconnect
                serverData.deletePendingRequest(requestId);
                socketData.cmd = "kill";
                logCommand(browserId, socketData);

                browser.on('close', function (code) {
                    // wait til process is actually closed to return 200
                    if (socketData.deleteSessionData) {
                        var sessionDataPath = path.resolve(browserUserDataFolder, browserId);
                        console.log('Cleaning up sessionData "' + sessionDataPath + '"');
                        attemptToDeleteFolderRecursive(sessionDataPath, MAX_FOLDER_DELETE_RETRIES, (httpCode) => {
                            res.writeHead(httpCode);
                            res.end();
                        });
                    } else {
                        res.writeHead(200);
                        res.end();
                    }
                });
            }

            serverData.purgeBrowserData(browserId);

            //todo: use OS-prefered way to stop the process without force-killing it: issue where chrome does not write session data (browser.kill does not give chrome enough time to stop on windows
            var spawn = childProcess.spawn;
            //var startupArgs = ["/pid", browser.pid, "/f"];
            var startupArgs = ["/pid", browser.pid];
            spawn("taskkill", startupArgs)
        }

        app.delete('/:id', function (req) {
            deleteBrowser(req.browserId, req.socketData)
        });

        app.use('/:id/url', function (req, res, next) {
            // gets/sets the browser's url
            switch (req.method) {
                case "GET": req.socketData.cmd = "get_url"; break;
                case "PUT": req.socketData.cmd = "set_url"; break;
            }
            next();
        });

        app.use('/:id/views*', function (req, res, next) {
            // accesses the browser's views
            let hasTabParam = req.params[0];

            switch (req.method) {
                case "GET":
                    req.socketData.cmd = "get_views_info";
                    break;

                case "DELETE":
                    if (hasTabParam)
                        req.socketData.cmd = "close_view";
                    else
                        req.socketData.cmd = "close_active_view";
                    break;

                case "PUT":
                    if (hasTabParam)
                        req.socketData.cmd = "set_active_view";
                    else
                        req.socketData.cmd = "set_views_info";
                    break;
            }

            if (hasTabParam)
                req.socketData.tabId = parseInt(req.params[0].substring(1));

            next();
        });

        app.use('/:id/errors', function (req, res, next) {
            // accesses the browser's errors
            switch (req.method) {
                case "GET": req.socketData.cmd = "get_errors"; break;
                case "DELETE": req.socketData.cmd = "clear_errors"; break;
            }
            next();
        });

        app.use('/:id/cookies*', function (req, res, next) {
            // access to browser cookies
            switch (req.method) {
                case "GET": req.socketData.cmd = "get_cookie"; break;
                case "PUT": req.socketData.cmd = "set_cookie"; break;
                case "DELETE": req.socketData.cmd = "remove_cookie"; break;
            }

            if (req.params[0])
                req.socketData.cookieName = req.params[0].substring(1);

            next();
        });

        app.use('/:id/doc*', function (req, res, next) {
            // interacts with the browser's html doc
            var timeOutInSeconds = req.headers["x-timeout-in-sec"];
            var expiry = new Date();
            if (timeOutInSeconds)
                expiry.setTime(expiry.getTime() + timeOutInSeconds * 1000);

            var socketData = req.socketData;
            socketData.requestExpiry = expiry.toString();

            var uri = url.parse(req.originalUrl);

            if (req.params[0]) {
                var encodedParams = encodeURI(req.params[0]);
                var fragments = uri.pathname.substring(uri.pathname.indexOf(encodedParams));
                socketData.path = decodeURI(fragments);
            }
            else
                socketData.path = '';

            if (!socketData.cmd) {
                var message = `no action provided in path ${socketData.path}`;
                handleRequestError(req, 400, message);
                return;
            }

            next();
        });

        app.use('/:id*', function (req, res, next) {
            // common to all previous methods: log & emit
            var socketData = req.socketData;
            logCommand(req.browserId, socketData);

            if (socketData.cmd === 'sleep') {
                setTimeout(function () {
                    res.writeHead(200);
                    res.end();
                    serverData.deletePendingRequest(socketData.requestId);
                }, (socketData.value * 1000))
            } else
                req.socket.emit('cmd', socketData);
        });

        function logCommand(browserId, socketData) {
            var logEntryText = '[' + browserId.substr(0, 5) + '] ' + socketData.cmd;

            if (socketData.path !== undefined)
                logEntryText += ' ' + socketData.path;

            if (socketData.value !== undefined)
                logEntryText += ' { ' + util.inspect(socketData.value) + ' }';

            console.log(logEntryText);
        }

        function handleRequestError(req, code, message) {
            console.log("[" + code + "]", message);

            var res = serverData.getPendingRequest(req.socketData.requestId);
            res.status(code).json(message);
            serverData.deletePendingRequest(req.socketData.requestId);
        }

        function dumpObjectToDisk(obj, filename) {
            var toString = util.inspect(obj, false, null);
            fs.writeFile(filename, toString, function (err) {
                if (err) {
                    return console.log(err);
                }
                console.log("dumpObjectToDisk to '" + filename + "' completed");
            });
        }

        function deleteFolderRecursive(path) {
            if (fs.existsSync(path)) {
                fs.readdirSync(path).forEach(function (file) {
                    var curPath = path + "/" + file;
                    if (fs.statSync(curPath).isDirectory())
                        deleteFolderRecursive(curPath);
                    else
                        fs.unlinkSync(curPath);
                });
                fs.rmdirSync(path);
            }
        }

        function attemptToDeleteFolderRecursive(folder, tries, callback) {
            try {
                deleteFolderRecursive(folder);
                callback(204);
            } catch (err) {
                if (tries > 0) {
                    setTimeout(function () {
                        console.log('-- retrying')
                        attemptToDeleteFolderRecursive(folder, tries--, callback)
                    }, (100));
                } else {
                    callback(500);
                }
            }
        }
    },
}<commit_msg>- refactoring<commit_after>// nodejs base
var fs = require("fs");
var util = require('util');
var path = require('path');
var url = require("url");
var childProcess = require('child_process');

// requires npm install
var express = require('express');
var uuid = require('node-uuid');
var bodyParser = require('body-parser');

const MAX_FOLDER_DELETE_RETRIES = 20;

module.exports = {
    start: function (port, browserPath, browserUserDataFolder, serverData, callback) {
        var app = express();

        const REQUEST_TIMEOUT_IN_MS = 600000; // 10mins
        const server = app.listen(port, function () {
            console.log("restbot running at http://localhost:" + port + "/\n");
            callback(this);
        });
        server.timeout = REQUEST_TIMEOUT_IN_MS;

        app.use(bodyParser.json()); // for parsing application/json
        app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

        app.use((req, res, next) => {
            // cross origin
            res.header("Access-Control-Allow-Origin", "*");
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, x-timeout-in-sec", "x-throttle-requests-in-ms");
            res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE");

            // no cache
            res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
            res.header('Expires', '-1');
            res.header('Pragma', 'no-cache');

            var throttleRequestInSecs = req.headers["x-throttle-requests-in-ms"];
            if (throttleRequestInSecs) {
                setTimeout(() => {
                    next();
                }, throttleRequestInSecs);
            }
            else
                next();
        });

        app.get('/', (req, res) => {
            // returns array of active browserIds
            var allBrowsers = getOpenedBrowsersList();
            res.send(allBrowsers);
            console.log('[*] list');
        });

        app.delete('/', (req, res) => {
            // close/delete all opened browser
            var allBrowsers = getOpenedBrowsersList();
            for (let browserId of allBrowsers)
                deleteBrowser(browserId)

            attemptToDeleteFolderRecursive(browserUserDataFolder, MAX_FOLDER_DELETE_RETRIES, (httpCode) => {
                res.sendStatus(httpCode);
            });
        })

        app.use('/:id*', (req, res, next) => {
            // browserId middleware:
            // 1. gets and stores: browserId and its associated socket,
            // 2. creates the socketData object,
            // 3. stores res object to be later used by (ie responded) the websocket server
            // 4. ensure that the browser socket exists

            if (req.method == "OPTIONS") { // deals with firefox
                res.json('verb not supported')
                return; /* breaks the chain: do not call next() */
            }

            req.browserId = req.params.id;
            req.socket = serverData.getSocket(req.browserId);

            var socketData = req.body; /* this will copy any extra field into the socket data - including body.value */
            req.socketData = socketData;
            socketData.requestId = uuid.v1();
            serverData.setPendingRequest(socketData.requestId, res);
            socketData.params = [];
            var decodedUri = url.parse(decodeURI(req.originalUrl));
            if (decodedUri.query) {
                var queryArgs = decodedUri.query.split('&');
                socketData.cmd = queryArgs[0];
                socketData.params = queryArgs.slice(1);
            }

            var isBrowserCreation = req.method === 'PUT' && (!req.params[0]);

            if (!isBrowserCreation) {
                // then browser needs to exist with a connected socket
                var message;
                if (!req.socket)
                    message = "Browser not found: " + req.browserId;;

                if (req.socket && !req.socket.connected)
                    message = "Browser disconnected: " + req.browserId;

                if (message) {
                    handleRequestError(req, 404, message);
                    return;
                }
            }

            next();
        });

        app.put('/:id', (req) => {
            // creates a new browser instance, defaults to chrome
            req.socketData.type = req.socketData.type || 'chrome';
            if (req.socketData.type !== 'chrome') {
                var message = "unsupported browser type: " + req.socketData.type;
                handleRequestError(req, 415, message);
                return;
            }

            req.socketData.autoOpenDeveloperTools = req.socketData.autoOpenDeveloperTools || false;

            if (serverData.hasBrowserSocket(req.browserId)) {
                var message = "browser " + req.browserId + " already running";
                handleRequestError(req, 409, message);
                return;
            }

            var sessionDataPath = path.resolve(browserUserDataFolder, req.browserId);
            var extensionPath = path.resolve(__dirname, 'chrome_extension');
            var titleExtensionPath = path.resolve(__dirname, '../', 'chromeTitle/chrome_extension');

            if (fs.existsSync(titleExtensionPath))
                extensionPath = extensionPath + "," + titleExtensionPath

            var spawn = childProcess.spawn;
            var startupArgs = ["--no-default-browser-check", "--no-first-run", "--test-type", "--ignore-certificate-errors",
                "--disable-popup-blocking", "--extensions-on-chrome-urls", "--user-data-dir=" + sessionDataPath,
                "--load-extension=" + extensionPath, "about:blank"];

            if (req.socketData.autoOpenDeveloperTools) {
                startupArgs.push("--auto-open-devtools-for-tabs");
            }

            var browser = spawn(browserPath, startupArgs);
            serverData.setBrowser(req.browserId, browser);

            logCommand(req.browserId, { cmd: 'start' })
        });

        app.delete('/:id', (req) => {
            deleteBrowser(req.browserId, req.socketData)
        });

        app.use('/:id/url', (req, res, next) => {
            // gets/sets the browser's url
            switch (req.method) {
                case "GET": req.socketData.cmd = "get_url"; break;
                case "PUT": req.socketData.cmd = "set_url"; break;
            }
            next();
        });

        app.use('/:id/views*', (req, res, next) => {
            // accesses the browser's views
            let hasTabParam = req.params[0];

            switch (req.method) {
                case "GET":
                    req.socketData.cmd = "get_views_info";
                    break;

                case "DELETE":
                    if (hasTabParam)
                        req.socketData.cmd = "close_view";
                    else
                        req.socketData.cmd = "close_active_view";
                    break;

                case "PUT":
                    if (hasTabParam)
                        req.socketData.cmd = "set_active_view";
                    else
                        req.socketData.cmd = "set_views_info";
                    break;
            }

            if (hasTabParam)
                req.socketData.tabId = parseInt(req.params[0].substring(1));

            next();
        });

        app.use('/:id/errors', (req, res, next) => {
            // accesses the browser's errors
            switch (req.method) {
                case "GET": req.socketData.cmd = "get_errors"; break;
                case "DELETE": req.socketData.cmd = "clear_errors"; break;
            }
            next();
        });

        app.use('/:id/cookies*', (req, res, next) => {
            // access to browser cookies
            switch (req.method) {
                case "GET": req.socketData.cmd = "get_cookie"; break;
                case "PUT": req.socketData.cmd = "set_cookie"; break;
                case "DELETE": req.socketData.cmd = "remove_cookie"; break;
            }

            if (req.params[0])
                req.socketData.cookieName = req.params[0].substring(1);

            next();
        });

        app.use('/:id/doc*', (req, res, next) => {
            // interacts with the browser's html doc
            var timeOutInSeconds = req.headers["x-timeout-in-sec"];
            var expiry = new Date();
            if (timeOutInSeconds)
                expiry.setTime(expiry.getTime() + timeOutInSeconds * 1000);

            var socketData = req.socketData;
            socketData.requestExpiry = expiry.toString();

            var uri = url.parse(req.originalUrl);

            if (req.params[0]) {
                var encodedParams = encodeURI(req.params[0]);
                var fragments = uri.pathname.substring(uri.pathname.indexOf(encodedParams));
                socketData.path = decodeURI(fragments);
            }
            else
                socketData.path = '';

            if (!socketData.cmd) {
                var message = `no action provided in path ${socketData.path}`;
                handleRequestError(req, 400, message);
                return;
            }

            next();
        });

        app.use('/:id*', (req, res, next) => {
            // common to all previous methods: log & emit
            var socketData = req.socketData;
            logCommand(req.browserId, socketData);

            if (socketData.cmd === 'sleep') {
                setTimeout(() => {
                    res.writeHead(200);
                    res.end();
                    serverData.deletePendingRequest(socketData.requestId);
                }, (socketData.value * 1000))
            } else
                req.socket.emit('cmd', socketData);
        });

        function deleteBrowser(browserId, socketData) {
            var browser = serverData.getBrowser(browserId);

            if (socketData) {
                var requestId = socketData.requestId;
                var res = serverData.getPendingRequest(requestId);
                // delete from the maps first, because we capture un-expected socket disconnect
                serverData.deletePendingRequest(requestId);
                socketData.cmd = "kill";
                logCommand(browserId, socketData);

                browser.on('close', (code) => {
                    // wait til process is actually closed to return 200
                    if (socketData.deleteSessionData) {
                        var sessionDataPath = path.resolve(browserUserDataFolder, browserId);
                        console.log('Cleaning up sessionData "' + sessionDataPath + '"');
                        attemptToDeleteFolderRecursive(sessionDataPath, MAX_FOLDER_DELETE_RETRIES, (httpCode) => {
                            res.writeHead(httpCode);
                            res.end();
                        });
                    } else {
                        res.writeHead(200);
                        res.end();
                    }
                });
            }

            serverData.purgeBrowserData(browserId);

            //todo: use OS-prefered way to stop the process without force-killing it: issue where chrome does not write session data (browser.kill does not give chrome enough time to stop on windows
            var spawn = childProcess.spawn;
            //var startupArgs = ["/pid", browser.pid, "/f"];
            var startupArgs = ["/pid", browser.pid];
            spawn("taskkill", startupArgs)
        }

        function getOpenedBrowsersList() {
            var result = [];
            for (let [browserId, socket] of serverData.socketsByBrowser)
                result.push(browserId);
            return result;
        }

        function logCommand(browserId, socketData) {
            var logEntryText = '[' + browserId.substr(0, 5) + '] ' + socketData.cmd;

            if (socketData.path !== undefined)
                logEntryText += ' ' + socketData.path;

            if (socketData.value !== undefined)
                logEntryText += ' { ' + util.inspect(socketData.value) + ' }';

            console.log(logEntryText);
        }

        function handleRequestError(req, code, message) {
            console.log("[" + code + "]", message);

            var res = serverData.getPendingRequest(req.socketData.requestId);
            res.status(code).json(message);
            serverData.deletePendingRequest(req.socketData.requestId);
        }

        function dumpObjectToDisk(obj, filename) {
            var toString = util.inspect(obj, false, null);
            fs.writeFile(filename, toString, (err) => {
                if (err) {
                    return console.log(err);
                }
                console.log("dumpObjectToDisk to '" + filename + "' completed");
            });
        }

        function deleteFolderRecursive(path) {
            if (fs.existsSync(path)) {
                fs.readdirSync(path).forEach((file) => {
                    var curPath = path + "/" + file;
                    if (fs.statSync(curPath).isDirectory())
                        deleteFolderRecursive(curPath);
                    else
                        fs.unlinkSync(curPath);
                });
                fs.rmdirSync(path);
            }
        }

        function attemptToDeleteFolderRecursive(folder, tries, callback) {
            try {
                deleteFolderRecursive(folder);
                callback(204);
            } catch (err) {
                if (tries > 0) {
                    setTimeout(() => {
                        console.log('-- retrying')
                        attemptToDeleteFolderRecursive(folder, tries--, callback)
                    }, (100));
                } else {
                    callback(500);
                }
            }
        }
    },
}<|endoftext|>


<commit_before>'use strict'

const { expect } = require('chai')
const config = require('./lib/test-config')
const fetch = require('node-fetch')
const fs = require('fs')
const isPng = require('is-png')
const isSvg = require('is-svg')
const path = require('path')
const serverHelpers = require('./lib/in-process-server-test-helpers')
const sinon = require('sinon')
const Camp = require('camp')
const svg2img = require('./gh-badges/lib/svg-to-img')
const { handleRequest } = require('./lib/request-handler')
const { loadServiceClasses } = require('./services')

describe('The server', function() {
  let dummyCamp = Camp.start({ port: config.port, hostname: '::' })
  before('Check the services', function() {
    // The responsibility of this `before()` hook is to verify that the server
    // will be able to register all the services. When it fails, the balance of
    // this `describe()` block – that is, the server tests – does not run.
    //
    // Without this block, the next `before()` hook fails while printing this
    // quite opaque message:
    //
    // Error: listen EADDRINUSE :::1111
    //   at Object._errnoException (util.js:1022:11)
    //   at _exceptionWithHostPort (util.js:1044:20)
    //   at Camp.setupListenHandle [as _listen2] (net.js:1367:14)
    //   at listenInCluster (net.js:1408:12)
    //   at doListen (net.js:1517:7)
    //   at _combinedTickCallback (internal/process/next_tick.js:141:11)
    //   at process._tickDomainCallback (internal/process/next_tick.js:218:9)
    loadServiceClasses().forEach(serviceClass =>
      serviceClass.register({ camp: dummyCamp, handleRequest }, {})
    )
    dummyCamp.close()
    dummyCamp = undefined
  })
  after(function() {
    // Free up the port and shut down the server immediately, even when the
    // `before()` block fails during registration.
    if (dummyCamp) {
      dummyCamp.close()
      dummyCamp = undefined
    }
  })

  const baseUri = `http://127.0.0.1:${config.port}`

  let server
  before('Start running the server', function() {
    this.timeout(5000)
    server = serverHelpers.start()
  })
  after('Shut down the server', function() {
    serverHelpers.stop(server)
  })

  it('should produce colorscheme badges', async function() {
    // This is the first server test to run, and often times out.
    this.timeout(5000)
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should produce colorscheme PNG badges', async function() {
    this.timeout(5000)
    const res = await fetch(`${baseUri}/:fruit-apple-green.png`)
    expect(res.ok).to.be.true
    expect(await res.buffer()).to.satisfy(isPng)
  })

  it('should preserve label case', async function() {
    const res = await fetch(`${baseUri}/:fRuiT-apple-green.svg`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fRuiT')
  })

  // https://github.com/badges/shields/pull/1319
  it('should not crash with a numeric logo', async function() {
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?logo=1`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should not crash with a numeric link', async function() {
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=1`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should not crash with a boolean link', async function() {
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=true`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  context('with svg2img error', function() {
    const expectedError = fs.readFileSync(
      path.resolve(__dirname, 'public', '500.html')
    )

    let toBufferStub
    beforeEach(function() {
      toBufferStub = sinon
        .stub(svg2img._imageMagick.prototype, 'toBuffer')
        .callsArgWith(1, Error('whoops'))
    })
    afterEach(function() {
      toBufferStub.restore()
    })

    it('should emit the 500 message', async function() {
      const res = await fetch(`${baseUri}/:some_new-badge-green.png`)
      // This emits status code 200, though 500 would be preferable.
      expect(res.status).to.equal(200)
      expect(await res.text()).to.include(expectedError)
    })
  })

  describe('analytics endpoint', function() {
    it('should return analytics in the expected format', async function() {
      const res = await fetch(`${baseUri}/$analytics/v1`)
      expect(res.ok).to.be.true
      const json = await res.json()
      const expectedKeys = [
        'vendorMonthly',
        'rawMonthly',
        'vendorFlatMonthly',
        'rawFlatMonthly',
        'vendorFlatSquareMonthly',
        'rawFlatSquareMonthly',
      ]
      expect(json).to.have.all.keys(...expectedKeys)

      Object.values(json).forEach(stats => {
        expect(stats)
          .to.be.an('array')
          .with.length(36)
      })
    })
  })
})
<commit_msg>Test the notfound routes in server.js (#2398)<commit_after>'use strict'

const { expect } = require('chai')
const config = require('./lib/test-config')
const fetch = require('node-fetch')
const fs = require('fs')
const isPng = require('is-png')
const isSvg = require('is-svg')
const path = require('path')
const serverHelpers = require('./lib/in-process-server-test-helpers')
const sinon = require('sinon')
const Camp = require('camp')
const svg2img = require('./gh-badges/lib/svg-to-img')
const { handleRequest } = require('./lib/request-handler')
const { loadServiceClasses } = require('./services')

describe('The server', function() {
  let dummyCamp = Camp.start({ port: config.port, hostname: '::' })
  before('Check the services', function() {
    // The responsibility of this `before()` hook is to verify that the server
    // will be able to register all the services. When it fails, the balance of
    // this `describe()` block – that is, the server tests – does not run.
    //
    // Without this block, the next `before()` hook fails while printing this
    // quite opaque message:
    //
    // Error: listen EADDRINUSE :::1111
    //   at Object._errnoException (util.js:1022:11)
    //   at _exceptionWithHostPort (util.js:1044:20)
    //   at Camp.setupListenHandle [as _listen2] (net.js:1367:14)
    //   at listenInCluster (net.js:1408:12)
    //   at doListen (net.js:1517:7)
    //   at _combinedTickCallback (internal/process/next_tick.js:141:11)
    //   at process._tickDomainCallback (internal/process/next_tick.js:218:9)
    loadServiceClasses().forEach(serviceClass =>
      serviceClass.register({ camp: dummyCamp, handleRequest }, {})
    )
    dummyCamp.close()
    dummyCamp = undefined
  })
  after(function() {
    // Free up the port and shut down the server immediately, even when the
    // `before()` block fails during registration.
    if (dummyCamp) {
      dummyCamp.close()
      dummyCamp = undefined
    }
  })

  const baseUri = `http://127.0.0.1:${config.port}`

  let server
  before('Start running the server', function() {
    this.timeout(5000)
    server = serverHelpers.start()
  })
  after('Shut down the server', function() {
    serverHelpers.stop(server)
  })

  it('should produce colorscheme badges', async function() {
    // This is the first server test to run, and often times out.
    this.timeout(5000)
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should produce colorscheme PNG badges', async function() {
    this.timeout(5000)
    const res = await fetch(`${baseUri}/:fruit-apple-green.png`)
    expect(res.ok).to.be.true
    expect(await res.buffer()).to.satisfy(isPng)
  })

  it('should preserve label case', async function() {
    const res = await fetch(`${baseUri}/:fRuiT-apple-green.svg`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fRuiT')
  })

  // https://github.com/badges/shields/pull/1319
  it('should not crash with a numeric logo', async function() {
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?logo=1`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should not crash with a numeric link', async function() {
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=1`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should not crash with a boolean link', async function() {
    const res = await fetch(`${baseUri}/:fruit-apple-green.svg?link=true`)
    expect(res.ok).to.be.true
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('fruit')
      .and.to.include('apple')
  })

  it('should return the 404 badge for unknown badges', async function() {
    const res = await fetch(`${baseUri}/this/is/not/a/badge.svg`)
    expect(res.status).to.equal(404)
    expect(await res.text())
      .to.satisfy(isSvg)
      .and.to.include('404')
      .and.to.include('badge not found')
  })

  it('should return the 404 html page for rando links', async function() {
    const res = await fetch(`${baseUri}/this/is/most/definitely/not/a/badge.js`)
    expect(res.status).to.equal(404)
    expect(await res.text()).to.include('blood, toil, tears and sweat')
  })

  context('with svg2img error', function() {
    const expectedError = fs.readFileSync(
      path.resolve(__dirname, 'public', '500.html')
    )

    let toBufferStub
    beforeEach(function() {
      toBufferStub = sinon
        .stub(svg2img._imageMagick.prototype, 'toBuffer')
        .callsArgWith(1, Error('whoops'))
    })
    afterEach(function() {
      toBufferStub.restore()
    })

    it('should emit the 500 message', async function() {
      const res = await fetch(`${baseUri}/:some_new-badge-green.png`)
      // This emits status code 200, though 500 would be preferable.
      expect(res.status).to.equal(200)
      expect(await res.text()).to.include(expectedError)
    })
  })

  describe('analytics endpoint', function() {
    it('should return analytics in the expected format', async function() {
      const res = await fetch(`${baseUri}/$analytics/v1`)
      expect(res.ok).to.be.true
      const json = await res.json()
      const expectedKeys = [
        'vendorMonthly',
        'rawMonthly',
        'vendorFlatMonthly',
        'rawFlatMonthly',
        'vendorFlatSquareMonthly',
        'rawFlatSquareMonthly',
      ]
      expect(json).to.have.all.keys(...expectedKeys)

      Object.values(json).forEach(stats => {
        expect(stats)
          .to.be.an('array')
          .with.length(36)
      })
    })
  })
})
<|endoftext|>


<commit_before>var assert, ldap, Future, LDAP;

ldap = Meteor.require('ldapjs');
assert = Npm.require('assert');
Future = Npm.require('fibers/future');

LDAP = {};
LDAP.searchOu = 'ou=People,dc=rit,dc=edu';
LDAP.searchQuery = function(user){
  return {
    filter: "(uid=" + user + ")",
    scope: 'sub'
  };
};

LDAP.checkAccount = function(options) {
  var dn, future;
  LDAP.client = ldap.createClient({
    url: process.env.LDAP_URL,
    maxConnections: 2,
    bindDN:          'uid=' + options.username + ',ou=People,dc=rit,dc=edu',
    bindCredentials: options.password
  });
  options = options || {};
  dn = [];
  future = new Future();
  if (options.password.length === 0 || options.username.length === 0) {
    future['return'](void 8);
    return;
  }
  LDAP.client.search(LDAP.searchOu, LDAP.searchQuery(options.username), function(err, search) {
    if (err) {
      future['return'](false);
      return false;
    } else {
      search.on('searchEntry', function(entry) {
        dn.push(entry.objectName);
        LDAP.displayName = entry.object.displayName;
        LDAP.givenName = entry.object.givenName;
        LDAP.initials = entry.object.initials;
        LDAP.sn = entry.object.sn;
        LDAP.ou = entry.object.ou;
        return LDAP.displayName = entry.object.displayName;
      });
      search.on('error', function(err){
        throw new Meteor.Error(500, "LDAP server error");
      });
      return search.on('end', function() {
        if (dn.length === 0) {
          future['return'](false);
          return false;
        }
        return LDAP.client.bind(dn[0], options.password, function(err) {
          if (err) {
            future['return'](false);
            return false;
          }
          return LDAP.client.unbind(function(err) {
            assert.ifError(err);
            return future['return'](!err);
          });
        });
      });
    }
  });
  return future.wait();
};

Accounts.registerLoginHandler('ldap', function(loginRequest) {
  var user, userId;
  if (LDAP.checkAccount(loginRequest)) {
    user = Meteor.users.findOne({
      username: loginRequest.username
    });
    if (user) {
      userId = user._id;
    } else {
      userId = Meteor.users.insert({
        username: loginRequest.username,
        profile: {
          displayName: LDAP.displayName,
          givenName: LDAP.givenName,
          initials: LDAP.initials,
          sn: LDAP.sn
        }
      });
    }
    return {
      userId: userId
    };
  }
});<commit_msg>case insensitive logins<commit_after>var assert, ldap, Future, LDAP;

ldap = Meteor.require('ldapjs');
assert = Npm.require('assert');
Future = Npm.require('fibers/future');

LDAP = {};
LDAP.searchOu = 'ou=People,dc=rit,dc=edu';
LDAP.searchQuery = function(user){
  return {
    filter: "(uid=" + user + ")",
    scope: 'sub'
  };
};

LDAP.checkAccount = function(options) {
  var dn, future;
  LDAP.client = ldap.createClient({
    url: process.env.LDAP_URL,
    maxConnections: 2,
    bindDN:          'uid=' + options.username + ',ou=People,dc=rit,dc=edu',
    bindCredentials: options.password
  });
  options = options || {};
  dn = [];
  future = new Future();
  if (options.password.length === 0 || options.username.length === 0) {
    future['return'](void 8);
    return;
  }
  LDAP.client.search(LDAP.searchOu, LDAP.searchQuery(options.username), function(err, search) {
    if (err) {
      future['return'](false);
      return false;
    } else {
      search.on('searchEntry', function(entry) {
        dn.push(entry.objectName);
        LDAP.displayName = entry.object.displayName;
        LDAP.givenName = entry.object.givenName;
        LDAP.initials = entry.object.initials;
        LDAP.sn = entry.object.sn;
        LDAP.ou = entry.object.ou;
        return LDAP.displayName = entry.object.displayName;
      });
      search.on('error', function(err){
        throw new Meteor.Error(500, "LDAP server error");
      });
      return search.on('end', function() {
        if (dn.length === 0) {
          future['return'](false);
          return false;
        }
        return LDAP.client.bind(dn[0], options.password, function(err) {
          if (err) {
            future['return'](false);
            return false;
          }
          return LDAP.client.unbind(function(err) {
            assert.ifError(err);
            return future['return'](!err);
          });
        });
      });
    }
  });
  return future.wait();
};

Accounts.registerLoginHandler('ldap', function(loginRequest) {
  var user, userId;
  if (LDAP.checkAccount(loginRequest)) {
    user = Meteor.users.findOne({
      username: loginRequest.username.toLowerCase()
    });
    if (user) {
      userId = user._id;
    } else {
      userId = Meteor.users.insert({
        username: loginRequest.username.toLowerCase(),
        profile: {
          displayName: LDAP.displayName,
          givenName: LDAP.givenName,
          initials: LDAP.initials,
          sn: LDAP.sn
        }
      });
    }
    return {
      userId: userId
    };
  }
});<|endoftext|>