thi-ng/umbrella

View on GitHub
packages/transclude/src/tpl/pkg.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import type { Fn, Fn2, IObjectOf } from "@thi.ng/api";
import { isString } from "@thi.ng/checks/is-string";
import type { TemplateFn } from "../api.js";
import { link, list } from "./markdown.js";

export interface Package {
    name: string;
    description: string;
    version: string;
    author: string | User;
    license: string;
    homepage: string;
    contributors?: (string | User)[];
    dependencies?: IObjectOf<string>;
    devDependencies?: IObjectOf<string>;
    keywords?: string[];
}

export interface User {
    name: string;
    email?: string;
    url?: string;
}

export const LICENSES: IObjectOf<{ name: string; url: string }> = {
    "Apache-2.0": {
        name: "Apache License 2.0",
        url: "https://spdx.org/licenses/Apache-2.0.html",
    },
    "BSD-2-Clause": {
        name: 'BSD 2-Clause "Simplified" License',
        url: "https://spdx.org/licenses/BSD-2-Clause.html",
    },
    "BSD-3-Clause": {
        name: 'BSD 3-Clause "New" or "Revised" License',
        url: "https://spdx.org/licenses/BSD-3-Clause.html",
    },
    "CC-BY-4.0": {
        name: "Creative Commons Attribution 4.0 International",
        url: "https://spdx.org/licenses/CC-BY-4.0.html",
    },
    "CC-BY-NC-4.0": {
        name: "Creative Commons Attribution Non Commercial 4.0 International",
        url: "https://spdx.org/licenses/CC-BY-NC-4.0.html",
    },
    "CC-BY-NC-ND-4.0": {
        name: "Creative Commons Attribution Non Commercial No Derivatives 4.0 International",
        url: "https://spdx.org/licenses/CC-BY-NC-ND-4.0.html",
    },
    "CC-BY-NC-SA-4.0": {
        name: "Creative Commons Attribution Non Commercial Share Alike 4.0 International",
        url: "https://spdx.org/licenses/CC-BY-NC-SA-4.0.html",
    },
    "CC-BY-ND-4.0": {
        name: "Creative Commons Attribution No Derivatives 4.0 International",
        url: "https://spdx.org/licenses/CC-BY-ND-4.0.html",
    },
    "CC-BY-SA-4.0": {
        name: "Creative Commons Attribution Share Alike 4.0 International",
        url: "https://spdx.org/licenses/CC-BY-SA-4.0.html",
    },
    "CPAL-1.0": {
        name: "Common Public Attribution License 1.0",
        url: "https://spdx.org/licenses/CPAL-1.0.html",
    },
    "CPL-1.0": {
        name: "Common Public License 1.0",
        url: "https://spdx.org/licenses/CPL-1.0.html",
    },
    "ECL-2.0": {
        name: "Educational Community License v2.0",
        url: "https://spdx.org/licenses/ECL-2.0.html",
    },
    "EPL-2.0": {
        name: "Eclipse Public License 2.0",
        url: "https://spdx.org/licenses/EPL-2.0.html",
    },
    EUDatagrid: {
        name: "EU DataGrid Software License",
        url: "https://spdx.org/licenses/EUDatagrid.html",
    },
    "EUPL-1.2": {
        name: "European Union Public License 1.2",
        url: "https://spdx.org/licenses/EUPL-1.2.html",
    },
    "GFDL-1.3-or-later": {
        name: "GNU Free Documentation License v1.3 or later",
        url: "https://spdx.org/licenses/GFDL-1.3-or-later.html",
    },
    "GPL-3.0-or-later": {
        name: "GNU General Public License v3.0 or later",
        url: "https://spdx.org/licenses/GPL-3.0-or-later.html",
    },
    ISC: { name: "ISC License", url: "https://spdx.org/licenses/ISC.html" },
    "LGPL-3.0-or-later": {
        name: "GNU Lesser General Public License v3.0 or later",
        url: "https://spdx.org/licenses/LGPL-3.0-or-later.html",
    },
    MIT: { name: "MIT License", url: "https://spdx.org/licenses/MIT.html" },
    "MPL-2.0": {
        name: "Mozilla Public License 2.0",
        url: "https://spdx.org/licenses/MPL-2.0.html",
    },
    "OFL-1.1": {
        name: "SIL Open Font License 1.1",
        url: "https://spdx.org/licenses/OFL-1.1.html",
    },
    Unlicense: {
        name: "The Unlicense",
        url: "https://spdx.org/licenses/Unlicense.html",
    },
    "UPL-1.0": {
        name: "Universal Permissive License v1.0",
        url: "https://spdx.org/licenses/UPL-1.0.html",
    },
    Zlib: { name: "zlib License", url: "https://spdx.org/licenses/Zlib.html" },
};

/**
 * Returns the unscoped name of a package, i.e. the name without the package's
 * group ID (if any).
 *
 * @param name
 */
