Skip to content

Commit

Permalink
Add facility for caching and prepopulating a cache
Browse files Browse the repository at this point in the history
  • Loading branch information
airhorns committed Nov 24, 2024
1 parent b122e89 commit 9bafefa
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 116 deletions.
32 changes: 13 additions & 19 deletions src/applyInflections.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import { inflections } from "./Inflector";

export function applyInflections(word: string, rules: [RegExp | string, string][]) {
let result = "" + word,
rule,
regex,
replacement;
let result = word;
const inflector = inflections();

if (result.length === 0) {
return result;
} else {
const match = result.toLowerCase().match(/\b\w+$/);
}

if (match && inflector.uncountables.indexOf(match[0]) > -1) {
return result;
} else {
for (let i = 0, ii = rules.length; i < ii; i++) {
rule = rules[i];
const match = result.toLowerCase().match(/\b\w+$/);

regex = rule[0];
replacement = rule[1];
if (match && inflector.uncountables.indexOf(match[0]) > -1) {
return result;
} else {
for (const rule of rules) {
const [regex, replacement] = rule;

if (result.match(regex)) {
result = result.replace(regex, replacement);
break;
}
if (result.match(regex)) {
result = result.replace(regex, replacement);
break;
}

return result;
}

return result;
}
}
21 changes: 21 additions & 0 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** Wrap a given function in a cache that is off by default */
export const cacheable = <T extends (...args: any[]) => any>(
fn: T,
getCacheKey: (...args: Parameters<T>) => string = ((str: string) => str) as unknown as (...args: Parameters<T>) => string
) => {
const cache = new Map<string, ReturnType<T>>();

const cachedFn = Object.assign(
function (this: unknown, ...args: Parameters<T>): ReturnType<T> {
return cache.get(getCacheKey(...args)) ?? fn.call(this, ...args);
},
{
cache,
populate: (...args: Parameters<T>) => {
cache.set(getCacheKey(...args), fn(...args));
},
}
);

return cachedFn as T & { cache: Map<string, ReturnType<T>>; populate: (...args: Parameters<T>) => void };
};
58 changes: 31 additions & 27 deletions src/camelize.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import { inflections } from "./Inflector";
import type { AhoCorasick } from "./ahoCorasick";
import { cacheable } from "./cache";
import { capitalize } from "./capitalize";

const separators = /(?:_|(\/))([a-z\d]*)/gi;

export function camelize(term: string, uppercaseFirstLetter = true) {
const inflector = inflections();
export const camelize = cacheable(
(term: string, uppercaseFirstLetter = true) => {
const inflector = inflections();

let result: string = term;
let result: string = term;

if (uppercaseFirstLetter) {
const startAcronym = findLongestStartAcronym(inflector.lowerAcronymMatcher, term);
if (startAcronym) {
result = inflector.lowerToAcronyms[startAcronym] + result.slice(startAcronym.length);
if (uppercaseFirstLetter) {
const startAcronym = findLongestStartAcronym(inflector.lowerAcronymMatcher, term);
if (startAcronym) {
result = inflector.lowerToAcronyms[startAcronym] + result.slice(startAcronym.length);
} else {
result = term.charAt(0).toUpperCase() + term.slice(1);
}
} else {
result = term.charAt(0).toUpperCase() + term.slice(1);
const startAcronym = findLongestStartAcronym(inflector.casedAcronymMatcher, term);
if (startAcronym) {
result = startAcronym.toLowerCase() + result.slice(startAcronym.length);
} else {
result = term.charAt(0).toLowerCase() + term.slice(1);
}
}
} else {
const startAcronym = findLongestStartAcronym(inflector.casedAcronymMatcher, term);
if (startAcronym) {
result = startAcronym.toLowerCase() + result.slice(startAcronym.length);
} else {
result = term.charAt(0).toLowerCase() + term.slice(1);
}
}

result = result.replace(separators, (_match, separator, word) => {
word = inflector.lowerToAcronyms[word] ?? capitalize(word);
if (separator) {
return separator + word;
} else {
return word;
}
});

return result;
}
result = result.replace(separators, (_match, separator, word) => {
word = inflector.lowerToAcronyms[word] ?? capitalize(word);
if (separator) {
return separator + word;
} else {
return word;
}
});

return result;
},
(term, uppercaseFirstLetter) => `${term}-${uppercaseFirstLetter}`
);

const findLongestStartAcronym = (matcher: AhoCorasick | null, word: string) => {
if (!matcher) return null;
Expand Down
5 changes: 2 additions & 3 deletions src/classify.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { cacheable } from "./cache";
import { camelize } from "./camelize";
import { singularize } from "./singularize";

export function classify(tableName: string) {
return camelize(singularize(tableName.replace(/.*\./g, "")));
}
export const classify = cacheable((tableName: string) => camelize(singularize(tableName.replace(/.*\./g, ""))));
9 changes: 4 additions & 5 deletions src/constantify.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { cacheable } from "./cache";
import { underscore } from "./underscore";

export function constantify(word: string) {
return underscore(word)
.toUpperCase()
.replace(/\s+/g, "_");
}
export const constantify = cacheable((word: string) => {
return underscore(word).toUpperCase().replace(/\s+/g, "_");
});
67 changes: 35 additions & 32 deletions src/humanize.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
import { inflections } from "./Inflector";
import { cacheable } from "./cache";
export const humanize = cacheable(
(lowerCaseAndUnderscoredWord: string, options?: { capitalize?: boolean }) => {
let result = "" + lowerCaseAndUnderscoredWord;
const inflector = inflections();
const humans = inflector.humans;
let human, rule, replacement;

options = options || {};

if (options.capitalize === null || options.capitalize === undefined) {
options.capitalize = true;
}

export function humanize(lowerCaseAndUnderscoredWord: string, options?: { capitalize?: boolean }) {
let result = "" + lowerCaseAndUnderscoredWord;
const inflector = inflections();
const humans = inflector.humans;
let human, rule, replacement;

options = options || {};

if (options.capitalize === null || options.capitalize === undefined) {
options.capitalize = true;
}

for (let i = 0, ii = humans.length; i < ii; i++) {
human = humans[i];
rule = human[0];
replacement = human[1];
for (let i = 0, ii = humans.length; i < ii; i++) {
human = humans[i];
rule = human[0];
replacement = human[1];

if (rule instanceof RegExp ? rule.test(result) : result.indexOf(rule) > -1) {
result = result.replace(rule, replacement);
break;
if (rule instanceof RegExp ? rule.test(result) : result.indexOf(rule) > -1) {
result = result.replace(rule, replacement);
break;
}
}
}

result = result.replace(/_id$/, "");
result = result.replace(/_/g, " ");
result = result.replace(/_id$/, "");
result = result.replace(/_/g, " ");

result = result.replace(/([a-z\d]*)/gi, function (match) {
return inflector.lowerToAcronyms[match] || match.toLowerCase();
});

if (options.capitalize) {
result = result.replace(/^\w/, function (match) {
return match.toUpperCase();
result = result.replace(/([a-z\d]*)/gi, function (match) {
return inflector.lowerToAcronyms[match] || match.toLowerCase();
});
}

return result;
}
if (options.capitalize) {
result = result.replace(/^\w/, function (match) {
return match.toUpperCase();
});
}

return result;
},
(lowerCaseAndUnderscoredWord, options) => `${lowerCaseAndUnderscoredWord}-${options?.capitalize}`
);
48 changes: 26 additions & 22 deletions src/parameterize.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { cacheable } from "./cache";
import { transliterate } from "./Transliterator";

export function parameterize(string: string, options: { locale?: string; separator?: string | null; preserveCase?: boolean } = {}) {
if (options.separator === undefined) {
options.separator = "-";
}
export const parameterize = cacheable(
(string: string, options: { locale?: string; separator?: string | null; preserveCase?: boolean } = {}) => {
if (options.separator === undefined) {
options.separator = "-";
}

if (options.separator === null) {
options.separator = "";
}
if (options.separator === null) {
options.separator = "";
}

// replace accented chars with their ascii equivalents
let result = transliterate(string, { locale: options.locale });
// replace accented chars with their ascii equivalents
let result = transliterate(string, { locale: options.locale });

result = result.replace(/[^a-z0-9\-_]+/gi, options.separator);
result = result.replace(/[^a-z0-9\-_]+/gi, options.separator);

if (options.separator.length) {
const separatorRegex = new RegExp(options.separator);
if (options.separator.length) {
const separatorRegex = new RegExp(options.separator);

// no more than one of the separator in a row
result = result.replace(new RegExp(separatorRegex.source + "{2,}"), options.separator);
// no more than one of the separator in a row
result = result.replace(new RegExp(separatorRegex.source + "{2,}"), options.separator);

// remove leading/trailing separator
result = result.replace(new RegExp("^" + separatorRegex.source + "|" + separatorRegex.source + "$", "i"), "");
}
// remove leading/trailing separator
result = result.replace(new RegExp("^" + separatorRegex.source + "|" + separatorRegex.source + "$", "i"), "");
}

if (options.preserveCase) {
return result;
}
if (options.preserveCase) {
return result;
}

return result.toLowerCase();
}
return result.toLowerCase();
},
(string, options) => `${string}-${options?.locale}-${options?.separator}-${options?.preserveCase}`
);
8 changes: 5 additions & 3 deletions src/pluralize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { applyInflections } from "./applyInflections";
import { cacheable } from "./cache";
import { inflections } from "./Inflector";

export function pluralize(word: string, locale = "en") {
return applyInflections(word, inflections(locale).plurals);
}
export const pluralize = cacheable(
(word: string, locale = "en") => applyInflections(word, inflections(locale).plurals),
(word, locale) => `${word}-${locale}`
);
8 changes: 5 additions & 3 deletions src/singularize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { applyInflections } from "./applyInflections";
import { cacheable } from "./cache";
import { inflections } from "./Inflector";

export function singularize(word: string, locale = "en") {
return applyInflections(word, inflections(locale).singulars);
}
export const singularize = cacheable(
(word: string, locale = "en") => applyInflections(word, inflections(locale).singulars),
(word, locale) => `${word}-${locale}`
);
5 changes: 3 additions & 2 deletions src/underscore.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { cacheable } from "./cache";
import { inflections } from "./Inflector";

const letterOrDigit = /[A-Za-z\d]/;
const wordBoundaryOrNonLetter = /\b|[^a-z]/;
const boundaryMatcher = /([A-Z\d]+)([A-Z][a-z])|([a-z\d])([A-Z])|(-)/g;

export function underscore(camelCasedWord: string) {
export const underscore = cacheable((camelCasedWord: string) => {
let result = camelCasedWord;
const acronymMatches = inflections().casedAcronymMatcher?.search(camelCasedWord, isWordBoundary);
if (acronymMatches) {
Expand Down Expand Up @@ -32,7 +33,7 @@ export function underscore(camelCasedWord: string) {
return `${p3}_${p4}`;
})
.toLowerCase();
}
});

function isWordBoundary(char: string): boolean {
const charCode = char.charCodeAt(0);
Expand Down

0 comments on commit 9bafefa

Please sign in to comment.