Skip to content

Commit 9e1ec95

Browse files
CanRausindresorhus
andauthored
Add counter for multiple occurences (#46)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent f1acfc9 commit 9e1ec95

File tree

5 files changed

+225
-24
lines changed

5 files changed

+225
-24
lines changed

index.d.ts

Lines changed: 89 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -113,31 +113,100 @@ declare namespace slugify {
113113
}
114114
}
115115

116-
/**
117-
Slugify a string.
116+
declare const slugify: {
117+
/**
118+
Slugify a string.
118119
119-
@param string - String to slugify.
120+
@param string - String to slugify.
120121
121-
@example
122-
```
123-
import slugify = require('@sindresorhus/slugify');
122+
@example
123+
```
124+
import slugify = require('@sindresorhus/slugify');
124125
125-
slugify('I ♥ Dogs');
126-
//=> 'i-love-dogs'
126+
slugify('I ♥ Dogs');
127+
//=> 'i-love-dogs'
127128
128-
slugify(' Déjà Vu! ');
129-
//=> 'deja-vu'
129+
slugify(' Déjà Vu! ');
130+
//=> 'deja-vu'
130131
131-
slugify('fooBar 123 $#%');
132-
//=> 'foo-bar-123'
132+
slugify('fooBar 123 $#%');
133+
//=> 'foo-bar-123'
133134
134-
slugify('я люблю единорогов');
135-
//=> 'ya-lyublyu-edinorogov'
136-
```
137-
*/
138-
declare function slugify(
139-
string: string,
140-
options?: slugify.Options
141-
): string;
135+
slugify('я люблю единорогов');
136+
//=> 'ya-lyublyu-edinorogov'
137+
```
138+
*/
139+
(
140+
string: string,
141+
options?: slugify.Options
142+
): string;
143+
144+
/**
145+
Returns a new instance of `slugify(string, options?)` with a counter to handle multiple occurences of the same string.
146+
147+
@param string - String to slugify.
148+
149+
@example
150+
```
151+
import slugify = require('@sindresorhus/slugify');
152+
153+
const countableSlugify = slugify.counter();
154+
countableSlugify('foo bar');
155+
//=> 'foo-bar'
156+
157+
countableSlugify('foo bar');
158+
//=> 'foo-bar-2'
159+
160+
countableSlugify.reset();
161+
162+
countableSlugify('foo bar');
163+
//=> 'foo-bar'
164+
```
165+
166+
__Use case example of counter__
167+
168+
If, for example, you have a document with multiple sections where each subsection has an example.
169+
170+
```
171+
## Section 1
172+
173+
### Example
174+
175+
## Section 2
176+
177+
### Example
178+
```
179+
180+
You can then use `slugify.counter()` to generate unique HTML `id`'s to ensure anchors will link to the right headline.
181+
*/
182+
counter: () => {
183+
(
184+
string: string,
185+
options?: slugify.Options
186+
): string;
187+
188+
/**
189+
Reset the counter.
190+
191+
@example
192+
```
193+
import slugify = require('@sindresorhus/slugify');
194+
195+
const countableSlugify = slugify.counter();
196+
countableSlugify('foo bar');
197+
//=> 'foo-bar'
198+
199+
countableSlugify('foo bar');
200+
//=> 'foo-bar-2'
201+
202+
countableSlugify.reset();
203+
204+
countableSlugify('foo bar');
205+
//=> 'foo-bar'
206+
```
207+
*/
208+
reset(): void;
209+
};
210+
}
142211

143212
export = slugify;

index.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const removeMootSeparators = (string, separator) => {
2121
.replace(new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), '');
2222
};
2323

24-
module.exports = (string, options) => {
24+
const slugify = (string, options) => {
2525
if (typeof string !== 'string') {
2626
throw new TypeError(`Expected a string, got \`${typeof string}\``);
2727
}
@@ -65,3 +65,35 @@ module.exports = (string, options) => {
6565

6666
return string;
6767
};
68+
69+
const counter = () => {
70+
const occurrences = new Map();
71+
72+
const countable = (string, options) => {
73+
string = slugify(string, options);
74+
75+
if (!string) {
76+
return '';
77+
}
78+
79+
const stringLower = string.toLowerCase();
80+
const numberless = occurrences.get(stringLower.replace(/(?:-\d+?)+?$/, '')) || 0;
81+
const counter = occurrences.get(stringLower);
82+
occurrences.set(stringLower, typeof counter === 'number' ? counter + 1 : 1);
83+
const newCounter = occurrences.get(stringLower) || 2;
84+
if (newCounter >= 2 || numberless > 2) {
85+
string = `${string}-${newCounter}`;
86+
}
87+
88+
return string;
89+
};
90+
91+
countable.reset = () => {
92+
occurrences.clear();
93+
};
94+
95+
return countable;
96+
};
97+
98+
module.exports = slugify;
99+
module.exports.counter = counter;

