Compare commits

..

3 Commits

Author SHA1 Message Date
e815126807 Added helpers
Some checks failed
Build / Tag Version (push) Has been cancelled
Build / Build NPM Project (push) Has been cancelled
Build / Publish Docs (push) Has been cancelled
2026-07-29 17:51:08 -04:00
6319f810b5 Added safty guardrails to matchAll 2026-07-29 17:42:13 -04:00
91f4abf1f1 Added size getter for cache
All checks were successful
Build / Publish Docs (push) Successful in 38s
Build / Build NPM Project (push) Successful in 44s
Build / Tag Version (push) Successful in 15s
2026-07-24 22:04:45 -04:00
5 changed files with 63 additions and 51 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "@ztimson/utils", "name": "@ztimson/utils",
"version": "0.30.1", "version": "0.30.3",
"description": "Utility library", "description": "Utility library",S
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",
"private": false, "private": false,

View File

@@ -31,7 +31,7 @@ export class Cache<K extends string | number | symbol, T> {
/** Await initial loading */ /** Await initial loading */
loading = new Promise<void>(r => this._loading = r); loading = new Promise<void>(r => this._loading = r);
get size() { return this.store.size; } get size() { return this.store.keys().toArray().length }
/** /**
* Create new cache * Create new cache

45
src/html.ts Normal file
View File

@@ -0,0 +1,45 @@
/**
* Decode HTML escaped characters
* @param html HTML to clean up
* @returns {any}
*/
export function decodeHtml(html: string) {
return html
.replace(/&nbsp;/g, '\u00A0')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&cent;/g, '¢')
.replace(/&pound;/g, '£')
.replace(/&yen;/g, '¥')
.replace(/&euro;/g, '€')
.replace(/&copy;/g, '©')
.replace(/&reg;/g, '®')
.replace(/&trade;/g, '™')
.replace(/&times;/g, '×')
.replace(/&divide;/g, '÷')
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
.replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => String.fromCharCode(parseInt(hex, 16)))
.replace(/&amp;/g, '&'); // Always last!
}
/**
* Parse markdown headers
* @param {string} content
* @returns {{meta: any, content: string} | {meta: {}, content: string}}
*/
export function parseMarkdown(content: string) {
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if(!match) return {meta: {}, content};
const meta: any = {};
for (const line of match[1].split('\n')) {
const colonIdx = line.indexOf(':');
if (colonIdx === -1) continue;
const key = line.slice(0, colonIdx).trim();
const value = line.slice(colonIdx + 1).trim();
try { meta[key] = JSON.parse(value); } catch { meta[key] = value; }
}
return {meta, content: match[2].trim()};
}

View File

@@ -7,9 +7,10 @@ export * from './cache';
export * from './color'; export * from './color';
export * from './csv'; export * from './csv';
export * from './database'; export * from './database';
export * from './files';
export * from './emitter'; export * from './emitter';
export * from './errors'; export * from './errors';
export * from './files';
export * from './html';
export * from './http'; export * from './http';
export * from './json'; export * from './json';
export * from './jwt'; export * from './jwt';

View File

@@ -27,32 +27,6 @@ export function camelCase(str?: string): string {
return pascal.charAt(0).toLowerCase() + pascal.slice(1); return pascal.charAt(0).toLowerCase() + pascal.slice(1);
} }
/**
* Decode HTML escaped characters
* @param html HTML to clean up
* @returns {any}
*/
export function decodeHtml(html: string) {
return html
.replace(/&nbsp;/g, '\u00A0')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&cent;/g, '¢')
.replace(/&pound;/g, '£')
.replace(/&yen;/g, '¥')
.replace(/&euro;/g, '€')
.replace(/&copy;/g, '©')
.replace(/&reg;/g, '®')
.replace(/&trade;/g, '™')
.replace(/&times;/g, '×')
.replace(/&divide;/g, '÷')
.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
.replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => String.fromCharCode(parseInt(hex, 16)))
.replace(/&amp;/g, '&'); // Always last!
}
/** /**
* Convert number of bytes into a human-readable size * Convert number of bytes into a human-readable size
* *
@@ -125,7 +99,6 @@ export function kebabCase(str?: string): string {
return wordSegments(str).map(w => w.toLowerCase()).join("-"); return wordSegments(str).map(w => w.toLowerCase()).join("-");
} }
/** /**
* Add padding to string * Add padding to string
* *
@@ -256,18 +229,14 @@ export function strSplice(str: string, start: number, deleteCount: number, inser
return before + insert + after; return before + insert + after;
} }
function titleCase(str: string) { /**
// Normalize separators: replace underscores and hyphens with spaces * Converts text to Title Case
let normalizedStr = str.replace(/(_|-)/g, ' '); */
// Handle CamelCase/PascalCase boundaries: insert a space before capital letters export function titleCase(str?: string): string {
normalizedStr = normalizedStr.replace(/([a-z])([A-Z])/g, '$1 $2'); if(!str) return '';
// Lowercase the whole string, split by any whitespace, and capitalize each word return wordSegments(str)
let words = normalizedStr.toLowerCase().split(/\s+/).filter(Boolean); .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
const titledWords = words.map(word => { .join(' ');
if (word.length === 0) return '';
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
});
return titledWords.join(' ');
} }
/** /**
@@ -280,19 +249,16 @@ function titleCase(str: string) {
* @return {RegExpExecArray[]} Found matches. * @return {RegExpExecArray[]} Found matches.
*/ */
export function matchAll(value: string, regex: RegExp | string): RegExpExecArray[] { export function matchAll(value: string, regex: RegExp | string): RegExpExecArray[] {
if(typeof regex === 'string') { if(typeof regex === 'string') regex = new RegExp(regex, 'g');
regex = new RegExp(regex, 'g'); if(!regex.global) throw new TypeError('Regular expression must be global.');
}
// https://stackoverflow.com/a/60290199
if(!regex.global) {
throw new TypeError('Regular expression must be global.');
}
let ret: RegExpExecArray[] = []; let ret: RegExpExecArray[] = [];
let match: RegExpExecArray | null; let match: RegExpExecArray | null;
while((match = regex.exec(value)) !== null) { while((match = regex.exec(value)) !== null) {
ret.push(match); ret.push(match);
if(match[0].length === 0) {
regex.lastIndex++;
}
} }
return ret; return ret;