@naturalcycles/js-lib 14.252.1 → 14.253.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -74,6 +74,7 @@ export * from './string/pupa';
74
74
  export * from './string/readingTime';
75
75
  export * from './string/regex';
76
76
  export * from './string/safeJsonStringify';
77
+ export * from './string/slugify';
77
78
  export * from './string/string.util';
78
79
  export * from './string/stringify';
79
80
  export * from './string/url.util';
package/dist/index.js CHANGED
@@ -78,6 +78,7 @@ tslib_1.__exportStar(require("./string/pupa"), exports);
78
78
  tslib_1.__exportStar(require("./string/readingTime"), exports);
79
79
  tslib_1.__exportStar(require("./string/regex"), exports);
80
80
  tslib_1.__exportStar(require("./string/safeJsonStringify"), exports);
81
+ tslib_1.__exportStar(require("./string/slugify"), exports);
81
82
  tslib_1.__exportStar(require("./string/string.util"), exports);
82
83
  tslib_1.__exportStar(require("./string/stringify"), exports);
83
84
  tslib_1.__exportStar(require("./string/url.util"), exports);
@@ -0,0 +1,19 @@
1
+ export interface SlugifyOptions {
2
+ /**
3
+ * Default: `-`
4
+ */
5
+ separator?: string;
6
+ /**
7
+ * Default: true
8
+ */
9
+ lowercase?: boolean;
10
+ /**
11
+ * Default: true
12
+ */
13
+ decamelize?: boolean;
14
+ /**
15
+ * Default: []
16
+ */
17
+ preserveCharacters?: string[];
18
+ }
19
+ export declare function _slugify(s: string, opt?: SlugifyOptions): string;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ // Credit to (adopted from): https://github.com/sindresorhus/slugify/
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports._slugify = _slugify;
5
+ function _slugify(s, opt = {}) {
6
+ opt = {
7
+ separator: '-',
8
+ lowercase: true,
9
+ decamelize: true,
10
+ preserveCharacters: [],
11
+ ...opt,
12
+ };
13
+ if (opt.decamelize) {
14
+ s = decamelize(s);
15
+ }
16
+ const patternSlug = buildPatternSlug(opt);
17
+ if (opt.lowercase) {
18
+ s = s.toLowerCase();
19
+ }
20
+ // based on https://stackoverflow.com/a/23633850/4919972
21
+ // Combining Diacritical Marks
22
+ // https://www.unicode.org/charts/PDF/U0300.pdf
23
+ // biome-ignore lint/suspicious/noMisleadingCharacterClass: ok
24
+ s = s.normalize('NFKD').replaceAll(/[\u0300-\u036F]/g, '');
25
+ // Detect contractions/possessives by looking for any word followed by a `'t`
26
+ // or `'s` in isolation and then remove it.
27
+ s = s.replaceAll(/([a-zA-Z\d]+)'([ts])(\s|$)/g, '$1$2$3');
28
+ s = s.replace(patternSlug, opt.separator);
29
+ s = s.replaceAll('\\', '');
30
+ if (opt.separator) {
31
+ s = removeMootSeparators(s, opt.separator);
32
+ }
33
+ return s;
34
+ }
35
+ function buildPatternSlug(options) {
36
+ let negationSetPattern = String.raw `a-z\d`;
37
+ negationSetPattern += options.lowercase ? '' : 'A-Z';
38
+ if (options.preserveCharacters.length > 0) {
39
+ for (const character of options.preserveCharacters) {
40
+ if (character === options.separator) {
41
+ throw new Error(`The separator character \`${options.separator}\` cannot be included in preserved characters: ${options.preserveCharacters}`);
42
+ }
43
+ negationSetPattern += escapeStringRegexp(character);
44
+ }
45
+ }
46
+ return new RegExp(`[^${negationSetPattern}]+`, 'g');
47
+ }
48
+ function removeMootSeparators(s, separator) {
49
+ const escapedSeparator = escapeStringRegexp(separator);
50
+ return s
51
+ .replaceAll(new RegExp(`${escapedSeparator}{2,}`, 'g'), separator)
52
+ .replaceAll(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '');
53
+ }
54
+ function decamelize(s) {
55
+ return (s
56
+ // Separate capitalized words.
57
+ .replaceAll(/([A-Z]{2,})(\d+)/g, '$1 $2')
58
+ .replaceAll(/([a-z\d]+)([A-Z]{2,})/g, '$1 $2')
59
+ .replaceAll(/([a-z\d])([A-Z])/g, '$1 $2')
60
+ // `[a-rt-z]` matches all lowercase characters except `s`.
61
+ // This avoids matching plural acronyms like `APIs`.
62
+ .replaceAll(/([A-Z]+)([A-Z][a-rt-z\d]+)/g, '$1 $2'));
63
+ }
64
+ // based on: https://github.com/sindresorhus/escape-string-regexp/
65
+ function escapeStringRegexp(s) {
66
+ // Escape characters with special meaning either inside or outside character sets.
67
+ // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
68
+ return s.replaceAll(/[|\\{}()[\]^$+*?.]/g, String.raw `\$&`).replaceAll('-', String.raw `\x2d`);
69
+ }
package/dist-esm/index.js CHANGED
@@ -74,6 +74,7 @@ export * from './string/pupa';
74
74
  export * from './string/readingTime';
75
75
  export * from './string/regex';
76
76
  export * from './string/safeJsonStringify';
77
+ export * from './string/slugify';
77
78
  export * from './string/string.util';
78
79
  export * from './string/stringify';
79
80
  export * from './string/url.util';
@@ -0,0 +1,66 @@
1
+ // Credit to (adopted from): https://github.com/sindresorhus/slugify/
2
+ export function _slugify(s, opt = {}) {
3
+ opt = {
4
+ separator: '-',
5
+ lowercase: true,
6
+ decamelize: true,
7
+ preserveCharacters: [],
8
+ ...opt,
9
+ };
10
+ if (opt.decamelize) {
11
+ s = decamelize(s);
12
+ }
13
+ const patternSlug = buildPatternSlug(opt);
14
+ if (opt.lowercase) {
15
+ s = s.toLowerCase();
16
+ }
17
+ // based on https://stackoverflow.com/a/23633850/4919972
18
+ // Combining Diacritical Marks
19
+ // https://www.unicode.org/charts/PDF/U0300.pdf
20
+ // biome-ignore lint/suspicious/noMisleadingCharacterClass: ok
21
+ s = s.normalize('NFKD').replaceAll(/[\u0300-\u036F]/g, '');
22
+ // Detect contractions/possessives by looking for any word followed by a `'t`
23
+ // or `'s` in isolation and then remove it.
24
+ s = s.replaceAll(/([a-zA-Z\d]+)'([ts])(\s|$)/g, '$1$2$3');
25
+ s = s.replace(patternSlug, opt.separator);
26
+ s = s.replaceAll('\\', '');
27
+ if (opt.separator) {
28
+ s = removeMootSeparators(s, opt.separator);
29
+ }
30
+ return s;
31
+ }
32
+ function buildPatternSlug(options) {
33
+ let negationSetPattern = String.raw `a-z\d`;
34
+ negationSetPattern += options.lowercase ? '' : 'A-Z';
35
+ if (options.preserveCharacters.length > 0) {
36
+ for (const character of options.preserveCharacters) {
37
+ if (character === options.separator) {
38
+ throw new Error(`The separator character \`${options.separator}\` cannot be included in preserved characters: ${options.preserveCharacters}`);
39
+ }
40
+ negationSetPattern += escapeStringRegexp(character);
41
+ }
42
+ }
43
+ return new RegExp(`[^${negationSetPattern}]+`, 'g');
44
+ }
45
+ function removeMootSeparators(s, separator) {
46
+ const escapedSeparator = escapeStringRegexp(separator);
47
+ return s
48
+ .replaceAll(new RegExp(`${escapedSeparator}{2,}`, 'g'), separator)
49
+ .replaceAll(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '');
50
+ }
51
+ function decamelize(s) {
52
+ return (s
53
+ // Separate capitalized words.
54
+ .replaceAll(/([A-Z]{2,})(\d+)/g, '$1 $2')
55
+ .replaceAll(/([a-z\d]+)([A-Z]{2,})/g, '$1 $2')
56
+ .replaceAll(/([a-z\d])([A-Z])/g, '$1 $2')
57
+ // `[a-rt-z]` matches all lowercase characters except `s`.
58
+ // This avoids matching plural acronyms like `APIs`.
59
+ .replaceAll(/([A-Z]+)([A-Z][a-rt-z\d]+)/g, '$1 $2'));
60
+ }
61
+ // based on: https://github.com/sindresorhus/escape-string-regexp/
62
+ function escapeStringRegexp(s) {
63
+ // Escape characters with special meaning either inside or outside character sets.
64
+ // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
65
+ return s.replaceAll(/[|\\{}()[\]^$+*?.]/g, String.raw `\$&`).replaceAll('-', String.raw `\x2d`);
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.252.1",
3
+ "version": "14.253.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "build": "dev-lib build-esm-cjs",
package/src/index.ts CHANGED
@@ -74,6 +74,7 @@ export * from './string/pupa'
74
74
  export * from './string/readingTime'
75
75
  export * from './string/regex'
76
76
  export * from './string/safeJsonStringify'
77
+ export * from './string/slugify'
77
78
  export * from './string/string.util'
78
79
  export * from './string/stringify'
79
80
  export * from './string/url.util'
@@ -0,0 +1,107 @@
1
+ // Credit to (adopted from): https://github.com/sindresorhus/slugify/
2
+
3
+ export interface SlugifyOptions {
4
+ /**
5
+ * Default: `-`
6
+ */
7
+ separator?: string
8
+ /**
9
+ * Default: true
10
+ */
11
+ lowercase?: boolean
12
+ /**
13
+ * Default: true
14
+ */
15
+ decamelize?: boolean
16
+ /**
17
+ * Default: []
18
+ */
19
+ preserveCharacters?: string[]
20
+ }
21
+
22
+ export function _slugify(s: string, opt: SlugifyOptions = {}): string {
23
+ opt = {
24
+ separator: '-',
25
+ lowercase: true,
26
+ decamelize: true,
27
+ preserveCharacters: [],
28
+ ...opt,
29
+ }
30
+
31
+ if (opt.decamelize) {
32
+ s = decamelize(s)
33
+ }
34
+
35
+ const patternSlug = buildPatternSlug(opt)
36
+
37
+ if (opt.lowercase) {
38
+ s = s.toLowerCase()
39
+ }
40
+
41
+ // based on https://stackoverflow.com/a/23633850/4919972
42
+ // Combining Diacritical Marks
43
+ // https://www.unicode.org/charts/PDF/U0300.pdf
44
+ // biome-ignore lint/suspicious/noMisleadingCharacterClass: ok
45
+ s = s.normalize('NFKD').replaceAll(/[\u0300-\u036F]/g, '')
46
+
47
+ // Detect contractions/possessives by looking for any word followed by a `'t`
48
+ // or `'s` in isolation and then remove it.
49
+ s = s.replaceAll(/([a-zA-Z\d]+)'([ts])(\s|$)/g, '$1$2$3')
50
+
51
+ s = s.replace(patternSlug, opt.separator!)
52
+ s = s.replaceAll('\\', '')
53
+
54
+ if (opt.separator) {
55
+ s = removeMootSeparators(s, opt.separator)
56
+ }
57
+
58
+ return s
59
+ }
60
+
61
+ function buildPatternSlug(options: any): RegExp {
62
+ let negationSetPattern = String.raw`a-z\d`
63
+ negationSetPattern += options.lowercase ? '' : 'A-Z'
64
+
65
+ if (options.preserveCharacters.length > 0) {
66
+ for (const character of options.preserveCharacters) {
67
+ if (character === options.separator) {
68
+ throw new Error(
69
+ `The separator character \`${options.separator}\` cannot be included in preserved characters: ${options.preserveCharacters}`,
70
+ )
71
+ }
72
+
73
+ negationSetPattern += escapeStringRegexp(character)
74
+ }
75
+ }
76
+
77
+ return new RegExp(`[^${negationSetPattern}]+`, 'g')
78
+ }
79
+
80
+ function removeMootSeparators(s: string, separator: string): string {
81
+ const escapedSeparator = escapeStringRegexp(separator)
82
+
83
+ return s
84
+ .replaceAll(new RegExp(`${escapedSeparator}{2,}`, 'g'), separator)
85
+ .replaceAll(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '')
86
+ }
87
+
88
+ function decamelize(s: string): string {
89
+ return (
90
+ s
91
+ // Separate capitalized words.
92
+ .replaceAll(/([A-Z]{2,})(\d+)/g, '$1 $2')
93
+ .replaceAll(/([a-z\d]+)([A-Z]{2,})/g, '$1 $2')
94
+
95
+ .replaceAll(/([a-z\d])([A-Z])/g, '$1 $2')
96
+ // `[a-rt-z]` matches all lowercase characters except `s`.
97
+ // This avoids matching plural acronyms like `APIs`.
98
+ .replaceAll(/([A-Z]+)([A-Z][a-rt-z\d]+)/g, '$1 $2')
99
+ )
100
+ }
101
+
102
+ // based on: https://github.com/sindresorhus/escape-string-regexp/
103
+ function escapeStringRegexp(s: string): string {
104
+ // Escape characters with special meaning either inside or outside character sets.
105
+ // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
106
+ return s.replaceAll(/[|\\{}()[\]^$+*?.]/g, String.raw`\$&`).replaceAll('-', String.raw`\x2d`)
107
+ }