index.test-d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ expectType<string>(slugify('I ♥ Dogs'));
55
expectType<string>(slugify('BAR and baz', {separator: '_'}));
66
expectType<string>(slugify('Déjà Vu!', {lowercase: false}));
77
expectType<string>(slugify('fooBar', {decamelize: false}));
8-
expectType<string>(
9-
slugify('I ♥ 🦄 & 🐶', {customReplacements: [['🐶', 'dog']]})
10-
);
8+
expectType<string>(slugify('I ♥ 🦄 & 🐶', {customReplacements: [['🐶', 'dog']]}));
119
expectType<string>(slugify('_foo_bar', {preserveLeadingUnderscore: true}));
10+
11+
// counter
12+
expectType<string>(slugify.counter()('I ♥ Dogs'));
13+
expectType<void>(slugify.counter().reset());

readme.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,68 @@ slugify('_foo_bar', {preserveLeadingUnderscore: true});
164164
//=> '_foo-bar'
165165
```
166166

167+
### slugify.counter()
168+
169+
Returns a new instance of `slugify(string, options?)` with a counter to handle multiple occurences of the same string.
170+
171+
#### Example
172+
173+
```js
174+
const slugify = require('@sindresorhus/slugify');
175+
176+
const countableSlugify = slugify.counter();
177+
178+
countableSlugify('foo bar');
179+
//=> 'foo-bar'
180+
181+
countableSlugify('foo bar');
182+
//=> 'foo-bar-2'
183+
184+
countableSlugify.reset();
185+
186+
countableSlugify('foo bar');
187+
//=> 'foo-bar'
188+
```
189+
190+
#### Use-case example of counter
191+
192+
If, for example, you have a document with multiple sections where each subsection has an example.
193+
194+
```md
195+
## Section 1
196+
197+
### Example
198+
199+
## Section 2
200+
201+
### Example
202+
```
203+
204+
You can then use `slugify.counter()` to generate unique HTML `id`'s to ensure anchors will link to the right headline.
205+
206+
### slugify.reset()
207+
208+
Reset the counter
209+
210+
#### Example
211+
212+
```js
213+
const slugify = require('@sindresorhus/slugify');
214+
215+
const countableSlugify = slugify.counter();
216+
217+
countableSlugify('foo bar');
218+
//=> 'foo-bar'
219+
220+
countableSlugify('foo bar');
221+
//=> 'foo-bar-2'
222+
223+
countableSlugify.reset();
224+
225+
countableSlugify('foo bar');
226+
//=> 'foo-bar'
227+
```
228+
167229
## Related
168230

169231
- [slugify-cli](https://github.com/sindresorhus/slugify-cli) - CLI for this module

test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,39 @@ test('leading underscore', t => {
131131
t.is(slugify('__foo__bar', {preserveLeadingUnderscore: true}), '_foo-bar');
132132
t.is(slugify('____-___foo__bar', {preserveLeadingUnderscore: true}), '_foo-bar');
133133
});
134+
135+
test('counter', t => {
136+
const countableSlugify = slugify.counter();
137+
t.is(countableSlugify('foo bar'), 'foo-bar');
138+
t.is(countableSlugify('foo bar'), 'foo-bar-2');
139+
140+
countableSlugify.reset();
141+
142+
t.is(countableSlugify('foo'), 'foo');
143+
t.is(countableSlugify('foo'), 'foo-2');
144+
t.is(countableSlugify('foo 1'), 'foo-1');
145+
t.is(countableSlugify('foo-1'), 'foo-1-2');
146+
t.is(countableSlugify('foo-1'), 'foo-1-3');
147+
t.is(countableSlugify('foo'), 'foo-3');
148+
t.is(countableSlugify('foo'), 'foo-4');
149+
t.is(countableSlugify('foo-1'), 'foo-1-4');
150+
t.is(countableSlugify('foo-2'), 'foo-2-1');
151+
t.is(countableSlugify('foo-2'), 'foo-2-2');
152+
t.is(countableSlugify('foo-2-1'), 'foo-2-1-1');
153+
t.is(countableSlugify('foo-2-1'), 'foo-2-1-2');
154+
t.is(countableSlugify('foo-11'), 'foo-11-1');
155+
t.is(countableSlugify('foo-111'), 'foo-111-1');
156+
t.is(countableSlugify('foo-111-1'), 'foo-111-1-1');
157+
t.is(countableSlugify('fooCamelCase', {lowercase: false, decamelize: false}), 'fooCamelCase');
158+
t.is(countableSlugify('fooCamelCase', {decamelize: false}), 'foocamelcase-2');
159+
t.is(countableSlugify('_foo'), 'foo-5');
160+
t.is(countableSlugify('_foo', {preserveLeadingUnderscore: true}), '_foo');
161+
t.is(countableSlugify('_foo', {preserveLeadingUnderscore: true}), '_foo-2');
162+
163+
const countableSlugify2 = slugify.counter();
164+
t.is(countableSlugify2('foo'), 'foo');
165+
t.is(countableSlugify2('foo'), 'foo-2');
166+
167+
t.is(countableSlugify2(''), '');
168+
t.is(countableSlugify2(''), '');
169+
});

0 commit comments

Comments
 (0)