UNPKG

2.44 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 .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1 $2');
13};
14
15const removeMootSeparators = (string, separator) => {
16 const escapedSeparator = escapeStringRegexp(separator);
17
18 return string
19 .replace(new RegExp(`${escapedSeparator}{2,}`, 'g'), separator)
20 .replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '');
21};
22
23export default function slugify(string, options) {
24 if (typeof string !== 'string') {
25 throw new TypeError(`Expected a string, got \`${typeof string}\``);
26 }
27
28 options = {
29 separator: '-',
30 lowercase: true,
31 decamelize: true,
32 customReplacements: [],
33 preserveLeadingUnderscore: false,
34 ...options
35 };
36
37 const shouldPrependUnderscore = options.preserveLeadingUnderscore && string.startsWith('_');
38
39 const customReplacements = new Map([
40 ...builtinOverridableReplacements,
41 ...options.customReplacements
42 ]);
43
44 string = transliterate(string, {customReplacements});
45
46 if (options.decamelize) {
47 string = decamelize(string);
48 }
49
50 let patternSlug = /[^a-zA-Z\d]+/g;
51
52 if (options.lowercase) {
53 string = string.toLowerCase();
54 patternSlug = /[^a-z\d]+/g;
55 }
56
57 string = string.replace(patternSlug, options.separator);
58 string = string.replace(/\\/g, '');
59 if (options.separator) {
60 string = removeMootSeparators(string, options.separator);
61 }
62
63 if (shouldPrependUnderscore) {
64 string = `_${string}`;
65 }
66
67 return string;
68}
69
70export function slugifyWithCounter() {
71 const occurrences = new Map();
72
73 const countable = (string, options) => {
74 string = slugify(string, options);
75
76 if (!string) {
77 return '';
78 }
79
80 const stringLower = string.toLowerCase();
81 const numberless = occurrences.get(stringLower.replace(/(?:-\d+?)+?$/, '')) || 0;
82 const counter = occurrences.get(stringLower);
83 occurrences.set(stringLower, typeof counter === 'number' ? counter + 1 : 1);
84 const newCounter = occurrences.get(stringLower) || 2;
85 if (newCounter >= 2 || numberless > 2) {
86 string = `${string}-${newCounter}`;
87 }
88
89 return string;
90 };
91
92 countable.reset = () => {
93 occurrences.clear();
94 };
95
96 return countable;
97}