@blameitonyourisp/blurrid
Version:
Generate and render blurred placeholders for lazy loaded images.
192 lines (171 loc) • 8.25 kB
JavaScript
// Copyright (c) 2023 James Reid. All rights reserved.
//
// This source code file is licensed under the terms of the MIT license, a copy
// of which may be found in the LICENSE.md file in the root of this repository.
//
// For a template copy of the license see one of the following 3rd party sites:
// - <https://opensource.org/licenses/MIT>
// - <https://choosealicense.com/licenses/mit>
// - <https://spdx.org/licenses/MIT>
/**
* @ignore
* @file Parses cli option arguments in a basic fashion for local scripts.
* @author James Reid
*/
// @ts-check
// @@imports-types
/* eslint-disable no-unused-vars -- Types only used in comments. */
import { decorateFg, padEndDecorated } from "./decorate-cli.js"
import { CliOption, CliArgument } from "../types/index.js"
/* eslint-enable no-unused-vars -- Close disable-enable pair. */
// @@body
/**
* Render help for a given cli option. Rendered string provides consistent
* padding for each part/fragment of the string, such that consecutive help
* strings are vertically aligned. Each string section is coloured differently,
* and section of surrounded by brackets render the brackets in magenta.
*
* @summary Render help for a given cli option.
* @param {string} alias - Option alias.
* @param {string} name - Option name.
* @param {string} type - Type of option argument.
* @param {CliArgument} value - Default value of option.
* @param {string} [description] - Optional description of option.
* @returns {void}
*/
const renderHelp = (alias, name, type, value, description) => {
// Reassign alias with single hyphen alias flag and "()" brackets.
alias = padEndDecorated(`${
decorateFg("(", "magenta")}${
decorateFg(`-${alias}`, "red")}${
decorateFg(")", "magenta")
}`, 12)
// Reassign option name with double hyphen option flag.
name = `--${name}`.padEnd(28)
// Reassign option type with "{}" brackets.
type = padEndDecorated(`${
decorateFg("{", "magenta")}${
decorateFg(type, "yellow")}${
decorateFg("}", "magenta")
}`, 12)
// Reassign default value with "[]" brackets.
value = `${
decorateFg("[", "magenta")}${
decorateFg(/** @type {string} */ (value), "blue")}${
decorateFg("]", "magenta")
}`
// Reassign description with line break if the description is set.
description = description
? `\n${decorateFg(`|- ${description}`, "gray")}`
: ""
// Log option help string.
console.log(`${alias}${name}${type}${value}${description}`)
}
/**
* Parses cli arguments into an arguments object given an object of default
* arguments and available aliases for each flag. Options without a default are
* assumed to be of type string. Default options may be of type string, boolean,
* or array.
*
* String type options override the previously set string. The final string will
* be the first argument following this cli flag, or an empty string if the flag
* is used without an argument. Any additional arguments before next cli flag
* will be ignored.
*
* Boolean type options MUST default to false. Options should be named to
* reflect this: for example "save" if the default should be to not perform a
* save operation, and "ignore-save" or "disable-save" if the default should be
* to perform a save operation. Using the cli flag will set option to true. Any
* option arguments before next cli flag will be ignored.
*
* Array type options override the default array. The final array will be an
* array of all arguments before the next cli flag, or an empty array if the
* flag is used without any arguments.
*
* @summary Parses cli arguments into an arguments object.
* @param {Object.<string,CliOption>} cli - Object containing the
* current state of parsed arguments, initial value passes option name,
* aliases, default values and descriptions for each cli option.
* @param {string} [option] - Current cli option being updated.
* @param {Map.<string,string>} [optionMap] - Map containing keys taken from all
* option names and aliases, mapping to string values of the corresponding
* option key in the cli parsed arguments object.
* @param {number} [pointer=2] - Current pointer in the node process arguments,
* starts by default at 2 in order to ignore the path arguments.
* @returns {Object.<string,CliArgument>} Returns object containing keys from
* original cli options object, each key mapping to the updated value
* of that option after all arguments have been parsed.
*/
const parseCliArguments = (cli, option, optionMap, pointer = 2) => {
// Print help if requested, otherwise initialise optionMap from cli options.
if (!optionMap) {
// If required, print help according to the aliases, defaults and
// descriptions supplied in the parsedArgs object then exit process.
if (process.argv.includes("--help") || process.argv.includes("-h")) {
// Log usage and format sections, as well as options header.
console.log("USAGE:\nchangelog [OPTIONS]\n\nHELP FORMAT:")
renderHelp("alias", "option-name", "type", "default", "description")
console.log("\nOPTIONS:")
// Log help string for each available cli option.
for (const key in cli) {
const { name, aliases, value, description } = cli[key]
// Determine type based on default value of object.
const type = value === null ? "string"
: typeof value === "undefined" ? "string"
: typeof value === "string" ? "string"
: typeof value === "boolean" ? "boolean"
: "string[]"
renderHelp(aliases[0] || "", name, type, value, description)
}
// Exit process since no further action should be taken.
process.exit()
}
// Initialise optionMap using the name and aliases of each option, and
// mapping to the string key of the option object.
optionMap = new Map()
for (const key in cli) {
const aliases = [...cli[key].aliases, cli[key].name]
for (const alias of aliases) {
// Note that if an name or alias is a duplicate of a previously
// set key in the map, the later key will overwrite the original
// without warning.
optionMap.set(alias, key)
}
}
}
// If all arguments are parsed, return all updated values in an object with
// keys from the original cli options object.
if (pointer >= process.argv.length) {
const parsedArgs = /** @type {Object.<string,CliArgument>} */ ({})
for (const key in cli) { parsedArgs[key] = cli[key].value }
return parsedArgs
}
// Parse current cli argument depending on if the argument is a new cli flag
// or an argument pertaining to the last seen flag.
const flag = process.argv[pointer].match(/(?<=^-).$|(?<=^--).{2,}$/)?.[0]
OPTION_CHAIN: if (flag) {
// If flag found, set the last seen flag to the new flag.
option = optionMap.get(flag)
if (!option) { break OPTION_CHAIN } // Break if option undefined
// Reset default argument according to type of default argument.
cli[option].value = cli[option].value === null ? ""
: typeof cli[option].value === "undefined" ? ""
: typeof cli[option].value === "string" ? ""
: typeof cli[option].value === "boolean" ? true
: []
}
else if (!option) { break OPTION_CHAIN } // Break if option undefined
else if (typeof cli[option].value === "string") {
// Reset string argument and option flag.
cli[option].value = process.argv[pointer]
option = undefined
}
else if (cli[option].value instanceof Array) {
// Push to array argument.
/** @type {string[]} */ (cli[option].value).push(process.argv[pointer])
}
// Recursively call with incremented pointer for cli argument array.
return parseCliArguments(cli, option, optionMap, ++pointer)
}
// @@exports
export { parseCliArguments }