No 3rd party dependencies. All dependencies and devDependencies, checked recursively, are Codsen packages.
Permalink to InstallationInstallation
Permalink to Quick TakeQuick Take
Permalink to ExamplesExamples
- 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
- Trim selected characters from both string edges
- 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:
| Export | Character | Description |
|---|---|---|
leftSingleQuote | ||
leftSingleQuote | ‘ | U+2018 |
rightSingleQuote | ||
rightSingleQuote | ’ | U+2019, also an apostrophe |
leftDoubleQuote | ||
leftDoubleQuote | “ | U+201C |
rightDoubleQuote | ||
rightDoubleQuote | ” | U+201D |
rawNDash | ||
rawNDash | – | en dash, U+2013 |
rawMDash | ||
rawMDash | — | em dash, U+2014 |
rawNbsp | ||
rawNbsp | | non-breaking space, U+00A0 |
ellipsis | ||
ellipsis | … | U+2026 |
hairspace | ||
hairspace | | U+200A |
thinSpace | ||
thinSpace | | U+2009 |
singlePrime | ||
singlePrime | ′ | U+2032, for feet and minutes |
doublePrime | ||
doublePrime | ″ | U+2033, for inches and seconds |
rawReplacementMark | ||
rawReplacementMark | � | U+FFFD |
multiplicationSign | ||
multiplicationSign | × | U+00D7 |
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
| Export | Type | Contents |
|---|---|---|
punctuationCharsType: string[] | ||
punctuationChars | string[] | [".", ",", ";", "!", "?"] — the sentence-ending and clause-separating characters |
voidTagsType: string[] | ||
voidTags | string[] | The 14 HTML void elements — area, base, br and so on |
inlineTagsType: Set<string> | ||
inlineTags | Set<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.
| Function | Returns true when the input is |
|---|---|
isNumberChar(value) | |
isNumberChar(value) | a string whose first character is 0–9 |
isLetter(value) | |
isLetter(value) | exactly one Unicode code point in the Letter category — a, ž, я, 東, and 𐐀 all pass |
isLatinLetter(value) | |
isLatinLetter(value) | a string whose first character is A–Z or a-z only |
isLowercaseLetter(value) | |
isLowercaseLetter(value) | exactly one lowercase Unicode code point, including letters outside the Basic Multilingual Plane |
isUppercaseLetter(value) | |
isUppercaseLetter(value) | exactly one uppercase Unicode code point, including letters outside the Basic Multilingual Plane |
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
| Function | Returns true when |
|---|---|
isStr(something) | |
isStr(something) | it’s a string. Narrows to string |
isNum(something) | |
isNum(something) | it’s a finite number. Narrows to number |
isInt(something) | |
isInt(something) | it’s a non-negative safe 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 usable same-realm or cross-realm regular expression, including a subclass. Narrows to RegExp |
isDate(something) | |
isDate(something) | it’s a same-realm or cross-realm Date, including an invalid date. Narrows to Date |
isPlainObject(value) | |
isPlainObject(value) | it’s an object literal, Object.create(null), new Object(), or a cross-realm plain object — but not an array, null, or a class instance. Narrows to PlainObject |
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]
isPlainObject() checks the container’s prototype, not whether its values are JSON-safe. Records can contain functions, undefined, bigint values, cycles, and symbol-keyed properties and still be plain objects. Use the exported JSONObject type only when your data already satisfies the JSON value contract.
API — array and object helpers
| Function | Description |
|---|---|
deepClone(value) | |
deepClone(value) | Clones a supported value graph without retaining object or collection references. Generic — the return type matches the input |
deepCloneWithMetadata(value) | |
deepCloneWithMetadata(value) | Applies the same clone contract and returns { value, hasRepeatedReferences } |
uniq(input) | |
uniq(input) | Returns a shallow copy of the array with only unique elements |
pullAll(input, remove) | |
pullAll(input, remove) | Returns a shallow copy of input without the elements listed in remove. Every valid-array path returns a new array; the input is never mutated |
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) | Safely checks an own string, number, or symbol key. Inherited properties return false, as do null and undefined inputs |
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(pullAll(undefined, undefined));
// => []
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"]
Deep-clone contract
deepClone() preserves cycles and repeated references inside the new graph. It supports arrays; plain and null-prototype records; dates; maps; sets; array buffers and views; regular expressions; errors; URLs; and ordinary class instances whose behaviour relies on cloneable own state. These built-in categories also work with cross-realm values. Primitive values and functions retain their identity.
Own __proto__ data stays an own property instead of changing the clone’s prototype, and null-prototype records remain null-prototype records. Enumerable accessors on records and arrays are read once and become data properties. Classes which depend on private fields or other hidden internal slots are outside the clone contract.
Use deepCloneWithMetadata() when you also need to know whether the source graph reused an object:
import { deepCloneWithMetadata } from "codsen-utils";
const shared = { enabled: true };
const result = deepCloneWithMetadata({ left: shared, right: shared });
console.log(result.hasRepeatedReferences);
// => true
console.log(result.value.left === result.value.right);
// => true
console.log(result.value.left === shared);
// => false
API — string helpers
| Function | Description |
|---|---|
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
Only usable regular expressions participate. RegExp.prototype, proxies, and objects which only spoof the RegExp tag are ignored without throwing. Subclasses and cross-realm regular expressions work. A global expression starts and finishes with lastIndex set to 0; other expressions keep JavaScript’s native lastIndex behaviour.
API — match()
The function match() is imported like this:
Matches a whole string against one or more wildcard patterns:
| Input argument | Type | Obligatory | Description |
|---|---|---|---|
inputType: String Obligatory: yes | |||
input | String | yes | String to match. |
patternsType: String or array of strings Obligatory: yes | |||
patterns | String or array of strings | yes | One pattern or an array of them. |
optionsType: Plain object Obligatory: no | |||
options | Plain object | no | Optional Options Object. |
| Key | Type | Default | Description |
|---|---|---|---|
caseSensitiveMatchType: Boolean Default: false | |||
caseSensitiveMatch | Boolean | false | Set 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
| Function | Description |
|---|---|
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 meow. 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 argument | Type | Obligatory | Description |
|---|---|---|---|
helpTextType: String Obligatory: no | |||
helpText | String | no | What --help prints. |
optionsType: Plain object Obligatory: no | |||
options | Plain object | no | Flag schema and the consuming program’s package.json. |
The Options Object has the following shape:
| Key | Type | Default | Description |
|---|---|---|---|
pkgType: Plain object Default: undefined | |||
pkg | Plain object | undefined | The consuming program’s package.json contents. |
flagsType: Plain object Default: undefined | |||
flags | Plain object | undefined | Flag schema, keyed by the flag’s camelCase name. See the table below. |
argvType: Array of strings Default: process.argv.slice(2) | |||
argv | Array of strings | process.argv.slice(2) | The arguments to parse. |
descriptionType: String or falseDefault: pkg.description | |||
description | String or false | pkg.description | Printed above the help text. Set to false to omit it. |
versionType: String Default: pkg.version | |||
version | String | pkg.version | The version to print for --version. |
helpIndentType: Number Default: 2 | |||
helpIndent | Number | 2 | How many spaces the help text is indented by. |
autoHelpType: Boolean Default: true | |||
autoHelp | Boolean | true | Print help and exit when the only argument is --help. |
autoVersionType: Boolean Default: true | |||
autoVersion | Boolean | true | Print version and exit when the only argument is --version. |
booleanDefaultType: Boolean Default: false | |||
booleanDefault | Boolean | false | Value given to declared boolean flags the user didn’t pass. Set to undefined to leave them unset. |
Each entry under flags describes one flag:
| Key | Type | Description |
|---|---|---|
typeType: "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). |
shortFlagType: String | ||
shortFlag | String | Single-letter alias, for example p to serve --pad as -p. |
defaultType: Any | ||
default | Any | Used when the flag is absent from argv. |
isMultipleType: Boolean | ||
isMultiple | Boolean | Collect every occurrence into an array instead of last-one-wins. |
The function returns a plain object:
| Returned object’s key | Type | Description |
|---|---|---|
inputType: Array of strings | ||
input | Array of strings | Positional arguments, in the order they were given. |
flagsType: Plain object | ||
flags | Plain object | Parsed flags, keyed by their camelCase names. |
pkgType: Plain object | ||
pkg | Plain object | The package.json contents you passed in. |
helpType: String | ||
help | String | The assembled help text, ready to print. |
showHelpType: Function | ||
showHelp | Function | Prints the help text, then exits (code 2 unless told otherwise). |
showVersionType: Function | ||
showVersion | Function | Prints 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"]
Number flags accept ordinary decimal and exponent spellings, including leading-dot values. Attached and separate values use the same grammar:
const flags = {
ratio: { type: "number", shortFlag: "r" },
};
codsenCLI("h", { flags, argv: ["--ratio", "-.5"] }).flags.ratio;
// => -0.5
codsenCLI("h", { flags, argv: ["-r.5e2"] }).flags.ratio;
// => 50
API — version
You can import version:
API — types
This package is written in TypeScript and exports the following types:
| Type | Description |
|---|---|
JSONValueType: JSONValue | |
JSONValue | Any JSON-representable value — string, number, boolean, null, JSONObject or JSONArray |
JSONObjectType: JSONObject | |
JSONObject | An object whose values are all JSONValues |
JSONArrayType: JSONArray | |
JSONArray | An array of JSONValues |
JsonObjectType: JsonObject | |
JsonObject | A mapped-type variant of JSONObject where every key is optional |
ObjType: Obj | |
Obj | An alias of JSONObject, kept for the packages which already import it under that name |
PlainObjectType: PlainObject | |
PlainObject | The truthful result of isPlainObject(): a record whose string, number, or symbol keys have unknown values |
DeepCloneResult<T>Type: DeepCloneResult<T> | |
DeepCloneResult<T> | The value and hasRepeatedReferences metadata returned by deepCloneWithMetadata() |
EolCharType: EolChar | |
EolChar | "\n", "\r\n" or "\r" — what detectEol() and resolveEolSetting() deal in |
EolSettingType: EolSetting | |
EolSetting | "lf", "crlf" or "cr" — what a user-facing EOL option accepts |
MatchOptionsType: MatchOptions | |
MatchOptions | The Optional Options Object of match() |
CliFlagTypeType: CliFlagType | |
CliFlagType | "boolean", "string" or "number" — what a flag’s value gets coerced to, once parsed |
CliFlagType: CliFlag | |
CliFlag | One entry of the codsenCLI() flag schema |
CliPkgType: CliPkg | |
CliPkg | The package.json shape codsenCLI() reads name, version, description and bin from |
CliOptionsType: CliOptions | |
CliOptions | The Options Object of codsenCLI() |
CliResultType: CliResult | |
CliResult | What codsenCLI() returns |
import type {
CliFlag,
CliFlagType,
CliOptions,
CliPkg,
CliResult,
DeepCloneResult,
EolChar,
EolSetting,
JSONArray,
JSONObject,
JSONValue,
JsonObject,
MatchOptions,
Obj,
PlainObject,
} from "codsen-utils";