@gkucmierz/utils 5.0.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main.mjs +28 -4
- package/package.json +2 -3
- package/src/data-structures/Stack.mjs +100 -0
- package/src/developer-utils/get-type.mjs +2 -16
- package/src/developer-utils/grid-navigation.mjs +175 -0
- package/src/developer-utils/quorum.mjs +345 -0
- package/src/developer-utils/type.mjs +24 -0
- package/src/geometry/math3d.mjs +164 -0
- package/src/geometry/range-set.mjs +748 -0
- package/src/string-arrays/parse-heading.mjs +38 -0
- package/src/string-arrays/slugify.mjs +74 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module string-arrays
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parses a heading text to safely extract section numbering prefix and clean title.
|
|
7
|
+
* Unlike naive regexes, never truncates words that begin with ASCII or Unicode letters.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} text - The raw heading string to parse.
|
|
10
|
+
* @returns {{ number: string, title: string }} An object with the extracted numbering prefix and clean title.
|
|
11
|
+
* @example
|
|
12
|
+
* parseHeading('1. Introduction');
|
|
13
|
+
* // returns { number: '1.', title: 'Introduction' }
|
|
14
|
+
*
|
|
15
|
+
* parseHeading('3.2.1. Network Configuration');
|
|
16
|
+
* // returns { number: '3.2.1.', title: 'Network Configuration' }
|
|
17
|
+
*
|
|
18
|
+
* parseHeading('Obsługiwane typy danych');
|
|
19
|
+
* // returns { number: '', title: 'Obsługiwane typy danych' }
|
|
20
|
+
* @see {@link https://instacode.app/run/FASwtgDg9gTgLgAgN4IgQxgZwKYAltoAmIAdgOYIC+CAZjFGAgEQACZA1gK4DGYI2MAF4B6TnBAAbTEwDcwYOix4CxcnKA|▶ Try it live in Instacode}
|
|
21
|
+
*/
|
|
22
|
+
export const parseHeading = (text) => {
|
|
23
|
+
if (!text || typeof text !== 'string') {
|
|
24
|
+
return { number: '', title: '' };
|
|
25
|
+
}
|
|
26
|
+
const trimmed = text.trim();
|
|
27
|
+
const match = trimmed.match(/^(\d+(?:[.-]\d+)*(?:[.:)]|\s+-)?)\s+(.+)$/u);
|
|
28
|
+
if (match) {
|
|
29
|
+
return {
|
|
30
|
+
number: match[1].trim(),
|
|
31
|
+
title: match[2].trim(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
number: '',
|
|
36
|
+
title: trimmed,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module string-arrays
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a clean, URL-safe slug from any string, with full Unicode support
|
|
7
|
+
* (preserving international characters like Polish diacritics) and optional collision deduplication.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} text - The input string to convert into a slug.
|
|
10
|
+
* @param {object} [options={}] - Optional configuration options.
|
|
11
|
+
* @param {Set<string>|Array<string>} [options.existing] - Collection of existing slugs for automatic -1, -2 deduplication.
|
|
12
|
+
* @param {string} [options.fallback='item'] - Fallback slug if the sanitized result is empty.
|
|
13
|
+
* @param {string} [options.separator='-'] - Separator string between words.
|
|
14
|
+
* @param {boolean} [options.lowercase=true] - Whether to lowercase the slug.
|
|
15
|
+
* @returns {string} The formatted URL-safe slug.
|
|
16
|
+
* @example
|
|
17
|
+
* slugify('Obsługiwane typy danych');
|
|
18
|
+
* // returns 'obsługiwane-typy-danych'
|
|
19
|
+
*
|
|
20
|
+
* slugify('Zażółć gęślą jaźń!');
|
|
21
|
+
* // returns 'zażółć-gęślą-jaźń'
|
|
22
|
+
*
|
|
23
|
+
* slugify('Introduction', { existing: new Set(['introduction']) });
|
|
24
|
+
* // returns 'introduction-1'
|
|
25
|
+
* @see {@link https://instacode.app/run/FASwtgDg9gTgLgAgN4IM4BsCuBzEAzATwQF8E8YowEAiAAWwGtMBjMEAUxgC8B6TOEOlTUA3MGAYc+AmKA|▶ Try it live in Instacode}
|
|
26
|
+
*/
|
|
27
|
+
export const slugify = (text, options = {}) => {
|
|
28
|
+
const {
|
|
29
|
+
existing = null,
|
|
30
|
+
fallback = 'item',
|
|
31
|
+
separator = '-',
|
|
32
|
+
lowercase = true,
|
|
33
|
+
} = options;
|
|
34
|
+
|
|
35
|
+
let str = (text || '')
|
|
36
|
+
.replace(/<[^>]+>/g, '')
|
|
37
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
38
|
+
.trim();
|
|
39
|
+
|
|
40
|
+
if (lowercase) {
|
|
41
|
+
str = str.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Collapse whitespace and underscores to separator
|
|
45
|
+
str = str.replace(/[\s_]+/g, separator);
|
|
46
|
+
|
|
47
|
+
// Preserve all Unicode letters (\p{L}), numbers (\p{N}), and the separator
|
|
48
|
+
const escapedSep = separator.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
49
|
+
str = str.replace(new RegExp(`[^\\p{L}\\p{N}${escapedSep}]`, 'gu'), '');
|
|
50
|
+
|
|
51
|
+
// Collapse multiple consecutive separators
|
|
52
|
+
str = str.replace(new RegExp(`${escapedSep}+`, 'g'), separator);
|
|
53
|
+
|
|
54
|
+
// Trim leading/trailing separators
|
|
55
|
+
str = str.replace(new RegExp(`^${escapedSep}+|${escapedSep}+$`, 'g'), '');
|
|
56
|
+
|
|
57
|
+
const baseSlug = str || fallback;
|
|
58
|
+
let slug = baseSlug;
|
|
59
|
+
|
|
60
|
+
if (existing) {
|
|
61
|
+
const hasSlug = (s) => (existing instanceof Set ? existing.has(s) : existing.includes(s));
|
|
62
|
+
let counter = 1;
|
|
63
|
+
while (hasSlug(slug)) {
|
|
64
|
+
slug = `${baseSlug}${separator}${counter++}`;
|
|
65
|
+
}
|
|
66
|
+
if (existing instanceof Set) {
|
|
67
|
+
existing.add(slug);
|
|
68
|
+
} else if (Array.isArray(existing)) {
|
|
69
|
+
existing.push(slug);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return slug;
|
|
74
|
+
};
|