File size: 5,731 Bytes
1dbc34b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/**
 * String utility functions for common text operations
 */

/**
 * Truncate a string to a maximum length, adding an ellipsis if truncated
 * @param str - The string to truncate
 * @param maxLength - Maximum length of the result (including ellipsis)
 * @param ellipsis - The ellipsis string to use (default: '...')
 * @returns The truncated string
 */
export function truncate(str: string, maxLength: number, ellipsis: string = '...'): string {
  if (maxLength < ellipsis.length) {
    throw new Error(
      `maxLength (${maxLength}) must be at least the length of ellipsis (${ellipsis.length})`
    );
  }

  if (str.length <= maxLength) {
    return str;
  }

  return str.slice(0, maxLength - ellipsis.length) + ellipsis;
}

/**
 * Convert a string to kebab-case (e.g., "Hello World" -> "hello-world")
 * @param str - The string to convert
 * @returns The kebab-case string
 */
export function toKebabCase(str: string): string {
  return str
    .replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase -> camel-Case
    .replace(/[\s_]+/g, '-') // spaces and underscores -> hyphens
    .replace(/[^a-zA-Z0-9-]/g, '') // remove non-alphanumeric (except hyphens)
    .replace(/-+/g, '-') // collapse multiple hyphens
    .replace(/^-|-$/g, '') // remove leading/trailing hyphens
    .toLowerCase();
}

/**
 * Convert a string to camelCase (e.g., "hello-world" -> "helloWorld")
 * @param str - The string to convert
 * @returns The camelCase string
 */
export function toCamelCase(str: string): string {
  return str
    .replace(/[^a-zA-Z0-9\s_-]/g, '') // remove special characters
    .replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
    .replace(/^[A-Z]/, (char) => char.toLowerCase());
}

/**
 * Convert a string to PascalCase (e.g., "hello-world" -> "HelloWorld")
 * @param str - The string to convert
 * @returns The PascalCase string
 */
export function toPascalCase(str: string): string {
  const camel = toCamelCase(str);
  return camel.charAt(0).toUpperCase() + camel.slice(1);
}

/**
 * Capitalize the first letter of a string
 * @param str - The string to capitalize
 * @returns The string with first letter capitalized
 */
export function capitalize(str: string): string {
  if (str.length === 0) {
    return str;
  }
  return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
 * Remove duplicate whitespace from a string, preserving single spaces
 * @param str - The string to clean
 * @returns The string with duplicate whitespace removed
 */
export function collapseWhitespace(str: string): string {
  return str.replace(/\s+/g, ' ').trim();
}

/**
 * Check if a string is empty or contains only whitespace
 * @param str - The string to check
 * @returns True if the string is blank
 */
export function isBlank(str: string | null | undefined): boolean {
  return str === null || str === undefined || str.trim().length === 0;
}

/**
 * Check if a string is not empty and contains non-whitespace characters
 * @param str - The string to check
 * @returns True if the string is not blank
 */
export function isNotBlank(str: string | null | undefined): boolean {
  return !isBlank(str);
}

/**
 * Safely parse a string to an integer, returning a default value on failure
 * @param str - The string to parse
 * @param defaultValue - The default value if parsing fails (default: 0)
 * @returns The parsed integer or the default value
 */
export function safeParseInt(str: string | null | undefined, defaultValue: number = 0): number {
  if (isBlank(str)) {
    return defaultValue;
  }

  const parsed = parseInt(str!, 10);
  return isNaN(parsed) ? defaultValue : parsed;
}

/**
 * Generate a slug from a string (URL-friendly identifier)
 * @param str - The string to convert to a slug
 * @param maxLength - Optional maximum length for the slug
 * @returns The slugified string
 */
export function slugify(str: string, maxLength?: number): string {
  let slug = str
    .toLowerCase()
    .normalize('NFD') // Normalize unicode characters
    .replace(/[\u0300-\u036f]/g, '') // Remove diacritics
    .replace(/[^a-z0-9\s-]/g, '') // Remove non-alphanumeric
    .replace(/\s+/g, '-') // Replace spaces with hyphens
    .replace(/-+/g, '-') // Collapse multiple hyphens
    .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens

  if (maxLength !== undefined && slug.length > maxLength) {
    // Truncate at word boundary if possible
    slug = slug.slice(0, maxLength);
    const lastHyphen = slug.lastIndexOf('-');
    if (lastHyphen > maxLength * 0.5) {
      slug = slug.slice(0, lastHyphen);
    }
    slug = slug.replace(/-$/g, ''); // Remove trailing hyphen after truncation
  }

  return slug;
}

/**
 * Escape special regex characters in a string
 * @param str - The string to escape
 * @returns The escaped string safe for use in a RegExp
 */
export function escapeRegex(str: string): string {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

/**
 * Pluralize a word based on count
 * @param word - The singular form of the word
 * @param count - The count to base pluralization on
 * @param pluralForm - Optional custom plural form (default: word + 's')
 * @returns The word in singular or plural form
 */
export function pluralize(word: string, count: number, pluralForm?: string): string {
  if (count === 1) {
    return word;
  }
  return pluralForm || `${word}s`;
}

/**
 * Format a count with its associated word (e.g., "1 item", "3 items")
 * @param count - The count
 * @param singular - The singular form of the word
 * @param plural - Optional custom plural form
 * @returns Formatted string with count and word
 */
export function formatCount(count: number, singular: string, plural?: string): string {
  return `${count} ${pluralize(singular, count, plural)}`;
}