merged upstream master

This commit is contained in:
Zahary Karadjov 2013-01-27 23:41:45 +02:00
commit 81a3585872
127 changed files with 4440 additions and 1496 deletions

View file

@ -35,6 +35,9 @@ import sockets, os
## that in the future this type's fields will not be exported therefore breaking
## your code.
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
##
## Asynchronous sockets
## ====================
##
@ -123,6 +126,7 @@ type
handleTask*: proc (s: PAsyncSocket) {.closure.}
lineBuffer: TaintedString ## Temporary storage for ``recvLine``
sendBuffer: string ## Temporary storage for ``send``
sslNeedAccept: bool
proto: TProtocol
deleg: PDelegate
@ -152,6 +156,7 @@ proc newAsyncSocket(): PAsyncSocket =
result.handleTask = (proc (s: PAsyncSocket) = nil)
result.lineBuffer = "".TaintedString
result.sendBuffer = ""
proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
protocol: TProtocol = IPPROTO_TCP,
@ -222,10 +227,22 @@ proc asyncSockHandleWrite(h: PObject) =
else:
PAsyncSocket(h).deleg.mode = fmReadWrite
else:
if PAsyncSocket(h).handleWrite != nil:
PAsyncSocket(h).handleWrite(PAsyncSocket(h))
if PAsyncSocket(h).sendBuffer != "":
let sock = PAsyncSocket(h)
let bytesSent = sock.socket.sendAsync(sock.sendBuffer)
assert bytesSent > 0
if bytesSent != sock.sendBuffer.len:
sock.sendBuffer = sock.sendBuffer[bytesSent .. -1]
elif bytesSent == sock.sendBuffer.len:
sock.sendBuffer = ""
if PAsyncSocket(h).handleWrite != nil:
PAsyncSocket(h).handleWrite(PAsyncSocket(h))
else:
PAsyncSocket(h).deleg.mode = fmRead
if PAsyncSocket(h).handleWrite != nil:
PAsyncSocket(h).handleWrite(PAsyncSocket(h))
else:
PAsyncSocket(h).deleg.mode = fmRead
when defined(ssl):
proc asyncSockDoHandshake(h: PObject) =
@ -337,7 +354,8 @@ proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
# deleg.open is set in ``toDelegate``.
client.socket = c
client.lineBuffer = ""
client.lineBuffer = "".TaintedString
client.sendBuffer = ""
client.info = SockConnected
proc accept*(server: PAsyncSocket, client: var PAsyncSocket) =
@ -422,6 +440,9 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool =
## sockets properly. This function guarantees that ``line`` is a full line,
## if this function can only retrieve some data; it will save this data and
## add it to the result when a full line is retrieved.
##
## Unlike ``sockets.recvLine`` this function will raise an EOS or ESSL
## exception if an error occurs.
setLen(line.string, 0)
var dataReceived = "".TaintedString
var ret = s.socket.recvLineAsync(dataReceived)
@ -440,8 +461,29 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool =
of RecvDisconnected:
result = true
of RecvFail:
s.SocketError(async = true)
result = false
proc send*(sock: PAsyncSocket, data: string) =
## Sends ``data`` to socket ``sock``. This is basically a nicer implementation
## of ``sockets.sendAsync``.
##
## If ``data`` cannot be sent immediately it will be buffered and sent
## when ``sock`` becomes writeable (during the ``handleWrite`` event).
## It's possible that only a part of ``data`` will be sent immediately, while
## the rest of it will be buffered and sent later.
if sock.sendBuffer.len != 0:
sock.sendBuffer.add(data)
return
let bytesSent = sock.socket.sendAsync(data)
assert bytesSent >= 0
if bytesSent == 0:
sock.sendBuffer.add(data)
sock.deleg.mode = fmReadWrite
elif bytesSent != data.len:
sock.sendBuffer.add(data[bytesSent .. -1])
sock.deleg.mode = fmReadWrite
proc timeValFromMilliseconds(timeout = 500): TTimeVal =
if timeout != -1:
var seconds = timeout div 1000
@ -556,7 +598,9 @@ when isMainModule:
proc testRead(s: PAsyncSocket, no: int) =
echo("Reading! " & $no)
var data = s.getSocket.recv()
var data = ""
if not s.recvLine(data):
OSError()
if data == "":
echo("Closing connection. " & $no)
s.close()

View file

@ -190,6 +190,11 @@ proc `$`*(s: TIntSet): string =
## The `$` operator for int sets.
dollarImpl()
proc empty*(s: TIntSet): bool {.inline.} =
## returns true if `s` is empty. This is safe to call even before
## the set has been initialized with `initIntSet`.
result = s.counter == 0
when isMainModule:
var x = initIntSet()
x.incl(1)

View file

