UNPKG

2.72 kBJavaScriptView Raw
1import escapeStringRegexp from 'escape-string-regexp';
2import transliterate from '@sindresorhus/transliterate';
3import builtinOverridableReplacements from './overridable-replacements.js';
4
5const decamelize = string => {
6 return string
7 // Separate capitalized words.
8 .replace(/([A-Z]{2,})(\d+)/g, '$1 $2')
9 .replace(/([a-z\d]+)([A-Z]{2,})/g, '$1 $2')
10
11 .replace(/([a-z\d])([A-Z])/g, '$1 $2')
12 // `[a-rt-z]` matches all lowercase characters except `s`.
13 // This avoids matching plural acronyms like `APIs`.
14 .replace(/([A-Z]+)([A-Z][a-rt-z\d]+)/g, '$1 $2');
15};
16
17const removeMootSeparators = (string, separator) => {
18 const escapedSeparator = escapeStringRegexp(separator);
19
20 return string
21 .replace(new RegExp(`${escapedSeparator}{2,}`, 'g'), separator)
22 .replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '');
23};
24
25export default function slugify(string, options) {
26 if (typeof string !== 'string') {
27 throw new TypeError(`Expected a string, got \`${typeof string}\``);
28 }
29
30 options = {
31 separator: '-',
32 lowercase: true,
33 decamelize: true,
34 customReplacements: [],
35 preserveLeadingUnderscore: false,
36 preserveTrailingDash: false,
37 ...options
38 };
39
40 const shouldPrependUnderscore = options.preserveLeadingUnderscore && string.startsWith('_');
41 const shouldAppendDash = options.preserveTrailingDash && string.endsWith('-');
42
43 const customReplacements = new Map([
44 ...builtinOverridableReplacements,
45 ...options.customReplacements
46 ]);
47
48 string = transliterate(string, {customReplacements});
49
50 if (options.decamelize) {
51 string = decamelize(string);
52 }
53
54 let patternSlug = /[^a-zA-Z\d]+/g;
55
56 if (options.lowercase) {
57 string = string.toLowerCase();
58 patternSlug = /[^a-z\d]+/g;
59 }
60
61 string = string.replace(patternSlug, options.separator);
62 string = string.replace(/\\/g, '');
63 if (options.separator) {
64 string = removeMootSeparators(string, options.separator);
65 }
66
67 if (shouldPrependUnderscore) {
68 string = `_${string}`;
69 }
70
71 if (shouldAppendDash) {
72 string = `${string}-`;
73 }
74
75 return string;
76}
77
78export function slugifyWithCounter() {
79 const occurrences = new Map();
80
81 const countable = (string, options) => {
82 string = slugify(string, options);
83
84 if (!string) {
85 return '';
86 }
87
88 const stringLower = string.toLowerCase();
89 const numberless = occurrences.get(stringLower.replace(/(?:-\d+?)+?$/, '')) || 0;
90 const counter = occurrences.get(stringLower);
91 occurrences.set(stringLower, typeof counter === 'number' ? counter + 1 : 1);
92 const newCounter = occurrences.get(stringLower) || 2;
93 if (newCounter >= 2 || numberless > 2) {
94 string = `${string}-${newCounter}`;
95 }
96
97 return string;
98 };
99
100 countable.reset = () => {
101 occurrences.clear();
102 };
103
104 return countable;
105}