1
+ // thank you https://github.com/blakeembrey/change-case
2
+ // for much of this code
3
+
4
+ // TODO: implement `string-ts` logic
5
+
1
6
/**
2
7
* First letter uppercase, other lowercase
3
8
* @category string
@@ -14,18 +19,291 @@ export function lowercase(str: string): string {
14
19
return str . toLowerCase ( )
15
20
}
16
21
17
- export {
18
- camelCase ,
19
- capitalCase ,
20
- constantCase ,
21
- dotCase ,
22
- kebabCase ,
23
- kebabCase as paramCase ,
24
- noCase ,
25
- pascalCase ,
26
- pathCase ,
27
- sentenceCase ,
28
- snakeCase ,
29
- split ,
30
- } from 'change-case'
31
- export { titleCase } from 'title-case'
22
+ // Regexps involved with splitting words in various case formats.
23
+ const SPLIT_LOWER_UPPER_RE = / ( [ \p{ Ll} \d ] ) ( \p{ Lu} ) / gu
24
+ const SPLIT_UPPER_UPPER_RE = / ( \p{ Lu} ) ( \p{ Lu} \p{ Ll} ) / gu
25
+
26
+ // Used to iterate over the initial split result and separate numbers.
27
+ const SPLIT_SEPARATE_NUMBER_RE = / ( \d ) \p{ Ll} | ( \p{ L} ) \d / u
28
+
29
+ // Regexp involved with stripping non-word characters from the result.
30
+ const DEFAULT_STRIP_REGEXP = / [ ^ \p{ L} \d ] + / giu
31
+
32
+ // The replacement value for splits.
33
+ const SPLIT_REPLACE_VALUE = '$1\0$2'
34
+
35
+ // The default characters to keep after transforming case.
36
+ const DEFAULT_PREFIX_SUFFIX_CHARACTERS = ''
37
+
38
+ /**
39
+ * Supported locale values. Use `false` to ignore locale.
40
+ * Defaults to `undefined`, which uses the host environment.
41
+ */
42
+ export type Locale = string [ ] | string | false | undefined
43
+
44
+ /**
45
+ * Options used for converting strings to pascal/camel case.
46
+ */
47
+ export interface PascalCaseOptions extends CaseOptions {
48
+ mergeAmbiguousCharacters ?: boolean
49
+ }
50
+
51
+ /**
52
+ * Options used for converting strings to any case.
53
+ */
54
+ export interface CaseOptions {
55
+ locale ?: Locale
56
+ split ?: ( value : string ) => string [ ]
57
+ delimiter ?: string
58
+ prefixCharacters ?: string
59
+ suffixCharacters ?: string
60
+ }
61
+
62
+ /**
63
+ * Split any cased input strings into an array of words.
64
+ */
65
+ export function split ( value : string ) : string [ ] {
66
+ let result = value . trim ( )
67
+
68
+ result = result
69
+ . replace ( SPLIT_LOWER_UPPER_RE , SPLIT_REPLACE_VALUE )
70
+ . replace ( SPLIT_UPPER_UPPER_RE , SPLIT_REPLACE_VALUE )
71
+
72
+ result = result . replace ( DEFAULT_STRIP_REGEXP , '\0' )
73
+
74
+ let start = 0
75
+ let end = result . length
76
+
77
+ // Trim the delimiter from around the output string.
78
+ while ( result . charAt ( start ) === '\0' ) start ++
79
+ if ( start === end )
80
+ return [ ]
81
+ while ( result . charAt ( end - 1 ) === '\0' ) end --
82
+
83
+ return result . slice ( start , end ) . split ( / \0 / g)
84
+ }
85
+
86
+ /**
87
+ * Split the input string into an array of words, separating numbers.
88
+ */
89
+ export function splitSeparateNumbers ( value : string ) : string [ ] {
90
+ const words = split ( value )
91
+ for ( let i = 0 ; i < words . length ; i ++ ) {
92
+ const word = words [ i ]
93
+ const match = SPLIT_SEPARATE_NUMBER_RE . exec ( word )
94
+ if ( match ) {
95
+ const offset = match . index + ( match [ 1 ] ?? match [ 2 ] ) . length
96
+ words . splice ( i , 1 , word . slice ( 0 , offset ) , word . slice ( offset ) )
97
+ }
98
+ }
99
+ return words
100
+ }
101
+
102
+ /**
103
+ * Convert a string to space separated lower case (`foo bar`).
104
+ */
105
+ export function noCase ( input : string , options ?: CaseOptions ) : string {
106
+ const [ prefix , words , suffix ] = splitPrefixSuffix ( input , options )
107
+ return (
108
+ prefix
109
+ + words . map ( lowerFactory ( options ?. locale ) ) . join ( options ?. delimiter ?? ' ' )
110
+ + suffix
111
+ )
112
+ }
113
+
114
+ /**
115
+ * Convert a string to camel case (`fooBar`).
116
+ */
117
+ export function camelCase ( input : string , options ?: PascalCaseOptions ) : string {
118
+ const [ prefix , words , suffix ] = splitPrefixSuffix ( input , options )
119
+ const lower = lowerFactory ( options ?. locale )
120
+ const upper = upperFactory ( options ?. locale )
121
+ const transform = options ?. mergeAmbiguousCharacters
122
+ ? capitalCaseTransformFactory ( lower , upper )
123
+ : pascalCaseTransformFactory ( lower , upper )
124
+ return (
125
+ prefix
126
+ + words
127
+ . map ( ( word , index ) => {
128
+ if ( index === 0 )
129
+ return lower ( word )
130
+ return transform ( word , index )
131
+ } )
132
+ . join ( options ?. delimiter ?? '' )
133
+ + suffix
134
+ )
135
+ }
136
+
137
+ /**
138
+ * Convert a string to pascal case (`FooBar`).
139
+ */
140
+ export function pascalCase ( input : string , options ?: PascalCaseOptions ) : string {
141
+ const [ prefix , words , suffix ] = splitPrefixSuffix ( input , options )
142
+ const lower = lowerFactory ( options ?. locale )
143
+ const upper = upperFactory ( options ?. locale )
144
+ const transform = options ?. mergeAmbiguousCharacters
145
+ ? capitalCaseTransformFactory ( lower , upper )
146
+ : pascalCaseTransformFactory ( lower , upper )
147
+ return prefix + words . map ( transform ) . join ( options ?. delimiter ?? '' ) + suffix
148
+ }
149
+
150
+ /**
151
+ * Convert a string to pascal snake case (`Foo_Bar`).
152
+ */
153
+ export function pascalSnakeCase ( input : string , options ?: CaseOptions ) : string {
154
+ return capitalCase ( input , { delimiter : '_' , ...options } )
155
+ }
156
+
157
+ /**
158
+ * Convert a string to capital case (`Foo Bar`).
159
+ */
160
+ export function capitalCase ( input : string , options ?: CaseOptions ) : string {
161
+ const [ prefix , words , suffix ] = splitPrefixSuffix ( input , options )
162
+ const lower = lowerFactory ( options ?. locale )
163
+ const upper = upperFactory ( options ?. locale )
164
+ return (
165
+ prefix
166
+ + words
167
+ . map ( capitalCaseTransformFactory ( lower , upper ) )
168
+ . join ( options ?. delimiter ?? ' ' )
169
+ + suffix
170
+ )
171
+ }
172
+
173
+ /**
174
+ * Convert a string to constant case (`FOO_BAR`).
175
+ */
176
+ export function constantCase ( input : string , options ?: CaseOptions ) : string {
177
+ const [ prefix , words , suffix ] = splitPrefixSuffix ( input , options )
178
+ return (
179
+ prefix
180
+ + words . map ( upperFactory ( options ?. locale ) ) . join ( options ?. delimiter ?? '_' )
181
+ + suffix
182
+ )
183
+ }
184
+
185
+ /**
186
+ * Convert a string to dot case (`foo.bar`).
187
+ */
188
+ export function dotCase ( input : string , options ?: CaseOptions ) : string {
189
+ return noCase ( input , { delimiter : '.' , ...options } )
190
+ }
191
+
192
+ /**
193
+ * Convert a string to kebab case (`foo-bar`).
194
+ */
195
+ export function kebabCase ( input : string , options ?: CaseOptions ) : string {
196
+ return noCase ( input , { delimiter : '-' , ...options } )
197
+ }
198
+
199
+ /**
200
+ * Convert a string to path case (`foo/bar`).
201
+ */
202
+ export function pathCase ( input : string , options ?: CaseOptions ) : string {
203
+ return noCase ( input , { delimiter : '/' , ...options } )
204
+ }
205
+
206
+ /**
207
+ * Convert a string to path case (`Foo bar`).
208
+ */
209
+ export function sentenceCase ( input : string , options ?: CaseOptions ) : string {
210
+ const [ prefix , words , suffix ] = splitPrefixSuffix ( input , options )
211
+ const lower = lowerFactory ( options ?. locale )
212
+ const upper = upperFactory ( options ?. locale )
213
+ const transform = capitalCaseTransformFactory ( lower , upper )
214
+ return (
215
+ prefix
216
+ + words
217
+ . map ( ( word , index ) => {
218
+ if ( index === 0 )
219
+ return transform ( word )
220
+ return lower ( word )
221
+ } )
222
+ . join ( options ?. delimiter ?? ' ' )
223
+ + suffix
224
+ )
225
+ }
226
+
227
+ /**
228
+ * Convert a string to snake case (`foo_bar`).
229
+ */
230
+ export function snakeCase ( input : string , options ?: CaseOptions ) : string {
231
+ return noCase ( input , { delimiter : '_' , ...options } )
232
+ }
233
+
234
+ /**
235
+ * Convert a string to header case (`Foo-Bar`).
236
+ */
237
+ export function trainCase ( input : string , options ?: CaseOptions ) : string {
238
+ return capitalCase ( input , { delimiter : '-' , ...options } )
239
+ }
240
+
241
+ function lowerFactory ( locale : Locale ) : ( input : string ) => string {
242
+ return locale === false
243
+ ? ( input : string ) => input . toLowerCase ( )
244
+ : ( input : string ) => input . toLocaleLowerCase ( locale )
245
+ }
246
+
247
+ function upperFactory ( locale : Locale ) : ( input : string ) => string {
248
+ return locale === false
249
+ ? ( input : string ) => input . toUpperCase ( )
250
+ : ( input : string ) => input . toLocaleUpperCase ( locale )
251
+ }
252
+
253
+ function capitalCaseTransformFactory (
254
+ lower : ( input : string ) => string ,
255
+ upper : ( input : string ) => string ,
256
+ ) {
257
+ return ( word : string ) => `${ upper ( word [ 0 ] ) } ${ lower ( word . slice ( 1 ) ) } `
258
+ }
259
+
260
+ function pascalCaseTransformFactory (
261
+ lower : ( input : string ) => string ,
262
+ upper : ( input : string ) => string ,
263
+ ) {
264
+ return ( word : string , index : number ) => {
265
+ const char0 = word [ 0 ]
266
+ const initial
267
+ = index > 0 && char0 >= '0' && char0 <= '9' ? `_${ char0 } ` : upper ( char0 )
268
+ return initial + lower ( word . slice ( 1 ) )
269
+ }
270
+ }
271
+
272
+ function splitPrefixSuffix (
273
+ input : string ,
274
+ options : CaseOptions = { } ,
275
+ ) : [ string , string [ ] , string ] {
276
+ const splitFn
277
+ = options . split ?? split
278
+ const prefixCharacters
279
+ = options . prefixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS
280
+ const suffixCharacters
281
+ = options . suffixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS
282
+ let prefixIndex = 0
283
+ let suffixIndex = input . length
284
+
285
+ while ( prefixIndex < input . length ) {
286
+ const char = input . charAt ( prefixIndex )
287
+ if ( ! prefixCharacters . includes ( char ) )
288
+ break
289
+ prefixIndex ++
290
+ }
291
+
292
+ while ( suffixIndex > prefixIndex ) {
293
+ const index = suffixIndex - 1
294
+ const char = input . charAt ( index )
295
+ if ( ! suffixCharacters . includes ( char ) )
296
+ break
297
+ suffixIndex = index
298
+ }
299
+
300
+ return [
301
+ input . slice ( 0 , prefixIndex ) ,
302
+ splitFn ( input . slice ( prefixIndex , suffixIndex ) ) ,
303
+ input . slice ( suffixIndex ) ,
304
+ ]
305
+ }
306
+
307
+ export * from './title-case'
308
+ export * from './swap-case'
309
+ export * from './sponge-case'
0 commit comments