Skip to content

Commit 02356e5

Browse files
committed
Add facility for caching and prepopulating a cache
1 parent b122e89 commit 02356e5

11 files changed

+152
-116
lines changed

gitpkg.config.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const { execSync } = require("child_process");
2+
3+
module.exports = () => ({
4+
getTagName: (pkg) => `${pkg.name}-v${pkg.version}-gitpkg-${execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim()}`,
5+
});

src/applyInflections.ts

+13-19
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,27 @@
11
import { inflections } from "./Inflector";
22

33
export function applyInflections(word: string, rules: [RegExp | string, string][]) {
4-
let result = "" + word,
5-
rule,
6-
regex,
7-
replacement;
4+
let result = word;
85
const inflector = inflections();
96

107
if (result.length === 0) {
118
return result;
12-
} else {
13-
const match = result.toLowerCase().match(/\b\w+$/);
9+
}
1410

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

21-
regex = rule[0];
22-
replacement = rule[1];
13+
if (match && inflector.uncountables.indexOf(match[0]) > -1) {
14+
return result;
15+
} else {
16+
for (const rule of rules) {
17+
const [regex, replacement] = rule;
2318

24-
if (result.match(regex)) {
25-
result = result.replace(regex, replacement);
26-
break;
27-
}
19+
if (result.match(regex)) {
20+
result = result.replace(regex, replacement);
21+
break;
2822
}
29-
30-
return result;
3123
}
24+
25+
return result;
3226
}
3327
}

src/cache.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** Wrap a given function in a cache that is off by default */
2+
export const cacheable = <T extends (...args: any[]) => any>(
3+
fn: T,
4+
getCacheKey: (...args: Parameters<T>) => string = ((str: string) => str) as unknown as (...args: Parameters<T>) => string
5+
) => {
6+
const cache = new Map<string, ReturnType<T>>();
7+
8+
const cachedFn = Object.assign(
9+
function (this: unknown, ...args: Parameters<T>): ReturnType<T> {
10+
return cache.get(getCacheKey(...args)) ?? fn.call(this, ...args);
11+
},
12+
{
13+
cache,
14+
populate: (...args: Parameters<T>): ReturnType<T> => {
15+
const result = fn(...args);
16+
cache.set(getCacheKey(...args), result);
17+
return result;
18+
},
19+
}
20+
);
21+
22+
return cachedFn as T & { cache: Map<string, ReturnType<T>>; populate: (...args: Parameters<T>) => ReturnType<T> };
23+
};

src/camelize.ts

+31-27
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
11
import { inflections } from "./Inflector";
22
import type { AhoCorasick } from "./ahoCorasick";
3+
import { cacheable } from "./cache";
34
import { capitalize } from "./capitalize";
45

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

7-
export function camelize(term: string, uppercaseFirstLetter = true) {
8-
const inflector = inflections();
8+
export const camelize = cacheable(
9+
(term: string, uppercaseFirstLetter = true) => {
10+
const inflector = inflections();
911

10-
let result: string = term;
12+
let result: string = term;
1113

12-
if (uppercaseFirstLetter) {
13-
const startAcronym = findLongestStartAcronym(inflector.lowerAcronymMatcher, term);
14-
if (startAcronym) {
15-
result = inflector.lowerToAcronyms[startAcronym] + result.slice(startAcronym.length);
14+
if (uppercaseFirstLetter) {
15+
const startAcronym = findLongestStartAcronym(inflector.lowerAcronymMatcher, term);
16+
if (startAcronym) {
17+
result = inflector.lowerToAcronyms[startAcronym] + result.slice(startAcronym.length);
18+
} else {
19+
result = term.charAt(0).toUpperCase() + term.slice(1);
20+
}
1621
} else {
17-
result = term.charAt(0).toUpperCase() + term.slice(1);
22+
const startAcronym = findLongestStartAcronym(inflector.casedAcronymMatcher, term);
23+
if (startAcronym) {
24+
result = startAcronym.toLowerCase() + result.slice(startAcronym.length);
25+
} else {
26+
result = term.charAt(0).toLowerCase() + term.slice(1);
27+
}
1828
}
19-
} else {
20-
const startAcronym = findLongestStartAcronym(inflector.casedAcronymMatcher, term);
21-
if (startAcronym) {
22-
result = startAcronym.toLowerCase() + result.slice(startAcronym.length);
23-
} else {
24-
result = term.charAt(0).toLowerCase() + term.slice(1);
25-
}
26-
}
27-
28-
result = result.replace(separators, (_match, separator, word) => {
29-
word = inflector.lowerToAcronyms[word] ?? capitalize(word);
30-
if (separator) {
31-
return separator + word;
32-
} else {
33-
return word;
34-
}
35-
});
3629

37-
return result;
38-
}
30+
result = result.replace(separators, (_match, separator, word) => {
31+
word = inflector.lowerToAcronyms[word] ?? capitalize(word);
32+
if (separator) {
33+
return separator + word;
34+
} else {
35+
return word;
36+
}
37+
});
38+
39+
return result;
40+
},
41+
(term, uppercaseFirstLetter) => `${term}-${uppercaseFirstLetter}`
42+
);
3943

4044
const findLongestStartAcronym = (matcher: AhoCorasick | null, word: string) => {
4145
if (!matcher) return null;

src/classify.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1+
import { cacheable } from "./cache";
12
import { camelize } from "./camelize";
23
import { singularize } from "./singularize";
34

4-
export function classify(tableName: string) {
5-
return camelize(singularize(tableName.replace(/.*\./g, "")));
6-
}
5+
export const classify = cacheable((tableName: string) => camelize(singularize(tableName.replace(/.*\./g, ""))));

src/constantify.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1+
import { cacheable } from "./cache";
12
import { underscore } from "./underscore";
23

3-
export function constantify(word: string) {
4-
return underscore(word)
5-
.toUpperCase()
6-
.replace(/\s+/g, "_");
7-
}
4+
export const constantify = cacheable((word: string) => {
5+
return underscore(word).toUpperCase().replace(/\s+/g, "_");
6+
});

src/humanize.ts

+35-32
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
11
import { inflections } from "./Inflector";
2+
import { cacheable } from "./cache";
3+
export const humanize = cacheable(
4+
(lowerCaseAndUnderscoredWord: string, options?: { capitalize?: boolean }) => {
5+
let result = "" + lowerCaseAndUnderscoredWord;
6+
const inflector = inflections();
7+
const humans = inflector.humans;
8+
let human, rule, replacement;
9+
10+
options = options || {};
11+
12+
if (options.capitalize === null || options.capitalize === undefined) {
13+
options.capitalize = true;
14+
}
215

3-
export function humanize(lowerCaseAndUnderscoredWord: string, options?: { capitalize?: boolean }) {
4-
let result = "" + lowerCaseAndUnderscoredWord;
5-
const inflector = inflections();
6-
const humans = inflector.humans;
7-
let human, rule, replacement;
8-
9-
options = options || {};
10-
11-
if (options.capitalize === null || options.capitalize === undefined) {
12-
options.capitalize = true;
13-
}
14-
15-
for (let i = 0, ii = humans.length; i < ii; i++) {
16-
human = humans[i];
17-
rule = human[0];
18-
replacement = human[1];
16+
for (let i = 0, ii = humans.length; i < ii; i++) {
17+
human = humans[i];
18+
rule = human[0];
19+
replacement = human[1];
1920

20-
if (rule instanceof RegExp ? rule.test(result) : result.indexOf(rule) > -1) {
21-
result = result.replace(rule, replacement);
22-
break;
21+
if (rule instanceof RegExp ? rule.test(result) : result.indexOf(rule) > -1) {
22+
result = result.replace(rule, replacement);
23+
break;
24+
}
2325
}
24-
}
2526

26-
result = result.replace(/_id$/, "");
27-
result = result.replace(/_/g, " ");
27+
result = result.replace(/_id$/, "");
28+
result = result.replace(/_/g, " ");
2829

29-
result = result.replace(/([a-z\d]*)/gi, function (match) {
30-
return inflector.lowerToAcronyms[match] || match.toLowerCase();
31-
});
32-
33-
if (options.capitalize) {
34-
result = result.replace(/^\w/, function (match) {
35-
return match.toUpperCase();
30+
result = result.replace(/([a-z\d]*)/gi, function (match) {
31+
return inflector.lowerToAcronyms[match] || match.toLowerCase();
3632
});
37-
}
3833

39-
return result;
40-
}
34+
if (options.capitalize) {
35+
result = result.replace(/^\w/, function (match) {
36+
return match.toUpperCase();
37+
});
38+
}
39+
40+
return result;
41+
},
42+
(lowerCaseAndUnderscoredWord, options) => `${lowerCaseAndUnderscoredWord}-${options?.capitalize}`
43+
);

src/parameterize.ts

+26-22
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
1+
import { cacheable } from "./cache";
12
import { transliterate } from "./Transliterator";
23

3-
export function parameterize(string: string, options: { locale?: string; separator?: string | null; preserveCase?: boolean } = {}) {
4-
if (options.separator === undefined) {
5-
options.separator = "-";
6-
}
4+
export const parameterize = cacheable(
5+
(string: string, options: { locale?: string; separator?: string | null; preserveCase?: boolean } = {}) => {
6+
if (options.separator === undefined) {
7+
options.separator = "-";
8+
}
79

8-
if (options.separator === null) {
9-
options.separator = "";
10-
}
10+
if (options.separator === null) {
11+
options.separator = "";
12+
}
1113

12-
// replace accented chars with their ascii equivalents
13-
let result = transliterate(string, { locale: options.locale });
14+
// replace accented chars with their ascii equivalents
15+
let result = transliterate(string, { locale: options.locale });
1416

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

17-
if (options.separator.length) {
18-
const separatorRegex = new RegExp(options.separator);
19+
if (options.separator.length) {
20+
const separatorRegex = new RegExp(options.separator);
1921

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

23-
// remove leading/trailing separator
24-
result = result.replace(new RegExp("^" + separatorRegex.source + "|" + separatorRegex.source + "$", "i"), "");
25-
}
25+
// remove leading/trailing separator
26+
result = result.replace(new RegExp("^" + separatorRegex.source + "|" + separatorRegex.source + "$", "i"), "");
27+
}
2628

27-
if (options.preserveCase) {
28-
return result;
29-
}
29+
if (options.preserveCase) {
30+
return result;
31+
}
3032

31-
return result.toLowerCase();
32-
}
33+
return result.toLowerCase();
34+
},
35+
(string, options) => `${string}-${options?.locale}-${options?.separator}-${options?.preserveCase}`
36+
);

src/pluralize.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { applyInflections } from "./applyInflections";
2+
import { cacheable } from "./cache";
23
import { inflections } from "./Inflector";
34

4-
export function pluralize(word: string, locale = "en") {
5-
return applyInflections(word, inflections(locale).plurals);
6-
}
5+
export const pluralize = cacheable(
6+
(word: string, locale = "en") => applyInflections(word, inflections(locale).plurals),
7+
(word, locale) => `${word}-${locale}`
8+
);

src/singularize.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { applyInflections } from "./applyInflections";
2+
import { cacheable } from "./cache";
23
import { inflections } from "./Inflector";
34

4-
export function singularize(word: string, locale = "en") {
5-
return applyInflections(word, inflections(locale).singulars);
6-
}
5+
export const singularize = cacheable(
6+
(word: string, locale = "en") => applyInflections(word, inflections(locale).singulars),
7+
(word, locale) => `${word}-${locale}`
8+
);

src/underscore.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { cacheable } from "./cache";
12
import { inflections } from "./Inflector";
23

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

7-
export function underscore(camelCasedWord: string) {
8+
export const underscore = cacheable((camelCasedWord: string) => {
89
let result = camelCasedWord;
910
const acronymMatches = inflections().casedAcronymMatcher?.search(camelCasedWord, isWordBoundary);
1011
if (acronymMatches) {
@@ -32,7 +33,7 @@ export function underscore(camelCasedWord: string) {
3233
return `${p3}_${p4}`;
3334
})
3435
.toLowerCase();
35-
}
36+
});
3637

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

0 commit comments

Comments
 (0)