-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathutils.nim
353 lines (312 loc) · 9.77 KB
/
utils.nim
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import std/[unicode, uri, strformat, os, strutils, options, json, jsonutils, sugar, net]
import chronos, chronicles, chronos/asyncproc
import "$nim/compiler/pathutils"
import json_rpc/private/jrpc_sys
type
FingerTable = seq[tuple[u16pos, offset: int]]
UriParseError* = object of Defect
uri: string
const
NIM_SCRIPT_API_TEMPLATE* = staticRead("templates/nimscriptapi.nim") #We add this file to nimsuggest and `nim check` to support nimble files
proc writeStackTrace*(ex = getCurrentException()) =
try:
if ex != nil:
stderr.write "An exception occured \n"
stderr.write ex.msg & "\n"
stderr.write ex.getStackTrace()
else:
stderr.write getStackTrace()
except IOError:
discard
proc createUTFMapping*(line: string): FingerTable =
var pos = 0
for rune in line.runes:
#echo pos
#echo rune.int32
case rune.int32
of 0x0000 .. 0x007F:
# One UTF-16 unit, one UTF-8 unit
pos += 1
of 0x0080 .. 0x07FF:
# One UTF-16 unit, two UTF-8 units
result.add (u16pos: pos, offset: 1)
pos += 1
of 0x0800 .. 0xFFFF:
# One UTF-16 unit, three UTF-8 units
result.add (u16pos: pos, offset: 2)
pos += 1
of 0x10000 .. 0x10FFFF:
# Two UTF-16 units, four UTF-8 units
result.add (u16pos: pos, offset: 2)
pos += 2
else:
discard
#echo fingerTable
proc utf16Len*(utf8Str: string): int =
result = 0
for rune in utf8Str.runes:
case rune.int32
of 0x0000 .. 0x007F,
0x0080 .. 0x07FF,
0x0800 .. 0xFFFF:
result += 1
of 0x10000 .. 0x10FFFF:
result += 2
else:
discard
proc utf16to8*(fingerTable: FingerTable, utf16pos: int): int =
result = utf16pos
for finger in fingerTable:
if finger.u16pos < utf16pos:
result += finger.offset
else:
break
proc utf8to16*(fingerTable: FingerTable, utf8pos: int): int =
result = utf8pos
for finger in fingerTable:
if finger.u16pos < result:
result -= finger.offset
else:
break
when isMainModule:
import termstyle
var x = "heållo☀☀wor𐐀𐐀☀ld heållo☀wor𐐀ld heållo☀wor𐐀ld"
var fingerTable = createUTFMapping(x)
var corrected = utf16to8(fingerTable, 5)
for y in x:
if corrected == 0:
echo "-"
if ord(y) > 125:
echo ord(y).red
else:
echo ord(y)
corrected -= 1
echo "utf16\tchar\tutf8\tchar\tchk"
var pos = 0
for c in x.runes:
stdout.write pos
stdout.write "\t"
stdout.write c
stdout.write "\t"
var corrected = utf16to8(fingerTable, pos)
stdout.write corrected
stdout.write "\t"
stdout.write x.runeAt(corrected)
if c.int32 == x.runeAt(corrected).int32:
stdout.write "\tOK".green
else:
stdout.write "\tERR".red
stdout.write "\n"
if c.int >= 0x10000:
pos += 2
else:
pos += 1
proc uriToPath*(uri: string): string =
## Convert an RFC 8089 file URI to a native, platform-specific, absolute path.
#let startIdx = when defined(windows): 8 else: 7
#normalizedPath(uri[startIdx..^1])
let parsed = uri.parseUri
if parsed.scheme != "file":
var e = newException(
UriParseError,
"Invalid scheme in uri \"{uri}\": {parsed.scheme}, only \"file\" is supported".fmt,
)
e.uri = uri
raise e
if parsed.hostname != "":
var e = newException(
UriParseError,
"Invalid hostname in uri \"{uri}\": {parsed.hostname}, only empty hostname is supported".fmt,
)
e.uri = uri
raise e
return normalizedPath(
when defined(windows):
parsed.path[1 ..^ 1]
else:
parsed.path
).decodeUrl
proc pathToUri*(path: string): string =
# This is a modified copy of encodeUrl in the uri module. This doesn't encode
# the / character, meaning a full file path can be passed in without breaking
# it.
result = "file://" & newStringOfCap(path.len + path.len shr 2)
# assume 12% non-alnum-chars
when defined(windows):
add(result, '/')
for c in path:
case c
# https://tools.ietf.org/html/rfc3986#section-2.3
of 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '-', '.', '_', '~', '/':
add(result, c)
of '\\':
when defined(windows):
add(result, '/')
else:
add(result, '%')
add(result, toHex(ord(c), 2))
else:
add(result, '%')
add(result, toHex(ord(c), 2))
proc catchOrQuit*(error: Exception) =
if error of CatchableError:
trace "Async operation ended with a recoverable error", err = error.msg
else:
fatal "Fatal exception reached", err = error.msg, stackTrace = getStackTrace()
quit 1
proc traceAsyncErrors*(fut: Future) =
fut.addCallback do(data: pointer):
if not fut.error.isNil:
catchOrQuit fut.error[]
iterator groupBy*[T, U](
s: openArray[T], f: proc(a: T): U {.gcsafe, raises: [].}
): tuple[k: U, v: seq[T]] =
var t = initTable[U, seq[T]]()
for x in s:
let fx = f(x)
t.mGetOrPut(fx, @[]).add(x)
for x in t.pairs:
yield x
#Compatibility layer with asyncdispatch
proc callSoon*(cb: proc() {.gcsafe.}) {.gcsafe.} =
proc cbWrapper() {.gcsafe.} =
try:
{.cast(raises: []).}:
cb()
except CatchableError:
discard #TODO handle
callSoon do(data: pointer) {.gcsafe.}:
cbWrapper()
proc addCallback*(
future: FutureBase, cb: proc() {.closure, gcsafe, raises: [].}
) {.deprecated: "Replace with built-in chronos mechanism".} =
## Adds the callbacks proc to be called when the future completes.
##
## If future has already completed then `cb` will be called immediately.
assert cb != nil
if future.finished:
callSoon do(data: pointer) {.gcsafe.}:
cb()
else:
future.addCallback do(data: pointer) {.gcsafe.}:
cb()
proc addCallbackNoEffects[T](
future: Future[T], cb: proc(future: Future[T]) {.closure, gcsafe, raises: [].}
) =
## Adds the callbacks proc to be called when the future completes.
##
## If future has already completed then `cb` will be called immediately.
future.addCallback(
proc() =
cb(future)
)
proc addCallback*[T](
future: Future[T], cb: proc(future: Future[T]) {.closure, gcsafe.}
) {.deprecated.} =
## Adds the callbacks proc to be called when the future completes.
##
## If future has already completed then `cb` will be called immediately.
proc cbWrapper(fut: Future[T]) {.closure, gcsafe, raises: [].} =
try:
{.cast(raises: []).}:
cb(fut)
except CatchableError as exc:
future.fail((ref CatchableError)(msg: exc.msg))
future.addCallbackNoEffects(
proc(fut: Future[T]) {.closure, gcsafe, raises: [].} =
cbWrapper(future)
)
proc isRelTo*(path, base: string): bool {.raises: [].} =
### isRelativeTo version that do not throws
try:
isRelativeTo(path, base)
except Exception:
false
proc tryRelativeTo*(path, base: string): Option[string] =
try:
some relativeTo(AbsoluteFile(path), base.AbsoluteDir).string
except Exception:
none(string)
proc get*[T](params: RequestParamsRx, key: string): T =
if params.kind == rpNamed:
for np in params.named:
if np.name == key:
return np.value.string.parseJson.to(T)
raise newException(KeyError, "Key not found")
proc to*(params: RequestParamsRx, T: typedesc): T =
let value = $params.toJson()
parseJson(value).to(T)
proc head*[T](xs: seq[T]): Option[T] =
if xs.len > 0:
some(xs[0])
else:
none(T)
proc partial*[A, B, C](
fn: proc(a: A, b: B): C {.gcsafe, raises: [], nimcall.}, a: A
): proc(b: B): C {.gcsafe, raises: [].} =
return proc(b: B): C {.gcsafe, raises: [].} =
return fn(a, b)
proc partial*[A, B](
fn: proc(a: A, b: B): void {.gcsafe, raises: [], nimcall.}, a: A
): proc(b: B): void {.gcsafe, raises: [].} =
return proc(b: B): void {.gcsafe, raises: [].} =
fn(a, b)
proc partial*[A, B, C, D](
fn: proc(a: A, b: B, c: C): D {.gcsafe, raises: [], nimcall.}, a: A
): proc(b: B, c: C): D {.gcsafe, raises: [].} =
return proc(b: B, c: C): D {.gcsafe, raises: [].} =
return fn(a, b, c)
proc ensureStorageDir*(): string =
result = getTempDir() / "nimlangserver"
discard existsOrCreateDir(result)
proc either*[T](fut1, fut2: Future[T]): Future[T] {.async.} =
let res = await race(fut1, fut2)
if fut1.finished:
result = fut1.read
cancelSoon fut2
else:
result = fut2.read
cancelSoon fut1
proc map*[T, U](
f: Future[T], fn: proc(t: T): U {.raises: [], gcsafe.}
): Future[U] {.async.} =
fn(await f)
proc map*[U](
f: Future[void], fn: proc(): U {.raises: [], gcsafe.}
): Future[U] {.async.} =
await f
fn()
proc withTimeout*[T](fut: Future[T], timeout: int = 500): Future[Option[T]] {.async.} =
#Returns None when the timeout is reached and cancels the fut. Otherwise returns the Fut
let timeoutFut = sleepAsync(timeout).map(() => none(T))
let optFut = fut.map((r: T) => some r)
await either(optFut, timeoutFut)
proc getNextFreePort*(): Port =
let s = newSocket()
s.bindAddr(Port(0), "localhost")
let (_, port) = s.getLocalAddr
s.close()
port
func isWord*(str: string): bool =
var str = str.toLower()
for c in str:
if c.int notin {48..57, 97..122}: # Allow 0-9 and a-z
return false
return true
proc getNimScriptAPITemplatePath*(): string =
result = getCacheDir("nimlangserver")
createDir(result)
result = result / "nimscriptapi.nim"
if not result.fileExists:
writeFile(result, NIM_SCRIPT_API_TEMPLATE)
debug "NimScriptApiPath", path = result
proc shutdownChildProcess*(p: AsyncProcessRef): Future[void] {.async.} =
try:
let exitCode = await p.terminateAndWaitForExit(2.seconds) # debug "Process terminated with exit code: ", exitCode
except CatchableError:
try:
let forcedExitCode = await p.killAndWaitForExit(3.seconds)
debug "Process forcibly killed with exit code: ", exitCode = forcedExitCode
except CatchableError:
debug "Could not kill process in time either!"
writeStackTrace()