-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathUtils.ts
More file actions
171 lines (155 loc) · 6.02 KB
/
Copy pathUtils.ts
File metadata and controls
171 lines (155 loc) · 6.02 KB
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
import { Data, String as EffectString } from 'effect';
/**
* Takes the input string and returns the camelCase equivalent
*
* @example
* ```ts
* import * as Utils from '@graphprotocol/typesync/Utils'
*
* expect(Utils.toCamelCase('Address line 1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('AddressLine1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('addressLine1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('address_line_1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('address-line-1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('address-line_1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('address-line 1')).toEqual('addressLine1');
* expect(Utils.toCamelCase('ADDRESS_LINE_1')).toEqual('addressLine1');
* ```
*
* @since 0.0.1
*
* @param str input string
* @returns camelCased value of the input string
*/
export function toCamelCase(str: string) {
if (EffectString.isEmpty(str) || /^\s+$/.test(str)) {
throw new InvalidInputError({ input: str, cause: 'Input is empty or contains only whitespace' });
}
let result = '';
let capitalizeNext = false;
let i = 0;
// Skip leading non-alphanumeric characters
while (i < EffectString.length(str) && !/[a-zA-Z0-9]/.test(str[i])) {
i++;
}
for (; i < EffectString.length(str); i++) {
const char = str[i];
if (/[a-zA-Z0-9]/.test(char)) {
if (capitalizeNext) {
result += EffectString.toUpperCase(char);
capitalizeNext = false;
} else if (EffectString.length(result) === 0) {
// First character should always be lowercase
result += EffectString.toLowerCase(char);
} else if (/[A-Z]/.test(char) && i > 0 && /[a-z0-9]/.test(str[i - 1])) {
// Capital letter following lowercase/number - this indicates a word boundary
// So we need to capitalize this letter (it starts a new word)
result += EffectString.toUpperCase(char);
} else {
result += EffectString.toLowerCase(char);
}
} else {
// Non-alphanumeric character - set flag to capitalize next letter
capitalizeNext = EffectString.length(result) > 0; // Only capitalize if we have existing content
}
}
return result;
}
/**
* Takes the input string and returns the PascalCase equivalent
*
* @example
* ```ts
* import * as Utils from '@graphprotocol/typesync/Utils'
*
* expect(Utils.toPascalCase('Address line 1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('AddressLine1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('addressLine1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('address_line_1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('address-line-1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('address-line_1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('address-line 1')).toEqual('AddressLine1');
* expect(Utils.toPascalCase('ADDRESS_LINE_1')).toEqual('AddressLine1');
* ```
*
* @since 0.0.1
*
* @param str input string
* @returns PascalCased value of the input string
*/
export function toPascalCase(str: string): string {
if (EffectString.isEmpty(str) || /^\s+$/.test(str)) {
throw new InvalidInputError({ input: str, cause: 'Input is empty or contains only whitespace' });
}
let result = '';
let capitalizeNext = true; // Start with true to capitalize the first letter
let i = 0;
// Skip leading non-alphanumeric characters
while (i < EffectString.length(str) && !/[a-zA-Z0-9]/.test(str[i])) {
i++;
}
for (; i < EffectString.length(str); i++) {
const char = str[i];
if (/[a-zA-Z0-9]/.test(char)) {
if (capitalizeNext) {
result += EffectString.toUpperCase(char);
capitalizeNext = false;
} else if (/[A-Z]/.test(char) && i > 0 && /[a-z0-9]/.test(str[i - 1])) {
// Capital letter following lowercase/number - this indicates a word boundary
// So we need to capitalize this letter (it starts a new word)
result += EffectString.toUpperCase(char);
} else {
result += EffectString.toLowerCase(char);
}
} else {
// Non-alphanumeric character - set flag to capitalize next letter
capitalizeNext = true;
}
}
return result;
}
export class InvalidInputError extends Data.TaggedError('/typesync/errors/InvalidInputError')<{
readonly input: string;
readonly cause: unknown;
}> {}
/* ------------------------------------------------------------------ */
/* Windows-safe migration loader */
/* ------------------------------------------------------------------ */
import { pathToFileURL } from 'node:url';
import { FileSystem } from '@effect/platform/FileSystem';
import type { Loader, ResolvedMigration } from '@effect/sql/Migrator';
import { MigrationError } from '@effect/sql/Migrator';
import * as Effect from 'effect/Effect';
/**
* Patched version of
* `@effect/sql/Migrator/FileSystem.fromFileSystem`.
*
* The only difference is that the dynamic `import()` receives a proper
* `file://` URL, so it works on Windows as well as on Linux / macOS.
*/
export const fromFileSystem = (dir: string): Loader<FileSystem> =>
FileSystem.pipe(
/* read directory ----------------------------------------------------- */
Effect.flatMap((FS) => FS.readDirectory(dir)),
Effect.mapError((e) => new MigrationError({ reason: 'failed', message: e.message })),
/* build migration list ---------------------------------------------- */
Effect.map(
(files): ReadonlyArray<ResolvedMigration> =>
files
.flatMap((file) => {
const m = file.match(/^(?:.*[\\/])?(\d+)_([^.]+)\.(js|ts)$/); // win/posix
if (!m) return [];
const [basename, id, name] = m;
return [
[
Number(id),
name,
Effect.promise(
() => import(/* @vite-ignore */ /* webpackIgnore: true */ pathToFileURL(`${dir}/${basename}`).href),
),
],
] as const;
})
.sort(([a], [b]) => a - b),
),
);