export const shortName = (name: string) => {
    const idx = name.indexOf("/");
    return idx > 0 ? name.substring(idx + 1) : name;
};

/**
 * Returns name of the author, given in any format supported by NPM.
 *
 * @param author
 */
export const author = (author: string | User) =>
    isString(author) ? author.split(/\s*[<(]/)[0] : author.name;

/**
 * Similar to {@link author}, but if an URL is available, returns a Markdown
 * link to the author's URL, else just the name.
 *
 * @param author
 */
export const authorLink = (author: string | User) => {
    if (isString(author)) {
        const [name, a, b] = author.split(/\s*[<(]/);
        const href = b ? (b[b.length - 1] == ")" ? b : a) : a ? a : "";
        return href.length && href[href.length - 1] === ")"
            ? link(name, href.substring(0, href.length - 1))
            : name;
    }
    return author.url ? link(author.name, author.url) : author.name;
};

/**
 * Formats the {@link Package.contributors} as Markdown list.
 *
 * @param people
 * @param eol
 */
export const contributors = (people: (string | User)[], eol = "\n") =>
    list(people.map(author), eol);

/**
 * Similar to {@link contributors}, but formatted as list of links.
 *
 * @param people
 * @param eol
 */
export const contributorLinks = (people: (string | User)[], eol = "\n") =>
    list(people.map(authorLink), eol);

/**
 * Returns the name for given SPDX license ID (only those in
 * {@link LICENSES} are supported).
 *
 * @param spdxID
 */
export const license = (spdxID: string) =>
    LICENSES[spdxID].name || `${spdxID} license`;

/**
 * Similar to {@link license}, but returns Markdown link to license.
 *
 * @param spdxID
 */
export const licenseLink = (spdxID: string) => {
    const license = LICENSES[spdxID];
    return license ? link(license.name, license.url) : `${spdxID} license`;
};

export interface PackageTemplateOpts {
    /**
     * Headline for list of contributors (only used if any present).
     */
    hdContributors: string;
}

export type PackageTemplates<T> = Record<
    | "pkg.name"
    | "pkg.shortName"
    | "pkg.version"
    | "pkg.description"
    | "pkg.link"
    | "pkg.author"
    | "pkg.authorLink"
    | "pkg.contributors"
    | "pkg.contributorLinks"
    | "pkg.allAuthors"
    | "pkg.allAuthorLinks"
    | "pkg.license"
    | "pkg.licenseLink",
    TemplateFn<T>
>;

/**
 * Higher order function returning various useful NPM package.json related
 * templates. The user provided `pkg` function is used to look up the loaded
 * {@link Package} data within the {@link TranscludeCtx.user} object.
 *
 * @param pkg
 * @param opts
 */
export const packageTemplates = <T = any>(
    pkg: Fn<T, Package>,
    opts?: Partial<PackageTemplateOpts>
) => {
    const $opts = {
        hdContributors: "### Contributors\n\n",
        ...opts,
    };
    const tpls: PackageTemplates<T> = {
        "pkg.name": ({ user }) => pkg(user).name,
        "pkg.shortName": ({ user }) => shortName(pkg(user).name),
        "pkg.version": ({ user }) => "v" + pkg(user).version,
        "pkg.description": ({ user }) => pkg(user).description,
        "pkg.link": ({ user }) => link(pkg(user).name, pkg(user).homepage),
        "pkg.author": ({ user }) => author(pkg(user).author),
        "pkg.authorLink": ({ user }) => authorLink(pkg(user).author),
        "pkg.allAuthors": __allAuthors(pkg, author),
        "pkg.allAuthorLinks": __allAuthors(pkg, authorLink),
        "pkg.contributors": __allContribs(pkg, $opts, contributors),
        "pkg.contributorLinks": __allContribs(pkg, $opts, contributorLinks),
        "pkg.license": ({ user }) => license(pkg(user).license),
        "pkg.licenseLink": ({ user }) => licenseLink(pkg(user).license),
    };
    return tpls;
};

/** @internal */
const __allAuthors =
    (pkg: Fn<any, Package>, itemFn: Fn<string | User, string>): TemplateFn =>
    ({ user, eol }) => {
        const $pkg = pkg(user);
        const $author = itemFn($pkg.author);
        const res = [];
        if ($pkg.contributors) {
            res.push(
                $author + " (Main author)",
                ...$pkg.contributors.map(itemFn)
            );
        } else {
            res.push($author);
        }
        return list(res, eol);
    };

/** @internal */
const __allContribs =
    (
        pkg: Fn<any, Package>,
        opts: PackageTemplateOpts,
        itemFn: Fn2<(string | User)[], string, string>
    ): TemplateFn =>
    ({ user, eol }) => {
        const people = pkg(user).contributors;
        return people ? opts.hdContributors + itemFn(people, eol) : "";
    };