Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→codsen-utilsnext

codsen-utils1.8.0

Various utility functions

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • IDEA
  • API — CHARACTER CO…
  • API — CHARACTER SET…
  • API — CHARACTER CH…
  • API — TYPE CHECKS
  • API — ARRAY AND OBJECT HE…
  • API — STRING HELPERS
  • API — INCLUDES()
  • API — MATCH()
  • API — END OF LINE
  • API — CODSENCLI()
  • API — VERSION
  • API — TYPES
  • Changelog

Installation

Quick Take

Examples

  • Classify individual characters
  • Parse typed, short and repeatable CLI flags
  • Assemble CLI help text
  • Deeply clone a cyclic object graph
  • Detect and resolve line endings
  • Test whether a value exists
  • Find overlapping substring occurrences
  • Match a string against literal and regular-expression choices
  • Intersect arrays in the first array's order
  • Reject a character that is not a digit
  • Omit keys and clone retained values
  • Check own properties safely
  • Distinguish plain objects from other object values
  • Remove selected array values without mutating the input
  • Remove one trailing slash
  • Sort strings with the shared comparator
  • Replace part of a string
  • Reuse shared text symbols and HTML tag sets
  • Check common JavaScript value types
  • Deduplicate values while preserving order
  • Match wildcard allow-lists and deny-lists

Idea

The small helpers that nearly every Codsen package needs: character classification, type guards, a few array and object utilities, EOL detection, wildcard matching and a CLI argument parser. They live here so that each of the other packages can depend on one place instead of on lodash, meow and friends.

Everything is a named export, so bundlers can tree-shake whatever you don’t use:

import { isNumberChar } from "codsen-utils";

console.log(isNumberChar("z"));
// => false
console.log(isNumberChar("0"));
// => true

This package is aimed at Codsen’s own programs first. The API is stable, but it grows to fit what those programs need.

API — character constants

Named constants for the characters which are easy to mistype and hard to spot in source code:

ExportCharacterDescription
leftSingleQuote
leftSingleQuote‘U+2018opens in a new tab
rightSingleQuote
rightSingleQuote’U+2019opens in a new tab, also an apostrophe
leftDoubleQuote
leftDoubleQuote“U+201Copens in a new tab
rightDoubleQuote
rightDoubleQuote”U+201Dopens in a new tab
rawNDash
rawNDash–en dash, U+2013opens in a new tab
rawMDash
rawMDash—em dash, U+2014opens in a new tab
rawNbsp
rawNbsp non-breaking space, U+00A0opens in a new tab
ellipsis
ellipsis…U+2026opens in a new tab
hairspace
hairspace U+200Aopens in a new tab
thinSpace
thinSpace U+2009opens in a new tab
singlePrime
singlePrime′U+2032opens in a new tab, for feet and minutes
doublePrime
doublePrime″U+2033opens in a new tab, for inches and seconds
rawReplacementMark
rawReplacementMark�U+FFFDopens in a new tab
multiplicationSign
multiplicationSign×U+00D7opens in a new tab
backslash
backslash\escapes cleanly in template literals
backtick
backtick`escapes cleanly in template literals
import { rawMDash, rightSingleQuote } from "codsen-utils";

console.log(`John${rightSingleQuote}s`);
// => "John’s"

API — character sets

ExportTypeContents
punctuationChars
Type: string[]
punctuationCharsstring[][".", ",", ";", "!", "?"] — the sentence-ending and clause-separating characters
voidTags
Type: string[]
voidTagsstring[]The 14 HTML void elementsopens in a new tab — area, base, br and so on
inlineTags
Type: Set<string>
inlineTagsSet<string>The 55 HTML inline elements — a, abbr, b, span and so on. It’s a Set, so use .has()
import { inlineTags, voidTags } from "codsen-utils";

console.log(inlineTags.has("span"));
// => true
console.log(voidTags.includes("br"));
// => true

API — character checks

Each takes an unknown value and returns a boolean. None of them throw — a wrong type is simply false, so you can feed them str[i - 1] without checking whether the index exists.

FunctionReturns true when the input is
isNumberChar(value)
isNumberChar(value)a string whose first character is 0-9
isLetter(value)
isLetter(value)a single character which is a letter in any alphabet — a, ž and я all pass
isLatinLetter(value)
isLatinLetter(value)a string whose first character is A-Z or a-z only
isLowercaseLetter(value)
isLowercaseLetter(value)a single lowercase letter
isUppercaseLetter(value)
isUppercaseLetter(value)a single uppercase letter
isWhitespaceChar(value)
isWhitespaceChar(value)a string whose first character is whitespace, including the non-breaking space
isQuote(value)
isQuote(value)", ', or any of the four curly quotes listed above
isCurrencyChar(value)
isCurrencyChar(value)a single-character currency symbol — $, £, €, ¥ and so on
isCurrencySymbol(value)
isCurrencySymbol(value)any known currency symbol, including multi-character ones such as CHF, BZ$ and B/.

isCurrencyChar() and isCurrencySymbol() differ only in length. Use the first when walking a string character by character, and the second when you already have a whole token:

import { isCurrencyChar, isCurrencySymbol } from "codsen-utils";

console.log(isCurrencyChar("CHF"));
// => false
console.log(isCurrencySymbol("CHF"));
// => true

API — type checks

FunctionReturns true when
isStr(something)
isStr(something)it’s a string. Narrows to string
isNum(something)
isNum(something)it’s a number. Narrows to number
isInt(something)
isInt(something)it’s an integer. Narrows to number
isBool(something)
isBool(something)it’s a boolean. Narrows to boolean
isNull(something)
isNull(something)it’s null. Narrows to null
isRegExp(something)
isRegExp(something)it’s a RegExp. Narrows to RegExp
isPlainObject(value)
isPlainObject(value)it’s an object literal, Object.create(null), or new Object() — but not an array, null or a class instance. Narrows to JSONObject
existy(x)
existy(x)it’s neither null nor undefined. Note 0 and "" are “existy”
import { existy, isPlainObject } from "codsen-utils";

console.log([isPlainObject({}), isPlainObject([]), isPlainObject(null)]);
// => [true, false, false]
console.log([existy(0), existy(null), existy(undefined)]);
// => [true, false, false]

API — array and object helpers

FunctionDescription
deepClone(value)
deepClone(value)Clones nested data without retaining object or collection references. Generic — the return type matches the input
uniq(input)
uniq(input)Returns a shallow copy of the array with only unique elements
pullAll(input, remove)
pullAll(input, remove)Returns a copy of input without the elements listed in remove. Unlike the lodash equivalent, it does not mutate the input
intersection(a, b)
intersection(a, b)Returns the elements present in both arrays. An alternative to lodash.intersection
omit(obj, keysToRemove)
omit(obj, keysToRemove)Returns a copy of the plain object without the named keys. An alternative to lodash.omit
hasOwnProp(obj, prop)
hasOwnProp(obj, prop)Safe Object.hasOwn() — inherited properties such as toString return false
compareFn(a, b)
compareFn(a, b)A localeCompare-based compare function to pass to Array.prototype.sort()
import { compareFn, omit, pullAll, uniq } from "codsen-utils";

console.log(pullAll([1, 2, 3, 4], [2, 4]));
// => [1, 3]
console.log(uniq([1, 1, 2, 3, 3]));
// => [1, 2, 3]
console.log(omit({ a: 1, b: 2, c: 3 }, ["b"]));
// => { a: 1, c: 3 }
console.log(["b", "a", "C"].sort(compareFn));
// => ["a", "b", "C"]

API — string helpers

FunctionDescription
stringSplice(str, index, count, add)
stringSplice(str, index, count, add)Replaces count characters starting at index with the string add, and returns the new string
findAllIdx(value, substring)
findAllIdx(value, substring)Returns an array of the indexes of every occurrence of substring
removeTrailingSlash(value)
removeTrailingSlash(value)Drops a single trailing / and trims the result. Anything which is not a string is passed through untouched
import { findAllIdx, removeTrailingSlash, stringSplice } from "codsen-utils";

console.log(stringSplice("abcdef", 2, 3, "XY"));
// => "abXYf"
console.log(findAllIdx("scissors", "s"));
// => [0, 3, 4, 7]
console.log(removeTrailingSlash("a/b/"));
// => "a/b"

API — includes()

The function includes() is imported like this:

Like Array.prototype.includes(), but the array can hold a mix of strings and regexes, and it’s matched against a string. It’s a friendly API — it will not throw if the inputs are wrong:

import { includes } from "codsen-utils";

console.log(includes(["a", /^b/], "bcd"));
// => true
console.log(includes(["a"], "z"));
// => false

API — match()

The function match() is imported like this:

Matches a whole string against one or more wildcard patterns:

Input argumentTypeObligatoryDescription
input
Type: String
Obligatory: yes
inputStringyesString to match.
patterns
Type: String or array of strings
Obligatory: yes
patternsString or array of stringsyesOne pattern or an array of them.
options
Type: Plain object
Obligatory: no
optionsPlain objectnoOptional Options Object.
KeyTypeDefaultDescription
caseSensitiveMatch
Type: Boolean
Default: false
caseSensitiveMatchBooleanfalseSet to true to match letter case exactly.

The rules are:

  • Patterns are anchored — they must consume the whole input, not a part of it.
  • * stands for zero or more characters, and it does cross line breaks.
  • A leading ! negates a pattern. Any negative pattern which matches vetoes the result outright, no matter what the positive ones did.
  • Given only negative patterns, anything they don’t catch passes.
  • An empty pattern array matches nothing.
  • \ escapes the character after it, so \* means a literal asterisk and \\ means a literal backslash.
  • Matching walks code points, not UTF-16 code units, so a wildcard can never consume half of a surrogate pair.
import { match } from "codsen-utils";

console.log(match("index.js", ["*.js", "!*.test.js"]));
// => true
console.log(match("index.test.js", ["*.js", "!*.test.js"]));
// => false
console.log(match("A.JS", ["*.js"], { caseSensitiveMatch: true }));
// => false

API — end of line

FunctionDescription
detectEol(str)
detectEol(str)Reports the line ending used in the string, checking for "\r\n" first, then "\n", then "\r". Returns undefined if the string has none
resolveEolSetting(str, eolSetting, defaultEolChar)
resolveEolSetting(str, eolSetting, defaultEolChar)Turns an "lf"/"crlf"/"cr" setting into the actual character. When eolSetting is missing, it falls back to what detectEol() found, then to defaultEolChar (which itself defaults to "\n"). It throws if defaultEolChar is not one of the three EOL characters
import { detectEol, resolveEolSetting } from "codsen-utils";

console.log(JSON.stringify(detectEol("a\r\nb")));
// => "\r\n"
console.log(JSON.stringify(resolveEolSetting("a\r\nb", "lf")));
// => "\n"
console.log(JSON.stringify(resolveEolSetting("a\r\nb", null)));
// => "\r\n"

That pairing is the point: resolveEolSetting() lets a program offer an explicit “write CRLF” option while still honouring whatever the input file already used when the caller says nothing.

API — codsenCLI()

The function codsenCLI() is imported like this:

An in-house stand-in for meowopens in a new tab. It parses argv the way a CLI expects: flags with values, short flags, bundles, --no- negation, camelCase names and a -- escape hatch. It prints help or version on request.

Input argumentTypeObligatoryDescription
helpText
Type: String
Obligatory: no
helpTextStringnoWhat --help prints.
options
Type: Plain object
Obligatory: no
optionsPlain objectnoFlag schema and the consuming program’s package.json.

The Options Object has the following shape:

KeyTypeDefaultDescription
pkg
Type: Plain object
Default: undefined
pkgPlain objectundefinedThe consuming program’s package.json contents.
flags
Type: Plain object
Default: undefined
flagsPlain objectundefinedFlag schema, keyed by the flag’s camelCase name. See the table below.
argv
Type: Array of strings
Default: process.argv.slice(2)
argvArray of stringsprocess.argv.slice(2)The arguments to parse.
description
Type: String or false
Default: pkg.description
descriptionString or falsepkg.descriptionPrinted above the help text. Set to false to omit it.
version
Type: String
Default: pkg.version
versionStringpkg.versionThe version to print for --version.
helpIndent
Type: Number
Default: 2
helpIndentNumber2How many spaces the help text is indented by.
autoHelp
Type: Boolean
Default: true
autoHelpBooleantruePrint help and exit when the only argument is --help.
autoVersion
Type: Boolean
Default: true
autoVersionBooleantruePrint version and exit when the only argument is --version.
booleanDefault
Type: Boolean
Default: false
booleanDefaultBooleanfalseValue given to declared boolean flags the user didn’t pass. Set to undefined to leave them unset.

Each entry under flags describes one flag:

KeyTypeDescription
type
Type: "boolean", "string", "number"
type"boolean", "string", "number"Coercion applied to whatever the user typed. If undeclared, the value is passed through as a raw string (or true for a bare flag).
shortFlag
Type: String
shortFlagStringSingle-letter alias, for example p to serve --pad as -p.
default
Type: Any
defaultAnyUsed when the flag is absent from argv.
isMultiple
Type: Boolean
isMultipleBooleanCollect every occurrence into an array instead of last-one-wins.

The function returns a plain object:

Returned object’s keyTypeDescription
input
Type: Array of strings
inputArray of stringsPositional arguments, in the order they were given.
flags
Type: Plain object
flagsPlain objectParsed flags, keyed by their camelCase names.
pkg
Type: Plain object
pkgPlain objectThe package.json contents you passed in.
help
Type: String
helpStringThe assembled help text, ready to print.
showHelp
Type: Function
showHelpFunctionPrints the help text, then exits (code 2 unless told otherwise).
showVersion
Type: Function
showVersionFunctionPrints the version, then exits with code 0.

For example:

import { codsenCLI } from "codsen-utils";

const cli = codsenCLI("Usage: mytool <file>", {
  pkg: { name: "mytool", version: "1.2.3", description: "Does a thing" },
  flags: {
    pad: { type: "boolean", shortFlag: "p" },
    name: { type: "string", default: "anon" },
    tag: { type: "string", isMultiple: true },
  },
  argv: ["a.txt", "-p", "--tag", "x", "--tag", "y"],
});

console.log(cli.input);
// => ["a.txt"]
console.log(cli.flags);
// => { pad: true, tag: ["x", "y"], name: "anon" }

--no- negation and the -- escape hatch work as you’d expect:

codsenCLI("h", { flags: { pad: { type: "boolean", default: true } }, argv: ["--no-pad"] }).flags;
// => { pad: false }

codsenCLI("h", { flags: {}, argv: ["a", "--", "--b"] }).input;
// => ["a", "--b"]

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
JSONValue
Type: JSONValue
JSONValueAny JSON-representable value — string, number, boolean, null, JSONObject or JSONArray
JSONObject
Type: JSONObject
JSONObjectAn object whose values are all JSONValues
JSONArray
Type: JSONArray
JSONArrayAn array of JSONValues
JsonObject
Type: JsonObject
JsonObjectA mapped-type variant of JSONObject where every key is optional
Obj
Type: Obj
ObjAn alias of JSONObject, kept for the packages which already import it under that name
EolChar
Type: EolChar
EolChar"\n", "\r\n" or "\r" — what detectEol() and resolveEolSetting() deal in
EolSetting
Type: EolSetting
EolSetting"lf", "crlf" or "cr" — what a user-facing EOL option accepts
MatchOptions
Type: MatchOptions
MatchOptionsThe Optional Options Object of match()
CliFlagType
Type: CliFlagType
CliFlagType"boolean", "string" or "number" — what a flag’s value gets coerced to, once parsed
CliFlag
Type: CliFlag
CliFlagOne entry of the codsenCLI() flag schema
CliPkg
Type: CliPkg
CliPkgThe package.json shape codsenCLI() reads name, version, description and bin from
CliOptions
Type: CliOptions
CliOptionsThe Options Object of codsenCLI()
CliResult
Type: CliResult
CliResultWhat codsenCLI() returns
import type { CliFlag, CliFlagType, CliOptions, CliPkg, CliResult, EolChar, EolSetting, JSONArray, JSONObject, JSONValue, JsonObject, MatchOptions, Obj } from "codsen-utils";

Changelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab