Skip to content

Commit

Permalink
Project update. [p][robotic]
Browse files Browse the repository at this point in the history
  • Loading branch information
jaswrks committed Dec 26, 2024
1 parent 426e740 commit 7522ffe
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 301 deletions.
8 changes: 4 additions & 4 deletions .env.vault
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ DOTENV_VAULT_MAIN="7GW98NfL4hM4N6HbfeDXCKreVxKvcVPupFRnZ8XJ+pomaQ=="
DOTENV_VAULT_MAIN_VERSION=1

# dev
DOTENV_VAULT_DEV="ZJiqCdev9u/FR/8ukgit8ksRtzcJ67EHTYhoORpMQJowqbSeZXpJbzC3yizDPGdRyEEAbw6hxW34aKbz4N54Yxdl7lvshgZYaRRu0aIaG4ejnjR5FNPekhI7KIq70Y3b13KmRAOhxCXRyICjwYWv6FpMMwCOCITfkum2l8PhERKDh7xPeUBq/JbK3hTnVMzqSAmsdB6c8ZejXkUx0P0jO3KtAcmwTFxplSMH0rndzw904QnNlg0sv04h/mEytBQxZQ=="
DOTENV_VAULT_DEV_VERSION=551
DOTENV_VAULT_DEV="rCSPG3dGGP0xJPmHjpti9TskRGZwgE/TB0Iq4KuTNS9wrCMRxIKrnCtV/YuvJwGrISAZEBtnA7nWJdKL3ueyi05/JiTlODbk1imm+k+wb66Iento4DugdpLzg9ikh/DKuPjqG5CFu3XKS9aMe/NP/fzjf3VSPzw+an0xMtxkmHPCQbtRu8v8jxJk8RxTCoobLUFrYYpHVihNgYa3iC/C+32JO4Y6X8LR824lRGyiXIIHLev3SuyyDbM1wZ6a+7DEng=="
DOTENV_VAULT_DEV_VERSION=553

# ci
DOTENV_VAULT_CI="SEtohW8eYpeCJNuQFr4SGFgiz4hsPB+Ta2JZCpMOw+ciZuRU4ySyk9dBZD59D66XzGKVEEEZLzc5y4u8NzlnYwL+sdx5KcTrD07OE3siy1ozKaHoXAcbAwfgrPvSp9/ABhNBuJxckgkRxqYkwkwkYO2WGovON6IMYl0fLYtkTA=="
DOTENV_VAULT_CI_VERSION=551
DOTENV_VAULT_CI="yklbTbRFAfnxbiRe2xJjvBuXmqqNW80RNcCdlovIfRKut7ro+tQPPLhly3AB+cml2GyRGWToiO8Q5zJuKAZdRBK4R637KJOiRgqjY2QSr3jsaPedkYSf80Dq2qRWEsvHho5VXA8o0NANmVtdSNieoPUUv4ptR8XHOoe7noVX9A=="
DOTENV_VAULT_CI_VERSION=553

# stage
DOTENV_VAULT_STAGE="aRP8su2YV4jZu3w1HZ/SLaots0IwJDFw75TCpvXEFeNp7tw="
Expand Down
525 changes: 266 additions & 259 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "1.0.962",
"version": "1.0.963",
"license": "GPL-3.0-or-later",
"name": "@clevercanyon/utilities",
"description": "Utilities for JavaScript apps running in any environment.",
Expand Down
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const pkgSlug = $fnꓺmemo(12, (value?: string): string => {
value ??= pkgName(); // Uses current app’s package name as the default value.
value = value.replace(/^@/u, '').split('/')[1] || value; // Scoped package names.

return $str.kebabCase(value, { asciiOnly: true, letterFirst: 'x' });
return $str.kebabCase(value, { asciiOnly: true, splitStrategy: 'boundariesAndCaseOnly', letterFirst: 'x' });
});

/**
Expand Down
2 changes: 1 addition & 1 deletion src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2074,7 +2074,7 @@ export const defaultSecurityHeaders = (options?: SecurityHeaderOptions): { [x: s
export const c10nSecurityHeaders = (options?: SecurityHeaderOptions): { [x: string]: string } => {
const opts = $obj.defaults({}, options || {}, { cspNonce: '', enableCORs: false }) as Required<SecurityHeaderOptions>,
defaultHeaders = defaultSecurityHeaders(opts),
trustedCSPHostnames = 'clevercanyon.com *.clevercanyon.com hop.gdn *.hop.gdn wobots.com *.wobots.com',
trustedCSPHostnames = 'clevercanyon.com *.clevercanyon.com hop.gdn *.hop.gdn o5p.me *.o5p.me',
cloudflareCSPHostnames = 'ajax.cloudflare.com challenges.cloudflare.com static.cloudflareinsights.com';

return {
Expand Down
80 changes: 49 additions & 31 deletions src/str.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,19 @@ const ipV4MaxLength = 15,

'&nbsp;': '\u00A0',
};
// Please be cautious, this has the `g`, and therefore it has state.
const wordSplittingRegExp = /([^\p{L}\p{N}]+|(?<=\p{L})(?=\p{N})|(?<=\p{N})(?=\p{L})|(?<=[\p{Ll}\p{N}])(?=\p{Lu})|(?<=\p{Lu})(?=\p{Lu}\p{Ll})|(?<=[\p{L}\p{N}])(?=\p{Lu}\p{Ll}))/gu;
// Whitespace-only regular expression for word splitting.
const whitespaceOnlyWordSplittingRegExp = /(\s+)/u;

// Regular expression for email validating; {@see https://o5p.me/goVSTH}.
// Boundaries-only regular expression for word splitting.
const boundariesOnlyWordSplittingRegExp = /([^\p{L}\p{N}]+)/u;

// Boundaries-and-caSe-only regular expression for word splitting.
const boundariesAndCaseOnlyWordSplittingRegExp = /([^\p{L}\p{N}]+|(?<=\p{Ll})(?=\p{Lu})|(?<=\p{L})(?=\p{Lu}\p{Ll}))/u;

// Default regular expression for word splitting, which includes everything, including numeric boundaries.
const defaultWordSplittingRegExp = /([^\p{L}\p{N}]+|(?<=\p{L})(?=\p{N})|(?<=\p{N})(?=\p{L})|(?<=[\p{Ll}\p{N}])(?=\p{Lu})|(?<=[\p{L}\p{N}])(?=\p{Lu}\p{Ll}))/u;

// Regular expression for email validation; {@see https://o5p.me/goVSTH}.
const emailRegExp = /^[a-z0-9.!#$%&*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9-]+)*$/iu;

// Regular expression for fulltext search query columns prefix.
Expand All @@ -69,16 +78,16 @@ const hasAsianCharsRegExp =
export type TrimOptions = { left?: boolean; right?: boolean };
export type ClipOptions = { maxBytes?: number; maxChars?: number; indicator?: string };
export type MidClipOptions = { maxBytes?: number; maxChars?: number; indicator?: string };
export type SplitWordOptions = { whitespaceOnly?: boolean };
export type SplitWordOptions = { strategy?: 'whitespaceOnly' | 'boundariesOnly' | 'boundariesAndCaseOnly' | 'default' };

export type TitleCaseOptions = { asciiOnly?: boolean; splitOnWhitespaceOnly?: boolean };
export type LowerCaseOptions = { asciiOnly?: boolean; splitOnWhitespaceOnly?: boolean };
export type UpperCaseOptions = LowerCaseOptions; // Same options as lowerCase.
export type TitleCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy'] };
export type LowerCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy'] };
export type UpperCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy'] };

export type StudlyCaseOptions = { asciiOnly?: boolean; letterFirst?: string };
export type CamelCaseOptions = { asciiOnly?: boolean; letterFirst?: string };
export type KebabCaseOptions = { asciiOnly?: boolean; letterFirst?: string };
export type SnakeCaseOptions = KebabCaseOptions; // Same options as kebabCase.
export type StudlyCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy']; letterFirst?: string };
export type CamelCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy']; letterFirst?: string };
export type KebabCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy']; letterFirst?: string };
export type SnakeCaseOptions = { asciiOnly?: boolean; splitStrategy?: SplitWordOptions['strategy']; letterFirst?: string };

export type QuoteOptions = { type?: 'single' | 'double' };
export type UnquoteOptions = { type?: string };
Expand Down Expand Up @@ -444,15 +453,23 @@ export const split = (str: string, delimiter: string | RegExp, limit?: number):
/**
* Splits words intelligently, including `-`, `_`, camelCase, PascalCase, and other considerations.
*
* @param str String to consider.
* @param str String to consider.
* @param options Options (all optional).
*
* @returns Array of all words.
* @returns Array of all words.
*/
export const splitWords = (str: string, options?: SplitWordOptions): string[] => {
const opts = $obj.defaults({}, options || {}, { whitespaceOnly: false }) as Required<SplitWordOptions>;
const opts = $obj.defaults({}, options || {}, { strategy: 'default' }) as Required<SplitWordOptions>;
let wordSplittingRegExp = defaultWordSplittingRegExp; // Initialize.

if (opts.whitespaceOnly) {
return str.split(/\s+/u); // Whitespace only.
if ('whitespaceOnly' === opts.strategy) {
wordSplittingRegExp = whitespaceOnlyWordSplittingRegExp;
//
} else if ('boundariesOnly' === opts.strategy) {
wordSplittingRegExp = boundariesOnlyWordSplittingRegExp;
//
} else if ('boundariesAndCaseOnly' === opts.strategy) {
wordSplittingRegExp = boundariesAndCaseOnlyWordSplittingRegExp;
}
return str
.split(wordSplittingRegExp)
Expand Down Expand Up @@ -582,11 +599,11 @@ export const capitalize = (str: string): string => {
* For some added inspiration, see: <https://o5p.me/DChQQ2>, <https://www.npmjs.com/package/title-case?activeTab=code>.
*/
export const titleCase = (str: string, options?: TitleCaseOptions): string => {
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitOnWhitespaceOnly: false }) as Required<TitleCaseOptions>;
let words = splitWords(str, { whitespaceOnly: opts.splitOnWhitespaceOnly }); // Splits words intelligently.
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitStrategy: 'default' }) as Required<TitleCaseOptions>;
let words = splitWords(str, { strategy: opts.splitStrategy }); // Splits words intelligently.

if (opts.asciiOnly /* Split again. */) {
words = splitWords(asciiOnly(words.join(' ')), { whitespaceOnly: opts.splitOnWhitespaceOnly });
words = splitWords(asciiOnly(words.join(' ')), { strategy: opts.splitStrategy });
}
for (let key = 0; key < words.length; key++) {
words[key] = words[key]
Expand All @@ -606,11 +623,11 @@ export const titleCase = (str: string, options?: TitleCaseOptions): string => {
* @returns Modified string.
*/
export const lowerCase = (str: string, options?: LowerCaseOptions): string => {
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitOnWhitespaceOnly: false }) as Required<LowerCaseOptions>;
let words = splitWords(str, { whitespaceOnly: opts.splitOnWhitespaceOnly }); // Splits words intelligently.
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitStrategy: 'default' }) as Required<LowerCaseOptions>;
let words = splitWords(str, { strategy: opts.splitStrategy }); // Splits words intelligently.

if (opts.asciiOnly /* Split again. */) {
words = splitWords(asciiOnly(words.join(' ')), { whitespaceOnly: opts.splitOnWhitespaceOnly });
words = splitWords(asciiOnly(words.join(' ')), { strategy: opts.splitStrategy });
}
return words.join(' ').toLowerCase();
};
Expand All @@ -636,11 +653,11 @@ export const upperCase = (str: string, options?: UpperCaseOptions): string => {
* @returns Modified string.
*/
export const studlyCase = (str: string, options?: StudlyCaseOptions): string => {
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, letterFirst: '' }) as Required<StudlyCaseOptions>;
let words = splitWords(str); // Splits words intelligently.
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitStrategy: 'default', letterFirst: '' }) as Required<StudlyCaseOptions>;
let words = splitWords(str, { strategy: opts.splitStrategy }); // Splits words intelligently.

if (opts.asciiOnly /* Split again. */) {
words = splitWords(asciiOnly(words.join(' ')));
words = splitWords(asciiOnly(words.join(' ')), { strategy: opts.splitStrategy });
}
for (let key = 0; key < words.length; key++) {
words[key] = capitalize(words[key]);
Expand All @@ -663,15 +680,16 @@ export const studlyCase = (str: string, options?: StudlyCaseOptions): string =>
* @returns Modified string.
*/
export const camelCase = (str: string, options?: CamelCaseOptions): string => {
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, letterFirst: '' }) as Required<CamelCaseOptions>;
let words = splitWords(str); // Splits words intelligently.
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitStrategy: 'default', letterFirst: '' }) as Required<CamelCaseOptions>;
let words = splitWords(str, { strategy: opts.splitStrategy }); // Splits words intelligently.

if (opts.asciiOnly /* Split again. */) {
words = splitWords(asciiOnly(words.join(' ')));
words = splitWords(asciiOnly(words.join(' ')), { strategy: opts.splitStrategy });
}
for (let key = 0; key < words.length; key++) {
if (0 === key) {
words[key] = words[key].toLowerCase();

if (opts.letterFirst && !/^\p{L}/u.test(words[key])) {
words[key] = opts.letterFirst + words[key];
}
Expand All @@ -695,11 +713,11 @@ export const camelCase = (str: string, options?: CamelCaseOptions): string => {
* @returns Modified string.
*/
export const kebabCase = (str: string, options?: KebabCaseOptions): string => {
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, letterFirst: '' }) as Required<KebabCaseOptions>;
let words = splitWords(str); // Splits words intelligently.
const opts = $obj.defaults({}, options || {}, { asciiOnly: false, splitStrategy: 'default', letterFirst: '' }) as Required<KebabCaseOptions>;
let words = splitWords(str, { strategy: opts.splitStrategy }); // Splits words intelligently.

if (opts.asciiOnly /* Split again. */) {
words = splitWords(asciiOnly(words.join(' ')));
words = splitWords(asciiOnly(words.join(' ')), { strategy: opts.splitStrategy });
}
for (let key = 0; key < words.length; key++) {
words[key] = words[key].toLowerCase();
Expand Down
3 changes: 3 additions & 0 deletions src/tests/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ describe('$app', async () => {
test('.pkgName', async () => {
expect($is.string($app.$pkgName)).toBe(true);
});
test('.pkgSlug()', async () => {
expect($app.pkgSlug('workers.o5p.me')).toBe('workers-o5p-me');
});
});
8 changes: 4 additions & 4 deletions src/tests/str/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('$str', async () => {
expect($str.splitWords('foo-bar-bazBiz-PascalCase-snake_case-kebab-case')) //
.toStrictEqual(['foo', 'bar', 'baz', 'Biz', 'Pascal', 'Case', 'snake', 'case', 'kebab', 'case']);

expect($str.splitWords('foo bar bazBiz PascalCase snake_case kebab-case', { whitespaceOnly: true })) //
expect($str.splitWords('foo bar bazBiz PascalCase snake_case kebab-case', { strategy: 'whitespaceOnly' })) //
.toStrictEqual(['foo', 'bar', 'bazBiz', 'PascalCase', 'snake_case', 'kebab-case']);

expect($str.splitWords('aeiouAEIOUaeiouyAEIOUYaeiouAEIOUanoANOaeiouyAEIOUYçÇßØøÅåÆæœ')) //
Expand Down Expand Up @@ -220,7 +220,7 @@ describe('$str', async () => {
expect($str.titleCase('àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ')).toBe('Àèìòù Àèìò Ùáéíóúý Áéíóú Ýâêîôû Âêîô Ûãñõ Ãñ Õäëïöüÿ Äëïöü Ÿç Çß Øø Åå Ææœ');
expect($str.titleCase('ÀÈÌÒÙàèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ')).toBe('Àèìò Ùàèìòùáéíóúý Áéíóú Ýâêîôû Âêîô Ûãñõ Ãñ Õäëïöüÿ Äëïöü Ÿç Çß Øø Åå Ææœ');

expect($str.titleCase('ÀÈÌÒÙ àèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { splitOnWhitespaceOnly: true })) //
expect($str.titleCase('ÀÈÌÒÙ àèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { splitStrategy: 'whitespaceOnly' })) //
.toBe('Àèìòù Àèìòùáéíóúýáéíóúýâêîôûâêîôûãñõãñõäëïöüÿäëïöüÿççßøøååææœ');

expect($str.titleCase('ÀÈÌÒÙàèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { asciiOnly: true })) //
Expand All @@ -239,7 +239,7 @@ describe('$str', async () => {
expect($str.lowerCase('àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ')).toBe('àèìòù àèìò ùáéíóúý áéíóú ýâêîôû âêîô ûãñõ ãñ õäëïöüÿ äëïöü ÿç çß øø åå ææœ');
expect($str.lowerCase('ÀÈÌÒÙàèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ')).toBe('àèìò ùàèìòùáéíóúý áéíóú ýâêîôû âêîô ûãñõ ãñ õäëïöüÿ äëïöü ÿç çß øø åå ææœ');

expect($str.lowerCase('ÀÈÌÒÙ àèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { splitOnWhitespaceOnly: true })) //
expect($str.lowerCase('ÀÈÌÒÙ àèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { splitStrategy: 'whitespaceOnly' })) //
.toBe('àèìòù àèìòùáéíóúýáéíóúýâêîôûâêîôûãñõãñõäëïöüÿäëïöüÿççßøøååææœ');

expect($str.lowerCase('ÀÈÌÒÙàèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { asciiOnly: true })) //
Expand All @@ -258,7 +258,7 @@ describe('$str', async () => {
expect($str.upperCase('àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ')).toBe('ÀÈÌÒÙ ÀÈÌÒ ÙÁÉÍÓÚÝ ÁÉÍÓÚ ÝÂÊÎÔÛ ÂÊÎÔ ÛÃÑÕ ÃÑ ÕÄËÏÖÜŸ ÄËÏÖÜ ŸÇ ÇSS ØØ ÅÅ ÆÆŒ');
expect($str.upperCase('ÀÈÌÒÙàèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ')).toBe('ÀÈÌÒ ÙÀÈÌÒÙÁÉÍÓÚÝ ÁÉÍÓÚ ÝÂÊÎÔÛ ÂÊÎÔ ÛÃÑÕ ÃÑ ÕÄËÏÖÜŸ ÄËÏÖÜ ŸÇ ÇSS ØØ ÅÅ ÆÆŒ');

expect($str.upperCase('ÀÈÌÒÙ àèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { splitOnWhitespaceOnly: true })) //
expect($str.upperCase('ÀÈÌÒÙ àèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { splitStrategy: 'whitespaceOnly' })) //
.toBe('ÀÈÌÒÙ ÀÈÌÒÙÁÉÍÓÚÝÁÉÍÓÚÝÂÊÎÔÛÂÊÎÔÛÃÑÕÃÑÕÄËÏÖÜŸÄËÏÖÜŸÇÇSSØØÅÅÆÆŒ');

expect($str.upperCase('ÀÈÌÒÙàèìòùáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ', { asciiOnly: true })) //
Expand Down

0 comments on commit 7522ffe

Please sign in to comment.