@ -9,8 +9,16 @@
## :Author: Alex Mitchell
##
## This module implements operations for the built-in `seq`:idx: type
## which were inspired by functional programming languages.
## This module implements operations for the built-in `seq`:idx: type which
## were inspired by functional programming languages. If you are looking for
## the typical `map` function which applies a function to every element in a
## sequence, it already exists in the `system <system.html>`_ module in both
## mutable and immutable styles.
##
## Also, for functional style programming you may want to pass `anonymous procs
## <manual.html#anonymous-procs>`_ to procs like ``filter`` to reduce typing.
## Anonymous procs can use `the special do notation <manual.html#do-notation>`_
## which is more convenient in certain situations.
##
## **Note**: This interface will change as soon as the compiler supports
## closures and proper coroutines.
@ -19,7 +27,17 @@ when not defined(nimhygiene):
{.pragma: dirty.}
proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
## Takes several sequences' items and returns them inside of one sequence.
## Takes several sequences' items and returns them inside a new sequence.
##
## Example:
##
## .. code-block:: nimrod
## let
## s1 = @[1, 2, 3]
## s2 = @[4, 5]
## s3 = @[6, 7]
## total = concat(s1, s2, s3)
## assert total == @[1, 2, 3, 4, 5, 6, 7]
var L = 0
for seqitm in items(seqs): inc(L, len(seqitm))
newSeq(result, L)
@ -30,14 +48,42 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
inc(i)
proc distnct*[T](seq1: seq[T]): seq[T] =
## Removes duplicates from a sequence and returns it.
## Returns a new sequence without duplicates.
##
## This proc is `misspelled` on purpose to avoid a clash with the keyword
## ``distinct`` used to `define a derived type incompatible with its base
## type <manual.html#distinct-type>`_. Example:
##
## .. code-block:: nimrod
## let
## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4]
## dup2 = @["a", "a", "c", "d", "d"]
## unique1 = distnct(dup1)
## unique2 = distnct(dup2)
## assert unique1 == @[1, 3, 4, 2, 8]
## assert unique2 == @["a", "c", "d"]
result = @[]
for itm in items(seq1):
if not result.contains(itm): result.add(itm)
proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] =
## Combines two sequences. If one sequence is too short,
## the remaining items in the longer sequence are discarded.
## Returns a new sequence with a combination of the two input sequences.
##
## For convenience you can access the returned tuples through the named
## fields `a` and `b`. If one sequence is shorter, the remaining items in the
## longer sequence are discarded. Example:
##
## .. code-block:: nimrod
## let
## short = @[1, 2, 3]
## long = @[6, 5, 4, 3, 2, 1]
## words = @["one", "two", "three"]
## zip1 = zip(short, long)
## zip2 = zip(short, words)
## assert zip1 == @[(1, 6), (2, 5), (3, 4)]
## assert zip2 == @[(1, "one"), (2, "two"), (3, "three")]
## assert zip1[2].b == 4
## assert zip2[2].b == "three"
var m = min(seq1.len, seq2.len)
newSeq(result, m)
for i in 0 .. m-1: result[i] = (seq1[i], seq2[i])
@ -45,21 +91,124 @@ proc zip*[S, T](seq1: seq[S], seq2: seq[T]): seq[tuple[a: S, b: T]] =
iterator filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): T =
## Iterates through a sequence and yields every item that fulfills the
## predicate.
##
## Example:
##
## .. code-block:: nimrod
## let numbers = @[1, 4, 5, 8, 9, 7, 4]
## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0):
## echo($n)
## # echoes 4, 8, 4 in separate lines
for i in countup(0, len(seq1) -1):
var item = seq1[i]
if pred(item): yield seq1[i]
proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] =
## Returns all items in a sequence that fulfilled the predicate.
## Returns a new sequence with all the items that fulfilled the predicate.
##
## Example:
##
## .. code-block:: nimrod
## let
## colors = @["red", "yellow", "black"]
## f1 = filter(colors, proc(x: string): bool = x.len < 6)
## f2 = filter(colors) do (x: string) -> bool : x.len > 5
## assert f1 == @["red", "black"]
## assert f2 == @["yellow"]
accumulateResult(filter(seq1, pred))
template filterIt*(seq1, pred: expr): expr {.immediate, dirty.} =
## Finds a specific item in a sequence as long as the
## predicate returns true. The predicate needs to be an expression
## containing ``it``: ``filterIt("abcxyz", it == 'x')``.
block:
var result: type(seq1) = @[]
for it in items(seq1):
if pred: result.add(it)
result
## Returns a new sequence with all the items that fulfilled the predicate.
##
## Unlike the `proc` version, the predicate needs to be an expression using
## the ``it`` variable for testing, like: ``filterIt("abcxyz", it == 'x')``.
## Example:
##
## .. code-block:: nimrod
## let
## temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44]
## acceptable = filterIt(temperatures, it < 50 and it > -10)
## assert acceptable == @[-2.0, 24.5, 44.31]
var result {.gensym.}: type(seq1) = @[]
for it in items(seq1):
if pred: result.add(it)
result
template toSeq*(iter: expr): expr {.immediate.} =
## Transforms any iterator into a sequence.
##
## Example:
##
## .. code-block:: nimrod
## let
## numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
## odd_numbers = toSeq(filter(numeric) do (x: int) -> bool:
## if x mod 2 == 1:
## result = true)
## assert odd_numbers == @[1, 3, 5, 7, 9]
##
var result {.gensym.}: seq[type(iter)] = @[]
for x in iter: add(result, x)
result
when isMainModule:
import strutils
proc toStr(x: int): string {.procvar.} = $x
# concat test
let
s1 = @[1, 2, 3]
s2 = @[4, 5]
s3 = @[6, 7]
total = concat(s1, s2, s3)
assert total == @[1, 2, 3, 4, 5, 6, 7]
# duplicates test
let
dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4]
dup2 = @["a", "a", "c", "d", "d"]
unique1 = distnct(dup1)
unique2 = distnct(dup2)
assert unique1 == @[1, 3, 4, 2, 8]
assert unique2 == @["a", "c", "d"]
# zip test
let
short = @[1, 2, 3]
long = @[6, 5, 4, 3, 2, 1]
words = @["one", "two", "three"]
zip1 = zip(short, long)
zip2 = zip(short, words)
assert zip1 == @[(1, 6), (2, 5), (3, 4)]
assert zip2 == @[(1, "one"), (2, "two"), (3, "three")]
assert zip1[2].b == 4
assert zip2[2].b == "three"
# filter proc test
let
colors = @["red", "yellow", "black"]
f1 = filter(colors, proc(x: string): bool = x.len < 6)
f2 = filter(colors) do (x: string) -> bool : x.len > 5
assert f1 == @["red", "black"]
assert f2 == @["yellow"]
# filter iterator test
let numbers = @[1, 4, 5, 8, 9, 7, 4]
for n in filter(numbers, proc (x: int): bool = x mod 2 == 0):
echo($n)
# echoes 4, 8, 4 in separate lines
# filterIt test
let
temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44]
acceptable = filterIt(temperatures, it < 50 and it > -10)
assert acceptable == @[-2.0, 24.5, 44.31]
# toSeq test
let
numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = toSeq(filter(numeric) do (x: int) -> bool:
if x mod 2 == 1:
result = true)
assert odd_numbers == @[1, 3, 5, 7, 9]
echo "Finished doc tests"

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -308,6 +308,52 @@ proc `$`*[A, B](t: TOrderedTable[A, B]): string =
## The `$` operator for ordered hash tables.
dollarImpl()
proc sort*[A, B](t: var TOrderedTable[A, B],
cmp: proc (x,y: tuple[key: A, val: B]): int) =
## sorts `t` according to `cmp`. This modifies the internal list
## that kept the insertion order, so insertion order is lost after this
## call but key lookup and insertions remain possible after `sort` (in
## contrast to the `sort` for count tables).
var list = t.first
var
p, q, e, tail, oldhead: int
nmerges, psize, qsize, i: int
if t.counter == 0: return
var insize = 1
while true:
p = list; oldhead = list
list = -1; tail = -1; nmerges = 0
while p >= 0:
inc(nmerges)
q = p
psize = 0
i = 0
while i < insize:
inc(psize)
q = t.data[q].next
if q < 0: break
inc(i)
qsize = insize
while psize > 0 or (qsize > 0 and q >= 0):
if psize == 0:
e = q; q = t.data[q].next; dec(qsize)
elif qsize == 0 or q < 0:
e = p; p = t.data[p].next; dec(psize)
elif cmp((t.data[p].key, t.data[p].val),
(t.data[q].key, t.data[q].val)) <= 0:
e = p; p = t.data[p].next; dec(psize)
else:
e = q; q = t.data[q].next; dec(qsize)
if tail >= 0: t.data[tail].next = e
else: list = e
tail = e
p = q
t.data[tail].next = -1
if nmerges <= 1: break
insize = insize * 2
t.first = list
t.last = tail
# ------------------------------ count tables -------------------------------
type

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -18,6 +18,10 @@ proc LoadLib*(path: string): TLibHandle
## loads a library from `path`. Returns nil if the library could not
## be loaded.
proc LoadLib*(): TLibHandle
## gets the handle from the current executable. Returns nil if the
## library could not be loaded.
proc UnloadLib*(lib: TLibHandle)
## unloads the library `lib`
@ -57,6 +61,7 @@ when defined(posix):
importc, header: "<dlfcn.h>".}
proc LoadLib(path: string): TLibHandle = return dlopen(path, RTLD_NOW)
proc LoadLib(): TLibHandle = return dlopen(nil, RTLD_NOW)
proc UnloadLib(lib: TLibHandle) = dlclose(lib)
proc symAddr(lib: TLibHandle, name: cstring): pointer =
return dlsym(lib, name)
@ -78,6 +83,8 @@ elif defined(windows) or defined(dos):
proc LoadLib(path: string): TLibHandle =
result = cast[TLibHandle](winLoadLibrary(path))
proc LoadLib(): TLibHandle =
result = cast[TLibHandle](winLoadLibrary(nil))
proc UnloadLib(lib: TLibHandle) = FreeLibrary(cast[THINSTANCE](lib))
proc symAddr(lib: TLibHandle, name: cstring): pointer =

View file

