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;
|
|
})(); |