Source: utils/url.js

/**
 * A class holding the details of a URL.
 * @alias qui.utils.URL
 */
class URL {

    static VALID_REGEX = new RegExp(
        '^(?:(?:http|https)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d' +
        '|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d' +
        '|2[0-4]\\d|25[0-4]))|(?:(?:[a-z_\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)' +
        '(?:\\.(?:[a-z_\\u00a1-\\uffff0-9]+-?)*[a-z_\\u00a1-\\uffff0-9]+)*(?:\\.?' +
        '(?:[a-z_\\u00a1-\\uffff]*)))|localhost)(?::\\d{1,5})?(?:(/|\\?|#)[^\\s]*)?$',
        'i'
    )


    /**
     * @constructs
     * @param {String} scheme
     * @param {String} [username]
     * @param {String} [password]
     * @param {String} host
     * @param {Number} [port]
     * @param {String} [path]
     * @param {Object} [query]
     * @param {String} [fragment]
     */
    constructor({
        scheme,
        username = '',
        password = '',
        host,
        port = null,
        path = '',
        query = {},
        fragment = ''
    }) {
        this.scheme = scheme
        this.username = username
        this.password = password
        this.host = host
        this.port = port
        this.path = path
        this.query = query
        this.fragment = fragment
    }

    /**
     * Serialize the query and returns it as a string.
     * @returns {String}
     */
    get queryStr() {
        return Object.entries(this.query)
                     .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
                     .join('&')
    }

    /**
     * Compose the string representation of this URL.
     * @returns {String}
     */
    toString() {
        /* Scheme */
        let url = `${this.scheme}://`

        /* Credentials */
        if (this.username) {
            url += this.username
            if (this.password) {
                url += `:${this.password}`
            }

            url += '@'
        }

        /* Host */
        url += this.host

        /* Port */
        let port = this.port
        if ((this.scheme === 'http' && this.port === 80) || (this.scheme === 'https' && this.port === 443)) {
            port = null
        }
        if (port != null) {
            url += `:${port}`
        }

        /* Path */
        let path = this.path
        if (path && !path.startsWith('/')) {
            path = `/${path}`
        }
        url += path

        /* Query */
        if (Object.keys(this.query).length > 0) {
            url += `?${this.queryStr}`
        }

        /* Fragment */
        if (this.fragment) {
            url += `#${this.fragment}`
        }

        return url
    }

    /**
     * Return all the attributes of this URL as a dictionary.
     * @returns {Object}
     */
    toAttributes() {
        return {
            scheme: this.scheme,
            username: this.username,
            password: this.password,
            host: this.host,
            port: this.port,
            path: this.path,
            query: this.query,
            fragment: this.fragment
        }
    }

    /**
     * Alter one or more attributes of this URL, returning the new URL.
     * @param {Object} attributes
     * @returns {qui.utils.URL}
     */
    alter(attributes) {
        let oldAttributes = this.toAttributes()
        Object.assign(oldAttributes, attributes)

        return new this.constructor(oldAttributes)
    }

    /**
     * Parse a string representation of a URL and returns a URL object.
     * @param {String} urlStr
     * @returns {qui.utils.URL}
     */
    static parse(urlStr = '') {
        let a = document.createElement('a')
        a.href = urlStr

        let params = {
            scheme: a.protocol.replace(':', ''),
            host: a.hostname,
            path: a.pathname,
            port: a.port ? parseInt(a.port) : null,
            username: a.username,
            password: a.password,
            query: {}
        }

        if (a.search) {
            a.search.substring(1).split('&').forEach(function (s) {
                let parts = s.split('=')
                let name = parts[0]
                let value = parts.slice(1).join('=')

                params.query[decodeURIComponent(name)] = decodeURIComponent(value)
            })
        }

        if (a.hash) {
            params.fragment = a.hash.substring(1)
        }

        return new this(params)
    }

    /**
     * Transform the string representation of a partial URL into a full URL.
     * @param {String} urlStr
     * @returns {String}
     */
    static qualify(urlStr) {
        let a = document.createElement('a')
        a.href = urlStr
        return a.href
    }

}


export default URL