102 lines
2.4 KiB
JavaScript
102 lines
2.4 KiB
JavaScript
|
'use strict';
|
||
|
const escapeStringRegexp = require('escape-string-regexp');
|
||
|
const transliterate = require('@sindresorhus/transliterate');
|
||
|
const builtinOverridableReplacements = require('./overridable-replacements');
|
||
|
|
||
|
const decamelize = string => {
|
||
|
return string
|
||
|
// Separate capitalized words.
|
||
|
.replace(/([A-Z]{2,})(\d+)/g, '$1 $2')
|
||
|
.replace(/([a-z\d]+)([A-Z]{2,})/g, '$1 $2')
|
||
|
|
||
|
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
||
|
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1 $2');
|
||
|
};
|
||
|
|
||
|
const removeMootSeparators = (string, separator) => {
|
||
|
const escapedSeparator = escapeStringRegexp(separator);
|
||
|
|
||
|
return string
|
||
|
.replace(new RegExp(`${escapedSeparator}{2,}`, 'g'), separator)
|
||
|
.replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '');
|
||
|
};
|
||
|
|
||
|
const slugify = (string, options) => {
|
||
|
if (typeof string !== 'string') {
|
||
|
throw new TypeError(`Expected a string, got \`${typeof string}\``);
|
||
|
}
|
||
|
|
||
|
options = {
|
||
|
separator: '-',
|
||
|
lowercase: true,
|
||
|
decamelize: true,
|
||
|
customReplacements: [],
|
||
|
preserveLeadingUnderscore: false,
|
||
|
...options
|
||
|
};
|
||
|
|
||
|
const shouldPrependUnderscore = options.preserveLeadingUnderscore && string.startsWith('_');
|
||
|
|
||
|
const customReplacements = new Map([
|
||
|
...builtinOverridableReplacements,
|
||
|
...options.customReplacements
|
||
|
]);
|
||
|
|
||
|
string = transliterate(string, {customReplacements});
|
||
|
|
||
|
if (options.decamelize) {
|
||
|
string = decamelize(string);
|
||
|
}
|
||
|
|
||
|
let patternSlug = /[^a-zA-Z\d]+/g;
|
||
|
|
||
|
if (options.lowercase) {
|
||
|
string = string.toLowerCase();
|
||
|
patternSlug = /[^a-z\d]+/g;
|
||
|
}
|
||
|
|
||
|
string = string.replace(patternSlug, options.separator);
|
||
|
string = string.replace(/\\/g, '');
|
||
|
if (options.separator) {
|
||
|
string = removeMootSeparators(string, options.separator);
|
||
|
}
|
||
|
|
||
|
if (shouldPrependUnderscore) {
|
||
|
string = `_${string}`;
|
||
|
}
|
||
|
|
||
|
return string;
|
||
|
};
|
||
|
|
||
|
const counter = () => {
|
||
|
const occurrences = new Map();
|
||
|
|
||
|
const countable = (string, options) => {
|
||
|
string = slugify(string, options);
|
||
|
|
||
|
if (!string) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
const stringLower = string.toLowerCase();
|
||
|
const numberless = occurrences.get(stringLower.replace(/(?:-\d+?)+?$/, '')) || 0;
|
||
|
const counter = occurrences.get(stringLower);
|
||
|
occurrences.set(stringLower, typeof counter === 'number' ? counter + 1 : 1);
|
||
|
const newCounter = occurrences.get(stringLower) || 2;
|
||
|
if (newCounter >= 2 || numberless > 2) {
|
||
|
string = `${string}-${newCounter}`;
|
||
|
}
|
||
|
|
||
|
return string;
|
||
|
};
|
||
|
|
||
|
countable.reset = () => {
|
||
|
occurrences.clear();
|
||
|
};
|
||
|
|
||
|
return countable;
|
||
|
};
|
||
|
|
||
|
module.exports = slugify;
|
||
|
module.exports.counter = counter;
|