<commit_before>'use strict';

const stringify = require('json-stable-stringify');
const HyperSwitch = require('hyperswitch');
const Template = HyperSwitch.Template;

const DEFAULT_RETRY_DELAY = 500;
const DEFAULT_RETRY_FACTOR = 6;
const DEFAULT_RETRY_LIMIT = 5;

/**
 * Creates a JS function that verifies property equality
 *
 * @param retryDefinition the condition in the format of 'retry_on' stanza
 * @returns {Function} a function that verifies the condition
 */
function _compileRetryCondition(retryDefinition) {
    function createCondition(retryCond, option) {
        if (retryCond === 'status') {
            const opt = option.toString();
            if (/^[0-9]+$/.test(opt)) {
                return `(res["${retryCond}"] === ${opt})`;
            }
            if (/^[0-9x]+$/.test(opt)) {
                return `/^${opt.replace(/x/g, "\\d")}$/.test(res["${retryCond}"])`;
            }
            throw new Error(`Invalid retry_on condition ${opt}`);
        } else {
            return `(stringify(res["${retryCond}"]) === '${stringify(option)}')`;
        }
    }

    const condition = [];
    Object.keys(retryDefinition).forEach((catchCond) => {
        if (Array.isArray(retryDefinition[catchCond])) {
            const orCondition = retryDefinition[catchCond].map((option) =>
                createCondition(catchCond, option));
            condition.push('(' + orCondition.join(' || ') + ')');
        } else {
            condition.push(createCondition(catchCond, retryDefinition[catchCond]));
        }
    });
    const code = `return (${condition.join(' && ')});`;
    /* jslint evil: true */
    return new Function('stringify', 'res', code).bind(null, stringify);
}

function _getMatchObjCode(obj) {
    if (obj.constructor === Object) {
        return '{' + Object.keys(obj).map((key) => {
            const field = obj[key];
            return key + ': ' + _getMatchObjCode(obj[key]);
        }).join(', ') + '}';
    }
    return obj;
}

function _compileArrayMatch(obj, result, name) {
    const itemsCheck = obj.map((item, index) =>
        `${name}.find((item) => ${_compileMatch(item, {}, 'item', index)})`).join(' && ');
    return `Array.isArray(${name})` + (itemsCheck.length ? (' && ' + itemsCheck) : '');
}

function _compileMatch(obj, result, name, fieldName) {
    if (obj.constructor !== Object) {
        if (Array.isArray(obj)) {
            return _compileArrayMatch(obj, result, name, fieldName);
        }
        if (obj === 'undefined') {
            return `${name} === undefined`;
        }
        if (typeof obj !== 'string') {
            // not a string, so it has to match exactly
            result[fieldName] = obj;
            return `${name} === ${obj}`;
        }
        if (obj[0] !== '/' && obj[obj.length - 1] !== '/') {
            // not a regex, quote the string
            result[fieldName] = `'${obj}'`;
            return `${name} === '${obj}'`;
        }
        // it's a regex, we have to the test the arg
        result[fieldName] = `${obj}.exec(${name})`;
        return `${obj}.test(${name})`;
    }

    // this is an object, we need to split it into components
    const subObj = fieldName ? {} : result;
    const test = Object.keys(obj).map(
        (key) => _compileMatch(obj[key], subObj, `${name}['${key}']`, key))
    .join(' && ');
    if (fieldName) {
        result[fieldName] = subObj;
    }
    return test;
}

class Rule {
    constructor(name, spec) {
        this.name = name;
        this.spec = spec || {};

        this.topic = this.spec.topic;
        if (!this.topic) {
            throw new Error(`No topic specified for rule ${this.name}`);
        }
        this.spec.retry_on = this.spec.retry_on || {
            status: [ '50x' ]
        };
        this.shouldRetry = _compileRetryCondition(this.spec.retry_on);
        this.exec = this._processExec(this.spec.exec);
        this._match = this._processMatch(this.spec.match);
        this._match_not = (this._processMatch(this.spec.match_not) || {}).test;
        this.spec.retry_delay = this.spec.retry_delay || DEFAULT_RETRY_DELAY;
        this.spec.retry_limit = this.spec.retry_limit || DEFAULT_RETRY_LIMIT;
        this.spec.retry_factor = this.spec.retry_factor || DEFAULT_RETRY_FACTOR;
    }

    /**
     * Tests the message against the compiled evaluation test
     *
     * @param {Object} message the message to test
     * @return true if no match is set for this rule or if the message matches
     */
    test(message) {
        var match = true;
        if (this._match && this._match.test) {
            match = this._match.test(message);
        }
        if (this._match_not) {
            match = match && !this._match_not(message);
        }
        return match;
    }

    /**
     * Expands the rule's match object with the given message's content
     *
     * @param {Object} message the message to use in the expansion
     * @return {Object} the object containing the expanded match portion of the rule
     */
    expand(message) {
        return this._match && this._match.expand ? this._match.expand(message) : {};
    }

    _processMatch(match) {
        if (!match) {
            // No particular match specified, so we
            // should accept all events for this topic
            return;
        }

        const obj = {};
        const test = _compileMatch(match, obj, 'message');
        try {
            return {
                /* jslint evil: true  */
                test: new Function('message', 'return ' + test),
                /* jslint evil: true  */
                expand: new Function('message', 'return ' + _getMatchObjCode(obj))
            };
        } catch (e) {
            throw new Error('Invalid match object given!');
        }
    }

    _processExec(exec) {
        if (!exec) {
            // nothing to do, the rule is a no-op
            this.noop = true;
            return;
        }

        if (!Array.isArray(exec)) {
            exec = [exec];
        }

        const templates = [];
        for (let idx = 0; idx < exec.length; idx++) {
            const req = exec[idx];
            if (req.constructor !== Object || !req.uri) {
                throw new Error(`In rule ${this.name}, request number ${idx}
                    must be an object and must have the "uri" property`);
            }
            req.method = req.method || 'get';
            req.headers = req.headers || {};
            templates.push(new Template(req));
        }
        return templates;
    }
}

module.exports = Rule;
<commit_msg>Make retry defaults more conservative (#26)<commit_after>'use strict';

const stringify = require('json-stable-stringify');
const HyperSwitch = require('hyperswitch');
const Template = HyperSwitch.Template;

const DEFAULT_RETRY_DELAY = 60000;  // One minute minimum retry delay
const DEFAULT_RETRY_FACTOR = 6;     // Exponential back-off
const DEFAULT_RETRY_LIMIT = 2;      // At most two retries

/**
 * Creates a JS function that verifies property equality
 *
 * @param retryDefinition the condition in the format of 'retry_on' stanza
 * @returns {Function} a function that verifies the condition
 */
function _compileRetryCondition(retryDefinition) {
    function createCondition(retryCond, option) {
        if (retryCond === 'status') {
            const opt = option.toString();
            if (/^[0-9]+$/.test(opt)) {
                return `(res["${retryCond}"] === ${opt})`;
            }
            if (/^[0-9x]+$/.test(opt)) {
                return `/^${opt.replace(/x/g, "\\d")}$/.test(res["${retryCond}"])`;
            }
            throw new Error(`Invalid retry_on condition ${opt}`);
        } else {
            return `(stringify(res["${retryCond}"]) === '${stringify(option)}')`;
        }
    }

    const condition = [];
    Object.keys(retryDefinition).forEach((catchCond) => {
        if (Array.isArray(retryDefinition[catchCond])) {
            const orCondition = retryDefinition[catchCond].map((option) =>
                createCondition(catchCond, option));
            condition.push('(' + orCondition.join(' || ') + ')');
        } else {
            condition.push(createCondition(catchCond, retryDefinition[catchCond]));
        }
    });
    const code = `return (${condition.join(' && ')});`;
    /* jslint evil: true */
    return new Function('stringify', 'res', code).bind(null, stringify);
}

function _getMatchObjCode(obj) {
    if (obj.constructor === Object) {
        return '{' + Object.keys(obj).map((key) => {
            const field = obj[key];
            return key + ': ' + _getMatchObjCode(obj[key]);
        }).join(', ') + '}';
    }
    return obj;
}

function _compileArrayMatch(obj, result, name) {
    const itemsCheck = obj.map((item, index) =>
        `${name}.find((item) => ${_compileMatch(item, {}, 'item', index)})`).join(' && ');
    return `Array.isArray(${name})` + (itemsCheck.length ? (' && ' + itemsCheck) : '');
}

function _compileMatch(obj, result, name, fieldName) {
    if (obj.constructor !== Object) {
        if (Array.isArray(obj)) {
            return _compileArrayMatch(obj, result, name, fieldName);
        }
        if (obj === 'undefined') {
            return `${name} === undefined`;
        }
        if (typeof obj !== 'string') {
            // not a string, so it has to match exactly
            result[fieldName] = obj;
            return `${name} === ${obj}`;
        }
        if (obj[0] !== '/' && obj[obj.length - 1] !== '/') {
            // not a regex, quote the string
            result[fieldName] = `'${obj}'`;
            return `${name} === '${obj}'`;
        }
        // it's a regex, we have to the test the arg
        result[fieldName] = `${obj}.exec(${name})`;
        return `${obj}.test(${name})`;
    }

    // this is an object, we need to split it into components
    const subObj = fieldName ? {} : result;
    const test = Object.keys(obj).map(
        (key) => _compileMatch(obj[key], subObj, `${name}['${key}']`, key))
    .join(' && ');
    if (fieldName) {
        result[fieldName] = subObj;
    }
    return test;
}

class Rule {
    constructor(name, spec) {
        this.name = name;
        this.spec = spec || {};

        this.topic = this.spec.topic;
        if (!this.topic) {
            throw new Error(`No topic specified for rule ${this.name}`);
        }
        this.spec.retry_on = this.spec.retry_on || {
            status: [ '50x' ]
        };
        this.shouldRetry = _compileRetryCondition(this.spec.retry_on);
        this.exec = this._processExec(this.spec.exec);
        this._match = this._processMatch(this.spec.match);
        this._match_not = (this._processMatch(this.spec.match_not) || {}).test;
        this.spec.retry_delay = this.spec.retry_delay || DEFAULT_RETRY_DELAY;
        this.spec.retry_limit = this.spec.retry_limit || DEFAULT_RETRY_LIMIT;
        this.spec.retry_factor = this.spec.retry_factor || DEFAULT_RETRY_FACTOR;
    }

    /**
     * Tests the message against the compiled evaluation test
     *
     * @param {Object} message the message to test
     * @return true if no match is set for this rule or if the message matches
     */
    test(message) {
        var match = true;
        if (this._match && this._match.test) {
            match = this._match.test(message);
        }
        if (this._match_not) {
            match = match && !this._match_not(message);
        }
        return match;
    }

    /**
     * Expands the rule's match object with the given message's content
     *
     * @param {Object} message the message to use in the expansion
     * @return {Object} the object containing the expanded match portion of the rule
     */
    expand(message) {
        return this._match && this._match.expand ? this._match.expand(message) : {};
    }

    _processMatch(match) {
        if (!match) {
            // No particular match specified, so we
            // should accept all events for this topic
            return;
        }

        const obj = {};
        const test = _compileMatch(match, obj, 'message');
        try {
            return {
                /* jslint evil: true  */
                test: new Function('message', 'return ' + test),
                /* jslint evil: true  */
                expand: new Function('message', 'return ' + _getMatchObjCode(obj))
            };
        } catch (e) {
            throw new Error('Invalid match object given!');
        }
    }

    _processExec(exec) {
        if (!exec) {
            // nothing to do, the rule is a no-op
            this.noop = true;
            return;
        }

        if (!Array.isArray(exec)) {
            exec = [exec];
        }

        const templates = [];
        for (let idx = 0; idx < exec.length; idx++) {
            const req = exec[idx];
            if (req.constructor !== Object || !req.uri) {
                throw new Error(`In rule ${this.name}, request number ${idx}
                    must be an object and must have the "uri" property`);
            }
            req.method = req.method || 'get';
            req.headers = req.headers || {};
            templates.push(new Template(req));
        }
        return templates;
    }
}

module.exports = Rule;
<|endoftext|>


<commit_before>import _ from 'lodash';
import directory from '../directory';

// Some validation regexes and rules in this file are taken from Stellar Laboratory
// Do not take code out from this file into other files
// Stellar Laboratory is licensed under Apache 2.0
// https://github.com/stellar/laboratory

// First argument is always input

const RESULT_EMPTY = {
  ready: false,
}
const RESULT_VALID = {
  ready: true,
}

function err(errorMessage) {
  return {
    ready: false,
    message: errorMessage,
  }
}

const Validate = {
  publicKey(input) {
    if (input === '') {
      return null;
    }
    return StellarSdk.Keypair.isValidPublicKey(input);
  },
  assetCode(input) {
    return _.isString(input) && input.match(/^[a-zA-Z0-9]+$/g) && input.length > 0 && input.length < 12;
  },
  amount(input) {
    if (input === '') {
      return null;
    }
    let inputIsPositive = !!input.charAt(0) !== '-';
    let inputValidNumber = !!input.match(/^[0-9]*(\.[0-9]+){0,1}$/g);
    let inputPrecisionLessThan7 = !input.match(/\.([0-9]){8,}$/g);
    return inputIsPositive && inputValidNumber && inputPrecisionLessThan7;
  },

  // Below are the Validators using the new compound return types
  memo(input, type) {
    if (input === '') {
      return RESULT_EMPTY;
    }
    // type is of type: 'MEMO_ID' |'MEMO_TEXT' | 'MEMO_HASH' | 'MEMO_RETURN'
    switch (type) {
    case 'MEMO_ID':
      if (!input.match(/^[0-9]*$/g)) {
        return err('MEMO_ID only accepts a positive integer.');
      }
      if (input !== StellarSdk.UnsignedHyper.fromString(input).toString()) {
        return err(`MEMO_ID is an unsigned 64-bit integer and the max valid
                       value is ${StellarSdk.UnsignedHyper.MAX_UNSIGNED_VALUE.toString()}`)
      }
      break;
    case 'MEMO_TEXT':
      let memoTextBytes = Buffer.byteLength(input, 'utf8');
      if (memoTextBytes > 28) {
        return err(`MEMO_TEXT accepts a string of up to 28 bytes. ${memoTextBytes} bytes entered.`);
      }
      break;
    case 'MEMO_HASH':
    case 'MEMO_RETURN':
      if (!input.match(/^[0-9a-f]{64}$/gi)) {
        return err(`${type} accepts a 32-byte hash in hexadecimal format (64 characters).`);
      }
      break;
    }

    return RESULT_VALID;
  },
  address(input, type) {
    if (input === '') {
      return RESULT_EMPTY;
    }

    // Regex covers 99% of the use cases.
    // - Allows any character in user part except * and , as specified in Stellar docs
    // - Includes all valid addresses and a few invalid ones too such as fake TLD or misuse of hyphens or excessive length
    if (!input.match(/^[^\*\,]+\*([\-a-zA-Z0-9]+\.)*([a-zA-Z0-9]{2,}){1}$/)) {
      return err('Stellar federation address is improperly formatted.');
    }

    return RESULT_VALID;
  },
};

export default Validate;
<commit_msg>Improve regex for validing federation address<commit_after>import _ from 'lodash';
import directory from '../directory';

// Some validation regexes and rules in this file are taken from Stellar Laboratory
// Do not take code out from this file into other files
// Stellar Laboratory is licensed under Apache 2.0
// https://github.com/stellar/laboratory

// First argument is always input

const RESULT_EMPTY = {
  ready: false,
}
const RESULT_VALID = {
  ready: true,
}

function err(errorMessage) {
  return {
    ready: false,
    message: errorMessage,
  }
}

const Validate = {
  publicKey(input) {
    if (input === '') {
      return null;
    }
    return StellarSdk.Keypair.isValidPublicKey(input);
  },
  assetCode(input) {
    return _.isString(input) && input.match(/^[a-zA-Z0-9]+$/g) && input.length > 0 && input.length < 12;
  },
  amount(input) {
    if (input === '') {
      return null;
    }
    let inputIsPositive = !!input.charAt(0) !== '-';
    let inputValidNumber = !!input.match(/^[0-9]*(\.[0-9]+){0,1}$/g);
    let inputPrecisionLessThan7 = !input.match(/\.([0-9]){8,}$/g);
    return inputIsPositive && inputValidNumber && inputPrecisionLessThan7;
  },

  // Below are the Validators using the new compound return types
  memo(input, type) {
    if (input === '') {
      return RESULT_EMPTY;
    }
    // type is of type: 'MEMO_ID' |'MEMO_TEXT' | 'MEMO_HASH' | 'MEMO_RETURN'
    switch (type) {
    case 'MEMO_ID':
      if (!input.match(/^[0-9]*$/g)) {
        return err('MEMO_ID only accepts a positive integer.');
      }
      if (input !== StellarSdk.UnsignedHyper.fromString(input).toString()) {
        return err(`MEMO_ID is an unsigned 64-bit integer and the max valid
                       value is ${StellarSdk.UnsignedHyper.MAX_UNSIGNED_VALUE.toString()}`)
      }
      break;
    case 'MEMO_TEXT':
      let memoTextBytes = Buffer.byteLength(input, 'utf8');
      if (memoTextBytes > 28) {
        return err(`MEMO_TEXT accepts a string of up to 28 bytes. ${memoTextBytes} bytes entered.`);
      }
      break;
    case 'MEMO_HASH':
    case 'MEMO_RETURN':
      if (!input.match(/^[0-9a-f]{64}$/gi)) {
        return err(`${type} accepts a 32-byte hash in hexadecimal format (64 characters).`);
      }
      break;
    }

    return RESULT_VALID;
  },
  address(input, type) {
    if (input === '') {
      return RESULT_EMPTY;
    }

    // Regex covers 99% of the use cases.
    // - Allows any character in user part except * and , as specified in Stellar docs
    // - Includes all valid addresses and a few invalid ones too such as fake TLD or misuse of hyphens or excessive length
    if (!input.match(/^[^\*\,]+\*([\-a-zA-Z0-9]+)?(\.[\-a-zA-Z0-9]+)*(\.[a-zA-Z0-9]{2,})$/)) {
      return err('Stellar federation address is improperly formatted.');
    }

    return RESULT_VALID;
  },
};

export default Validate;
<|endoftext|>


<commit_before>// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.

/* ------------------ IMPORTS ------------------ */

const remote = require('electron').remote
const main = remote.require('./main.js')
const fs = require('fs')
const os = require('os')

// Those are needed to download the torrents
const path = require('path')
const request = require('request')
const exec = require('child_process').exec
const findRemoveSync = require('find-remove')

// Mal API
const mal = require('malapi').Anime
// Nyaa API
const Nyaa = require('node-nyaa-api')

/* ----------------- END IMPORTS ----------------- */

/* -----------------  FUNCTIONS  ----------------- */

function reduceString(string) {
    if (string.length > 100)
        return string.substring(0, 100) + ('...')
    return string
}

// I like HorribleSubs
function horribleSubsFilter(name) {
    if ('[HorribleSubs]' === name.split(' ')[0])
    {
        if ('[720p].mkv' === name.split(' ').reverse()[0])
            return true
    }
    return false
}

function getNameOnly(name) {
    let tmp = name.split(' ')
    tmp.pop()
    tmp.shift()
    return tmp.join(' ')
}

function getNameForResearch(name) {
    let tmp = getNameOnly(name).split(' ')
    tmp.pop()   // Episode number

    return tmp.join(' ')
}

function byProperty(prop) {
    return function(a, b) {
        if (typeof a[prop] == "number") {
            return (a[prop] - b[prop])
        }
        return ((a[prop] < b[prop]) ? -1 : ((a[prop] > b[prop]) ? 1 : 0))
    }
}

// Make the research for the latest animes
function getLatest () {
    Nyaa.search('[HorribleSubs]', function (err, animes) {
        // Initialize
        releases.releases = []
        releases.show = false
        loader.show = true
        if (err) throw err

        for(let anime in animes)
        {
            if (horribleSubsFilter(animes[anime].title))
            {
                let tmp = animes[anime].title.split(' ')
                tmp.pop()   // Remove the extension
                tmp.pop()   // Remove the episode number
                tmp.shift() // Remove the Horrible Subs tag

                // Make the actual research
                mal.fromName(tmp.join(' ')).then(result => {
                    releases.releases.push({
                        realTitle: animes[anime].title,
                        title: getNameOnly(animes[anime].title),
                        link: animes[anime].link,
                        synopsis: reduceString(result.synopsis),
                        picture: result.image,
                        published: animes[anime].published
                    })
                }).then( () => {
                    if (releases.releases.length === 35)
                    {
                        releases.releases.sort(byProperty('published'))
                        releases.releases.reverse()

                        // 35 elements is too much, reducing to 18
                        for (let i = 0; i < 17; ++i)
                            releases.releases.pop()
                    }
                }).then( () => {
                    setTimeout( () => {
                        loader.show = false
                        releases.show = true
                    }, 1200)
                })
            }
        }
    })
}

function downloadFile (file_url, name){
    let req = request({
        method: 'GET',
        uri: file_url
    })

    let out = fs.createWriteStream(path.join(__dirname, 'resources', 'tmp', `${name}.torrent`))
    req.pipe(out)
}

function startTorrent (file_url, name) {
    const torrents = path.join(__dirname, 'resources', 'tmp', `*.torrent`)
    let openCmd

    findRemoveSync(path.join(__dirname, 'resources', 'tmp'), {extensions: ['.torrent']})

    downloadFile(file_url, name)

    switch (process.platform)
    {
        case 'darwin':
            openCmd = 'open '
            break
        case 'linux':
            openCmd = 'xdg-open '
            break
        case 'win32':
            openCmd = 'start '
    }

    exec(openCmd + torrents, (error, stdout, stderr) => {
        if (error)
        {
            console.error(`exec error: ${error}`)
            return
        }
        if (stdout == null && stderr == null)
        {
            console.log(`Starting torrent stdout: ${stdout}`)
            console.log(`Starting torrent stderr: ${stderr}`)
        }

    })
}

function makeResearchOnMal (name) {

    mal.fromName(name).then(anime => {
        info.infos.title = anime.title
        info.infos.japTitle = anime.alternativeTitles.japanese[0].slice(10)
        info.infos.image = anime.image
        info.infos.synopsis = anime.synopsis
        info.infos.episodes = anime.episodes
        info.infos.studios = anime.studios
        info.infos.stats = anime.statistics
        info.infos.genres = anime.genres
        info.infos.type = anime.type.split(' ').slice(0, 3).join(' ')
        info.infos.characters = anime.characters
        info.infos.staff = anime.staff
        info.infos.aired = anime.aired
        info.infos.status = anime.status

        releases.show = false
        loader.show = false
        info.display = 'block'
        info.show = true
    })
}

/* -------------------- END FUNCTIONS ----------------- */

// First research at kawAnime's start
getLatest()

/* ------------------- VUE.JS OBJECTS ----------------- */

let releases = new Vue({
    el: '#releases',
    data: {
        releases: [],
        show: false
    },
    watch: {
        releases: function () {  // Whenever releases changes, this function will run
            // Code
        }
    },
    methods: {
        download: function (url, name) {
            startTorrent(url, name)
        },
        refresh: function () {
            getLatest()
        },
        searchThis: function (arg) {
            info.infos = {}
            this.show = false
            loader.show = true
            makeResearchOnMal(getNameForResearch(arg))
        }
    }
})

let loader = new Vue({
    el: '#loader-container',
    data: {
        show: true
    },
    methods: {
        test: function () {
            this.show = false
            releases.show = true
        }
    }
})

let info = new Vue({
    el: '#info-container',
    data: {
        infos: {
            stats: {
                score: {value: ''},
                ranking: ''
            }
        },
        display: 'none',
        show: false
    },
    methods: {
        downlaodThis: function () {

        },
        show: function () {
            this.display = 'block'
            this.show = true
        },
        hide: function () {
            this.show = false
        },
        backToMain: function () {
            this.hide()
            releases.show = true
        }
    },
    watch: {
        infos: function () {

        }
    }
})

// Vue object to open the other pages
new Vue({
    el: '.mdl-navigation',
    methods: {
        getDownloader: function () {
            main.openDownloader()
        },
        getInfoPage: function () {
            main.getInfoPage()
        }
    }
})

// For the greeting's message
new Vue({
    el: '.greetings',
    data: {
        username: os.userInfo().username
    }
})

// Research button on top right
let searchButton = document.getElementById('fixed-header-drawer-exp')

searchButton.addEventListener('keydown', (key) => {
    if (key.keyCode === 13)
    {
        info.show = false
        if (searchButton.value.length > 3)
        {
            releases.show = false
            loader.show = true
            makeResearchOnMal(searchButton.value.toString())
        }
        searchButton.value = ''
    }
})<commit_msg>Latest episodes will now display from newest to oldest<commit_after>// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.

/* ------------------ IMPORTS ------------------ */

const remote = require('electron').remote
const main = remote.require('./main.js')
const fs = require('fs')
const os = require('os')

// Those are needed to download the torrents
const path = require('path')
const request = require('request')
const exec = require('child_process').exec
const findRemoveSync = require('find-remove')

// Mal API
const mal = require('malapi').Anime
// Nyaa API
const Nyaa = require('node-nyaa-api')

/* ----------------- END IMPORTS ----------------- */

/* -----------------  FUNCTIONS  ----------------- */

function reduceString(string) {
    if (string.length > 100)
        return string.substring(0, 100) + ('...')
    return string
}

// I like HorribleSubs
function horribleSubsFilter(name) {
    if ('[HorribleSubs]' === name.split(' ')[0])
    {
        if ('[720p].mkv' === name.split(' ').reverse()[0])
            return true
    }
    return false
}

function getNameOnly(name) {
    let tmp = name.split(' ')
    tmp.pop()
    tmp.shift()
    return tmp.join(' ')
}

function getNameForResearch(name) {
    let tmp = getNameOnly(name).split(' ')
    tmp.pop()   // Episode number

    return tmp.join(' ')
}

function byProperty(prop) {
    return function(a, b) {
        if (typeof a[prop] == "number") {
            return (a[prop] - b[prop])
        }
        return ((a[prop] < b[prop]) ? -1 : ((a[prop] > b[prop]) ? 1 : 0))
    }
}

// Make the research for the latest animes
function getLatest () {
    let sorted = false

    Nyaa.search('[HorribleSubs]', function (err, animes) {
        // Initialize
        releases.releases = []
        releases.show = false
        loader.show = true
        if (err) throw err

        for(let anime in animes)
        {
            if (horribleSubsFilter(animes[anime].title))
            {
                let tmp = animes[anime].title.split(' ')
                tmp.pop()   // Remove the extension
                tmp.pop()   // Remove the episode number
                tmp.shift() // Remove the Horrible Subs tag

                // Make the actual research
                mal.fromName(tmp.join(' ')).then(result => {
                    releases.releases.push({
                        realTitle: animes[anime].title,
                        title: getNameOnly(animes[anime].title),
                        link: animes[anime].link,
                        synopsis: reduceString(result.synopsis),
                        picture: result.image,
                        published: animes[anime].published
                    })
                }).then( () => {
                    if (releases.releases.length > 32)
                    {
                        releases.releases.sort(byProperty('published'))
                        releases.releases.reverse()
                        sorted = true
                    }
                }).then( () => {
                    // 35 elements is too much, reducing to 18
                    if (sorted)
                        while (releases.releases.length > 18)
                            releases.releases.pop()
                }).then( () => {
                    setTimeout( () => {
                        loader.show = false
                        releases.show = true
                    }, 1200)
                })
            }
        }
    })
}

function downloadFile (file_url, name){
    let req = request({
        method: 'GET',
        uri: file_url
    })

    let out = fs.createWriteStream(path.join(__dirname, 'resources', 'tmp', `${name}.torrent`))
    req.pipe(out)
}

function startTorrent (file_url, name) {
    const torrents = path.join(__dirname, 'resources', 'tmp', `*.torrent`)
    let openCmd

    findRemoveSync(path.join(__dirname, 'resources', 'tmp'), {extensions: ['.torrent']})

    downloadFile(file_url, name)

    switch (process.platform)
    {
        case 'darwin':
            openCmd = 'open '
            break
        case 'linux':
            openCmd = 'xdg-open '
            break
        case 'win32':
            openCmd = 'start '
    }

    exec(openCmd + torrents, (error, stdout, stderr) => {
        if (error)
        {
            console.error(`exec error: ${error}`)
            return
        }
        if (stdout == null && stderr == null)
        {
            console.log(`Starting torrent stdout: ${stdout}`)
            console.log(`Starting torrent stderr: ${stderr}`)
        }

    })
}

function makeResearchOnMal (name) {

    mal.fromName(name).then(anime => {
        info.infos.title = anime.title
        info.infos.japTitle = anime.alternativeTitles.japanese[0].slice(10)
        info.infos.image = anime.image
        info.infos.synopsis = anime.synopsis
        info.infos.episodes = anime.episodes
        info.infos.studios = anime.studios
        info.infos.stats = anime.statistics
        info.infos.genres = anime.genres
        info.infos.type = anime.type.split(' ').slice(0, 3).join(' ')
        info.infos.characters = anime.characters
        info.infos.staff = anime.staff
        info.infos.aired = anime.aired
        info.infos.status = anime.status

        releases.show = false
        loader.show = false
        info.display = 'block'
        info.show = true
    })
}

/* -------------------- END FUNCTIONS ----------------- */

// First research at kawAnime's start
getLatest()

/* ------------------- VUE.JS OBJECTS ----------------- */

let releases = new Vue({
    el: '#releases',
    data: {
        releases: [],
        show: false
    },
    watch: {
        releases: function () {  // Whenever releases changes, this function will run
            // Code
        }
    },
    methods: {
        download: function (url, name) {
            startTorrent(url, name)
        },
        refresh: function () {
            getLatest()
        },
        searchThis: function (arg) {
            info.infos = {}
            this.show = false
            loader.show = true
            makeResearchOnMal(getNameForResearch(arg))
        }
    }
})

let loader = new Vue({
    el: '#loader-container',
    data: {
        show: true
    },
    methods: {
        test: function () {
            this.show = false
            releases.show = true
        }
    }
})

let info = new Vue({
    el: '#info-container',
    data: {
        infos: {
            stats: {
                score: {value: ''},
                ranking: ''
            }
        },
        display: 'none',
        show: false
    },
    methods: {
        downlaodThis: function () {

        },
        show: function () {
            this.display = 'block'
            this.show = true
        },
        hide: function () {
            this.show = false
        },
        backToMain: function () {
            this.hide()
            releases.show = true
        }
    },
    watch: {
        infos: function () {

        }
    }
})

// Vue object to open the other pages
new Vue({
    el: '.mdl-navigation',
    methods: {
        getDownloader: function () {
            main.openDownloader()
        },
        getInfoPage: function () {
            main.getInfoPage()
        }
    }
})

// For the greeting's message
new Vue({
    el: '.greetings',
    data: {
        username: os.userInfo().username
    }
})

// Research button on top right
let searchButton = document.getElementById('fixed-header-drawer-exp')

searchButton.addEventListener('keydown', (key) => {
    if (key.keyCode === 13)
    {
        info.show = false
        if (searchButton.value.length > 3)
        {
            releases.show = false
            loader.show = true
            makeResearchOnMal(searchButton.value.toString())
        }
        searchButton.value = ''
    }
})<|endoftext|>


<commit_before>define(function(require){
    'use strict';

    var d3 = require('d3');

    return function module() {

        var margin = {
                top: 20,
                right: 50,
                bottom: 20,
                left: 50
            },
            width = 300,
            height = 300,
            externalRadius = 140,
            internalRadius = 45.5,
            colorScale = d3.scale.category20c(),
            colorScheme,
            tooltipWidth = externalRadius + internalRadius,
            data,
            chartWidth, chartHeight,
            svg,
            ease = 'bounce',
            layout,
            shape,
            slices,
            sortComparator = null,
            getQuantity = function(d) { return parseInt(d.quantity, 10); },
            getSliceFill = function(d) { return colorScale(d.data.name); };

        function buildColorScale() {
            if (colorScheme) {
                colorScale = d3.scale.ordinal().range(colorScheme);
            }
        }

        function buildContainerGroups() {
            var container = svg.append('g').classed('container-group', true);

            container.append('g').classed('chart-group', true);
            container.append('g').classed('tooltip-group', true);
        }

        function buildLayout() {
            layout = d3.layout.pie()
                .value(getQuantity)
                .sort(sortComparator);
        }

        function buildShape() {
            shape = d3.svg.arc()
                .innerRadius(externalRadius - internalRadius)
                .outerRadius(externalRadius);
        }

        function drawSVG(container) {
            svg = d3.select(container)
                .selectAll('svg')
                .data([data]);

            svg.enter().append('svg')
                .attr('class', 'js-donut-chart');

            if (!$('.container-group').length) {
                buildContainerGroups();
            }

            svg.transition().ease(ease)
                .attr('width', width + margin.left + margin.right)
                .attr('height', height + margin.top + margin.bottom);

            svg.select('.container-group')
                .attr('transform', 'translate(' + (chartWidth / 2) + ',' + (chartHeight / 2) + ')');
        }

        function arcTween(a) {
            var i = d3.interpolate(this._current, a);
            this._current = i(0);
            return function(t) {
                return shape(i(t));
            };
        }

        function drawSlices() {
            if (!slices) {
                slices = svg.select('.chart-group').selectAll('path')
                    .data(layout(data))
                  .enter().append('path')
                    .each(function(d) { this._current = d; })
                    .attr('d', shape)
                    .attr('fill', getSliceFill)
                    .attr('class', 'slice');
            }
            else {
                slices = svg.select('.chart-group').selectAll('path')
                    .data(layout(data))
                    .attr('d', shape);

                slices.transition().duration(750).attrTween('d', arcTween);
            }
        }

        function wrap(text, tooltipWidth) {
            text.each(function() {
                var text = d3.select(this),
                    words = text.text().split(/\s+/).reverse(),
                    word,
                    line = [],
                    lineNumber = 0,
                    lineHeight = 1.1,
                    y = text.attr('y'),
                    dy = parseFloat(text.attr('dy')),
                    tspan = text.text(null).append('tspan')
                        .attr('x', 0)
                        .attr('y', y)
                        .attr('dy', dy + 'em')
                        .style('font-weight', 'bold')
                        .style('font-size', externalRadius / 3.5 + 'px');

                tspan.text(words.pop());
                tspan = text.append('tspan')
                    .attr('x', 0)
                    .attr('y', y)
                    .attr('dy', ++lineNumber * lineHeight + dy + 'em');

                while (word = words.pop()) {
                    line.push(word);
                    tspan.text(line.join(' '));
                    if (tspan.node().getComputedTextLength() > tooltipWidth - 50) {
                        line.pop();
                        tspan.text(line.join(' '));
                        line = [word];
                        tspan = text.append('tspan')
                            .attr('x', 0)
                            .attr('y', y)
                            .attr('dy', ++lineNumber * lineHeight + dy + 'em')
                            .text(word);
                    }
                }
            });
        }

        function drawTooltip() {
            svg.select('.tooltip-group').append('text')
                .attr('class', 'tooltip-text')
                .style('font-weight', 'normal')
                .style('font-size', internalRadius / 3 + 'px')
                .attr('x', -100);

            svg.selectAll('.slice').on('mouseover', function(obj) {
                svg.select('.tooltip-text')
                // TODO: make this an accessor so people can customize how
                // their data is presented in the tooltip?
                    .text(function() {
                        return obj.data.percentage + '% ' + obj.data.name;
                    })
                    .attr('dy', '.35em')
                    .attr('text-anchor', 'middle');

                svg.select('.tooltip-text').call(wrap, tooltipWidth);
            });

            svg.selectAll('.slice').on('mouseout', function() {
                svg.select('.tooltip-text')
                    .text('');
            });
        }

        function exports(_selection) {
            _selection.each(function(_data) {
                chartWidth = width - margin.left - margin.right;
                chartHeight = height - margin.top - margin.bottom;
                data = _data;

                buildLayout();
                buildColorScale();
                buildShape();
                drawSVG(this);
                drawSlices();

                drawTooltip();
            });
        }

        // Accessors
        // TODO: Maybe remove these accessors?
        exports.layout = function(_x) {
            if (!arguments.length) {
                return layout;
            }
            layout = _x;
            return this;
        };

        exports.shape = function(_x) {
            if (!arguments.length) {
                return shape;
            }
            shape = _x;
            return this;
        };

        // Accessors for API calls
        exports.externalRadius = function(_x) {
            if (!arguments.length) {
                return externalRadius;
            }
            externalRadius = _x;
            return this;
        };

        exports.internalRadius = function(_x) {
            if (!arguments.length) {
                return internalRadius;
            }
            internalRadius = _x;
            return this;
        };

        exports.colorScheme = function(_x) {
            if (!arguments.length) {
                return colorScheme;
            }
            colorScheme = _x;
            return this;
        };

        exports.margin = function(_x) {
            if (!arguments.length) {
                return margin;
            }
            margin = _x;
            return this;
        };

        exports.width = function(_x) {
            if (!arguments.length) {
                return width;
            }
            width = _x;
            return this;
        };

        exports.height = function(_x) {
            if (!arguments.length) {
                return height;
            }
            height = _x;
            return this;
        };

        return exports;
    };
});
<commit_msg>Fixing container bug<commit_after>define(function(require){
    'use strict';

    var d3 = require('d3');

    return function module() {

        var margin = {
                top: 20,
                right: 50,
                bottom: 20,
                left: 50
            },
            width = 300,
            height = 300,
            externalRadius = 140,
            internalRadius = 45.5,
            colorScale = d3.scale.category20c(),
            colorScheme,
            tooltipWidth = externalRadius + internalRadius,
            data,
            chartWidth, chartHeight,
            svg,
            ease = 'bounce',
            layout,
            shape,
            slices,
            sortComparator = null,
            getQuantity = function(d) { return parseInt(d.quantity, 10); },
            getSliceFill = function(d) { return colorScale(d.data.name); };

        function buildColorScale() {
            if (colorScheme) {
                colorScale = d3.scale.ordinal().range(colorScheme);
            }
        }

        function buildContainerGroups() {
            var container = svg.append('g').classed('container-group', true);

            container.append('g').classed('chart-group', true);
            container.append('g').classed('tooltip-group', true);
        }

        function buildLayout() {
            layout = d3.layout.pie()
                .value(getQuantity)
                .sort(sortComparator);
        }

        function buildShape() {
            shape = d3.svg.arc()
                .innerRadius(externalRadius - internalRadius)
                .outerRadius(externalRadius);
        }

        function drawSVG(container) {
            svg = d3.select(container)
                .selectAll('svg')
                .data([data]);

            svg.enter().append('svg')
                .attr('class', 'js-donut-chart');

            buildContainerGroups();

            svg.transition().ease(ease)
                .attr('width', width + margin.left + margin.right)
                .attr('height', height + margin.top + margin.bottom);

            svg.select('.container-group')
                .attr('transform', 'translate(' + (chartWidth / 2) + ',' + (chartHeight / 2) + ')');
        }

        function arcTween(a) {
            var i = d3.interpolate(this._current, a);
            this._current = i(0);
            return function(t) {
                return shape(i(t));
            };
        }

        function drawSlices() {
            if (!slices) {
                slices = svg.select('.chart-group').selectAll('path')
                    .data(layout(data))
                  .enter().append('path')
                    .each(function(d) { this._current = d; })
                    .attr('d', shape)
                    .attr('fill', getSliceFill)
                    .attr('class', 'slice');
            }
            else {
                slices = svg.select('.chart-group').selectAll('path')
                    .data(layout(data))
                    .attr('d', shape);

                slices.transition().duration(750).attrTween('d', arcTween);
            }
        }

        function wrap(text, tooltipWidth) {
            text.each(function() {
                var text = d3.select(this),
                    words = text.text().split(/\s+/).reverse(),
                    word,
                    line = [],
                    lineNumber = 0,
                    lineHeight = 1.1,
                    y = text.attr('y'),
                    dy = parseFloat(text.attr('dy')),
                    tspan = text.text(null).append('tspan')
                        .attr('x', 0)
                        .attr('y', y)
                        .attr('dy', dy + 'em')
                        .style('font-weight', 'bold')
                        .style('font-size', externalRadius / 3.5 + 'px');

                tspan.text(words.pop());
                tspan = text.append('tspan')
                    .attr('x', 0)
                    .attr('y', y)
                    .attr('dy', ++lineNumber * lineHeight + dy + 'em');

                while (word = words.pop()) {
                    line.push(word);
                    tspan.text(line.join(' '));
                    if (tspan.node().getComputedTextLength() > tooltipWidth - 50) {
                        line.pop();
                        tspan.text(line.join(' '));
                        line = [word];
                        tspan = text.append('tspan')
                            .attr('x', 0)
                            .attr('y', y)
                            .attr('dy', ++lineNumber * lineHeight + dy + 'em')
                            .text(word);
                    }
                }
            });
        }

        function drawTooltip() {
            svg.select('.tooltip-group').append('text')
                .attr('class', 'tooltip-text')
                .style('font-weight', 'normal')
                .style('font-size', internalRadius / 3 + 'px')
                .attr('x', -100);

            svg.selectAll('.slice').on('mouseover', function(obj) {
                svg.select('.tooltip-text')
                // TODO: make this an accessor so people can customize how
                // their data is presented in the tooltip?
                    .text(function() {
                        return obj.data.percentage + '% ' + obj.data.name;
                    })
                    .attr('dy', '.35em')
                    .attr('text-anchor', 'middle');

                svg.select('.tooltip-text').call(wrap, tooltipWidth);
            });

            svg.selectAll('.slice').on('mouseout', function() {
                svg.select('.tooltip-text')
                    .text('');
            });
        }

        function exports(_selection) {
            _selection.each(function(_data) {
                chartWidth = width - margin.left - margin.right;
                chartHeight = height - margin.top - margin.bottom;
                data = _data;

                buildLayout();
                buildColorScale();
                buildShape();
                drawSVG(this);
                drawSlices();

                drawTooltip();
            });
        }

        // Accessors
        // TODO: Maybe remove these accessors?
        exports.layout = function(_x) {
            if (!arguments.length) {
                return layout;
            }
            layout = _x;
            return this;
        };

        exports.shape = function(_x) {
            if (!arguments.length) {
                return shape;
            }
            shape = _x;
            return this;
        };

        // Accessors for API calls
        exports.externalRadius = function(_x) {
            if (!arguments.length) {
                return externalRadius;
            }
            externalRadius = _x;
            return this;
        };

        exports.internalRadius = function(_x) {
            if (!arguments.length) {
                return internalRadius;
            }
            internalRadius = _x;
            return this;
        };

        exports.colorScheme = function(_x) {
            if (!arguments.length) {
                return colorScheme;
            }
            colorScheme = _x;
            return this;
        };

        exports.margin = function(_x) {
            if (!arguments.length) {
                return margin;
            }
            margin = _x;
            return this;
        };

        exports.width = function(_x) {
            if (!arguments.length) {
                return width;
            }
            width = _x;
            return this;
        };

        exports.height = function(_x) {
            if (!arguments.length) {
                return height;
            }
            height = _x;
            return this;
        };

        return exports;
    };
});
<|endoftext|>


<commit_before>(function($, _, undefined) {

    const TEMPLATE = $('

').load(_.extension.getURL('template.html') + ' .scry'),
          IMAGE_URL = 'http://gatherer.wizards.com/Handlers/Image.ashx?type=card&multiverseid=';

    function prices(card) {
        return $.Deferred(function(dfd) {
            _.runtime.sendMessage({
                type : 'prices',
                card : card
            }, function(prices) {
                if (prices) dfd.resolve(prices);
                else dfd.reject();
            });
        }).promise();
    }

    function oracle(query) {
        return $.Deferred(function(dfd) {
            query.type = 'oracle';
            _.runtime.sendMessage(query, function(card) {
                console.log(card);
                if (card) dfd.resolve(card);
                else dfd.reject();
            });
        }).promise();
    }

    function show(e) {
        var scry = $(this).data('scry'),
            w = scry.width(), h = scry.height(),
            b = $(window).width(), s = $(window).scrollTop(),
            x = e.pageX, y = e.pageY,
            cl, ct;
        if (b - 15 >= w + x) cl = x + 5;
        else cl = b - w - 15;
        if (s + 20 >= y - h) ct = y + 10;
        else ct = y - h - 10;
        scry.css({ left : cl, top : ct }).fadeIn(300);
    }

    function hide() {
        var timeout = window.setTimeout($.proxy(function() {
            $(this).data('scry').fadeOut(300).queue(function() {
                $(this).find('.scry-info').hide();
                $(this).dequeue();
            });
        }, this), 500);
        $(this).data('scry-timeout', timeout);
    }

    function clearTimeout() {
        window.clearTimeout($(this).data('scry-timeout'));
    }

    function flip(card) {
        var flipped = this.data('scry-flipped') || false;
        this.transition({ rotate : (flipped ? '-' : '+') + '=180' })
            .queue(function() {
                $(this).css({
                    'background-image' : 'url(' + IMAGE_URL + card.multiverseid
                        + (flipped ? '' : '&options=rotate180') + ')',
                    'rotate' : 0
                }).toggleClass('scry-flipped', !flipped).dequeue();
                oracleConstruct.apply($(this).find('.scry-oracle'), [ flipped ? card : card.other ]);
            }).data('scry-flipped', !flipped);
    }

    function transform(card) {
        var transformed = this.data('scry-transformed'),
            op = transformed ? '-' : '+',
            c = transformed ? card : card.other;
        this.transition({
            duration : 200,
            easing : 'in',
            perspective : 250,
            rotateY : op + '=90'
        }).queue(function() {
            $(this).css({
                'background-image' : 'url(' + IMAGE_URL + c.multiverseid + ')',
                scale : [ (transformed ? 1 : -1), 1 ]
            }).dequeue();
            oracleConstruct.apply($(this).find('.scry-oracle'), [ transformed ? card : card.other ]);
        }).transition({
            duration : 200,
            easing : 'out',
            perspective : 250,
            rotateY : op + '=90'
        }).data('scry-transformed', !transformed);
    }

    function content(card) {
        this.css({
            'background-image' : 'url(' + IMAGE_URL + card.multiverseid +
                (card.layout === 'split' ? '&options=rotate90' : '') + ')',
            'border-color' : card.border
        }).toggleClass('scry-alpha', card.setcode === 'LEA');
        $.when(prices(card)).done($.proxy(pricesConstruct, this.find('.scry-prices')));
        this.find('.scry-oracle.scry-right').remove();
        oracleConstruct.apply(this.find('.scry-oracle').removeClass('scry-left'), [ card ]);
        rulingsConstruct.apply(this.find('.scry-rulings'), [ card ]);
        printingsConstruct.apply(this.find('.scry-printings'), [ card ]);

        if (card.layout === 'split') {
            var other = this.find('.scry-oracle').clone().addClass('scry-right');
            oracleConstruct.apply(other, [ card.other ]);
            this.find('.scry-oracle').addClass('scry-left').after(other);
        }
    }

    function oracleConstruct(card) {
        var self = this;
        self.find('div').empty();
        $.each(card, function(prop, value) {
            var s = self.find('.scry-oracle-' + prop);
            if (prop === 'text') value = value.replace(/\n/g, '
').replace(/(\(.*\))/g, function(m) {
                return '' + m + '';
            });
            s.html(value);
        });
        $.each([ 'power', 'toughness', 'loyalty' ], function(i, prop) {
            if (!card[prop]) self.find('.scry-oracle-' + prop).remove();
        });
    }

    function pricesConstruct(prices) {
        var self = this, template = self.find('.scry-vendor.scry-template');
        self.find('.scry-vendor').filter(':not(.scry-template)').empty();
        self.find('.scry-prices-link').on('click.scry', function() {
            window.open(prices.link);
        });
        $.each(prices.range, function(prop, value) {
            self.find('.scry-prices-range .scry-prices-' + prop).text(value);
        });
        $.each(prices.vendors, function(i, vendor) {
            var v = template.clone().removeClass('scry-template');
            $.each(vendor, function(prop, value) {
                v.find('.scry-vendor-' + prop).text(value);
            });
            v.find('.scry-vendor-name').on('click.scry', function() {
                window.open(vendor.link);
            });
            self.find('.scry-vendors').append(v);
        });
    }

    function rulingsConstruct(card) {
        if (!card.rulings) return;
        var rulings = this.find('ul'),
            template = this.find('.scry-ruling.scry-template');
        rulings.find('li').filter(':not(.scry-template)').remove();
        $.each(card.rulings, function(i, ruling) {
            var r = template.clone().removeClass('scry-template');
            $.each(ruling, function(prop, value) {
                r.find('.scry-ruling-' + prop).text(value);
            });
            rulings.append(r);
        });
    }

    function printingsConstruct(card) {
        var printings = this.find('ul').off('.scry'),
            template = this.find('.scry-printing.scry-template'),
            sets = $.merge([], card.sets);
        if (card.other) $.merge(sets, card.other.sets);
        printings.find('li').filter(':not(.scry-template)').remove();
        $.each(sets, function(i, set) {
            var p = template.clone().removeClass('scry-template').data('scry-set', set);
            $.each(set, function(prop, value) {
                p.find('.scry-printing-' + prop).text(value);
            });
            p.find('.scry-printing-number').toggle(set.number !== undefined);
            printings.append(p);
        });
        printings.on('click.scry', '.scry-printing', function(e) {
            var set = $(this).data('scry-set');
            if (set.setcode === card.setcode) return;
            $.extend(true, card, set);
            content.apply($(this).parents('.scry'), [ card ]);
        });
    }

    function construct(card) {
        var scry = TEMPLATE.find('.scry').clone().data('card', card);
        content.apply(scry, [ card ]);

        if (card.layout === 'flip')
            scry.find('.scry-flip').on('click.scry', $.proxy(flip, scry, card)).show();
        else if (card.layout === 'double-faced')
            scry.find('.scry-transform').on('click.scry', $.proxy(transform, scry, card)).show();

        scry.find('.scry-info-toggle').on('click.scry', function() {
            scry.find('.scry-info').slideToggle();
        });
        scry.find('.scry-tabs>li').on('click.scry', function() {
            var type = $(this).text().toLowerCase();
            $(this).addClass('scry-active').siblings().removeClass('scry-active');
            scry.find('.scry-panels .scry-' + type).show()
                .siblings().filter(':not(.scry-' + type + ')').hide();
        });
        return scry.toggleClass('scry-split', card.layout === 'split').appendTo('body');
    }

    function attach(scry) {
        $(this).data('scry', scry);
        $(this).on('mouseenter.scry', show).on('mouseleave.scry', hide);
        scry.on('mouseenter.scry', $.proxy(clearTimeout, this))
            .on('mouseleave.scry', $.proxy(hide, this));
        return scry;
    }

    function init(e) {
        var query = e.data.query.apply(this);
        if (!query) return;
        if (typeof query === 'string') query = { name : query };
        else if (typeof query === 'number') query = { multiverseid : query };
        $.when(oracle(query))
            .then(construct)
            .then($.proxy(attach, this))
            .done($.proxy(show, this, e));
    }

    function receive(msg) {
        var temp = $('

'),
            e = $('body').data('scry-event');
        $.when(oracle(msg))
            .then(construct)
            .then($.proxy(attach, temp))
            .done($.proxy(show, temp, e));
    }

    $.fn.scry = function(options) {
        var settings = $.extend({
            query : function() { return $(this).text() },
            selector : null
        }, options);

        _.runtime.sendMessage({ type : 'init' });
        _.runtime.onMessage.addListener(receive);
        $('body').on('mouseup', function(e) {
            $(this).data('scry-event', e);
        });
        return this.each(function() {
            $(this).on('mouseover.scry', settings.selector, settings,
                function(e) {
                    if ($(this).data('scry')) return;
                    init.apply(this, [ e ]);
                });
        });
    }

})(jQuery, chrome);
<commit_msg>Hide other Scry popups when showing a popup<commit_after>(function($, _, undefined) {

    const TEMPLATE = $('

').load(_.extension.getURL('template.html') + ' .scry'),
          IMAGE_URL = 'http://gatherer.wizards.com/Handlers/Image.ashx?type=card&multiverseid=';

    function prices(card) {
        return $.Deferred(function(dfd) {
            _.runtime.sendMessage({
                type : 'prices',
                card : card
            }, function(prices) {
                if (prices) dfd.resolve(prices);
                else dfd.reject();
            });
        }).promise();
    }

    function oracle(query) {
        return $.Deferred(function(dfd) {
            query.type = 'oracle';
            _.runtime.sendMessage(query, function(card) {
                console.log(card);
                if (card) dfd.resolve(card);
                else dfd.reject();
            });
        }).promise();
    }

    function show(e) {
        var scry = $(this).data('scry'),
            w = scry.width(), h = scry.height(),
            b = $(window).width(), s = $(window).scrollTop(),
            x = e.pageX, y = e.pageY,
            cl, ct;
        $('.scry').fadeOut(300);
        if (b - 15 >= w + x) cl = x + 5;
        else cl = b - w - 15;
        if (s + 20 >= y - h) ct = y + 10;
        else ct = y - h - 10;
        scry.css({ left : cl, top : ct }).fadeIn(300);
    }

    function hide() {
        var timeout = window.setTimeout($.proxy(function() {
            $(this).data('scry').fadeOut(300).queue(function() {
                $(this).find('.scry-info').hide();
                $(this).dequeue();
            });
        }, this), 500);
        $(this).data('scry-timeout', timeout);
    }

    function clearTimeout() {
        window.clearTimeout($(this).data('scry-timeout'));
    }

    function flip(card) {
        var flipped = this.data('scry-flipped') || false;
        this.transition({ rotate : (flipped ? '-' : '+') + '=180' })
            .queue(function() {
                $(this).css({
                    'background-image' : 'url(' + IMAGE_URL + card.multiverseid
                        + (flipped ? '' : '&options=rotate180') + ')',
                    'rotate' : 0
                }).toggleClass('scry-flipped', !flipped).dequeue();
                oracleConstruct.apply($(this).find('.scry-oracle'), [ flipped ? card : card.other ]);
            }).data('scry-flipped', !flipped);
    }

    function transform(card) {
        var transformed = this.data('scry-transformed'),
            op = transformed ? '-' : '+',
            c = transformed ? card : card.other;
        this.transition({
            duration : 200,
            easing : 'in',
            perspective : 250,
            rotateY : op + '=90'
        }).queue(function() {
            $(this).css({
                'background-image' : 'url(' + IMAGE_URL + c.multiverseid + ')',
                scale : [ (transformed ? 1 : -1), 1 ]
            }).dequeue();
            oracleConstruct.apply($(this).find('.scry-oracle'), [ transformed ? card : card.other ]);
        }).transition({
            duration : 200,
            easing : 'out',
            perspective : 250,
            rotateY : op + '=90'
        }).data('scry-transformed', !transformed);
    }

    function content(card) {
        this.css({
            'background-image' : 'url(' + IMAGE_URL + card.multiverseid +
                (card.layout === 'split' ? '&options=rotate90' : '') + ')',
            'border-color' : card.border
        }).toggleClass('scry-alpha', card.setcode === 'LEA');
        $.when(prices(card)).done($.proxy(pricesConstruct, this.find('.scry-prices')));
        this.find('.scry-oracle.scry-right').remove();
        oracleConstruct.apply(this.find('.scry-oracle').removeClass('scry-left'), [ card ]);
        rulingsConstruct.apply(this.find('.scry-rulings'), [ card ]);
        printingsConstruct.apply(this.find('.scry-printings'), [ card ]);

        if (card.layout === 'split') {
            var other = this.find('.scry-oracle').clone().addClass('scry-right');
            oracleConstruct.apply(other, [ card.other ]);
            this.find('.scry-oracle').addClass('scry-left').after(other);
        }
    }

    function oracleConstruct(card) {
        var self = this;
        self.find('div').empty();
        $.each(card, function(prop, value) {
            var s = self.find('.scry-oracle-' + prop);
            if (prop === 'text') value = value.replace(/\n/g, '
').replace(/(\(.*\))/g, function(m) {
                return '' + m + '';
            });
            s.html(value);
        });
        $.each([ 'power', 'toughness', 'loyalty' ], function(i, prop) {
            if (!card[prop]) self.find('.scry-oracle-' + prop).remove();
        });
    }

    function pricesConstruct(prices) {
        var self = this, template = self.find('.scry-vendor.scry-template');
        self.find('.scry-vendor').filter(':not(.scry-template)').empty();
        self.find('.scry-prices-link').on('click.scry', function() {
            window.open(prices.link);
        });
        $.each(prices.range, function(prop, value) {
            self.find('.scry-prices-range .scry-prices-' + prop).text(value);
        });
        $.each(prices.vendors, function(i, vendor) {
            var v = template.clone().removeClass('scry-template');
            $.each(vendor, function(prop, value) {
                v.find('.scry-vendor-' + prop).text(value);
            });
            v.find('.scry-vendor-name').on('click.scry', function() {
                window.open(vendor.link);
            });
            self.find('.scry-vendors').append(v);
        });
    }

    function rulingsConstruct(card) {
        if (!card.rulings) return;
        var rulings = this.find('ul'),
            template = this.find('.scry-ruling.scry-template');
        rulings.find('li').filter(':not(.scry-template)').remove();
        $.each(card.rulings, function(i, ruling) {
            var r = template.clone().removeClass('scry-template');
            $.each(ruling, function(prop, value) {
                r.find('.scry-ruling-' + prop).text(value);
            });
            rulings.append(r);
        });
    }

    function printingsConstruct(card) {
        var printings = this.find('ul').off('.scry'),
            template = this.find('.scry-printing.scry-template'),
            sets = $.merge([], card.sets);
        if (card.other) $.merge(sets, card.other.sets);
        printings.find('li').filter(':not(.scry-template)').remove();
        $.each(sets, function(i, set) {
            var p = template.clone().removeClass('scry-template').data('scry-set', set);
            $.each(set, function(prop, value) {
                p.find('.scry-printing-' + prop).text(value);
            });
            p.find('.scry-printing-number').toggle(set.number !== undefined);
            printings.append(p);
        });
        printings.on('click.scry', '.scry-printing', function(e) {
            var set = $(this).data('scry-set');
            if (set.setcode === card.setcode) return;
            $.extend(true, card, set);
            content.apply($(this).parents('.scry'), [ card ]);
        });
    }

    function construct(card) {
        var scry = TEMPLATE.find('.scry').clone().data('card', card);
        content.apply(scry, [ card ]);

        if (card.layout === 'flip')
            scry.find('.scry-flip').on('click.scry', $.proxy(flip, scry, card)).show();
        else if (card.layout === 'double-faced')
            scry.find('.scry-transform').on('click.scry', $.proxy(transform, scry, card)).show();

        scry.find('.scry-info-toggle').on('click.scry', function() {
            scry.find('.scry-info').slideToggle();
        });
        scry.find('.scry-tabs>li').on('click.scry', function() {
            var type = $(this).text().toLowerCase();
            $(this).addClass('scry-active').siblings().removeClass('scry-active');
            scry.find('.scry-panels .scry-' + type).show()
                .siblings().filter(':not(.scry-' + type + ')').hide();
        });
        return scry.toggleClass('scry-split', card.layout === 'split').appendTo('body');
    }

    function attach(scry) {
        $(this).data('scry', scry);
        $(this).on('mouseenter.scry', show).on('mouseleave.scry', hide);
        scry.on('mouseenter.scry', $.proxy(clearTimeout, this))
            .on('mouseleave.scry', $.proxy(hide, this));
        return scry;
    }

    function init(e) {
        var query = e.data.query.apply(this);
        if (!query) return;
        if (typeof query === 'string') query = { name : query };
        else if (typeof query === 'number') query = { multiverseid : query };
        $.when(oracle(query))
            .then(construct)
            .then($.proxy(attach, this))
            .done($.proxy(show, this, e));
    }

    function receive(msg) {
        var temp = $('

'),
            e = $('body').data('scry-event');
        $.when(oracle(msg))
            .then(construct)
            .then($.proxy(attach, temp))
            .done($.proxy(show, temp, e));
    }

    $.fn.scry = function(options) {
        var settings = $.extend({
            query : function() { return $(this).text() },
            selector : null
        }, options);

        _.runtime.sendMessage({ type : 'init' });
        _.runtime.onMessage.addListener(receive);
        $('body').on('mouseup', function(e) {
            $(this).data('scry-event', e);
        });
        return this.each(function() {
            $(this).on('mouseover.scry', settings.selector, settings,
                function(e) {
                    if ($(this).data('scry')) return;
                    init.apply(this, [ e ]);
                });
        });
    }

})(jQuery, chrome);
<|endoftext|>


<commit_before>var string = require('../utils/string');

var  ATTRIBUTES_OPERATORS = {
  '' : 'present',
  '=' : 'equals',
  '*=' : 'contains',
  '^=' : 'beginsWith',
  '$=' : 'endsWith',
  '~=' : 'spaced',
  '|=' : 'hyphenated'
};

var HIERARCHY_OPERATORS = {
  ' ' : 'descendant',
  '>' : 'directChild',
  '~' : 'generalSibling',
  '+' : 'adjacentSibling'
};

var PSEUDOE_NOT_REGEX = /:not[^\)]*/g;
var NON_VAL_ATTRS_REGEX = /\[-?[_a-zA-Z]+[_a-zA-Z0-9-]*\]/g;
var WITH_VAL_ATTRS_REGEX = /\[-?[_a-zA-Z]+[_a-zA-Z0-9-]*[\^\*\$\~\|]*\=[\"\'].*[\"\']\]/g;
var ID_REGEX = /\#-?[_a-zA-Z]+[_a-zA-Z0-9-]*/g;
var CLASS_REGEX = /\.-?[_a-zA-Z]+[_a-zA-Z0-9-]*/g;
var PSEUDOE_CLASS_REGEX = /\:-?[_a-zA-Z]+[_a-zA-Z0-9-\(\+\)]*/g;
var PSEUDOE_ELEMENT_REGEX = /\:\:-?[_a-zA-Z]+[_a-zA-Z0-9-\(\+\)]*/g;

/**
 * var getRegexMatches - get string and regular expression and return matches
 *
 * @param  {string} str
 * @param  {RegExp} rgx
 * @param  {function} [onmatch] to run on each match
 * @returns {array}
 */
var getRegexMatches = function(str, rgx, onmatch) {
  rgx = new RegExp(rgx);

  onmatch = onmatch || function(match) {
    return match;
  };

  var matches = [];

  var res = rgx.exec(str);
  while (res) {
    res = onmatch(res[0]);
    matches.push(res);
    res = rgx.exec(str);
  }

  return matches;
};

/**
 * var getTag - get selector string and extract the tag from it
 *
 * @param  {string} selector
 * @returns {string}
 */
var getTag = function(selector) {
  return string.trim(selector.split('.')[0].split('#')[0].split(':')[0].split(' ')[0]) || null;
};

/**
 * var getIds - get selector string and extract the ids from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getIds = function(selector) {
  // following might include id selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  var matches = getRegexMatches(selector, ID_REGEX, function(match) {
    return match.substr(1, match.length); // get rid of '#'
  });

  return matches;
};

/**
 * var getClasses - get selector string and extract the classes from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getClasses = function(selector) {
  // following might include class selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  var matches = getRegexMatches(selector, CLASS_REGEX, function(match) {
    return match.substr(1, match.length); // get rid of '.'
  });

  return matches;
};

/**
 * var getPseudoElements - get selector string and extract the pseudo elements from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getPseudoElements = function(selector) {
  // following might include pseudo class selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  var matches = getRegexMatches(selector, PSEUDOE_ELEMENT_REGEX, function(match) {
    return match.substr(2, match.length); // get rid of '::'
  });

  return matches;
};

/**
 * var getPseudoClasses - get selector string and extract the pseudo classes from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getPseudoClasses = function(selector) {
  // following might include pseudo class selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');
  selector = selector.replace(PSEUDOE_ELEMENT_REGEX, '');

  var matches = getRegexMatches(selector, PSEUDOE_CLASS_REGEX, function(match) {
    return match.substr(1, match.length); // get rid of ':'
  });

  return matches;
};

/**
 * var getNots - get selector string and extract the pseudos nots from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getNots = function(selector) {
  var matches = getRegexMatches(selector, PSEUDOE_NOT_REGEX, function(match) {
    return match.slice(5, match.length); // get rid of ':not('
  });

  if (!matches.length) {
    return null;
  }

  return matches;
};

/**
 * var getAttributes - get selector string and extract the attributes from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getAttributes = function(selector) {
  // following might include attributes selector regex as well. get rid of before matching
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  // helps regex work correcly. TODO: REGEX SHOULD BE FIXED
  selector = selector.split('][').join(']\n[');

  var matches = getRegexMatches(selector, WITH_VAL_ATTRS_REGEX).concat(getRegexMatches(selector, NON_VAL_ATTRS_REGEX));

  var arr = []; // will include all attributes

  for (var i in matches) {
    var prop, val, behav, operator = '';
    var attr = matches[i].slice(1, matches[i].length - 1); // get rid of '[' and ']'
    var attrObj = {};

    attrObj['raw'] = attr;

    var eqSignPos = attr.indexOf('=');

    if (-1 === eqSignPos) {
      prop = attr;
      val = null;
      behav = ATTRIBUTES_OPERATORS[operator];

    } else {
      operator = attr.slice(eqSignPos - 1, eqSignPos + 1); // in case operator is like '{X}='

      if (!Object.keys(ATTRIBUTES_OPERATORS).includes(operator)) {
        operator = '=';
      }

      prop = attr.split(operator)[0];
      val = attr.substring(attr.indexOf(operator) + operator.length + 1, attr.length - 1);
      behav = ATTRIBUTES_OPERATORS[operator];
    }

    attrObj['property'] = prop;
    attrObj['value'] = val;
    attrObj['behaviour'] = behav;
    attrObj['operator'] = operator;

    arr[i] = attrObj;
  }

  return arr;
};

/**
 * var splitByHierarchy - get selector string, parse the next hierarchy in it, and return it
 *
 * @param  {string} selector
 * @returns {array} [selector before hierarchy operator occurence, kind of hierarchy, selector after hierarchy operator occurence]
 */
var splitByHierarchy = function(selector) {
  var parts = selector.split(/( )/); // split by whitespaces but include them in splitted arr

  for (var i = 0; i < parts.length; i++) {
    var bfrChar = parts[i - 1];
    var curChar = parts[i]; // possibly an operator
    var afrChar = parts[i + 1];

    if (Object.keys(HIERARCHY_OPERATORS).includes(curChar)) {
      var operator = curChar; // curChar is an operator

      // in case operator is not a whitespace, it must be between whitespaces inside array
      // this helps distinguish between regular whitespace and hierarchy operator whitespace
      if (' ' === bfrChar && ' ' === afrChar) {
        return [parts.slice(0, i + 1).join(''), HIERARCHY_OPERATORS[operator], parts.slice(i + 2).join('')];
      }

      var isBfrCharAnOperator = Object.keys(HIERARCHY_OPERATORS).includes(bfrChar);
      var isAfrCharAnOperator = Object.keys(HIERARCHY_OPERATORS).includes(afrChar)

      // in case operator is simply a whitespace and not the real operator, continue
      if (' ' === operator && (isBfrCharAnOperator || isAfrCharAnOperator)) {
        continue;
      }

      return [parts.slice(0, i - 1).join(''), HIERARCHY_OPERATORS[operator], parts.slice(i).join('')];
    }
  }

  return null;
};

/**
 * var parse - convert selector string into the selector's representation as an array
 *
 * @param  {string} str
 * @returns {array} array of Selectors
 */
var parse = function(str) {
  var arr = []; // will include all selector's components

  var selectors = str.split(','); // ',' sepperates between different general sub selectors
  for (var i in selectors) {
    var selector = selectors[i];

    if (!selector) {
      continue;
    }

    var obj = {};

    selector = string.trim(selector);
    obj['raw'] = selector.split(' ')[0]; // sub selector hierarchy components are separated by ' '

    obj['tag'] = getTag(selector) || '*';

    var ret = splitByHierarchy(selector);
    if (ret) {
      var bfrSelector = ret[0];
      var hierarchyKind = ret[1];
      var afrSelector = ret[2];

      obj[hierarchyKind] = parse(afrSelector)[0]; // parse the next hierarchy component

      // only the selector of before the operator occurence should be extracted for information
      selector = bfrSelector;
    }

    obj['ids'] = getIds(selector);
    obj['classes'] = getClasses(selector);
    obj['pseudoElements'] = getPseudoElements(selector);
    obj['pseudoClasses'] = getPseudoClasses(selector);

    obj['nots'] = [];
    var nots = getNots(obj['raw']);
    for (var j in nots) {
      obj['nots'][j] = parse(nots[j])[0]; // every :not includes another selector
    }

    obj['attributes'] = getAttributes(selector);

    arr[i] = new Selector(obj);
  }

  return arr;
};

/**
 * var stringify - convert selector's representation as an array into the raw selector as a string
 *
 * @param  {array} arr - array of Selectors
 * @returns {string}
 */
var stringify = function(arr) {
  var stringifyRecursive = function(arr) {
    var str = '';

    for (var i in arr) {
      var selector = arr[i];

      str += selector['raw'];

      for (var operator in HIERARCHY_OPERATORS) {
        var hir = HIERARCHY_OPERATORS[operator];

        if (selector[hir]) {
          str += (' ' + operator + ' ').replace('   ', ' '); // in case operator is whitespace
          str += stringify([selector[hir]]);
        }
      }

      str += ', ';
    }

    return str;
  };

  var str = stringifyRecursive(arr);
  if (', ' === str.slice(str.length - 2, str.length)) {
    str = str.slice(0, str.length - 2); // cut last redundent comma
  }
  return str;
};

/**
 * Selector - constructor of Selector type
 *
 * @param  {object} selectorObj
 * @returns {Selector}
 */
function Selector(selectorObj) {
  for (var prop in selectorObj) {
    this[prop] = selectorObj[prop];
  }

  this.stringify = stringify.bind(null, [selectorObj]);
  this.parse = parse.bind(null, selectorObj.raw);
};

module.exports._Selector = Selector;
module.exports.parse = parse;
module.exports.stringify = stringify;
<commit_msg>modules: selector: when calling stringify() reconstruct raw selector properly<commit_after>var string = require('../utils/string');

var  ATTRIBUTES_OPERATORS = {
  '' : 'present',
  '=' : 'equals',
  '*=' : 'contains',
  '^=' : 'beginsWith',
  '$=' : 'endsWith',
  '~=' : 'spaced',
  '|=' : 'hyphenated'
};

var HIERARCHY_OPERATORS = {
  ' ' : 'descendant',
  '>' : 'directChild',
  '~' : 'generalSibling',
  '+' : 'adjacentSibling'
};

var PSEUDOE_NOT_REGEX = /:not[^\)]*/g;
var NON_VAL_ATTRS_REGEX = /\[-?[_a-zA-Z]+[_a-zA-Z0-9-]*\]/g;
var WITH_VAL_ATTRS_REGEX = /\[-?[_a-zA-Z]+[_a-zA-Z0-9-]*[\^\*\$\~\|]*\=[\"\'].*[\"\']\]/g;
var ID_REGEX = /\#-?[_a-zA-Z]+[_a-zA-Z0-9-]*/g;
var CLASS_REGEX = /\.-?[_a-zA-Z]+[_a-zA-Z0-9-]*/g;
var PSEUDOE_CLASS_REGEX = /\:-?[_a-zA-Z]+[_a-zA-Z0-9-\(\+\)]*/g;
var PSEUDOE_ELEMENT_REGEX = /\:\:-?[_a-zA-Z]+[_a-zA-Z0-9-\(\+\)]*/g;

/**
 * var getRegexMatches - get string and regular expression and return matches
 *
 * @param  {string} str
 * @param  {RegExp} rgx
 * @param  {function} [onmatch] to run on each match
 * @returns {array}
 */
var getRegexMatches = function(str, rgx, onmatch) {
  rgx = new RegExp(rgx);

  onmatch = onmatch || function(match) {
    return match;
  };

  var matches = [];

  var res = rgx.exec(str);
  while (res) {
    res = onmatch(res[0]);
    matches.push(res);
    res = rgx.exec(str);
  }

  return matches;
};

/**
 * var getTag - get selector string and extract the tag from it
 *
 * @param  {string} selector
 * @returns {string}
 */
var getTag = function(selector) {
  return string.trim(selector.split('.')[0].split('#')[0].split(':')[0].split(' ')[0]) || null;
};

/**
 * var getIds - get selector string and extract the ids from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getIds = function(selector) {
  // following might include id selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  var matches = getRegexMatches(selector, ID_REGEX, function(match) {
    return match.substr(1, match.length); // get rid of '#'
  });

  return matches;
};

/**
 * var getClasses - get selector string and extract the classes from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getClasses = function(selector) {
  // following might include class selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  var matches = getRegexMatches(selector, CLASS_REGEX, function(match) {
    return match.substr(1, match.length); // get rid of '.'
  });

  return matches;
};

/**
 * var getPseudoElements - get selector string and extract the pseudo elements from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getPseudoElements = function(selector) {
  // following might include pseudo class selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  var matches = getRegexMatches(selector, PSEUDOE_ELEMENT_REGEX, function(match) {
    return match.substr(2, match.length); // get rid of '::'
  });

  return matches;
};

/**
 * var getPseudoClasses - get selector string and extract the pseudo classes from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getPseudoClasses = function(selector) {
  // following might include pseudo class selector regex as well. get rid of before matching
  selector = selector.replace(NON_VAL_ATTRS_REGEX, '');
  selector = selector.replace(WITH_VAL_ATTRS_REGEX, '');
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');
  selector = selector.replace(PSEUDOE_ELEMENT_REGEX, '');

  var matches = getRegexMatches(selector, PSEUDOE_CLASS_REGEX, function(match) {
    return match.substr(1, match.length); // get rid of ':'
  });

  return matches;
};

/**
 * var getNots - get selector string and extract the pseudos nots from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getNots = function(selector) {
  var matches = getRegexMatches(selector, PSEUDOE_NOT_REGEX, function(match) {
    return match.slice(5, match.length); // get rid of ':not('
  });

  if (!matches.length) {
    return null;
  }

  return matches;
};

/**
 * var getAttributes - get selector string and extract the attributes from it
 *
 * @param  {string} selector
 * @returns {array}
 */
var getAttributes = function(selector) {
  // following might include attributes selector regex as well. get rid of before matching
  selector = selector.replace(PSEUDOE_NOT_REGEX, '');

  // helps regex work correcly. TODO: REGEX SHOULD BE FIXED
  selector = selector.split('][').join(']\n[');

  var matches = getRegexMatches(selector, WITH_VAL_ATTRS_REGEX).concat(getRegexMatches(selector, NON_VAL_ATTRS_REGEX));

  var arr = []; // will include all attributes

  for (var i in matches) {
    var prop, val, behav, operator = '';
    var attr = matches[i].slice(1, matches[i].length - 1); // get rid of '[' and ']'
    var attrObj = {};

    attrObj['raw'] = attr;

    var eqSignPos = attr.indexOf('=');

    if (-1 === eqSignPos) {
      prop = attr;
      val = null;
      behav = ATTRIBUTES_OPERATORS[operator];

    } else {
      operator = attr.slice(eqSignPos - 1, eqSignPos + 1); // in case operator is like '{X}='

      if (!Object.keys(ATTRIBUTES_OPERATORS).includes(operator)) {
        operator = '=';
      }

      prop = attr.split(operator)[0];
      val = attr.substring(attr.indexOf(operator) + operator.length + 1, attr.length - 1);
      behav = ATTRIBUTES_OPERATORS[operator];
    }

    attrObj['property'] = prop;
    attrObj['value'] = val;
    attrObj['behaviour'] = behav;
    attrObj['operator'] = operator;

    arr[i] = attrObj;
  }

  return arr;
};

/**
 * var splitByHierarchy - get selector string, parse the next hierarchy in it, and return it
 *
 * @param  {string} selector
 * @returns {array} [selector before hierarchy operator occurence, kind of hierarchy, selector after hierarchy operator occurence]
 */
var splitByHierarchy = function(selector) {
  var parts = selector.split(/( )/); // split by whitespaces but include them in splitted arr

  for (var i = 0; i < parts.length; i++) {
    var bfrChar = parts[i - 1];
    var curChar = parts[i]; // possibly an operator
    var afrChar = parts[i + 1];

    if (Object.keys(HIERARCHY_OPERATORS).includes(curChar)) {
      var operator = curChar; // curChar is an operator

      // in case operator is not a whitespace, it must be between whitespaces inside array
      // this helps distinguish between regular whitespace and hierarchy operator whitespace
      if (' ' === bfrChar && ' ' === afrChar) {
        return [parts.slice(0, i + 1).join(''), HIERARCHY_OPERATORS[operator], parts.slice(i + 2).join('')];
      }

      var isBfrCharAnOperator = Object.keys(HIERARCHY_OPERATORS).includes(bfrChar);
      var isAfrCharAnOperator = Object.keys(HIERARCHY_OPERATORS).includes(afrChar)

      // in case operator is simply a whitespace and not the real operator, continue
      if (' ' === operator && (isBfrCharAnOperator || isAfrCharAnOperator)) {
        continue;
      }

      return [parts.slice(0, i - 1).join(''), HIERARCHY_OPERATORS[operator], parts.slice(i).join('')];
    }
  }

  return null;
};

/**
 * var parse - convert selector string into the selector's representation as an array
 *
 * @param  {string} str
 * @returns {array} array of Selectors
 */
var parse = function(str) {
  var arr = []; // will include all selector's components

  var selectors = str.split(','); // ',' sepperates between different general sub selectors
  for (var i in selectors) {
    var selector = selectors[i];

    if (!selector) {
      continue;
    }

    var obj = {};

    selector = string.trim(selector);
    obj['raw'] = selector.split(' ')[0]; // sub selector hierarchy components are separated by ' '

    obj['tag'] = getTag(selector) || '*';

    var ret = splitByHierarchy(selector);
    if (ret) {
      var bfrSelector = ret[0];
      var hierarchyKind = ret[1];
      var afrSelector = ret[2];

      obj[hierarchyKind] = parse(afrSelector)[0]; // parse the next hierarchy component

      // only the selector of before the operator occurence should be extracted for information
      selector = bfrSelector;
    }

    obj['ids'] = getIds(selector);
    obj['classes'] = getClasses(selector);
    obj['pseudoElements'] = getPseudoElements(selector);
    obj['pseudoClasses'] = getPseudoClasses(selector);

    obj['nots'] = [];
    var nots = getNots(obj['raw']);
    for (var j in nots) {
      obj['nots'][j] = parse(nots[j])[0]; // every :not includes another selector
    }

    obj['attributes'] = getAttributes(selector);

    arr[i] = new Selector(obj);
  }

  return arr;
};

/**
 * var stringify - convert selector's representation as an array into the raw selector as a string
 *
 * @param  {array} arr - array of Selectors
 * @returns {string}
 */
var stringify = function(arr) {
  var constructRawSelector = function(selector) {
    var str = '';

    str += getTag(selector['raw']) || '';

    for (var i in selector['ids']) {
      str += '#' + selector['ids'][i];
    }

    for (var i in selector['classes']) {
      str += '.' + selector['classes'][i];
    }

    for (var i in selector['pseudoClasses']) {
      str += ':' + selector['pseudoClasses'][i];
    }

    for (var i in selector['pseudoElements']) {
      str += '::' + selector['pseudoElements'][i];
    }

    for (var i in selector['attributes']) {
      str += '[' + selector['attributes'][i]['raw'] + ']';
    }

    for (var i in selector['nots']) {
      str += ':not(' + constructRawSelector(selector['nots'][i]) + ')';
    }

    return str;
  };

  var stringifyRecursive = function(arr) {
    var str = '';

    for (var i in arr) {
      var selector = arr[i];

      str += constructRawSelector(selector);

      for (var operator in HIERARCHY_OPERATORS) {
        var hir = HIERARCHY_OPERATORS[operator];

        if (selector[hir]) {
          str += (' ' + operator + ' ').replace('   ', ' '); // in case operator is whitespace
          str += stringify([selector[hir]]);
        }
      }

      str += ', ';
    }

    return str;
  };

  var str = stringifyRecursive(arr);
  if (', ' === str.slice(str.length - 2, str.length)) {
    str = str.slice(0, str.length - 2); // cut last redundent comma
  }
  return str;
};

/**
 * Selector - constructor of Selector type
 *
 * @param  {object} selectorObj
 * @returns {Selector}
 */
function Selector(selectorObj) {
  for (var prop in selectorObj) {
    this[prop] = selectorObj[prop];
  }

  this.stringify = stringify.bind(null, [selectorObj]);
  this.parse = parse.bind(null, selectorObj.raw);
};

module.exports._Selector = Selector;
module.exports.parse = parse;
module.exports.stringify = stringify;
<|endoftext|>