aboutsummaryrefslogtreecommitdiff
/**
 * This file is part of Haketilo.
 *
 * Function: Operations on resources and mappings.
 *
 * Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute forms of that code without the copy of the GNU
 * GPL normally required by section 4, provided you include this
 * license notice and, in case of non-source distribution, a URL
 * through which recipients can access the Corresponding Source.
 * If you modify file(s) with this exception, you may extend this
 * exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * As a special exception to the GPL, any HTML file which merely
 * makes function calls to this code, and for that purpose
 * includes it by reference shall be deemed a separate work for
 * copyright law purposes. If you modify this code, you may extend
 * this exception to your version of the code, but you are not
 * obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
 * license. Although I request that you do not make use of this code in a
 * proprietary program, I am not going to enforce this in court.
 */

/*
 * Convert ver_str into an array representation, e.g. for ver_str="4.6.13.0"
 * return [4, 6, 13, 0].
 */
const parse_version = ver_str => ver_str.split(".").map(n => parseInt(n));

/*
 * ver is an array of integers. rev is an optional integer. Produce string
 * representation of version (optionally with revision number), like:
 *     1.2.3-5
 * No version normalization is performed.
 */
const version_string = (ver, rev=0) => ver.join(".") + (rev ? `-${rev}` : "");
#EXPORT version_string

/*
 * This function overloads on the number of arguments. If one argument is
 * passed, it is an item definition (it need not be complete, only identifier,
 * version and, if applicable, revision properties are relevant). If two or
 * three arguments are given, they are in order: item identifier, item version
 * and item revision.
 * Returned is a string identifying this version of item.
 */
function item_id_string(...args) {
    let def = args[0]
    if (args.length > 1)
	def = {identifier: args[0], version: args[1], revision: args[2]};
    return !Array.isArray(def.version) ? def.identifier :
	`${def.identifier}-${version_string(def.version, def.revision)}`;
}
#EXPORT item_id_string

/* vers should be an array of comparable values. Return the greatest one. */
const max = vals => vals.reduce((v1, v2) => v1 > v2 ? v1 : v2);

/*
 * versioned_item should be a dict with keys being version strings and values
 * being definitions of the respective versions of a single resource/mapping.
 * Example:
 *     {
 *         "1": {
 *             version: [1]//,
 *             // more stuff
 *         },
 *         "1.1": {
 *             version: [1, 1]//,
 *             // more stuff
 *         }
 *      }
 *
 * Returns the definition with the highest version.
 */
function get_newest_version(versioned_item)
{
    const best_ver = max(Object.keys(versioned_item).map(parse_version));
    return versioned_item[version_string(best_ver)];
}
#EXPORT get_newest_version AS get_newest

/*
 * item is a definition of a resource or mapping. Yield all file references
 * (objects with `file` and `sha256` properties) this definition has.
 */
function* get_used_files(item)
{
    for (const file of item.source_copyright)
	yield file;

    if (item.type === "resource") {
	for (const file of item.scripts || [])
	    yield file;
    }
}
#EXPORT get_used_files AS get_files

/*
 * Function to parse URIs like:
 *     https://hydrilla.koszko.org/schemas/api_mapping_description-2.schema.json
 */
const name_base_re    = "([^/]*)";
const major_number_re = "([1-9][0-9]*)";
const minor_number_re = "(?:[1-9][0-9]*|0)";
const numbers_rest_re = `(?:\\.${minor_number_re})*`;
const version_re      = `(${major_number_re}${numbers_rest_re})`;
const schema_name_re  = `${name_base_re}-${version_re}\\.schema\\.json`;

const schema_name_regex = new RegExp(schema_name_re);

const schema_name_parts = ["full", "name_base", "version", "major"];

function parse_schema_uri(uri) {
    const match = schema_name_regex.exec(uri);
    if (!match)
	return match;

    const result = {};

    for (let i = 0; i < schema_name_parts.length; i++)
	result[schema_name_parts[i]] = match[i];

    return result
}
#EXPORT parse_schema_uri

/* Extract the number that indicates entity's compatibility mode. */
function get_schema_major_version(instance) {
    return parseInt(parse_schema_uri(instance.$schema).major);
}
#EXPORT get_schema_major_version

#IF NEVER

/*
 * Note: the functions below were overeagerly written and are not used now but
 * might prove useful to once we add more functionalities and are hence kept...
 */

/*
 * Clone recursively all objects. Leave other items (arrays, strings) untouched.
 */
function deep_object_copy(object)
{
    const orig = {object};
    const result = {};
    const to_copy = [[orig, {}]];

    while (to_copy.length > 0) {
	const [object, copy] = to_copy.pop();

	for (const [key, value] of Object.entries(object)) {
	    copy[key] = value;

	    if (typeof value === "object" && !Array.isArray(value)) {
		const value_copy = {};
		to_copy.push([value, value_copy]);
		copy[key] = value_copy;
	    }
	}
    }

    return result.orig;
}

/* helper function for normalize_version() */
const version_reductor = (acc, n) => [...(n || acc.length ? [n] : []), ...acc];
/*
 * ver is an array of integers. Strip right-most zeroes from ver.
 *
 * Returns a *new* array. Doesn't modify its argument.
 */
const normalize_version = ver => ver.reduceRight(version_reductor, []);

#ENDIF