426 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			426 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
import {Check} from "../Utils/Check.ecma.js";
 | 
						|
import {RandomFormat} from "./Format/RandomFormat.ecma.js";
 | 
						|
import {RangeFormat} from "./Format/RangeFormat.ecma.js";
 | 
						|
import {MixFormat} from "./Format/MixFormat.ecma.js";
 | 
						|
import {SerieFormat} from "./Format/SerieFormat.ecma.js";
 | 
						|
import {SelectFormat} from "./Format/SelectFormat.ecma.js";
 | 
						|
import {PlainFormat} from "./Format/PlainFormat.ecma.js";
 | 
						|
import {CapitalizeFormat} from "./Format/CapitalizeFormat.ecma.js";
 | 
						|
 | 
						|
/**
 | 
						|
 * @typedef {import("../OpoTests.ecma.js").OpoTests} OpoTests
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @class FormatModule
 | 
						|
 * @constructor
 | 
						|
 * @param {!OpoTests} ot 
 | 
						|
 * @returns {void}
 | 
						|
 * @access public
 | 
						|
 * @static
 | 
						|
 */
 | 
						|
export const FormatModule = (function(){
 | 
						|
 | 
						|
    /**
 | 
						|
     * @callback format_recursive_callback
 | 
						|
     * @param {?any} data
 | 
						|
     * @return {?any}
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * @callback format_mode_callback
 | 
						|
     * @param {!(string|Array.<any|null>)} inputs 
 | 
						|
     * @param {!Object.<string, any|null>} [shared = {}] 
 | 
						|
     * @param {!Array.<string>} [fragments = []] 
 | 
						|
     * @return {string}
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * @callback format_check_callback
 | 
						|
     * @param {!string} string 
 | 
						|
     * @param {!(string|Array.<string>)} inputs 
 | 
						|
     * @param {!Object.<string, any|null>} [shared = {}] 
 | 
						|
     * @param {!Array.<string>} [fragments = []] 
 | 
						|
     * @return {number}
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * @constructs FormatModule
 | 
						|
     * @param {!OpoTests} ot 
 | 
						|
     * @returns {void}
 | 
						|
     * @access private
 | 
						|
     * @static
 | 
						|
     */
 | 
						|
    const FormatModule = function(ot){
 | 
						|
 | 
						|
        /** @type {FormatModule} */
 | 
						|
        const self = this;
 | 
						|
 | 
						|
        /** @type {Object.<string, format_mode_callback>} */
 | 
						|
        this.modes = {};
 | 
						|
        /** @type {Object.<string, format_check_callback>} */
 | 
						|
        this.checks = {};
 | 
						|
 | 
						|
        /** @type {RandomFormat} */
 | 
						|
        this.random = new RandomFormat(self);
 | 
						|
        /** @type {RangeFormat} */
 | 
						|
        this.range = new RangeFormat(self);
 | 
						|
        /** @type {MixFormat} */
 | 
						|
        this.mix = new MixFormat(self);
 | 
						|
        /** @type {SerieFormat} */
 | 
						|
        this.serie = new SerieFormat(self);
 | 
						|
        /** @type {SelectFormat} */
 | 
						|
        this.select = new SelectFormat(self);
 | 
						|
        /** @type {PlainFormat} */
 | 
						|
        this.plain = new PlainFormat(self);
 | 
						|
        /** @type {CapitalizeFormat} */
 | 
						|
        this.capitalize = new CapitalizeFormat(self);
 | 
						|
 | 
						|
        /**
 | 
						|
         * @returns {void}
 | 
						|
         * @access private
 | 
						|
         */
 | 
						|
        const constructor = () => {};
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {!string} string
 | 
						|
         * @param {!(string|Array.<string>)} options
 | 
						|
         * @param {!Object.<string, any|null>} [shared = {}]
 | 
						|
         * @param {!Array.<string>} [fragments = []]
 | 
						|
         * @param {!boolean} [check_full = true]
 | 
						|
         * @returns {number}
 | 
						|
         * @access public
 | 
						|
         */
 | 
						|
        this.get_check_length = (string, options, shared = {}, fragments = [], check_full = true) => {
 | 
						|
 | 
						|
            /** @type {[boolean, number]} */
 | 
						|
            const [has, length] = self.check(string, options, shared, fragments, check_full);
 | 
						|
 | 
						|
            return has ? length : -1;
 | 
						|
        };
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {!string} string
 | 
						|
         * @param {!(string|Array.<string>)} options
 | 
						|
         * @param {!Object.<string, any|null>} [shared = {}]
 | 
						|
         * @param {!Array.<string>} [fragments = []]
 | 
						|
         * @param {!boolean} [check_full = true]
 | 
						|
         * @returns {[boolean, number]}
 | 
						|
         * @access public
 | 
						|
         */
 | 
						|
        this.check = (string, options, shared = {}, fragments = [], check_full = true) => {
 | 
						|
 | 
						|
            /** @type {number} */
 | 
						|
            let length = 0;
 | 
						|
 | 
						|
            string = string.toLowerCase();
 | 
						|
 | 
						|
            return [options.some(option => {
 | 
						|
 | 
						|
                /** @type {string} */
 | 
						|
                let clone = "" + string;
 | 
						|
                /** @type {Array.<string>} */
 | 
						|
                const parts = [];
 | 
						|
 | 
						|
                self.build_fragments(option, fragments).replace(/\{[^\{\}]+\}|[^\{]+|\{/g, part => {
 | 
						|
                    parts.push(
 | 
						|
                        /^\{.+\}$/.test(part) ? part : 
 | 
						|
                    part.toLowerCase());
 | 
						|
                    return "";
 | 
						|
                });
 | 
						|
                length = 0;
 | 
						|
 | 
						|
                return parts.every((part, i) => {
 | 
						|
 | 
						|
                    /** @type {number} */
 | 
						|
                    let j = -1;
 | 
						|
                    /** @type {RegExpMatchArray} */
 | 
						|
                    const matches = part.match(/^\{([^\{\}]+)\}$/);
 | 
						|
 | 
						|
                    if(matches){
 | 
						|
 | 
						|
                        /** @type {string} */
 | 
						|
                        const key = matches[1];
 | 
						|
                        
 | 
						|
                        if(/^[a-z0-9_]+$/i.test(key)){
 | 
						|
                            if(key in ot.variables){
 | 
						|
 | 
						|
                                /** @type {[string, ...(any|null)]} */
 | 
						|
                                const [method, ...data] = ot.variables[key];
 | 
						|
 | 
						|
                                method in self.checks && 
 | 
						|
                                // self.checks[method](clone, data, shared, fragments) != -1 && 
 | 
						|
                                (j = self.checks[method](clone, data, shared, fragments));
 | 
						|
 | 
						|
                            }else if(parts[i + 1]){
 | 
						|
                                // TEMPORARY SOLUTION. NEEDS BUILD REAL SOLUTION.
 | 
						|
                                j = clone.indexOf(parts[i + 1]);
 | 
						|
                            };
 | 
						|
                        }else{
 | 
						|
 | 
						|
                            /** @type {RegExpMatchArray} */
 | 
						|
                            const matches = key.match(/^([^,:]+)[,:](.*)$/);
 | 
						|
 | 
						|
                            if(matches){
 | 
						|
 | 
						|
                                /** @type {[string, string]} */
 | 
						|
                                const [method, data] = matches.slice(1, 3);
 | 
						|
 | 
						|
                                method in self.checks && 
 | 
						|
                                (j = self.checks[method](clone, data, shared, fragments));
 | 
						|
 | 
						|
                            };
 | 
						|
                        };
 | 
						|
                    };
 | 
						|
                    j == -1 && (j = FormatModule.prepare_result(clone, part, shared));
 | 
						|
 | 
						|
                    if(j != -1){
 | 
						|
                        clone = clone.slice(j);
 | 
						|
                        length += j;
 | 
						|
                        return true;
 | 
						|
                    };
 | 
						|
                    return false;
 | 
						|
                }) && (!check_full || !clone.length);
 | 
						|
            }), length];
 | 
						|
        };
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {!string} string 
 | 
						|
         * @param {!Array.<string>} fragments 
 | 
						|
         * @returns {string}
 | 
						|
         * @access public
 | 
						|
         */
 | 
						|
        this.set_fragments_level = (string, fragments) => {
 | 
						|
            return (
 | 
						|
                !fragments.length ? string : 
 | 
						|
            string.replace(/\${3}([0-9]+)\${3}/g, (_, i) => fragments[Number(i)]));
 | 
						|
        };
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {!string} string 
 | 
						|
         * @param {!Array.<string>} [fragments = []]
 | 
						|
         * @returns {string}
 | 
						|
         * @access public
 | 
						|
         */
 | 
						|
        this.build_fragments = (string, fragments = []) => (
 | 
						|
            self.set_fragments_level(FormatModule.recursive(string, string => (
 | 
						|
                string.replace(/\{[^\{\}]+\}/g, fragment => {
 | 
						|
                    fragments.push(fragment);
 | 
						|
                    return "$$$" + (fragments.length - 1) + "$$$";
 | 
						|
                })
 | 
						|
            )), fragments)
 | 
						|
        );
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {!string} string 
 | 
						|
         * @param {!Object.<string, any|null>} [shared = {}]
 | 
						|
         * @param {!Array.<string>} [fragments = []]
 | 
						|
         * @returns {string}
 | 
						|
         * @access public
 | 
						|
         */
 | 
						|
        this.execute = (string, shared = {}, fragments = []) => (
 | 
						|
            FormatModule.recursive(self.build_fragments(string, fragments), substring => {
 | 
						|
                try{
 | 
						|
                    return substring.replace(/\{([^\{\}]+)\}/g, (original, key) => {
 | 
						|
                        if(/^[a-z0-9_]+$/i.test(key)){
 | 
						|
                            if(key in shared)
 | 
						|
                                return shared[key];
 | 
						|
                            else if(key in ot.variables){
 | 
						|
 | 
						|
                                /** @type {[string, ...(any|null)]} */
 | 
						|
                                const [method, ...data] = ot.variables[key];
 | 
						|
 | 
						|
                                if(method in self.modes)
 | 
						|
                                    return self.modes[method](data, shared, fragments);
 | 
						|
                            };
 | 
						|
                        }else{
 | 
						|
 | 
						|
                            /** @type {RegExpMatchArray} */
 | 
						|
                            const matches = key.match(/^([^,:]+)[,:](.*)$/);
 | 
						|
 | 
						|
                            if(matches){
 | 
						|
 | 
						|
                                /** @type {[string, string]} */
 | 
						|
                                const [method, data] = matches.slice(1, 3);
 | 
						|
 | 
						|
                                if(method in self.modes)
 | 
						|
                                    return self.modes[method](data, shared, fragments);
 | 
						|
                            };
 | 
						|
                        };
 | 
						|
                        return original
 | 
						|
                    });
 | 
						|
                }catch(exception){
 | 
						|
                    console.error([exception, fragments, string, substring]);
 | 
						|
                };
 | 
						|
                return string;
 | 
						|
            })
 | 
						|
        );
 | 
						|
 | 
						|
        /**
 | 
						|
         * @param {!string} string 
 | 
						|
         * @param {[[number, number], string, Array.<string>]} parameters 
 | 
						|
         * @param {!Object.<string, any|null>} [shared = {}]
 | 
						|
         * @param {!Array.<string>} [fragments = []]
 | 
						|
         * @returns {number}
 | 
						|
         * @access public
 | 
						|
         */
 | 
						|
        this.check_select = (string, [[minimum, maximum], separator, items], shared = {}, fragments = []) => {
 | 
						|
 | 
						|
            /** @type {number} */
 | 
						|
            let length = 0, 
 | 
						|
                /** @type {number} */
 | 
						|
                k = 0, 
 | 
						|
                check_last = false;
 | 
						|
 | 
						|
            for(; k < maximum; k ++){
 | 
						|
 | 
						|
                /** @type {boolean} */
 | 
						|
                let ok = false;
 | 
						|
                /** @type {number} */
 | 
						|
                const l = items.length;
 | 
						|
 | 
						|
                if(check_last || (k && k == l - 1)){
 | 
						|
 | 
						|
                    if(!string.startsWith(" " + separator + " "))
 | 
						|
                        return -1;
 | 
						|
 | 
						|
                    length += separator.length + 2;
 | 
						|
                    string = string.substring(0, separator.length + 2);
 | 
						|
 | 
						|
                };
 | 
						|
 | 
						|
                for(let i = 0; i < l; i ++){
 | 
						|
 | 
						|
                    /** @type {number} */
 | 
						|
                    const item_length = (
 | 
						|
                        /\{[^\{\}]+\}/.test(self.set_fragments_level(items[i], shared, fragments)) ? self.get_check_length(string, [items[i]], shared, fragments, false) : 
 | 
						|
                    FormatModule.prepare_result(string, items[i], shared));
 | 
						|
 | 
						|
                    if(item_length != -1){
 | 
						|
                        string = string.substring(0, item_length);
 | 
						|
                        items.splice(i, 1);
 | 
						|
                        length += item_length;
 | 
						|
                        ok = true;
 | 
						|
                        break;
 | 
						|
                    };
 | 
						|
 | 
						|
                };
 | 
						|
 | 
						|
                if(ok)
 | 
						|
                    shared.capitalized && (shared.capitalized = false);
 | 
						|
                else if(check_last || !k)
 | 
						|
                    return -1;
 | 
						|
                else{
 | 
						|
                    check_last = true;
 | 
						|
                    k --;
 | 
						|
                };
 | 
						|
 | 
						|
            };
 | 
						|
 | 
						|
            return k < minimum ? -1 : length;
 | 
						|
        };
 | 
						|
 | 
						|
        constructor();
 | 
						|
 | 
						|
    };
 | 
						|
 | 
						|
    /** @type {RegExp} */
 | 
						|
    FormatModule.PATTERN = new RegExp("9876543210[".split("").reduce((pattern, character) => {
 | 
						|
 | 
						|
        /** @type {[string, string]} */
 | 
						|
        const [start, end] = character != "[" ? ["\\[" + character, character + "\\]"] : ["\\[{2}", "\\]{2}"];
 | 
						|
 | 
						|
        return (pattern ? pattern + "|" : "") + start + "((?:(?!(?:" + start + "|" + end + ")).)+)" + end;
 | 
						|
    }, ""), "g");
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {?any} data 
 | 
						|
     * @param {!format_recursive_callback} callback 
 | 
						|
     * @returns {any|null}
 | 
						|
     * @access public
 | 
						|
     * @static
 | 
						|
     */
 | 
						|
    FormatModule.recursive = (data, callback) => {
 | 
						|
 | 
						|
        /** @type {any|null} */
 | 
						|
        let cache = data;
 | 
						|
        const x = data;
 | 
						|
 | 
						|
        // while((cache = callback(data = cache)) != data);
 | 
						|
        do{
 | 
						|
            cache === undefined && console.warn([x, cache]);
 | 
						|
            cache = callback(data = cache);
 | 
						|
        } while(cache != data);
 | 
						|
 | 
						|
        return data;
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {!string} string 
 | 
						|
     * @returns {string}
 | 
						|
     * @access public
 | 
						|
     * @static
 | 
						|
     */
 | 
						|
    FormatModule.update = string => FormatModule.recursive(string, string => string.replace(FormatModule.PATTERN, (original, ...data) => {
 | 
						|
        for(let i = 0; i < data.length; i ++)
 | 
						|
            if(data[i])
 | 
						|
                return "{" + data[i].replace(/:{3}/g, "|") + "}";
 | 
						|
        return original;
 | 
						|
    }));
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {!string} string 
 | 
						|
     * @param {!string} option 
 | 
						|
     * @param {!object.<string, any|null>} shared 
 | 
						|
     * @returns {string}
 | 
						|
     * @access public
 | 
						|
     * @static
 | 
						|
     */
 | 
						|
    FormatModule.prepare_result = (string, option, shared) => {
 | 
						|
        if(string.toLowerCase().startsWith(option.toLowerCase())){
 | 
						|
            shared.capitalized && (shared.capitalized = false);
 | 
						|
            return option.length;
 | 
						|
        };
 | 
						|
        return -1;
 | 
						|
    };
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param {!string} string 
 | 
						|
     * @param {!(string|Array.<any|null>)} inputs 
 | 
						|
     * @returns {number}
 | 
						|
     * @access public
 | 
						|
     * @static
 | 
						|
     */
 | 
						|
    FormatModule.check_range = (string, inputs) => {
 | 
						|
 | 
						|
        /** @type {RegExpMatchArray} */
 | 
						|
        let number_match = string.match(/^[0-9]+/);
 | 
						|
 | 
						|
        if(number_match){
 | 
						|
 | 
						|
            /** @type {number} */
 | 
						|
            const number = Number(number_match[0]);
 | 
						|
 | 
						|
            for(const range of (
 | 
						|
                Check.is_string(inputs) ? Utils.get_random(inputs.split("|")).split("-").map(Number) : 
 | 
						|
                Check.is_array(inputs) ? inputs : 
 | 
						|
            [])){
 | 
						|
                if(Check.is_number(range)){
 | 
						|
                    if(number == range)
 | 
						|
                        return ("" + number).length;
 | 
						|
                }else if(range.length == 1){
 | 
						|
                    if(number == range[0])
 | 
						|
                        return ("" + number).length;
 | 
						|
                }else{
 | 
						|
                    if(number >= range[0] && number <= range[1])
 | 
						|
                        return ("" + number).length;
 | 
						|
                };
 | 
						|
            };
 | 
						|
        };
 | 
						|
        return -1;
 | 
						|
    };
 | 
						|
 | 
						|
    return FormatModule;
 | 
						|
})(); |