@ -254,6 +254,8 @@ when defined(windows):
else:
when defined(haiku):
const iconvDll = "(libc.so.6|libiconv.so|libtextencoding.so)"
elif defined(macosx):
const iconvDll = "libiconv.dylib"
else:
const iconvDll = "(libc.so.6|libiconv.so)"
@ -449,8 +451,13 @@ proc convert*(s: string, destEncoding = "UTF-8",
close(c)
when IsMainModule:
var orig = "öäüß"
var crap = convert(orig, "CP1252", "UTF-8")
echo convert(crap, "ibm850", "CP1252")
echo getCurrentEncoding()
let
orig = "öäüß"
cp1252 = convert(orig, "CP1252", "UTF-8")
ibm850 = convert(cp1252, "ibm850", "CP1252")
current = getCurrentEncoding()
echo "Original string from source code: ", orig
echo "Forced ibm850 encoding: ", ibm850
echo "Current encoding: ", current
echo "From ibm850 to current: ", convert(ibm850, current, "ibm850")

View file

@ -17,9 +17,7 @@
## module will therefore not work with any Linux kernel prior to that, unless
## it has been patched to support inotify.
when defined(windows):
{.error: "Windows is not yet supported by this module.".}
elif defined(linux):
when defined(linux) or defined(nimdoc):
from posix import read
else:
{.error: "Your platform is not supported.".}

View file

@ -26,6 +26,10 @@ import sockets, strutils, parseutils, times, os, asyncio
## var ftp = FTPClient("example.org", user = "user", pass = "pass")
## ftp.connect()
## ftp.retrFile("file.ext", "file.ext")
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
type
TFTPClient* = object of TObject
@ -92,8 +96,9 @@ type
EFTP* = object of ESynch
proc FTPClient*(address: string, port = TPort(21),
user, pass = ""): TFTPClient =
## Create a ``TFTPClient`` object.
user, pass = ""): PFTPClient =
## Create a ``PFTPClient`` object.
new(result)
result.user = user
result.pass = pass
result.address = address
@ -109,7 +114,7 @@ proc getDSock(ftp: PFTPClient): TSocket =
proc getCSock(ftp: PFTPClient): TSocket =
if ftp.isAsync: return ftp.asyncCSock else: return ftp.csock
template blockingOperation(sock: TSocket, body: stmt) =
template blockingOperation(sock: TSocket, body: stmt) {.immediate.} =
if ftp.isAsync:
sock.setBlocking(true)
body
@ -274,11 +279,20 @@ proc getLines(ftp: PFTPClient, async: bool = false): bool =
## It doesn't if `async` is true, because it doesn't check for 226 then.
if ftp.dsockConnected:
var r = TaintedString""
if getDSock(ftp).recvAsync(r):
if r.string != "":
ftp.job.lines.add(r.string)
else:
ftp.dsockConnected = False
if ftp.isAsync:
if ftp.asyncDSock.recvLine(r):
if r.string == "":
ftp.dsockConnected = false
else:
ftp.job.lines.add(r.string & "\n")
else:
assert(not async)
if ftp.dsock.recvLine(r):
if r.string == "":
ftp.dsockConnected = false
else:
ftp.job.lines.add(r.string & "\n")
else: OSError()
if not async:
var readSocks: seq[TSocket] = @[ftp.getCSock()]
@ -385,7 +399,7 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string =
proc retrText*(ftp: PFTPClient, file: string, async = false): string =
## Retrieves ``file``. File must be ASCII text.
## If ``async`` is true, this function will return immediately and
## it will be your job to call ``poll`` to progress this operation.
## it will be your job to call asyncio's ``poll`` to progress this operation.
ftp.createJob(getLines, JRetrText)
ftp.pasv()
assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"]
@ -400,12 +414,14 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string =
proc getFile(ftp: PFTPClient, async = false): bool =
if ftp.dsockConnected:
var r = "".TaintedString
var bytesRead = 0
var returned = false
if async:
if not ftp.isAsync: raise newException(EFTP, "FTPClient must be async.")
returned = ftp.AsyncDSock.recvAsync(r)
bytesRead = ftp.AsyncDSock.recvAsync(r, BufferSize)
returned = bytesRead != -1
else:
r = getDSock(ftp).recv()
bytesRead = getDSock(ftp).recv(r, BufferSize)
returned = true
let r2 = r.string
if r2 != "":
@ -425,8 +441,9 @@ proc getFile(ftp: PFTPClient, async = false): bool =
proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) =
## Downloads ``file`` and saves it to ``dest``. Usage of this function
## asynchronously is recommended to view the progress of the download.
## The ``EvRetr`` event is given by ``poll`` when the download is finished,
## and the ``filename`` field will be equal to ``file``.
## The ``EvRetr`` event is passed to the specified ``handleEvent`` function
## when the download is finished, and the ``filename`` field will be equal
## to ``file``.
ftp.createJob(getFile, JRetr)
ftp.job.file = open(dest, mode = fmWrite)
ftp.pasv()
@ -450,11 +467,13 @@ proc doUpload(ftp: PFTPClient, async = false): bool =
if ftp.dsockConnected:
if ftp.job.toStore.len() > 0:
assert(async)
if ftp.asyncDSock.sendAsync(ftp.job.toStore):
let bytesSent = ftp.asyncDSock.sendAsync(ftp.job.toStore)
if bytesSent == ftp.job.toStore.len:
ftp.job.toStore = ""
ftp.job.progress.inc(ftp.job.toStore.len)
ftp.job.oneSecond.inc(ftp.job.toStore.len)
elif bytesSent != ftp.job.toStore.len and bytesSent != 0:
ftp.job.toStore = ftp.job.toStore[bytesSent .. -1]
ftp.job.progress.inc(bytesSent)
ftp.job.oneSecond.inc(bytesSent)
else:
var s = newStringOfCap(4000)
var len = ftp.job.file.readBuffer(addr(s[0]), 4000)
@ -472,8 +491,12 @@ proc doUpload(ftp: PFTPClient, async = false): bool =
if not async:
getDSock(ftp).send(s)
else:
if not ftp.asyncDSock.sendAsync(s):
ftp.job.toStore = s
let bytesSent = ftp.asyncDSock.sendAsync(s)
if bytesSent == 0:
ftp.job.toStore.add(s)
elif bytesSent != s.len:
ftp.job.toStore.add(s[bytesSent .. -1])
len = bytesSent
ftp.job.progress.inc(len)
ftp.job.oneSecond.inc(len)
@ -482,8 +505,9 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) =
## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
## function asynchronously is recommended to view the progress of
## the download.
## The ``EvStore`` event is given by ``poll`` when the upload is finished,
## and the ``filename`` field will be equal to ``file``.
## The ``EvStore`` event is passed to the specified ``handleEvent`` function
## when the upload is finished, and the ``filename`` field will be
## equal to ``file``.
ftp.createJob(doUpload, JStore)
ftp.job.file = open(file)
ftp.job.total = ftp.job.file.getFileSize()
@ -508,16 +532,6 @@ proc close*(ftp: PFTPClient) =
ftp.csock.close()
ftp.dsock.close()
discard """proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] =
result = (SockIdle, InvalidSocket)
var ftp = PAsyncFTPClient(h)
if ftp.jobInProgress:
case ftp.job.typ
of JRetrText, JRetr, JStore:
if ftp.dsockStatus == SockConnecting or ftp.dsockStatus == SockConnected:
result = (ftp.dsockStatus, ftp.dsock)
else: result = (SockIdle, ftp.dsock)"""
proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
if ftp.jobInProgress:
assertReply ftp.expectReply(), "226" # Make sure the transfer completed.
@ -540,32 +554,6 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) =
ftp.handleEvent(ftp, r)
discard """proc handleConnect(h: PObject) =
var ftp = PAsyncFTPClient(h)
ftp.dsockStatus = SockConnected
assert(ftp.jobInProgress)
if ftp.job.typ == JStore:
ftp.dele.mode = MWriteable
else:
ftp.dele.mode = MReadable"""
discard """proc handleRead(h: PObject) =
var ftp = PAsyncFTPClient(h)
assert(ftp.jobInProgress)
assert(ftp.job.typ != JStore)
# This can never return true, because it shouldn't check for code
# 226 from csock.
assert(not ftp.job.prc(ftp[], true))
"""
discard """proc csockGetSocket(h: PObject): tuple[info: TInfo, sock: TSocket] =
# This only returns the csock if a job is in progress. Otherwise handle read
# would capture data which is not for it to capture.
result = (SockIdle, InvalidSocket)
var ftp = PAsyncFTPClient(h)
if ftp.jobInProgress:
result = (SockConnected, ftp.csock)"""
proc AsyncFTPClient*(address: string, port = TPort(21),
user, pass = "",
handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} =

View file

