merged upstream master
This commit is contained in:
commit
81a3585872
127 changed files with 4440 additions and 1496 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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.".}
|
||||
|
|
|
|||
|
|
@ -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.} =
|
||||
|
|
|
|||
|
|
@ -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 ``Ü`` to its UTF-8 equivalent.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
202
lib/pure/irc.nim
202
lib/pure/irc.nim
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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``
|
||||
|
|
|
|||
|
|
@ -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".} =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue