utilitish 0.0.9 → 0.0.10
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.
|
@@ -152,31 +152,31 @@ declare global {
|
|
|
152
152
|
*
|
|
153
153
|
* @example
|
|
154
154
|
* // Custom replacements
|
|
155
|
-
* 'Test ♀'.slugify({
|
|
156
|
-
* 'User♂@domain.com'.slugify({
|
|
155
|
+
* 'Test ♀'.slugify(SlugifyConfig.builder().withCustomReplacements({ "♀": "feminin" }).build()); // 'test-feminin'
|
|
156
|
+
* 'User♂@domain.com'.slugify(SlugifyConfig.builder().withCustomReplacements({ "♂": "masculin", "@": "at" }).build()); // 'usermasculinatdomain-com'
|
|
157
157
|
*
|
|
158
158
|
* @example
|
|
159
159
|
* // Custom separator
|
|
160
|
-
* 'Hello World'.slugify(
|
|
161
|
-
* 'Hello World'.slugify(
|
|
160
|
+
* 'Hello World'.slugify(SlugifyConfig.builder().withSeparator("_").build()); // 'hello_world'
|
|
161
|
+
* 'Hello World'.slugify(SlugifyConfig.builder().withSeparator("--").build()); // 'hello--world'
|
|
162
162
|
*
|
|
163
163
|
* @example
|
|
164
164
|
* // Preserve accents
|
|
165
|
-
* 'Éléphant'.slugify(
|
|
165
|
+
* 'Éléphant'.slugify(SlugifyConfig.builder().withRemoveAccents(false).build()); // 'éléphant'
|
|
166
166
|
*
|
|
167
167
|
* @example
|
|
168
168
|
* // Disable lowercasing
|
|
169
|
-
* 'Hello World'.slugify(
|
|
169
|
+
* 'Hello World'.slugify(SlugifyConfig.builder().withCase('default').build()); // 'Hello-World'
|
|
170
170
|
*
|
|
171
171
|
* @example
|
|
172
172
|
* // Max length
|
|
173
|
-
* 'Very long string'.slugify(
|
|
173
|
+
* 'Very long string'.slugify(SlugifyConfig.builder().withMaxLength(8).build()); // 'very-lon'
|
|
174
174
|
*
|
|
175
175
|
* @example
|
|
176
176
|
* // Custom transformers
|
|
177
|
-
* 'hello world'.slugify(
|
|
178
|
-
*
|
|
179
|
-
*
|
|
177
|
+
* 'hello world'.slugify(SlugifyConfig.builder()
|
|
178
|
+
* .withTransformers([(str) => str.replace(/world/g, 'universe')])
|
|
179
|
+
* .build()); // 'hello-universe'
|
|
180
180
|
*
|
|
181
181
|
* @remarks
|
|
182
182
|
* - Uses global configuration by default (see setSlugifyConfig)
|
|
@@ -187,6 +187,7 @@ declare global {
|
|
|
187
187
|
* - Removes leading/trailing separators
|
|
188
188
|
* - Collapses multiple consecutive separators into one
|
|
189
189
|
*
|
|
190
|
+
* @see SlugifyConfig
|
|
190
191
|
* @see setSlugifyConfig
|
|
191
192
|
* @see getSlugifyConfig
|
|
192
193
|
* @see resetSlugifyConfig
|
|
@@ -78,68 +78,86 @@ describe('String.prototype', () => {
|
|
|
78
78
|
});
|
|
79
79
|
describe('custom replacements', () => {
|
|
80
80
|
it('should apply custom replacements per call', () => {
|
|
81
|
-
expect('Test ♀'.slugify({
|
|
82
|
-
expect('User♂@domain.com'.slugify({
|
|
81
|
+
expect('Test ♀'.slugify(slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '♀': 'feminin' }).build())).toBe('test-feminin');
|
|
82
|
+
expect('User♂@domain.com'.slugify(slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '♂': 'masculin', '@': 'at' }).build())).toBe('usermasculinatdomain-com');
|
|
83
83
|
});
|
|
84
84
|
it('should handle special regex characters in replacements', () => {
|
|
85
|
-
expect('Test [bracket]'.slugify({
|
|
85
|
+
expect('Test [bracket]'.slugify(slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '[': 'left', ']': 'right' }).build())).toBe('test-leftbracketright');
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
88
|
describe('separator customization', () => {
|
|
89
89
|
it('should use custom separator globally', () => {
|
|
90
|
-
(0, slugify_config_1.setSlugifyConfig)(
|
|
90
|
+
(0, slugify_config_1.setSlugifyConfig)(slugify_config_1.SlugifyConfig.builder().withSeparator('_').build());
|
|
91
91
|
expect('Hello World'.slugify()).toBe('hello_world');
|
|
92
92
|
expect('Test String'.slugify()).toBe('test_string');
|
|
93
93
|
});
|
|
94
94
|
it('should use custom separator per call', () => {
|
|
95
|
-
expect('Hello World'.slugify(
|
|
96
|
-
expect('Test String'.slugify(
|
|
95
|
+
expect('Hello World'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('_').build())).toBe('hello_world');
|
|
96
|
+
expect('Test String'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('__').build())).toBe('test__string');
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
describe('case transformation', () => {
|
|
100
100
|
it('should respect lowercase setting globally', () => {
|
|
101
|
-
(0, slugify_config_1.setSlugifyConfig)(
|
|
101
|
+
(0, slugify_config_1.setSlugifyConfig)(slugify_config_1.SlugifyConfig.builder().withCase('default').build());
|
|
102
102
|
expect('Hello World'.slugify()).toBe('Hello-World');
|
|
103
103
|
});
|
|
104
104
|
it('should respect lowercase setting per call', () => {
|
|
105
|
-
expect('Hello World'.slugify(
|
|
105
|
+
expect('Hello World'.slugify(slugify_config_1.SlugifyConfig.builder().withCase('default').build())).toBe('Hello-World');
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
describe('accent removal', () => {
|
|
109
109
|
it('should respect removeAccents setting per call', () => {
|
|
110
|
-
expect('Éléphant'.slugify(
|
|
110
|
+
expect('Éléphant'.slugify(slugify_config_1.SlugifyConfig.builder().withRemoveAccents(false).build())).toBe('éléphant');
|
|
111
111
|
});
|
|
112
112
|
});
|
|
113
113
|
describe('allowed characters', () => {
|
|
114
114
|
it('should use custom allowed characters', () => {
|
|
115
|
-
expect('Hello123!@#'.slugify(
|
|
115
|
+
expect('Hello123!@#'.slugify(slugify_config_1.SlugifyConfig.builder()
|
|
116
|
+
.withAllowedChars(/[a-zA-Z0-9_]/)
|
|
117
|
+
.build())).toBe('hello123');
|
|
118
|
+
});
|
|
119
|
+
it('should throw for invalid allowedChars regex in call', () => {
|
|
120
|
+
expect(() => slugify_config_1.SlugifyConfig.builder()
|
|
121
|
+
.withAllowedChars(/foo|bar/)
|
|
122
|
+
.build()).toThrowError(/Invalid allowedChars:/);
|
|
123
|
+
});
|
|
124
|
+
it('should throw for invalid allowedChars regex in global config', () => {
|
|
125
|
+
expect(() => (0, slugify_config_1.setSlugifyConfig)(slugify_config_1.SlugifyConfig.builder()
|
|
126
|
+
.withAllowedChars(/foo|bar/)
|
|
127
|
+
.build())).toThrowError(/Invalid allowedChars:/);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('separator special characters', () => {
|
|
131
|
+
it('should handle regex-special separators without breaking', () => {
|
|
132
|
+
expect('a b+c'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('.').build())).toBe('a.b.c');
|
|
133
|
+
expect('a.b.c'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('*').build())).toBe('a*b*c');
|
|
116
134
|
});
|
|
117
135
|
});
|
|
118
136
|
describe('max length', () => {
|
|
119
137
|
it('should truncate to max length', () => {
|
|
120
|
-
expect('very-long-string-here'.slugify(
|
|
121
|
-
expect('short'.slugify(
|
|
138
|
+
expect('very-long-string-here'.slugify(slugify_config_1.SlugifyConfig.builder().withMaxLength(10).build())).toBe('very-long');
|
|
139
|
+
expect('short'.slugify(slugify_config_1.SlugifyConfig.builder().withMaxLength(10).build())).toBe('short');
|
|
122
140
|
});
|
|
123
141
|
it('should remove trailing separator after truncation', () => {
|
|
124
|
-
expect('hello-world-test'.slugify(
|
|
142
|
+
expect('hello-world-test'.slugify(slugify_config_1.SlugifyConfig.builder().withMaxLength(8).build())).toBe('hello-wo');
|
|
125
143
|
});
|
|
126
144
|
});
|
|
127
145
|
describe('custom transformers', () => {
|
|
128
146
|
it('should apply custom transformers', () => {
|
|
129
|
-
const config =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
const config = slugify_config_1.SlugifyConfig.builder()
|
|
148
|
+
.withTransformers([
|
|
149
|
+
(str) => str.replace(/test/g, 'example'),
|
|
150
|
+
(str) => str.toUpperCase(),
|
|
151
|
+
])
|
|
152
|
+
.withCase('upper')
|
|
153
|
+
.build();
|
|
136
154
|
expect('this is a test'.slugify(config)).toBe('THIS-IS-A-EXAMPLE');
|
|
137
155
|
});
|
|
138
156
|
});
|
|
139
157
|
describe('configuration merging', () => {
|
|
140
158
|
it('should merge global and local config correctly', () => {
|
|
141
159
|
// Local config should override global defaults
|
|
142
|
-
expect('Test ♀'.slugify(
|
|
160
|
+
expect('Test ♀'.slugify(slugify_config_1.SlugifyConfig.builder().withSeparator('_').withCustomReplacements({ '♀': 'female' }).build())).toBe('test_female');
|
|
143
161
|
});
|
|
144
162
|
});
|
|
145
163
|
});
|
|
@@ -1,73 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Configuration
|
|
2
|
+
* Configuration class for the slugify functionality.
|
|
3
3
|
* Provides comprehensive customization options for URL slug generation.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
5
|
+
export declare class SlugifyConfig {
|
|
6
|
+
private readonly _customReplacements;
|
|
7
|
+
private readonly _separator;
|
|
8
|
+
private readonly _case;
|
|
9
|
+
private readonly _removeAccents;
|
|
10
|
+
private readonly _allowedChars?;
|
|
11
|
+
private readonly _maxLength?;
|
|
12
|
+
private readonly _transformers;
|
|
13
|
+
private constructor();
|
|
14
|
+
static builder(): SlugifyConfigBuilder;
|
|
15
|
+
static default(): SlugifyConfig;
|
|
16
|
+
static create(builder: SlugifyConfigBuilder): SlugifyConfig;
|
|
17
|
+
get customReplacements(): Record<string, string>;
|
|
18
|
+
get separator(): string;
|
|
19
|
+
get case(): 'lower' | 'upper' | 'default';
|
|
20
|
+
get removeAccents(): boolean;
|
|
21
|
+
get allowedChars(): RegExp | undefined;
|
|
22
|
+
get maxLength(): number | undefined;
|
|
23
|
+
get transformers(): Array<(str: string) => string>;
|
|
24
|
+
merge(other: SlugifyConfig): SlugifyConfig;
|
|
25
|
+
private escapeRegex;
|
|
26
|
+
getCharClass(): RegExp;
|
|
27
|
+
applyCustomReplacements(str: string): string;
|
|
28
|
+
applyTransformers(str: string): string;
|
|
29
|
+
applyRemoveAccents(str: string): string;
|
|
30
|
+
replaceNonAllowedChars(str: string): string;
|
|
31
|
+
trimSeparators(str: string): string;
|
|
32
|
+
applyCase(str: string): string;
|
|
33
|
+
applyMaxLength(str: string): string;
|
|
34
|
+
collapseSeparators(str: string): string;
|
|
6
35
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* { "♀": "feminin", "♂": "masculin", "@": "at" }
|
|
36
|
+
* Applies the slugify transformations to the given string using this configuration.
|
|
37
|
+
* @param str - The input string to slugify
|
|
38
|
+
* @returns The slugified string
|
|
12
39
|
*/
|
|
13
|
-
|
|
40
|
+
slugify(str: string): string;
|
|
14
41
|
/**
|
|
15
|
-
*
|
|
16
|
-
* @
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Whether to convert the result to lowercase.
|
|
21
|
-
* @default true
|
|
22
|
-
*/
|
|
23
|
-
lowercase?: boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Whether to remove accents and diacritical marks.
|
|
26
|
-
* @default true
|
|
27
|
-
*/
|
|
28
|
-
removeAccents?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Regular expression pattern for characters to keep.
|
|
31
|
-
* Characters not matching this pattern will be replaced with the separator.
|
|
32
|
-
* @default "/[a-zA-Z0-9]/"
|
|
42
|
+
* Static method to slugify a string with a temporary configuration built from the builder.
|
|
43
|
+
* @param str - The input string to slugify
|
|
44
|
+
* @param builderFn - A function that configures the builder
|
|
45
|
+
* @returns The slugified string
|
|
33
46
|
*/
|
|
47
|
+
static slugifyWith(str: string, builderFn: (builder: SlugifyConfigBuilder) => SlugifyConfigBuilder): string;
|
|
48
|
+
}
|
|
49
|
+
export declare class SlugifyConfigBuilder {
|
|
50
|
+
customReplacements: Record<string, string>;
|
|
51
|
+
separator: string;
|
|
52
|
+
case: 'lower' | 'upper' | 'default';
|
|
53
|
+
removeAccents: boolean;
|
|
34
54
|
allowedChars?: RegExp;
|
|
35
|
-
/**
|
|
36
|
-
* Maximum length of the resulting slug (excluding separator trimming).
|
|
37
|
-
* If exceeded, the slug will be truncated.
|
|
38
|
-
* @default undefined (no limit)
|
|
39
|
-
*/
|
|
40
55
|
maxLength?: number;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
transformers
|
|
56
|
+
transformers: Array<(str: string) => string>;
|
|
57
|
+
withCustomReplacements(replacements: Record<string, string>): this;
|
|
58
|
+
withSeparator(separator: string): this;
|
|
59
|
+
withCase(caseValue: 'lower' | 'upper' | 'default'): this;
|
|
60
|
+
withRemoveAccents(remove: boolean): this;
|
|
61
|
+
withAllowedChars(regex?: RegExp): this;
|
|
62
|
+
withMaxLength(length?: number): this;
|
|
63
|
+
withTransformers(transformers: Array<(str: string) => string>): this;
|
|
64
|
+
build(): SlugifyConfig;
|
|
49
65
|
}
|
|
50
66
|
/**
|
|
51
67
|
* Default configuration for slugify operations.
|
|
52
68
|
* Provides sensible defaults that can be overridden by users.
|
|
53
69
|
*/
|
|
54
|
-
export declare
|
|
70
|
+
export declare function getDefaultSlugifyConfig(): SlugifyConfig;
|
|
55
71
|
/**
|
|
56
72
|
* Sets the global slugify configuration that will be used by default.
|
|
57
|
-
*
|
|
73
|
+
* Replaces the current global config with the provided config.
|
|
58
74
|
*
|
|
59
|
-
* @param config -
|
|
75
|
+
* @param config - Configuration object to set as global config
|
|
60
76
|
*
|
|
61
77
|
* @example
|
|
62
|
-
* setSlugifyConfig(
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
78
|
+
* setSlugifyConfig(
|
|
79
|
+
* SlugifyConfig.builder()
|
|
80
|
+
* .withCustomReplacements({ "♀": "feminin", "♂": "masculin" })
|
|
81
|
+
* .withSeparator("_")
|
|
82
|
+
* .build()
|
|
83
|
+
* );
|
|
66
84
|
*/
|
|
85
|
+
export declare function assertCharClass(regex: RegExp): void;
|
|
86
|
+
export declare function assertSlugifyConfig(builderOrConfig: SlugifyConfigBuilder | SlugifyConfig): void;
|
|
67
87
|
export declare function setSlugifyConfig(config: SlugifyConfig): void;
|
|
68
88
|
/**
|
|
69
89
|
* Gets the current global slugify configuration.
|
|
70
|
-
* Returns
|
|
90
|
+
* Returns the current global config instance.
|
|
71
91
|
*
|
|
72
92
|
* @returns Current global configuration object
|
|
73
93
|
*/
|
|
@@ -1,58 +1,271 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Configuration types and interfaces for the slugify functionality.
|
|
4
|
-
* Provides comprehensive customization options for URL slug generation.
|
|
5
|
-
*/
|
|
6
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
3
|
+
exports.SlugifyConfigBuilder = exports.SlugifyConfig = void 0;
|
|
4
|
+
exports.getDefaultSlugifyConfig = getDefaultSlugifyConfig;
|
|
5
|
+
exports.assertCharClass = assertCharClass;
|
|
6
|
+
exports.assertSlugifyConfig = assertSlugifyConfig;
|
|
8
7
|
exports.setSlugifyConfig = setSlugifyConfig;
|
|
9
8
|
exports.getSlugifyConfig = getSlugifyConfig;
|
|
10
9
|
exports.resetSlugifyConfig = resetSlugifyConfig;
|
|
10
|
+
/**
|
|
11
|
+
* Configuration class for the slugify functionality.
|
|
12
|
+
* Provides comprehensive customization options for URL slug generation.
|
|
13
|
+
*/
|
|
14
|
+
class SlugifyConfig {
|
|
15
|
+
constructor(builder) {
|
|
16
|
+
this._customReplacements = builder.customReplacements;
|
|
17
|
+
this._separator = builder.separator;
|
|
18
|
+
this._case = builder.case;
|
|
19
|
+
this._removeAccents = builder.removeAccents;
|
|
20
|
+
this._allowedChars = builder.allowedChars;
|
|
21
|
+
this._maxLength = builder.maxLength;
|
|
22
|
+
this._transformers = builder.transformers;
|
|
23
|
+
}
|
|
24
|
+
static builder() {
|
|
25
|
+
return new SlugifyConfigBuilder();
|
|
26
|
+
}
|
|
27
|
+
static default() {
|
|
28
|
+
return SlugifyConfig.builder().build();
|
|
29
|
+
}
|
|
30
|
+
static create(builder) {
|
|
31
|
+
assertSlugifyConfig(builder);
|
|
32
|
+
const config = new SlugifyConfig(builder);
|
|
33
|
+
return Object.freeze(config);
|
|
34
|
+
}
|
|
35
|
+
get customReplacements() {
|
|
36
|
+
return { ...this._customReplacements };
|
|
37
|
+
}
|
|
38
|
+
get separator() {
|
|
39
|
+
return this._separator;
|
|
40
|
+
}
|
|
41
|
+
get case() {
|
|
42
|
+
return this._case;
|
|
43
|
+
}
|
|
44
|
+
get removeAccents() {
|
|
45
|
+
return this._removeAccents;
|
|
46
|
+
}
|
|
47
|
+
get allowedChars() {
|
|
48
|
+
return this._allowedChars;
|
|
49
|
+
}
|
|
50
|
+
get maxLength() {
|
|
51
|
+
return this._maxLength;
|
|
52
|
+
}
|
|
53
|
+
get transformers() {
|
|
54
|
+
return [...this._transformers];
|
|
55
|
+
}
|
|
56
|
+
merge(other) {
|
|
57
|
+
return SlugifyConfig.builder()
|
|
58
|
+
.withCustomReplacements({ ...this.customReplacements, ...other.customReplacements })
|
|
59
|
+
.withSeparator(other.separator || this.separator)
|
|
60
|
+
.withCase(other.case || this.case)
|
|
61
|
+
.withRemoveAccents(other.removeAccents !== undefined ? other.removeAccents : this.removeAccents)
|
|
62
|
+
.withAllowedChars(other.allowedChars || this.allowedChars)
|
|
63
|
+
.withMaxLength(other.maxLength || this.maxLength)
|
|
64
|
+
.withTransformers([...this.transformers, ...other.transformers])
|
|
65
|
+
.build();
|
|
66
|
+
}
|
|
67
|
+
escapeRegex(str) {
|
|
68
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
69
|
+
}
|
|
70
|
+
getCharClass() {
|
|
71
|
+
return this._allowedChars || (this._removeAccents ? /[a-zA-Z0-9]/ : /[a-zA-Z0-9\u00C0-\u017F]/);
|
|
72
|
+
}
|
|
73
|
+
applyCustomReplacements(str) {
|
|
74
|
+
let result = str;
|
|
75
|
+
for (const [pattern, replacement] of Object.entries(this._customReplacements)) {
|
|
76
|
+
const escapedPattern = this.escapeRegex(pattern);
|
|
77
|
+
result = result.replace(new RegExp(escapedPattern, 'g'), replacement);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
applyTransformers(str) {
|
|
82
|
+
return this._transformers.reduce((result, transformer) => transformer(result), str);
|
|
83
|
+
}
|
|
84
|
+
applyRemoveAccents(str) {
|
|
85
|
+
if (!this._removeAccents)
|
|
86
|
+
return str;
|
|
87
|
+
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
88
|
+
}
|
|
89
|
+
replaceNonAllowedChars(str) {
|
|
90
|
+
const charClass = this.getCharClass().source;
|
|
91
|
+
const safeCharClass = charClass.startsWith('[') && charClass.endsWith(']') ? charClass.slice(1, -1) : charClass;
|
|
92
|
+
return str.replace(new RegExp(`[^${safeCharClass}]+`, 'g'), this._separator);
|
|
93
|
+
}
|
|
94
|
+
trimSeparators(str) {
|
|
95
|
+
const safeSeparator = this.escapeRegex(this._separator);
|
|
96
|
+
return str.replace(new RegExp(`^${safeSeparator}+|${safeSeparator}+$`, 'g'), '');
|
|
97
|
+
}
|
|
98
|
+
applyCase(str) {
|
|
99
|
+
if (this._case === 'lower')
|
|
100
|
+
return str.toLowerCase();
|
|
101
|
+
if (this._case === 'upper')
|
|
102
|
+
return str.toUpperCase();
|
|
103
|
+
return str;
|
|
104
|
+
}
|
|
105
|
+
applyMaxLength(str) {
|
|
106
|
+
if (!this._maxLength || this._maxLength <= 0)
|
|
107
|
+
return str;
|
|
108
|
+
let result = str.slice(0, this._maxLength);
|
|
109
|
+
const safeSeparator = this.escapeRegex(this._separator);
|
|
110
|
+
return result.replace(new RegExp(`${safeSeparator}$`), '');
|
|
111
|
+
}
|
|
112
|
+
collapseSeparators(str) {
|
|
113
|
+
const safeSeparator = this.escapeRegex(this._separator);
|
|
114
|
+
return str.replace(new RegExp(`${safeSeparator}+`, 'g'), this._separator);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Applies the slugify transformations to the given string using this configuration.
|
|
118
|
+
* @param str - The input string to slugify
|
|
119
|
+
* @returns The slugified string
|
|
120
|
+
*/
|
|
121
|
+
slugify(str) {
|
|
122
|
+
let result = str;
|
|
123
|
+
result = this.applyCustomReplacements(result);
|
|
124
|
+
result = this.applyTransformers(result);
|
|
125
|
+
result = this.applyRemoveAccents(result);
|
|
126
|
+
result = this.replaceNonAllowedChars(result);
|
|
127
|
+
result = this.trimSeparators(result);
|
|
128
|
+
result = this.applyCase(result);
|
|
129
|
+
result = this.applyMaxLength(result);
|
|
130
|
+
result = this.collapseSeparators(result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Static method to slugify a string with a temporary configuration built from the builder.
|
|
135
|
+
* @param str - The input string to slugify
|
|
136
|
+
* @param builderFn - A function that configures the builder
|
|
137
|
+
* @returns The slugified string
|
|
138
|
+
*/
|
|
139
|
+
static slugifyWith(str, builderFn) {
|
|
140
|
+
const config = builderFn(SlugifyConfig.builder()).build();
|
|
141
|
+
return config.slugify(str);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.SlugifyConfig = SlugifyConfig;
|
|
145
|
+
class SlugifyConfigBuilder {
|
|
146
|
+
constructor() {
|
|
147
|
+
this.customReplacements = {};
|
|
148
|
+
this.separator = '-';
|
|
149
|
+
this.case = 'lower';
|
|
150
|
+
this.removeAccents = true;
|
|
151
|
+
this.transformers = [];
|
|
152
|
+
}
|
|
153
|
+
withCustomReplacements(replacements) {
|
|
154
|
+
this.customReplacements = { ...replacements };
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
withSeparator(separator) {
|
|
158
|
+
this.separator = separator;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
withCase(caseValue) {
|
|
162
|
+
this.case = caseValue;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
withRemoveAccents(remove) {
|
|
166
|
+
this.removeAccents = remove;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
withAllowedChars(regex) {
|
|
170
|
+
this.allowedChars = regex;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
withMaxLength(length) {
|
|
174
|
+
this.maxLength = length;
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
withTransformers(transformers) {
|
|
178
|
+
this.transformers = [...transformers];
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
build() {
|
|
182
|
+
const config = SlugifyConfig.create(this);
|
|
183
|
+
return config;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.SlugifyConfigBuilder = SlugifyConfigBuilder;
|
|
11
187
|
/**
|
|
12
188
|
* Default configuration for slugify operations.
|
|
13
189
|
* Provides sensible defaults that can be overridden by users.
|
|
14
190
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
lowercase: true,
|
|
19
|
-
removeAccents: true,
|
|
20
|
-
allowedChars: undefined, // Will be determined dynamically based on removeAccents
|
|
21
|
-
maxLength: undefined,
|
|
22
|
-
transformers: [],
|
|
23
|
-
};
|
|
191
|
+
function getDefaultSlugifyConfig() {
|
|
192
|
+
return SlugifyConfig.default();
|
|
193
|
+
}
|
|
24
194
|
/**
|
|
25
195
|
* Global configuration instance that can be modified by users.
|
|
26
196
|
* Starts with default values but can be updated via setSlugifyConfig().
|
|
27
197
|
*/
|
|
28
|
-
let globalSlugifyConfig =
|
|
198
|
+
let globalSlugifyConfig = getDefaultSlugifyConfig();
|
|
29
199
|
/**
|
|
30
200
|
* Sets the global slugify configuration that will be used by default.
|
|
31
|
-
*
|
|
201
|
+
* Replaces the current global config with the provided config.
|
|
32
202
|
*
|
|
33
|
-
* @param config -
|
|
203
|
+
* @param config - Configuration object to set as global config
|
|
34
204
|
*
|
|
35
205
|
* @example
|
|
36
|
-
* setSlugifyConfig(
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
206
|
+
* setSlugifyConfig(
|
|
207
|
+
* SlugifyConfig.builder()
|
|
208
|
+
* .withCustomReplacements({ "♀": "feminin", "♂": "masculin" })
|
|
209
|
+
* .withSeparator("_")
|
|
210
|
+
* .build()
|
|
211
|
+
* );
|
|
40
212
|
*/
|
|
213
|
+
function assertCharClass(regex) {
|
|
214
|
+
const src = regex.source;
|
|
215
|
+
if (!src.startsWith('[') || !src.endsWith(']')) {
|
|
216
|
+
throw new Error(`Invalid allowedChars: must be a character class like /[a-z]/, received ${regex}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function assertSlugifyConfig(builderOrConfig) {
|
|
220
|
+
const allowedChars = builderOrConfig.allowedChars;
|
|
221
|
+
const separator = builderOrConfig.separator;
|
|
222
|
+
const caseValue = builderOrConfig.case;
|
|
223
|
+
const removeAccents = builderOrConfig.removeAccents;
|
|
224
|
+
const maxLength = builderOrConfig.maxLength;
|
|
225
|
+
const customReplacements = builderOrConfig.customReplacements;
|
|
226
|
+
const transformers = builderOrConfig.transformers;
|
|
227
|
+
if (allowedChars !== undefined) {
|
|
228
|
+
if (!(allowedChars instanceof RegExp)) {
|
|
229
|
+
throw new TypeError(`Invalid allowedChars: expected a RegExp, received ${typeof allowedChars}`);
|
|
230
|
+
}
|
|
231
|
+
assertCharClass(allowedChars);
|
|
232
|
+
}
|
|
233
|
+
if (typeof separator !== 'string') {
|
|
234
|
+
throw new TypeError(`Invalid separator: expected a string, received ${typeof separator}`);
|
|
235
|
+
}
|
|
236
|
+
if (caseValue !== 'lower' && caseValue !== 'upper' && caseValue !== 'default') {
|
|
237
|
+
throw new TypeError(`Invalid case value: expected 'lower' | 'upper' | 'default', received ${caseValue}`);
|
|
238
|
+
}
|
|
239
|
+
if (typeof removeAccents !== 'boolean') {
|
|
240
|
+
throw new TypeError(`Invalid removeAccents: expected a boolean, received ${typeof removeAccents}`);
|
|
241
|
+
}
|
|
242
|
+
if (maxLength !== undefined) {
|
|
243
|
+
if (typeof maxLength !== 'number' || !Number.isInteger(maxLength) || maxLength < 0) {
|
|
244
|
+
throw new TypeError(`Invalid maxLength: expected a non-negative integer, received ${maxLength}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (typeof customReplacements !== 'object' || customReplacements === null) {
|
|
248
|
+
throw new TypeError(`Invalid customReplacements: expected an object, received ${typeof customReplacements}`);
|
|
249
|
+
}
|
|
250
|
+
if (!Array.isArray(transformers) || !transformers.every((fn) => typeof fn === 'function')) {
|
|
251
|
+
throw new TypeError(`Invalid transformers: expected an array of functions`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
41
254
|
function setSlugifyConfig(config) {
|
|
42
|
-
globalSlugifyConfig =
|
|
255
|
+
globalSlugifyConfig = config;
|
|
43
256
|
}
|
|
44
257
|
/**
|
|
45
258
|
* Gets the current global slugify configuration.
|
|
46
|
-
* Returns
|
|
259
|
+
* Returns the current global config instance.
|
|
47
260
|
*
|
|
48
261
|
* @returns Current global configuration object
|
|
49
262
|
*/
|
|
50
263
|
function getSlugifyConfig() {
|
|
51
|
-
return
|
|
264
|
+
return globalSlugifyConfig;
|
|
52
265
|
}
|
|
53
266
|
/**
|
|
54
267
|
* Resets the global slugify configuration to default values.
|
|
55
268
|
*/
|
|
56
269
|
function resetSlugifyConfig() {
|
|
57
|
-
globalSlugifyConfig =
|
|
270
|
+
globalSlugifyConfig = getDefaultSlugifyConfig();
|
|
58
271
|
}
|
|
@@ -12,48 +12,7 @@ const slugify_config_1 = require("./slugify.config");
|
|
|
12
12
|
*/
|
|
13
13
|
function slugifyString(str, config) {
|
|
14
14
|
// Merge provided config with global config
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (finalConfig.customReplacements && Object.keys(finalConfig.customReplacements).length > 0) {
|
|
19
|
-
for (const [pattern, replacement] of Object.entries(finalConfig.customReplacements)) {
|
|
20
|
-
// Escape special regex characters in the pattern
|
|
21
|
-
const escapedPattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
-
result = result.replace(new RegExp(escapedPattern, 'g'), replacement);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
// 2. Apply custom transformers
|
|
26
|
-
if (finalConfig.transformers && finalConfig.transformers.length > 0) {
|
|
27
|
-
for (const transformer of finalConfig.transformers) {
|
|
28
|
-
result = transformer(result);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// 3. Remove accents if configured
|
|
32
|
-
if (finalConfig.removeAccents) {
|
|
33
|
-
result = result.normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
34
|
-
}
|
|
35
|
-
// 4. Replace non-allowed characters with separator
|
|
36
|
-
const separator = finalConfig.separator || '-';
|
|
37
|
-
// Determine allowed characters based on removeAccents setting
|
|
38
|
-
const defaultAllowedChars = finalConfig.removeAccents ? /[a-zA-Z0-9]/ : /[a-zA-Z0-9\u00C0-\u017F]/; // Include accented Latin characters if accents are preserved
|
|
39
|
-
const allowedRegex = finalConfig.allowedChars || defaultAllowedChars;
|
|
40
|
-
const allowedPattern = allowedRegex.source;
|
|
41
|
-
// Extract the character class content (remove surrounding brackets if present)
|
|
42
|
-
const charClass = allowedPattern.startsWith('[') && allowedPattern.endsWith(']') ? allowedPattern.slice(1, -1) : allowedPattern;
|
|
43
|
-
result = result.replace(new RegExp(`[^${charClass}]+`, 'g'), separator);
|
|
44
|
-
// 5. Remove leading/trailing separators
|
|
45
|
-
result = result.replace(new RegExp(`^${separator}+|${separator}+$`, 'g'), '');
|
|
46
|
-
// 6. Convert to lowercase if configured
|
|
47
|
-
if (finalConfig.lowercase) {
|
|
48
|
-
result = result.toLowerCase();
|
|
49
|
-
}
|
|
50
|
-
// 7. Apply max length if configured
|
|
51
|
-
if (finalConfig.maxLength && finalConfig.maxLength > 0) {
|
|
52
|
-
result = result.slice(0, finalConfig.maxLength);
|
|
53
|
-
// Remove trailing separator after truncation
|
|
54
|
-
result = result.replace(new RegExp(`${separator}$`), '');
|
|
55
|
-
}
|
|
56
|
-
// 8. Collapse multiple consecutive separators
|
|
57
|
-
result = result.replace(new RegExp(`${separator}+`, 'g'), separator);
|
|
58
|
-
return result;
|
|
15
|
+
const globalConfig = (0, slugify_config_1.getSlugifyConfig)();
|
|
16
|
+
const finalConfig = config ? globalConfig.merge(config) : globalConfig;
|
|
17
|
+
return finalConfig.slugify(str);
|
|
59
18
|
}
|