//Import Node.js libraries
global.fs = require("fs");
global.path = require("path");
//Initialise functions
{
if (!global.ve)
/**
* The root namespace for all Vercengen classes and options.
*
* @namespace ve
*/
global.ve = {};
ve.debug_mode = false;
/**
* Returns all non-evaluated files in a folder, so long as an evaluated set is provided.
*
* @param {string} arg0_folder_path
* @param {Set<string>} arg1_evaluated_set
*
* @returns {Array<string>}
*/
ve.getFilesInFolder = function (arg0_folder_path, arg1_evaluated_set) {
//Convert from parameters
let folder_path = arg0_folder_path;
let evaluated_set = arg1_evaluated_set;
//Declare local instance variables
let file_list = fs.readdirSync(folder_path, { withFileTypes: true });
let return_files = [];
//Iterate over all entries in file_list
for (let local_file_entry of file_list) {
let full_path = path.join(folder_path, local_file_entry.name);
//Skip any full_path in the evaluated_set
if (evaluated_set.has(full_path)) continue;
evaluated_set.add(full_path);
//Recursively import files from directories
if (local_file_entry.isDirectory()) {
return_files = return_files.concat(ve.getFilesInFolder(full_path, evaluated_set));
} else {
return_files.push(full_path);
}
}
//Return statement
return return_files;
};
/**
* Returns an Array<String> from a list of patterns. The last pattern
* to match a file determines its final position in the load order. This
* function iterates patterns in reverse to ensure that more specific
* patterns listed later correctly claim files from broader patterns
* listed earlier.
*
* @param {Array<string>} patterns The list of patterns to resolve.
* @returns {Array<string>} The final, ordered list of file paths.
*/
ve.getImportFiles = function (patterns) { //[WIP] - Refactor from AI
const base = process.cwd();
const finalFiles = [];
const handledPaths = new Set(); // Tracks files that have already been placed.
const excludedPaths = new Set(); // Tracks files explicitly excluded by `!`.
// Process patterns in reverse order (from last to first).
// This is the key to ensuring the "last match wins" rule.
for (let i = patterns.length - 1; i >= 0; i--) {
let pattern = patterns[i];
const isExclusion = pattern.startsWith("!");
if (isExclusion) {
pattern = pattern.slice(1);
}
let files = [];
if (pattern.includes("*")) {
files = ve.getWildcardsInFolder(base, pattern);
} else {
const absolutePath = path.resolve(base, pattern);
if (fs.existsSync(absolutePath)) {
// Use your original, RECURSIVE function for directories.
if (fs.statSync(absolutePath).isDirectory()) {
files = ve.getFilesInFolder(absolutePath, new Set());
} else {
files = [absolutePath]; // It's a single file.
}
}
// Silently ignore patterns that don't exist.
}
// Since we're iterating backwards, we prepend files to maintain order.
// We also process the files found by a pattern in reverse to counteract
// the unshift, keeping the original readdir order.
for (let j = files.length - 1; j >= 0; j--) {
const file = files[j];
if (isExclusion) {
excludedPaths.add(file);
} else {
// A file is only added if it hasn't been claimed by a later,
// higher-priority pattern already.
if (!handledPaths.has(file)) {
finalFiles.unshift(file);
handledPaths.add(file);
}
}
}
}
// Final pass: filter out any files that were explicitly excluded.
return finalFiles.filter(file => !excludedPaths.has(file));
};
/**
* Returns the absolute file paths of all wildcards within a given folder.
*
* @param {string} arg0_folder_path
* @param {string} arg1_wildcard_pattern
*
* @returns {Array<string>}
*/
ve.getWildcardsInFolder = function (arg0_folder_path, arg1_wildcard_pattern) {
//Convert from parameters
let folder_path = arg0_folder_path;
let wildcard_pattern = arg1_wildcard_pattern;
//Declare local instance variables
let base = path.basename(wildcard_pattern);
let directory = path.dirname(wildcard_pattern);
//Non-wildcard resolution
if (!base.includes("*")) {
//No wildcard, simply return if the file exists
let absolute_path = path.resolve(folder_path, wildcard_pattern);
//Return statement
if (fs.existsSync(absolute_path) && fs.statSync(absolute_path).isFile())
return [absolute_path];
if (fs.existsSync(absolute_path) && fs.statSync(absolute_path).isDirectory())
return ve.getFilesInFolder(absolute_path, new Set());
return [];
}
//Wildcard: match files in the directory
let absolute_dir = path.resolve(folder_path, directory);
//Return statement
if (!fs.existsSync(absolute_dir) || !fs.statSync(absolute_dir).isDirectory())
return [];
//Return regex wildcard; return statement
let regex = new RegExp("^" +
base.replace(/\./g, "\\.")
.replace(/\*/g, ".*") + "$"
);
//Return statement
return fs.readdirSync(absolute_dir)
.filter((f) => regex.test(f))
.map((f) => path.join(absolute_dir, f))
.filter((f) => fs.statSync(f).isFile());
};
/**
* Initialises Vercengen and associated UF files.
*/
ve.initialise = function () {
//Initialise UF handlers
new DALS.Timeline(); //Initialise starting timeline
HTML.initialise();
ve.scene_el = document.createElement("div");
ve.scene_el.id = "ve-scene";
ve.scene_el.setAttribute("class", "ve scene");
document.body.appendChild(ve.scene_el);
ve.window_overlay_el = document.createElement("div");
ve.window_overlay_el.id = "ve-overlay";
ve.window_overlay_el.setAttribute("class", "ve overlay");
document.body.appendChild(ve.window_overlay_el);
setTimeout(() => {
ve.Component.linter(); //Lint ve.Component library
}, 100);
};
/**
* Initialises a Vercengen app, alongside necessary UF imports.
*
* arg0_options: {@link Object}
* - `.do_not_import_UF`: {@link boolean} - Whether to refuse UF imports unrelated to Vercengen startup functions.
* - `.load_files`: {@link Array}<{@link string}> - The sequence of files to load. `!` should be used as an exclusion prefix, whilst `*` functions as a wildcard pattern.
* - `.is_browser=true`: {@link boolean} - Whether the imports are for the Browser/Electron. Imports are assumed to be eval/Node.js otherwise.
* - `.is_node=false`: {@link boolean} - Whether the imports are for Node.js. Overridden by `.is_browser`. Inputs are assumed to be for eval if false.
* - `.special_function`: {@link function} - The function to execute upon startup and Vercengen initialisation.
*
* @returns Array<string>
*/
ve.start = function (arg0_options) { //[WIP] - Move Browser/Node/Eval selection to `.mode` optioning.
//Convert from parameters
let options = (arg0_options) ? arg0_options : {};
//Initialise options
if (options.is_browser === undefined) options.is_browser = true;
if (options.load_files === undefined) options.load_files = [];
//Declare local instance variables
let load_patterns = (!options.do_not_import_UF) ? [
"!UF/archives",
//"UF/js/vercengen/(vercengen_initialisation).js",
"UF",
"UF/libraries",
"UF/libraries/maptalks.min.js",
"UF/libraries/mapbox-gl.js",
"UF/libraries/mapbox-gl.css",
"UF/libraries/maptalks.mapboxgl.min.js",
"UF/js/dals/Timeline.js",
"UF/js/dals/Timeline_state.js",
"UF/js/vercengen/engine",
"UF/js/vercengen/engine/Demo.js",
"UF/js/vercengen/components",
"UF/js/vercengen/components/ComponentFileExplorer/file_operations_ui.js",
"UF/js/vercengen/features"
] : [];
load_patterns = load_patterns.concat(options.load_files);
let load_files = ve.getImportFiles(load_patterns);
console.log(`[VERCENGEN] Importing ${load_files.length} files.`);
//1. Handle browser <link>/<script> tags
if (options.is_browser) { //[WIP] - Refactor at a later date
// Build up the full HTML snippet for all files in order
let html_concat = "";
for (let i = 0; i < load_files.length; i++) {
const local_file_path = load_files[i];
const local_file_extension = path.extname(local_file_path).toLowerCase();
// Each file becomes HTML markup in correct order
if (local_file_extension === ".css") {
html_concat += `<link rel="stylesheet" type="text/css" href="${local_file_path}">`;
} else if (local_file_extension === ".js") {
// close the script tag safely
html_concat += `<script type="text/javascript" src="${local_file_path}"></` + `script>`;
}
}
// Inject all tags via HTML concatenation
//
// document.write() integrates the tags into the parsing process.
// This ensures that <script> blocks execute *in exact given order*,
// per HTML parser rules.
//
// This must run during document loading (not after DOM is complete).
injectConcatenatedHTML(html_concat);
}
//2. Handle eval/Node.js require tags
if (!options.is_browser) {
for (let i = 0; i < load_files.length; i++) {
let local_file_extension = path.extname(load_files[i]);
let local_file_path = load_files[i];
if (local_file_extension === ".js")
if (options.is_node) {
const local_library = require(local_file_path);
//Destructure Node.js objects into global
for (let [key, value] of Object.entries(local_library)) {
if (global[key]) {
console.error(`${key} is already a defined function namespace; ${local_file_path} is attempting an override. The present function is as follows:`, global[key]);
continue;
}
global[key] = value;
}
} else {
eval(fs.readFileSync(local_file_path, "utf8"));
}
}
}
//Initialise ve after import
global.initialise_ve_loop = setInterval(function(){
try {
ve.initialise();
clearInterval(global.initialise_ve_loop);
if (options.special_function)
options.special_function();
} catch (e) {}
}, 100);
//Return statement
return load_files;
};
}
//[WIP] - Refactor later
function injectConcatenatedHTML(htmlMarkup) {
const tempContainer = document.createElement("div");
tempContainer.innerHTML = htmlMarkup;
const head = document.head || document.getElementsByTagName("head")[0];
const body = document.body || document.getElementsByTagName("body")[0];
const fragment = document.createDocumentFragment();
[...tempContainer.children].forEach((el) => {
if (el.tagName === "SCRIPT") {
const script = document.createElement("script");
script.src = el.getAttribute("src");
script.type = el.type || "text/javascript";
script.async = false; // preserves order!
fragment.appendChild(script);
} else if (el.tagName === "LINK") {
fragment.appendChild(el);
}
});
body.appendChild(fragment);
}