@ -123,7 +123,7 @@ type
tagVar ## the HTML ``var`` element
const
tagStrs = [
tagToStr* = [
"a", "abbr", "acronym", "address", "applet", "area",
"b", "base", "basefont", "bdo", "big", "blockquote", "body",
"br", "button", "caption", "center", "cite", "code",
@ -243,13 +243,13 @@ proc binaryStrSearch(x: openarray[string], y: string): int =
proc htmlTag*(n: PXmlNode): THtmlTag =
## gets `n`'s tag as a ``THtmlTag``.
if n.clientData == 0:
n.clientData = binaryStrSearch(tagStrs, n.tag)+1
n.clientData = binaryStrSearch(tagToStr, n.tag)+1
result = THtmlTag(n.clientData)
proc htmlTag*(s: string): THtmlTag =
## converts `s` to a ``THtmlTag``. If `s` is no HTML tag, ``tagUnknown`` is
## returned.
result = THtmlTag(binaryStrSearch(tagStrs, s.toLower)+1)
result = THtmlTag(binaryStrSearch(tagToStr, s.toLower)+1)
proc entityToUtf8*(entity: string): string =
## converts an HTML entity name like ``&Uuml;`` to its UTF-8 equivalent.

View file

@ -80,62 +80,43 @@ proc fileError(msg: string) =
e.msg = msg
raise e
proc charAt(d: var string, i: var int, s: TSocket): char {.inline.} =
result = d[i]
while result == '\0':
d = string(s.recv())
i = 0
result = d[i]
proc parseChunks(s: TSocket): string =
# get chunks:
var i = 0
result = ""
var d = s.recv().string
var ri = 0
while true:
var chunkSizeStr = ""
var chunkSize = 0
var digitFound = false
while true:
case d[i]
of '0'..'9':
digitFound = true
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('0'))
of 'a'..'f':
digitFound = true
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('a') + 10)
of 'A'..'F':
digitFound = true
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('A') + 10)
of '\0':
d = string(s.recv())
i = -1
else: break
inc(i)
if not digitFound: httpError("Chunksize expected")
if s.recvLine(chunkSizeStr):
var i = 0
if chunkSizeStr == "":
httpError("Server terminated connection prematurely")
while true:
case chunkSizeStr[i]
of '0'..'9':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
of 'a'..'f':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
of 'A'..'F':
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
of '\0':
break
of ';':
# http://tools.ietf.org/html/rfc2616#section-3.6.1
# We don't care about chunk-extensions.
break
else:
httpError("Invalid chunk size: " & chunkSizeStr)
inc(i)
if chunkSize <= 0: break
while charAt(d, i, s) notin {'\C', '\L', '\0'}: inc(i)
if charAt(d, i, s) == '\C': inc(i)
if charAt(d, i, s) == '\L': inc(i)
else: httpError("CR-LF after chunksize expected")
var x = substr(d, i, i+chunkSize-1)
var size = x.len
result.add(x)
inc(i, size)
if size < chunkSize:
# read in the rest:
var missing = chunkSize - size
var L = result.len
setLen(result, L + missing)
while missing > 0:
var bytesRead = s.recv(addr(result[L]), missing)
inc(L, bytesRead)
dec(missing, bytesRead)
# next chunk:
d = string(s.recv())
i = 0
# skip trailing CR-LF:
while charAt(d, i, s) in {'\C', '\L'}: inc(i)
result.setLen(ri+chunkSize)
var bytesRead = 0
while bytesRead != chunkSize:
let ret = recv(s, addr(result[ri]), chunkSize-bytesRead)
ri += ret
bytesRead += ret
s.skip(2) # Skip \c\L
# Trailer headers will only be sent if the request specifies that we want
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
proc parseBody(s: TSocket,
headers: PStringTable): string =
@ -238,12 +219,18 @@ type
httpCONNECT ## Converts the request connection to a transparent
## TCP/IP tunnel, usually used for proxies.
when not defined(ssl):
type PSSLContext = ref object
let defaultSSLContext: PSSLContext = nil
else:
let defaultSSLContext = newContext(verifyMode = CVerifyNone)
proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
body = ""): TResponse =
body = "",
sslContext: PSSLContext = defaultSSLContext): TResponse =
## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be seperated by ``\c\L``
var r = parseUrl(url)
var headers = substr($httpMethod, len("http"))
headers.add(" /" & r.path & r.query)
@ -257,7 +244,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
var port = TPort(80)
if r.scheme == "https":
when defined(ssl):
s.wrapSocket(verifyMode = CVerifyNone)
sslContext.wrapSocket(s)
else:
raise newException(EHttpRequestErr, "SSL support was not compiled in. Cannot connect over SSL.")
port = TPort(443)
@ -277,32 +264,32 @@ proc redirection(status: string): bool =
if status.startsWith(i):
return True
proc get*(url: string, maxRedirects = 5): TResponse =
## | GET's the ``url`` and returns a ``TResponse`` object
proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse =
## | GETs the ``url`` and returns a ``TResponse`` object
## | This proc also handles redirection
result = request(url)
for i in 1..maxRedirects:
if result.status.redirection():
var locationHeader = result.headers["Location"]
if locationHeader == "": httpError("location header expected")
result = request(locationHeader)
result = request(locationHeader, sslContext = sslContext)
proc getContent*(url: string): string =
## | GET's the body and returns it as a string.
proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string =
## | GETs the body and returns it as a string.
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
var r = get(url)
var r = get(url, sslContext = sslContext)
if r.status[0] in {'4','5'}:
raise newException(EHTTPRequestErr, r.status)
else:
return r.body
proc post*(url: string, extraHeaders = "", body = "",
maxRedirects = 5): TResponse =
## | POST's ``body`` to the ``url`` and returns a ``TResponse`` object.
maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse =
## | POSTs ``body`` to the ``url`` and returns a ``TResponse`` object.
## | This proc adds the necessary Content-Length header.
## | This proc also handles redirection.
var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L"
result = request(url, httpPOST, xh, body)
result = request(url, httpPOST, xh, body, sslContext)
for i in 1..maxRedirects:
if result.status.redirection():
var locationHeader = result.headers["Location"]
@ -310,8 +297,9 @@ proc post*(url: string, extraHeaders = "", body = "",
var meth = if result.status != "307": httpGet else: httpPost
result = request(locationHeader, meth, xh, body)
proc postContent*(url: string, extraHeaders = "", body = ""): string =
## | POST's ``body`` to ``url`` and returns the response's body as a string
proc postContent*(url: string, extraHeaders = "", body = "",
sslContext: PSSLContext = defaultSSLContext): string =
## | POSTs ``body`` to ``url`` and returns the response's body as a string
## | Raises exceptions for the status codes ``4xx`` and ``5xx``
var r = post(url, extraHeaders, body)
if r.status[0] in {'4','5'}:
@ -319,11 +307,12 @@ proc postContent*(url: string, extraHeaders = "", body = ""): string =
else:
return r.body
proc downloadFile*(url: string, outputFilename: string) =
proc downloadFile*(url: string, outputFilename: string,
sslContext: PSSLContext = defaultSSLContext) =
## Downloads ``url`` and saves it to ``outputFilename``
var f: TFile
if open(f, outputFilename, fmWrite):
f.write(getContent(url))
f.write(getContent(url, sslContext))
f.close()
else:
fileError("Unable to open file")

View file

@ -22,6 +22,8 @@
##
## run(handleRequest, TPort(80))
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio

View file

@ -16,17 +16,23 @@
## the amount of lag.
##
## .. code-block:: Nimrod
## var client = irc("irc.server.net", joinChans = @["#channel"])
##
## var client = irc("picheta.me", joinChans = @["#bots"])
## client.connect()
## while True:
## var event: TIRCEvent
## if client.poll(event):
## case event.typ
## of EvDisconnected: break
## of EvConnected: nil
## of EvDisconnected:
## client.reconnect()
## of EvMsg:
## # Where all the magic happens.
## # Write your message reading code here.
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
import sockets, strutils, parseutils, times, asyncio
import sockets, strutils, parseutils, times, asyncio, os
type
TIRC* = object of TObject
@ -34,11 +40,15 @@ type
port: TPort
nick, user, realname, serverPass: string
case isAsync: bool
of false:
sock: TSocket
of true:
handleEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent) {.closure.}
handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.}
asyncSock: PAsyncSocket
myDispatcher: PDispatcher
of false:
dummyA: pointer
dummyB: pointer # workaround a Nimrod API issue
dummyC: pointer
sock: TSocket
status: TInfo
lastPing: float
lastPong: float
@ -46,6 +56,9 @@ type
channelsToJoin: seq[string]
msgLimit: bool
messageBuffer: seq[tuple[timeToSend: float, m: string]]
lastReconnect: float
PIRC* = ref TIRC
PAsyncIRC* = ref TAsyncIRC
TAsyncIRC* = object of TIRC
@ -68,9 +81,13 @@ type
MError
TIRCEventType* = enum
EvMsg, EvDisconnected
EvMsg, EvConnected, EvDisconnected
TIRCEvent* = object ## IRC Event
case typ*: TIRCEventType
of EvConnected:
## Connected to server.
## Only occurs with AsyncIRC.
nil
of EvDisconnected:
## Disconnected from the server
nil
@ -82,7 +99,7 @@ type
origin*: string ## The channel/user that this msg originated from
raw*: string ## Raw IRC message
proc send*(irc: var TIRC, message: string, sendImmediately = false) =
proc send*(irc: PIRC, message: string, sendImmediately = false) =
## Sends ``message`` as a raw command. It adds ``\c\L`` for you.
var sendMsg = true
if irc.msgLimit and not sendImmediately:
@ -104,15 +121,15 @@ proc send*(irc: var TIRC, message: string, sendImmediately = false) =
# but I can't exactly check for EBrokenPipe.
irc.status = SockClosed
proc privmsg*(irc: var TIRC, target, message: string) =
proc privmsg*(irc: PIRC, target, message: string) =
## Sends ``message`` to ``target``. ``Target`` can be a channel, or a user.
irc.send("PRIVMSG $1 :$2" % [target, message])
proc notice*(irc: var TIRC, target, message: string) =
proc notice*(irc: PIRC, target, message: string) =
## Sends ``notice`` to ``target``. ``Target`` can be a channel, or a user.
irc.send("NOTICE $1 :$2" % [target, message])
proc join*(irc: var TIRC, channel: string, key = "") =
proc join*(irc: PIRC, channel: string, key = "") =
## Joins ``channel``.
##
## If key is not ``""``, then channel is assumed to be key protected and this
@ -122,16 +139,19 @@ proc join*(irc: var TIRC, channel: string, key = "") =
else:
irc.send("JOIN " & channel & " " & key)
proc part*(irc: var TIRC, channel, message: string) =
proc part*(irc: PIRC, channel, message: string) =
## Leaves ``channel`` with ``message``.
irc.send("PART " & channel & " :" & message)
proc close*(irc: var TIRC) =
proc close*(irc: PIRC) =
## Closes connection to an IRC server.
##
## **Warning:** This procedure does not send a ``QUIT`` message to the server.
irc.status = SockClosed
irc.sock.close()
if irc.isAsync:
irc.asyncSock.close()
else:
irc.sock.close()
proc isNumber(s: string): bool =
## Checks if `s` contains only numbers.
@ -202,12 +222,11 @@ proc parseMessage(msg: string): TIRCEvent =
inc(i) # Skip `:`.
result.params.add(msg[i..msg.len-1])
proc connect*(irc: var TIRC) =
proc connect*(irc: PIRC) =
## Connects to an IRC server as specified by ``irc``.
assert(irc.address != "")
assert(irc.port != TPort(0))
irc.sock = socket()
irc.sock.connect(irc.address, irc.port)
irc.status = SockConnected
@ -217,13 +236,28 @@ proc connect*(irc: var TIRC) =
irc.send("NICK " & irc.nick, true)
irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true)
proc reconnect*(irc: PIRC, timeout = 5000) =
## Reconnects to an IRC server.
##
## ``Timeout`` specifies the time to wait in miliseconds between multiple
## consecutive reconnections.
##
## This should be used when an ``EvDisconnected`` event occurs.
let secSinceReconnect = int(epochTime() - irc.lastReconnect)
if secSinceReconnect < timeout:
sleep(timeout - secSinceReconnect)
irc.sock = socket()
irc.connect()
irc.lastReconnect = epochTime()
proc irc*(address: string, port: TPort = 6667.TPort,
nick = "NimrodBot",
user = "NimrodBot",
realname = "NimrodBot", serverPass = "",
joinChans: seq[string] = @[],
msgLimit: bool = true): TIRC =
msgLimit: bool = true): PIRC =
## Creates a ``TIRC`` object.
new(result)
result.address = address
result.port = port
result.nick = nick
@ -237,8 +271,9 @@ proc irc*(address: string, port: TPort = 6667.TPort,
result.msgLimit = msgLimit
result.messageBuffer = @[]
result.status = SockIdle
result.sock = socket()
proc processLine(irc: var TIRC, line: string): TIRCEvent =
proc processLine(irc: PIRC, line: string): TIRCEvent =
if line.len == 0:
irc.close()
result.typ = EvDisconnected
@ -271,7 +306,7 @@ proc processLine(irc: var TIRC, line: string): TIRCEvent =
if result.nick == irc.nick:
irc.nick = result.params[0]
proc processOther(irc: var TIRC, ev: var TIRCEvent): bool =
proc processOther(irc: PIRC, ev: var TIRCEvent): bool =
result = false
if epochTime() - irc.lastPing >= 20.0:
irc.lastPing = epochTime()
@ -290,7 +325,7 @@ proc processOther(irc: var TIRC, ev: var TIRCEvent): bool =
break # messageBuffer is guaranteed to be from the quickest to the
# later-est.
proc poll*(irc: var TIRC, ev: var TIRCEvent,
proc poll*(irc: PIRC, ev: var TIRCEvent,
timeout: int = 500): bool =
## This function parses a single message from the IRC server and returns
## a TIRCEvent.
@ -316,46 +351,32 @@ proc poll*(irc: var TIRC, ev: var TIRCEvent,
if processOther(irc, ev): result = true
proc getLag*(irc: var TIRC): float =
proc getLag*(irc: PIRC): float =
## Returns the latency between this client and the IRC server in seconds.
##
## If latency is unknown, returns -1.0.
return irc.lag
proc isConnected*(irc: var TIRC): bool =
proc isConnected*(irc: PIRC): bool =
## Returns whether this IRC client is connected to an IRC server.
return irc.status == SockConnected
proc getNick*(irc: var TIRC): string =
proc getNick*(irc: PIRC): string =
## Returns the current nickname of the client.
return irc.nick
# -- Asyncio dispatcher
proc connect*(irc: PAsyncIRC) =
## Equivalent of connect for ``TIRC`` but specifically created for asyncio.
assert(irc.address != "")
assert(irc.port != TPort(0))
irc.asyncSock = AsyncSocket()
irc.asyncSock.connect(irc.address, irc.port)
proc handleConnect(s: PAsyncSocket, irc: PAsyncIRC) =
# Greet the server :)
if irc.serverPass != "": irc[].send("PASS " & irc.serverPass, true)
irc[].send("NICK " & irc.nick, true)
irc[].send("USER $1 * 0 :$2" % [irc.user, irc.realname], true)
discard """proc handleConnect(h: PObject) =
var irc = PAsyncIRC(h)
# Greet the server :)
if irc.serverPass != "": irc[].send("PASS " & irc.serverPass, true)
irc[].send("NICK " & irc.nick, true)
irc[].send("USER $1 * 0 :$2" % [irc.user, irc.realname], true)
if irc.serverPass != "": irc.send("PASS " & irc.serverPass, true)
irc.send("NICK " & irc.nick, true)
irc.send("USER $1 * 0 :$2" % [irc.user, irc.realname], true)
irc.status = SockConnected
"""
var ev: TIRCEvent
ev.typ = EvConnected
irc.handleEvent(irc, ev)
proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) =
var line = "".TaintedString
@ -363,42 +384,55 @@ proc handleRead(s: PAsyncSocket, irc: PAsyncIRC) =
if ret:
if line == "":
var ev: TIRCEvent
irc[].close()
irc.close()
ev.typ = EvDisconnected
irc[].handleEvent(irc[], ev)
irc.handleEvent(irc, ev)
else:
var ev = irc[].processLine(line.string)
irc[].handleEvent(irc[], ev)
discard """proc handleRead(h: PObject) =
var irc = PAsyncIRC(h)
var line = "".TaintedString
var ret = irc.sock.recvLineAsync(line)
case ret
of RecvFullLine:
var ev = irc[].processLine(irc.lineBuffer.string & line.string)
irc.handleEvent(irc[], ev, irc.userArg)
irc.lineBuffer = "".TaintedString
of RecvPartialLine:
if line.string != "":
string(irc.lineBuffer).add(line.string)
of RecvDisconnected:
var ev: TIRCEvent
irc[].close()
ev.typ = EvDisconnected
irc.handleEvent(irc[], ev, irc.userArg)
of RecvFail: nil"""
var ev = irc.processLine(line.string)
irc.handleEvent(irc, ev)
proc handleTask(s: PAsyncSocket, irc: PAsyncIRC) =
var ev: TIRCEvent
if irc[].processOther(ev):
irc.handleEvent(irc[], ev)
if irc.processOther(ev):
irc.handleEvent(irc, ev)
proc register*(d: PDispatcher, irc: PAsyncIRC) =
## Registers ``irc`` with dispatcher ``d``.
irc.asyncSock.handleConnect =
proc (s: PAsyncSocket) =
handleConnect(s, irc)
irc.asyncSock.handleRead =
proc (s: PAsyncSocket) =
handleRead(s, irc)
irc.asyncSock.handleTask =
proc (s: PAsyncSocket) =
handleTask(s, irc)
d.register(irc.asyncSock)
irc.myDispatcher = d
proc connect*(irc: PAsyncIRC) =
## Equivalent of connect for ``TIRC`` but specifically created for asyncio.
assert(irc.address != "")
assert(irc.port != TPort(0))
discard """proc handleTask(h: PObject) =
var irc = PAsyncIRC(h)
var ev: TIRCEvent
if PAsyncIRC(h)[].processOther(ev):
irc.handleEvent(irc[], ev, irc.userArg)"""
irc.asyncSock.connect(irc.address, irc.port)
proc reconnect*(irc: PAsyncIRC, timeout = 5000) =
## Reconnects to an IRC server.
##
## ``Timeout`` specifies the time to wait in miliseconds between multiple
## consecutive reconnections.
##
## This should be used when an ``EvDisconnected`` event occurs.
##
## When successfully reconnected an ``EvConnected`` event will occur.
let secSinceReconnect = int(epochTime() - irc.lastReconnect)
if secSinceReconnect < timeout:
sleep(timeout - secSinceReconnect)
irc.asyncSock = AsyncSocket()
irc.myDispatcher.register(irc)
irc.connect()
irc.lastReconnect = epochTime()
proc asyncIRC*(address: string, port: TPort = 6667.TPort,
nick = "NimrodBot",
@ -406,7 +440,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort,
realname = "NimrodBot", serverPass = "",
joinChans: seq[string] = @[],
msgLimit: bool = true,
ircEvent: proc (irc: var TAsyncIRC, ev: TIRCEvent) {.closure.}
ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.}
): PAsyncIRC =
## Use this function if you want to use asyncio's dispatcher.
##
@ -429,19 +463,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort,
result.msgLimit = msgLimit
result.messageBuffer = @[]
result.handleEvent = ircEvent
proc register*(d: PDispatcher, irc: PAsyncIRC) =
## Registers ``irc`` with dispatcher ``d``.
irc.asyncSock.handleConnect =
proc (s: PAsyncSocket) =
handleConnect(s, irc)
irc.asyncSock.handleRead =
proc (s: PAsyncSocket) =
handleRead(s, irc)
irc.asyncSock.handleTask =
proc (s: PAsyncSocket) =
handleTask(s, irc)
d.register(irc.asyncSock)
result.asyncSock = AsyncSocket()
when isMainModule:
#var m = parseMessage("ERROR :Closing Link: dom96.co.cc (Ping timeout: 252 seconds)")
@ -456,6 +478,8 @@ when isMainModule:
var event: TIRCEvent
if client.poll(event):
case event.typ
of EvConnected:
nil
of EvDisconnected:
break
of EvMsg:

View file

@ -13,6 +13,23 @@
## (unlike XML). It is easy for machines to parse and generate.
## JSON is based on a subset of the JavaScript Programming Language,
## Standard ECMA-262 3rd Edition - December 1999.
##
## Usage example:
##
## .. code-block:: nimrod
## let
## small_json = """{"test": 1.3, "key2": true}"""
## jobj = parseJson(small_json)
## assert (jobj.kind == JObject)
## echo($jobj["test"].fnum)
## echo($jobj["key2"].bval)
##
## Results in:
##
## .. code-block:: nimrod
##
## 1.3000000000000000e+00
## true
import
hashes, strutils, lexbase, streams, unicode
@ -524,6 +541,11 @@ proc newJString*(s: String): PJsonNode =
result.kind = JString
result.str = s
proc newJStringMove(s: String): PJsonNode =
new(result)
result.kind = JString
shallowCopy(result.str, s)
proc newJInt*(n: biggestInt): PJsonNode =
## Creates a new `JInt PJsonNode`.
new(result)
@ -607,7 +629,7 @@ proc len*(n: PJsonNode): int =
else: nil
proc `[]`*(node: PJsonNode, name: String): PJsonNode =
## Gets a field from a `JObject`.
## Gets a field from a `JObject`. Returns nil if the key is not found.
assert(node.kind == JObject)
for key, item in items(node.fields):
if key == name:
@ -792,7 +814,9 @@ proc parseJson(p: var TJsonParser): PJsonNode =
## Parses JSON from a JSON Parser `p`.
case p.tok
of tkString:
result = newJString(p.a)
# we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
result = newJStringMove(p.a)
p.a = ""
discard getTok(p)
of tkInt:
result = newJInt(parseBiggestInt(p.a))
@ -893,6 +917,10 @@ when isMainModule:
echo(parsed["keyÄÖöoßß"])
echo()
echo(pretty(parsed2))
try:
echo(parsed["key2"][12123])
raise newException(EInvalidValue, "That line was expected to fail")
except EInvalidIndex: echo()
discard """
while true:

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.

View file

@ -141,6 +141,11 @@ proc randomize*()
## number, i.e. a tickcount. Note: Does nothing for the ECMAScript target,
## as ECMAScript does not support this.
proc randomize*(seed: int)
## initializes the random number generator with a specific seed.
## Note: Does nothing for the ECMAScript target,
## as ECMAScript does not support this.
when not defined(ECMAScript):
proc sqrt*(x: float): float {.importc: "sqrt", header: "<math.h>".}
## computes the square root of `x`.
@ -190,15 +195,17 @@ when not defined(ECMAScript):
proc rand(): cint {.importc: "rand", nodecl.}
when not defined(windows):
proc srand48(seed: cint) {.importc: "srand48", nodecl.}
proc srand48(seed: clong) {.importc: "srand48", nodecl.}
proc drand48(): float {.importc: "drand48", nodecl.}
proc random(max: float): float =
result = drand48() * max
proc randomize() =
let x = gettime(nil)
srand(x)
when defined(srand48): srand48(x)
randomize(gettime(nil))
proc randomize(seed: int) =
srand(cint(seed))
when defined(srand48): srand48(seed)
proc random(max: int): int =
result = int(rand()) mod max
@ -217,6 +224,7 @@ else:
proc random(max: float): float =
result = float(mathrandom() * float(max))
proc randomize() = nil
proc randomize(seed: int) = nil
proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.}
proc ln*(x: float): float {.importc: "Math.log", nodecl.}
@ -301,3 +309,18 @@ proc standardDeviation*(s: TRunningStat): float =
{.pop.}
{.pop.}
when isMainModule and not defined(ECMAScript):
# Verifies random seed initialization.
let seed = gettime(nil)
randomize(seed)
const SIZE = 10
var buf : array[0..SIZE, int]
# Fill the buffer with random values
for i in 0..SIZE-1:
buf[i] = random(high(int))
# Check that the second random calls are the same for each position.
randomize(seed)
for i in 0..SIZE-1:
assert buf[i] == random(high(int)), "non deterministic random seeding"
echo "random values equal after reseeding"

View file

@ -554,7 +554,8 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {.
var dotPos = path.len
for i in countdown(len(path)-1, 0):
if path[i] == ExtSep:
if dotPos == path.len and i > 0: dotPos = i
if dotPos == path.len and i > 0 and
path[i-1] notin {dirsep, altsep}: dotPos = i
elif path[i] in {dirsep, altsep}:
sepPos = i
break
@ -1448,7 +1449,7 @@ proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [FTime].} =
else:
var a, b: Ttimespec
a.tv_sec = TTime(milsecs div 1000)
a.tv_nsec = (milsecs mod 1000) * 1000
a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
discard posix.nanosleep(a, b)
proc getFileSize*(file: string): biggestInt {.rtl, extern: "nos$1",

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -201,9 +201,9 @@ proc execProcesses*(cmds: openArray[string],
q[r] = startCmd(cmds[i], options=options)
inc(i)
if i > high(cmds): break
for i in 0..m-1:
if q[i] != nil: close(q[i])
result = max(waitForExit(q[i]), result)
for j in 0..m-1:
if q[j] != nil: close(q[j])
result = max(waitForExit(q[j]), result)
else:
for i in 0..high(cmds):
var p = startCmd(cmds[i], options=options)

View file

@ -49,26 +49,35 @@ proc raiseNoOK(status: string) =
raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status)
proc parseStatus(r: TRedis): TRedisStatus =
var line = r.socket.recv.string
var line = ""
if r.socket.recvLine(line):
if line == "":
raise newException(ERedis, "Server closed connection prematurely")
if line[0] == '-':
raise newException(ERedis, strip(line))
if line[0] != '+':
raiseInvalidReply('+', line[0])
return line.substr(1, line.len-3) # Strip '+' and \c\L.
if line[0] == '-':
raise newException(ERedis, strip(line))
if line[0] != '+':
raiseInvalidReply('+', line[0])
return line.substr(1) # Strip '+'
else:
OSError()
proc parseInteger(r: TRedis): TRedisInteger =
var line = r.socket.recv.string
var line = ""
if r.socket.recvLine(line):
if line == "":
raise newException(ERedis, "Server closed connection prematurely")
if line[0] == '-':
raise newException(ERedis, strip(line))
if line[0] != ':':
raiseInvalidReply(':', line[0])
# Strip ':' and \c\L.
if parseBiggestInt(line, result, 1) == 0:
raise newException(EInvalidReply, "Unable to parse integer.")
if line[0] == '-':
raise newException(ERedis, strip(line))
if line[0] != ':':
raiseInvalidReply(':', line[0])
# Strip ':'
if parseBiggestInt(line, result, 1) == 0:
raise newException(EInvalidReply, "Unable to parse integer.")
else: OSError()
proc recv(sock: TSocket, size: int): TaintedString =
result = newString(size).TaintedString
@ -838,8 +847,11 @@ proc save*(r: TRedis) =
proc shutdown*(r: TRedis) =
## Synchronously save the dataset to disk and then shut down the server
r.sendCommand("SHUTDOWN")
var s = r.socket.recv()
if s.string.len != 0: raise newException(ERedis, s.string)
var s = "".TaintedString
if r.socket.recvLine(s):
if s.string.len != 0: raise newException(ERedis, s.string)
else:
OSError()
proc slaveof*(r: TRedis, host: string, port: string) =
## Make the server a slave of another instance, or promote it as master

View file

@ -23,6 +23,8 @@
##
## run(handleRequest)
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
import sockets, strutils, os, strtabs, asyncio

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -14,6 +14,8 @@
## For OpenSSL support compile with ``-d:ssl``. When using SSL be aware that
## most functions will then raise ``ESSL`` on SSL errors.
{.deadCodeElim: on.}
when hostos == "solaris":
{.passl: "-lsocket -lnsl".}
@ -45,12 +47,15 @@ when defined(ssl):
TSSLAcceptResult* = enum
AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
const
BufferSize*: int = 4000 ## size of a buffered socket's buffer
type
TSocketImpl = object ## socket type
fd: cint
case isBuffered: bool # determines whether this socket is buffered.
of true:
buffer: array[0..4000, char]
buffer: array[0..BufferSize, char]
currPos: int # current index in buffer
bufLen: int # current length of buffer
of false: nil
@ -60,6 +65,8 @@ type
sslHandle: PSSL
sslContext: PSSLContext
sslNoHandshake: bool # True if needs handshake.
sslHasPeekChar: bool
sslPeekChar: char
of false: nil
TSocket* = ref TSocketImpl
@ -256,7 +263,10 @@ when defined(ssl):
of protSSLv23:
newCTX = SSL_CTX_new(SSLv23_method()) # SSlv2,3 and TLS1 support.
of protSSLv2:
newCTX = SSL_CTX_new(SSLv2_method())
when not defined(linux):
newCTX = SSL_CTX_new(SSLv2_method())
else:
SSLError()
of protSSLv3:
newCTX = SSL_CTX_new(SSLv3_method())
of protTLSv1:
@ -286,12 +296,55 @@ when defined(ssl):
socket.sslContext = ctx
socket.sslHandle = SSLNew(PSSLCTX(socket.sslContext))
socket.sslNoHandshake = false
socket.sslHasPeekChar = false
if socket.sslHandle == nil:
SSLError()
if SSLSetFd(socket.sslHandle, socket.fd) != 1:
SSLError()
proc SocketError*(socket: TSocket, err: int = -1, async = false) =
## Raises proper errors based on return values of ``recv`` functions.
##
## If ``async`` is ``True`` no error will be thrown in the case when the
## error was caused by no data being available to be read.
##
## If ``err`` is not lower than 0 no exception will be raised.
when defined(ssl):
if socket.isSSL:
if err <= 0:
var ret = SSLGetError(socket.sslHandle, err.cint)
case ret
of SSL_ERROR_ZERO_RETURN:
SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
if async:
return
else: SSLError("Not enough data on socket.")
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
if async:
return
else: SSLError("Not enough data on socket.")
of SSL_ERROR_WANT_X509_LOOKUP:
SSLError("Function for x509 lookup has been called.")
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError()
else: SSLError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false):
if async:
when defined(windows):
# TODO: Test on Windows
var err = WSAGetLastError()
if err == WSAEWOULDBLOCK:
return
else: OSError()
else:
if errno == EAGAIN or errno == EWOULDBLOCK:
return
else: OSError()
else: OSError()
proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} =
## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the
@ -844,11 +897,8 @@ proc checkBuffer(readfds: var seq[TSocket]): int =
var res: seq[TSocket] = @[]
result = 0
for s in readfds:
if s.isBuffered:
if s.bufLen <= 0 or s.currPos == s.bufLen:
res.add(s)
else:
inc(result)
if hasDataBuffered(s):
inc(result)
else:
res.add(s)
readfds = res
@ -970,47 +1020,76 @@ template retRead(flags, readBytes: int) =
proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} =
## receives data from a socket
if size == 0: return
if socket.isBuffered:
if socket.bufLen == 0:
retRead(0'i32, 0)
when true:
var read = 0
while read < size:
if socket.currPos >= socket.bufLen:
retRead(0'i32, read)
let chunk = min(socket.bufLen-socket.currPos, size-read)
var d = cast[cstring](data)
copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk)
read.inc(chunk)
socket.currPos.inc(chunk)
else:
var read = 0
while read < size:
if socket.currPos >= socket.bufLen:
retRead(0'i32, read)
var d = cast[cstring](data)
d[read] = socket.buffer[socket.currPos]
read.inc(1)
socket.currPos.inc(1)
var read = 0
while read < size:
if socket.currPos >= socket.bufLen:
retRead(0'i32, read)
let chunk = min(socket.bufLen-socket.currPos, size-read)
var d = cast[cstring](data)
copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk)
read.inc(chunk)
socket.currPos.inc(chunk)
result = read
else:
when defined(ssl):
if socket.isSSL:
result = SSLRead(socket.sslHandle, data, size)
if socket.sslHasPeekChar:
copyMem(data, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = false
if size-1 > 0:
var d = cast[cstring](data)
result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1
else:
result = 1
else:
result = SSLRead(socket.sslHandle, data, size)
else:
result = recv(socket.fd, data, size.cint, 0'i32)
else:
result = recv(socket.fd, data, size.cint, 0'i32)
proc recv*(socket: TSocket, data: var string, size: int): int =
## higher-level version of the above
##
## When 0 is returned the socket's connection has been closed.
##
## This function will throw an EOS exception when an error occurs. A value
## lower than 0 is never returned.
##
## **Note**: ``data`` must be initialised.
data.setLen(size)
result = recv(socket, cstring(data), size)
if result < 0:
data.setLen(0)
socket.SocketError(result)
data.setLen(result)
proc recvAsync*(socket: TSocket, data: var string, size: int): int =
## Async version of the above.
##
## When socket is non-blocking and no data is available on the socket,
## ``-1`` will be returned and ``data`` will be ``""``.
##
## **Note**: ``data`` must be initialised.
data.setLen(size)
result = recv(socket, cstring(data), size)
if result < 0:
data.setLen(0)
socket.SocketError(async = true)
result = -1
data.setLen(result)
proc waitFor(socket: TSocket, waited: var float, timeout: int): int {.
tags: [FTime].} =
## returns the number of characters available to be read. In unbuffered
## sockets this is always 1, otherwise this may as big as the buffer, currently
## 4000.
## sockets this is always 1, otherwise this may as big as ``BufferSize``.
result = 1
if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos:
result = socket.bufLen - socket.currPos
@ -1040,6 +1119,18 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {.
result = read
proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int =
## higher-level version of the above.
##
## Similar to the non-timeout version this will throw an EOS exception
## when an error occurs.
data.setLen(size)
result = recv(socket, cstring(data), size, timeout)
if result < 0:
data.setLen(0)
socket.SocketError()
data.setLen(result)
proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} =
if socket.isBuffered:
result = 1
@ -1052,8 +1143,12 @@ proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} =
else:
when defined(ssl):
if socket.isSSL:
raise newException(ESSL, "Sorry, you cannot use recvLine on an unbuffered SSL socket.")
if not socket.sslHasPeekChar:
result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1)
socket.sslHasPeekChar = true
c = socket.sslPeekChar
return
result = recv(socket.fd, addr(c), 1, MSG_PEEK)
proc recvLine*(socket: TSocket, line: var TaintedString): bool {.
@ -1062,14 +1157,12 @@ proc recvLine*(socket: TSocket, line: var TaintedString): bool {.
## added to ``line``, however if solely ``\r\L`` is received then ``line``
## will be set to it.
##
## ``True`` is returned if data is available. ``False`` usually suggests an
## error, EOS exceptions are not raised in favour of this.
## ``True`` is returned if data is available. ``False`` suggests an
## error, EOS exceptions are not raised and ``False`` is simply returned
## instead.
##
## If the socket is disconnected, ``line`` will be set to ``""`` and ``True``
## will be returned.
##
## **Warning:** Using this function on a unbuffered ssl socket will result
## in an error.
template addNLIfEmpty(): stmt =
if line.len == 0:
line.add("\c\L")
@ -1096,6 +1189,8 @@ proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool {.
tags: [FReadIO, FTime].} =
## variant with a ``timeout`` parameter, the timeout parameter specifies
## how many miliseconds to wait for data.
##
## ``ETimeout`` will be raised if ``timeout`` is exceeded.
template addNLIfEmpty(): stmt =
if line.len == 0:
line.add("\c\L")
@ -1148,11 +1243,13 @@ proc recvLineAsync*(socket: TSocket,
elif c == '\L': return RecvFullLine
add(line.string, c)
proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} =
proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} =
## receives all the available data from the socket.
## Socket errors will result in an ``EOS`` error.
## If socket is not a connectionless socket and socket is not connected
## ``""`` will be returned.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 4000
result = newStringOfCap(bufSize).TaintedString
var pos = 0
@ -1177,25 +1274,31 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} =
add(result.string, buf)
if bytesRead != bufSize-1: break
{.push warning[deprecated]: off.}
proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {.
tags: [FReadIO].} =
tags: [FReadIO], deprecated.} =
## overloaded variant to support a ``timeout`` parameter, the ``timeout``
## parameter specifies the amount of miliseconds to wait for data on the
## socket.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
if socket.bufLen == 0:
var s = @[socket]
if s.select(timeout) != 1:
raise newException(ETimeout, "Call to recv() timed out.")
return socket.recv
{.pop.}
proc recvAsync*(socket: TSocket, s: var TaintedString): bool {.
tags: [FReadIO].} =
tags: [FReadIO], deprecated.} =
## receives all the data from a non-blocking socket. If socket is non-blocking
## and there are no messages available, `False` will be returned.
## Other socket errors will result in an ``EOS`` error.
## If socket is not a connectionless socket and socket is not connected
## ``s`` will be set to ``""``.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 1000
# ensure bufSize capacity:
setLen(s.string, bufSize)
@ -1282,13 +1385,25 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int,
return False
else: OSError()
proc skip*(socket: TSocket) {.tags: [FReadIO].} =
proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} =
## skips all the data that is pending for the socket
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 1000
var buf = alloc(bufSize)
while recv(socket, buf, bufSize) == bufSize: nil
dealloc(buf)
proc skip*(socket: TSocket, size: int) =
## Skips ``size`` amount of bytes.
##
## Returns the number of skipped bytes.
var dummy = alloc(size)
var bytesSkipped = 0
while bytesSkipped != size:
bytesSkipped += recv(socket, dummy, size-bytesSkipped)
dealloc(dummy)
proc send*(socket: TSocket, data: pointer, size: int): int {.
tags: [FWriteIO].} =
## sends data to a socket.
@ -1312,21 +1427,26 @@ proc send*(socket: TSocket, data: string) {.tags: [FWriteIO].} =
OSError()
proc sendAsync*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
## sends data to a non-blocking socket. Returns whether ``data`` was sent.
result = true
var bytesSent = send(socket, cstring(data), data.len)
proc sendAsync*(socket: TSocket, data: string): int {.tags: [FWriteIO].} =
## sends data to a non-blocking socket.
## Returns ``0`` if no data could be sent, if data has been sent
## returns the amount of bytes of ``data`` that was successfully sent. This
## number may not always be the length of ``data`` but typically is.
##
## An EOS (or ESSL if socket is an SSL socket) exception is raised if an error
## occurs.
result = send(socket, cstring(data), data.len)
when defined(ssl):
if socket.isSSL:
if bytesSent <= 0:
let ret = SSLGetError(socket.sslHandle, bytesSent.cint)
if result <= 0:
let ret = SSLGetError(socket.sslHandle, result.cint)
case ret
of SSL_ERROR_ZERO_RETURN:
SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
SSLError("Unexpected error occured.") # This should just not happen.
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
return false
return 0
of SSL_ERROR_WANT_X509_LOOKUP:
SSLError("Function for x509 lookup has been called.")
of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
@ -1334,17 +1454,18 @@ proc sendAsync*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
else: SSLError("Unknown Error")
else:
return
if bytesSent == -1:
if result == -1:
when defined(windows):
var err = WSAGetLastError()
# TODO: Test on windows.
if err == WSAEINPROGRESS:
return false
return 0
else: OSError()
else:
if errno == EAGAIN or errno == EWOULDBLOCK:
return false
return 0
else: OSError()
proc trySend*(socket: TSocket, data: string): bool {.tags: [FWriteIO].} =
## safe alternative to ``send``. Does not raise an EOS when an error occurs,
@ -1385,8 +1506,7 @@ proc sendTo*(socket: TSocket, address: string, port: TPort,
result = socket.sendTo(address, port, cstring(data), data.len)
when defined(Windows):
const
SOCKET_ERROR = -1
const
IOCPARM_MASK = 127
IOC_IN = int(-2147483648)
FIONBIO = int(IOC_IN or ((sizeof(int) and IOCPARM_MASK) shl 16) or
@ -1399,7 +1519,7 @@ when defined(Windows):
proc setBlocking(s: TSocket, blocking: bool) =
when defined(Windows):
var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking
if SOCKET_ERROR == ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)):
if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1:
OSError()
else: # BSD sockets
var x: int = fcntl(s.fd, F_GETFL, 0)

View file

@ -267,7 +267,8 @@ proc newFileStream*(f: TFile): PFileStream =
proc newFileStream*(filename: string, mode: TFileMode): PFileStream =
## creates a new stream from the file named `filename` with the mode `mode`.
## If the file cannot be opened, nil is returned.
## If the file cannot be opened, nil is returned. See the `system
## <system.html>`_ module for a list of available TFileMode enums.
var f: TFile
if Open(f, filename, mode): result = newFileStream(f)

View file

@ -36,11 +36,23 @@ proc len*(t: PStringTable): int {.rtl, extern: "nst$1".} =
result = t.counter
iterator pairs*(t: PStringTable): tuple[key, value: string] =
## iterates over any (key, value) pair in the table `t`.
## iterates over every (key, value) pair in the table `t`.
for h in 0..high(t.data):
if not isNil(t.data[h].key):
yield (t.data[h].key, t.data[h].val)
iterator keys*(t: PStringTable): string =
## iterates over every key in the table `t`.
for h in 0..high(t.data):
if not isNil(t.data[h].key):
yield t.data[h].key
iterator values*(t: PStringTable): string =
## iterates over every value in the table `t`.
for h in 0..high(t.data):
if not isNil(t.data[h].key):
yield t.data[h].val
type
TFormatFlag* = enum ## flags for the `%` operator
useEnvironment, ## use environment variable if the ``$key``

View file

@ -186,7 +186,24 @@ iterator split*(s: string, seps: set[char] = Whitespace): string =
## for word in split(";;this;is;an;;example;;;", {';'}):
## writeln(stdout, word)
##
## produces the same output.
## produces the same output. The code:
##
## .. code-block:: nimrod
## let date = "2012-11-20T22:08:08.398990"
## let separators = {' ', '-', ':', 'T'}
## for number in split(date, separators):
## writeln(stdout, number)
##
## Results in:
##
## .. code-block:: nimrod
## "2012"
## "11"
## "20"
## "22"
## "08"
## "08.398990"
##
var last = 0
assert(not ('\0' in seps))
while last < len(s):
@ -833,13 +850,51 @@ proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,
for c in items(s):
case c
of '\0'..'\31', '\128'..'\255':
add(result, '\\')
add(result, "\\x")
add(result, toHex(ord(c), 2))
of '\\': add(result, "\\\\")
of '\'': add(result, "\\'")
of '\"': add(result, "\\\"")
else: add(result, c)
add(result, suffix)
proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,
rtl, extern: "nsuUnescape".} =
## Unescapes a string `s`. This complements ``escape`` as it performs the
## opposite operations.
##
## If `s` does not begin with ``prefix`` and end with ``suffix`` a EInvalidValue
## exception will be raised.
result = newStringOfCap(s.len)
var i = 0
if s[0 .. prefix.len-1] != prefix:
raise newException(EInvalidValue,
"String does not start with a prefix of: " & prefix)
i.inc()
while True:
if i == s.len-suffix.len: break
case s[i]
of '\\':
case s[i+1]:
of 'x':
let j = parseHexInt(s[i+2 .. i+3])
result.add(chr(j))
inc(i, 2)
of '\\':
result.add('\\')
of '\'':
result.add('\'')
of '\"':
result.add('\"')
else: result.add("\\" & s[i+1])
inc(i)
of '\0': break
else:
result.add(s[i])
i.inc()
if s[i .. -1] != suffix:
raise newException(EInvalidValue,
"String does not end with a suffix of: " & suffix)
proc validIdentifier*(s: string): bool {.noSideEffect,
rtl, extern: "nsuValidIdentifier".} =