123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- import escapeStringRegexp from 'escape-string-regexp';
- import transliterate from '@sindresorhus/transliterate';
- import builtinOverridableReplacements from './overridable-replacements.js';
- 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')
- // `[a-rt-z]` matches all lowercase characters except `s`.
- // This avoids matching plural acronyms like `APIs`.
- .replace(/([A-Z]+)([A-Z][a-rt-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 buildPatternSlug = options => {
- let negationSetPattern = 'a-z\\d';
- negationSetPattern += options.lowercase ? '' : 'A-Z';
- if (options.preserveCharacters.length > 0) {
- for (const character of options.preserveCharacters) {
- if (character === options.separator) {
- throw new Error(`The separator character \`${options.separator}\` cannot be included in preserved characters: ${options.preserveCharacters}`);
- }
- negationSetPattern += escapeStringRegexp(character);
- }
- }
- return new RegExp(`[^${negationSetPattern}]+`, 'g');
- };
- export default function 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,
- preserveTrailingDash: false,
- preserveCharacters: [],
- ...options
- };
- const shouldPrependUnderscore = options.preserveLeadingUnderscore && string.startsWith('_');
- const shouldAppendDash = options.preserveTrailingDash && string.endsWith('-');
- const customReplacements = new Map([
- ...builtinOverridableReplacements,
- ...options.customReplacements
- ]);
- string = transliterate(string, {customReplacements});
- if (options.decamelize) {
- string = decamelize(string);
- }
- const patternSlug = buildPatternSlug(options);
- if (options.lowercase) {
- string = string.toLowerCase();
- }
- // Detect contractions/possessives by looking for any word followed by a `'t`
- // or `'s` in isolation and then remove it.
- string = string.replace(/([a-zA-Z\d]+)'([ts])(\s|$)/g, '$1$2$3');
- string = string.replace(patternSlug, options.separator);
- string = string.replace(/\\/g, '');
- if (options.separator) {
- string = removeMootSeparators(string, options.separator);
- }
- if (shouldPrependUnderscore) {
- string = `_${string}`;
- }
- if (shouldAppendDash) {
- string = `${string}-`;
- }
- return string;
- }
- export function slugifyWithCounter() {
- 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;
- }
|