diff options
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/asyncftpclient.nim | 4 | ||||
-rw-r--r-- | lib/pure/collections/critbits.nim | 40 | ||||
-rw-r--r-- | lib/pure/collections/sequtils.nim | 9 | ||||
-rw-r--r-- | lib/pure/db_common.nim | 103 | ||||
-rw-r--r-- | lib/pure/events.nim | 2 | ||||
-rw-r--r-- | lib/pure/fsmonitor.nim | 45 | ||||
-rw-r--r-- | lib/pure/httpclient.nim | 10 | ||||
-rw-r--r-- | lib/pure/lexbase.nim | 25 | ||||
-rw-r--r-- | lib/pure/nativesockets.nim | 7 | ||||
-rw-r--r-- | lib/pure/nimprof.nim | 38 | ||||
-rw-r--r-- | lib/pure/options.nim | 4 | ||||
-rw-r--r-- | lib/pure/os.nim | 92 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 11 | ||||
-rw-r--r-- | lib/pure/oswalkdir.nim | 27 | ||||
-rw-r--r-- | lib/pure/parseopt2.nim | 4 | ||||
-rw-r--r-- | lib/pure/parseutils.nim | 7 | ||||
-rw-r--r-- | lib/pure/redis.nim | 1096 | ||||
-rw-r--r-- | lib/pure/strutils.nim | 75 | ||||
-rw-r--r-- | lib/pure/times.nim | 305 | ||||
-rw-r--r-- | lib/pure/unicode.nim | 1 | ||||
-rw-r--r-- | lib/pure/xmlparser.nim | 29 |
21 files changed, 532 insertions, 1402 deletions
diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim index b806f4235..fd899e080 100644 --- a/lib/pure/asyncftpclient.nim +++ b/lib/pure/asyncftpclient.nim @@ -288,7 +288,7 @@ proc defaultOnProgressChanged*(total, progress: BiggestInt, result.complete() proc retrFile*(ftp: AsyncFtpClient, file, dest: string, - onProgressChanged = defaultOnProgressChanged) {.async.} = + onProgressChanged: ProgressChangedProc = defaultOnProgressChanged) {.async.} = ## Downloads ``file`` and saves it to ``dest``. ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function ## when the download is finished. The event's ``filename`` field will be equal @@ -339,7 +339,7 @@ proc doUpload(ftp: AsyncFtpClient, file: File, await countdownFut or sendFut proc store*(ftp: AsyncFtpClient, file, dest: string, - onProgressChanged = defaultOnProgressChanged) {.async.} = + onProgressChanged: ProgressChangedProc = defaultOnProgressChanged) {.async.} = ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## function asynchronously is recommended to view the progress of ## the download. diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 8c507d4fb..bb234565b 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -232,7 +232,7 @@ iterator mpairs*[T](c: var CritBitTree[T]): tuple[key: string, val: var T] = ## yields all (key, value)-pairs of `c`. The yielded values can be modified. for x in leaves(c.root): yield (x.key, x.val) -proc allprefixedAux[T](c: CritBitTree[T], key: string): Node[T] = +proc allprefixedAux[T](c: CritBitTree[T], key: string; longestMatch: bool): Node[T] = var p = c.root var top = p if p != nil: @@ -242,43 +242,51 @@ proc allprefixedAux[T](c: CritBitTree[T], key: string): Node[T] = let dir = (1 + (ch.ord or p.otherBits.ord)) shr 8 p = p.child[dir] if q.byte < key.len: top = p - for i in 0 .. <key.len: - if p.key[i] != key[i]: return + if not longestMatch: + for i in 0 .. <key.len: + if p.key[i] != key[i]: return result = top -iterator itemsWithPrefix*[T](c: CritBitTree[T], prefix: string): string = - ## yields all keys starting with `prefix`. - let top = allprefixedAux(c, prefix) +iterator itemsWithPrefix*[T](c: CritBitTree[T], prefix: string; + longestMatch=false): string = + ## yields all keys starting with `prefix`. If `longestMatch` is true, + ## the longest match is returned, it doesn't have to be a complete match then. + let top = allprefixedAux(c, prefix, longestMatch) for x in leaves(top): yield x.key -iterator keysWithPrefix*[T](c: CritBitTree[T], prefix: string): string = +iterator keysWithPrefix*[T](c: CritBitTree[T], prefix: string; + longestMatch=false): string = ## yields all keys starting with `prefix`. - let top = allprefixedAux(c, prefix) + let top = allprefixedAux(c, prefix, longestMatch) for x in leaves(top): yield x.key -iterator valuesWithPrefix*[T](c: CritBitTree[T], prefix: string): T = +iterator valuesWithPrefix*[T](c: CritBitTree[T], prefix: string; + longestMatch=false): T = ## yields all values of `c` starting with `prefix` of the ## corresponding keys. - let top = allprefixedAux(c, prefix) + let top = allprefixedAux(c, prefix, longestMatch) for x in leaves(top): yield x.val -iterator mvaluesWithPrefix*[T](c: var CritBitTree[T], prefix: string): var T = +iterator mvaluesWithPrefix*[T](c: var CritBitTree[T], prefix: string; + longestMatch=false): var T = ## yields all values of `c` starting with `prefix` of the ## corresponding keys. The values can be modified. - let top = allprefixedAux(c, prefix) + let top = allprefixedAux(c, prefix, longestMatch) for x in leaves(top): yield x.val iterator pairsWithPrefix*[T](c: CritBitTree[T], - prefix: string): tuple[key: string, val: T] = + prefix: string; + longestMatch=false): tuple[key: string, val: T] = ## yields all (key, value)-pairs of `c` starting with `prefix`. - let top = allprefixedAux(c, prefix) + let top = allprefixedAux(c, prefix, longestMatch) for x in leaves(top): yield (x.key, x.val) iterator mpairsWithPrefix*[T](c: var CritBitTree[T], - prefix: string): tuple[key: string, val: var T] = + prefix: string; + longestMatch=false): tuple[key: string, val: var T] = ## yields all (key, value)-pairs of `c` starting with `prefix`. ## The yielded values can be modified. - let top = allprefixedAux(c, prefix) + let top = allprefixedAux(c, prefix, longestMatch) for x in leaves(top): yield (x.key, x.val) proc `$`*[T](c: CritBitTree[T]): string = diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 71babe93b..b72face91 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -10,12 +10,9 @@ ## :Author: Alexander Mitchell-Robinson (Amrykid) ## ## 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. +## were inspired by functional programming languages. ## -## Also, for functional style programming you may want to pass `anonymous procs +## 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. @@ -471,7 +468,7 @@ template toSeq*(iter: expr): expr {.immediate.} = ## if x mod 2 == 1: ## result = true) ## assert odd_numbers == @[1, 3, 5, 7, 9] - + when compiles(iter.len): var i = 0 var result = newSeq[type(iter)](iter.len) diff --git a/lib/pure/db_common.nim b/lib/pure/db_common.nim new file mode 100644 index 000000000..957389605 --- /dev/null +++ b/lib/pure/db_common.nim @@ -0,0 +1,103 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Common datatypes and definitions for all ``db_*.nim`` ( +## `db_mysql <db_mysql.html>`_, `db_postgres <db_postgres.html>`_, +## and `db_sqlite <db_sqlite.html>`_) modules. + +type + DbError* = object of IOError ## exception that is raised if a database error occurs + + SqlQuery* = distinct string ## an SQL query string + + + DbEffect* = object of IOEffect ## effect that denotes a database operation + ReadDbEffect* = object of DbEffect ## effect that denotes a read operation + WriteDbEffect* = object of DbEffect ## effect that denotes a write operation + + DbTypeKind* = enum ## a superset of datatypes that might be supported. + dbUnknown, ## unknown datatype + dbSerial, ## datatype used for primary auto-increment keys + dbNull, ## datatype used for the NULL value + dbBit, ## bit datatype + dbBool, ## boolean datatype + dbBlob, ## blob datatype + dbFixedChar, ## string of fixed length + dbVarchar, ## string datatype + dbJson, ## JSON datatype + dbXml, ## XML datatype + dbInt, ## some integer type + dbUInt, ## some unsigned integer type + dbDecimal, ## decimal numbers (fixed-point number) + dbFloat, ## some floating point type + dbDate, ## a year-month-day description + dbTime, ## HH:MM:SS information + dbDatetime, ## year-month-day and HH:MM:SS information, + ## plus optional time or timezone information + dbTimestamp, ## Timestamp values are stored as the number of seconds + ## since the epoch ('1970-01-01 00:00:00' UTC). + dbTimeInterval, ## an interval [a,b] of times + dbEnum, ## some enum + dbSet, ## set of enum values + dbArray, ## an array of values + dbComposite, ## composite type (record, struct, etc) + dbUrl, ## a URL + dbUuid, ## a UUID + dbInet, ## an IP address + dbMacAddress, ## a MAC address + dbGeometry, ## some geometric type + dbPoint, ## Point on a plane (x,y) + dbLine, ## Infinite line ((x1,y1),(x2,y2)) + dbLseg, ## Finite line segment ((x1,y1),(x2,y2)) + dbBox, ## Rectangular box ((x1,y1),(x2,y2)) + dbPath, ## Closed or open path (similar to polygon) ((x1,y1),...) + dbPolygon, ## Polygon (similar to closed path) ((x1,y1),...) + dbCircle, ## Circle <(x,y),r> (center point and radius) + dbUser1, ## user definable datatype 1 (for unknown extensions) + dbUser2, ## user definable datatype 2 (for unknown extensions) + dbUser3, ## user definable datatype 3 (for unknown extensions) + dbUser4, ## user definable datatype 4 (for unknown extensions) + dbUser5 ## user definable datatype 5 (for unknown extensions) + + DbType* = object ## describes a database type + kind*: DbTypeKind ## the kind of the described type + notNull*: bool ## does the type contain NULL? + name*: string ## the name of the type + size*: Natural ## the size of the datatype; 0 if of variable size + maxReprLen*: Natural ## maximal length required for the representation + precision*, scale*: Natural ## precision and scale of the number + min*, max*: BiggestInt ## the minimum and maximum of allowed values + validValues*: seq[string] ## valid values of an enum or a set + + DbColumn* = object ## information about a database column + name*: string ## name of the column + tableName*: string ## name of the table the column belongs to (optional) + typ*: DbType ## type of the column + primaryKey*: bool ## is this a primary key? + foreignKey*: bool ## is this a foreign key? + DbColumns* = seq[DbColumn] + +{.deprecated: [EDb: DbError, TSqlQuery: SqlQuery, FDb: DbEffect, + FReadDb: ReadDbEffect, FWriteDb: WriteDbEffect].} + +template sql*(query: string): SqlQuery = + ## constructs a SqlQuery from the string `query`. This is supposed to be + ## used as a raw-string-literal modifier: + ## ``sql"update user set counter = counter + 1"`` + ## + ## If assertions are turned off, it does nothing. If assertions are turned + ## on, later versions will check the string for valid syntax. + SqlQuery(query) + +proc dbError*(msg: string) {.noreturn, noinline.} = + ## raises an DbError exception with message `msg`. + var e: ref DbError + new(e) + e.msg = msg + raise e diff --git a/lib/pure/events.nim b/lib/pure/events.nim index 62800c5c8..23a8a2c58 100644 --- a/lib/pure/events.nim +++ b/lib/pure/events.nim @@ -57,7 +57,7 @@ proc addHandler*(handler: var EventHandler, fn: proc(e: EventArgs) {.closure.}) proc removeHandler*(handler: var EventHandler, fn: proc(e: EventArgs) {.closure.}) = ## Removes the callback from the specified event handler. - for i in countup(0, len(handler.handlers) -1): + for i in countup(0, len(handler.handlers)-1): if fn == handler.handlers[i]: handler.handlers.del(i) break diff --git a/lib/pure/fsmonitor.nim b/lib/pure/fsmonitor.nim index 787acb5d4..115c4739e 100644 --- a/lib/pure/fsmonitor.nim +++ b/lib/pure/fsmonitor.nim @@ -34,8 +34,8 @@ type MonitorEventType* = enum ## Monitor event type MonitorAccess, ## File was accessed. MonitorAttrib, ## Metadata changed. - MonitorCloseWrite, ## Writtable file was closed. - MonitorCloseNoWrite, ## Unwrittable file closed. + MonitorCloseWrite, ## Writable file was closed. + MonitorCloseNoWrite, ## Non-writable file closed. MonitorCreate, ## Subfile was created. MonitorDelete, ## Subfile was deleted. MonitorDeleteSelf, ## Watched file/directory was itself deleted. @@ -78,21 +78,21 @@ proc add*(monitor: FSMonitor, target: string, ## watched paths of ``monitor``. ## You can specify the events to report using the ``filters`` parameter. - var INFilter = -1 + var INFilter = 0 for f in filters: case f - of MonitorAccess: INFilter = INFilter and IN_ACCESS - of MonitorAttrib: INFilter = INFilter and IN_ATTRIB - of MonitorCloseWrite: INFilter = INFilter and IN_CLOSE_WRITE - of MonitorCloseNoWrite: INFilter = INFilter and IN_CLOSE_NO_WRITE - of MonitorCreate: INFilter = INFilter and IN_CREATE - of MonitorDelete: INFilter = INFilter and IN_DELETE - of MonitorDeleteSelf: INFilter = INFilter and IN_DELETE_SELF - of MonitorModify: INFilter = INFilter and IN_MODIFY - of MonitorMoveSelf: INFilter = INFilter and IN_MOVE_SELF - of MonitorMoved: INFilter = INFilter and IN_MOVED_FROM and IN_MOVED_TO - of MonitorOpen: INFilter = INFilter and IN_OPEN - of MonitorAll: INFilter = INFilter and IN_ALL_EVENTS + of MonitorAccess: INFilter = INFilter or IN_ACCESS + of MonitorAttrib: INFilter = INFilter or IN_ATTRIB + of MonitorCloseWrite: INFilter = INFilter or IN_CLOSE_WRITE + of MonitorCloseNoWrite: INFilter = INFilter or IN_CLOSE_NO_WRITE + of MonitorCreate: INFilter = INFilter or IN_CREATE + of MonitorDelete: INFilter = INFilter or IN_DELETE + of MonitorDeleteSelf: INFilter = INFilter or IN_DELETE_SELF + of MonitorModify: INFilter = INFilter or IN_MODIFY + of MonitorMoveSelf: INFilter = INFilter or IN_MOVE_SELF + of MonitorMoved: INFilter = INFilter or IN_MOVED_FROM or IN_MOVED_TO + of MonitorOpen: INFilter = INFilter or IN_OPEN + of MonitorAll: INFilter = INFilter or IN_ALL_EVENTS result = inotifyAddWatch(monitor.fd, target, INFilter.uint32) if result < 0: @@ -200,9 +200,18 @@ proc register*(d: Dispatcher, monitor: FSMonitor, when not defined(testing) and isMainModule: proc main = - var disp = newDispatcher() - var monitor = newMonitor() - echo monitor.add("/home/dom/inotifytests/") + var + disp = newDispatcher() + monitor = newMonitor() + n = 0 + n = monitor.add("/tmp") + assert n == 1 + n = monitor.add("/tmp", {MonitorAll}) + assert n == 1 + n = monitor.add("/tmp", {MonitorCloseWrite, MonitorCloseNoWrite}) + assert n == 1 + n = monitor.add("/tmp", {MonitorMoved, MonitorOpen, MonitorAccess}) + assert n == 1 disp.register(monitor, proc (m: FSMonitor, ev: MonitorEvent) = echo("Got event: ", ev.kind) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 8e182e274..1b91132db 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -110,7 +110,7 @@ type EInvalidProtocol: ProtocolError, EHttpRequestErr: HttpRequestError ].} -const defUserAgent* = "Nim httpclient/0.1" +const defUserAgent* = "Nim httpclient/" & NimVersion proc httpError(msg: string) = var e: ref ProtocolError @@ -389,6 +389,7 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", ## | An optional timeout can be specified in milliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. var r = if proxy == nil: parseUri(url) else: proxy.url + var hostUrl = if proxy == nil: r else: parseUri(url) var headers = substr(httpMethod, len("http")) # TODO: Use generateHeaders further down once it supports proxies. if proxy == nil: @@ -402,10 +403,10 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", headers.add(" HTTP/1.1\c\L") - if r.port == "": - add(headers, "Host: " & r.hostname & "\c\L") + if hostUrl.port == "": + add(headers, "Host: " & hostUrl.hostname & "\c\L") else: - add(headers, "Host: " & r.hostname & ":" & r.port & "\c\L") + add(headers, "Host: " & hostUrl.hostname & ":" & hostUrl.port & "\c\L") if userAgent != "": add(headers, "User-Agent: " & userAgent & "\c\L") @@ -414,7 +415,6 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", add(headers, "Proxy-Authorization: basic " & auth & "\c\L") add(headers, extraHeaders) add(headers, "\c\L") - var s = newSocket() if s == nil: raiseOSError(osLastError()) var port = net.Port(80) diff --git a/lib/pure/lexbase.nim b/lib/pure/lexbase.nim index bfecf6a58..cf2e8bb89 100644 --- a/lib/pure/lexbase.nim +++ b/lib/pure/lexbase.nim @@ -28,7 +28,10 @@ type BaseLexer* = object of RootObj ## the base lexer. Inherit your lexer from ## this object. bufpos*: int ## the current position within the buffer - buf*: cstring ## the buffer itself + when defined(js): ## the buffer itself + buf*: string + else: + buf*: cstring bufLen*: int ## length of buffer in characters input: Stream ## the input stream lineNumber*: int ## the current line number @@ -43,7 +46,8 @@ const proc close*(L: var BaseLexer) = ## closes the base lexer. This closes `L`'s associated stream too. - dealloc(L.buf) + when not defined(js): + dealloc(L.buf) close(L.input) proc fillBuffer(L: var BaseLexer) = @@ -58,8 +62,11 @@ proc fillBuffer(L: var BaseLexer) = toCopy = L.bufLen - L.sentinel - 1 assert(toCopy >= 0) if toCopy > 0: - moveMem(L.buf, addr(L.buf[L.sentinel + 1]), toCopy * chrSize) - # "moveMem" handles overlapping regions + when defined(js): + for i in 0 ..< toCopy: L.buf[i] = L.buf[L.sentinel + 1 + i] + else: + # "moveMem" handles overlapping regions + moveMem(L.buf, addr L.buf[L.sentinel + 1], toCopy * chrSize) charsRead = readData(L.input, addr(L.buf[toCopy]), (L.sentinel + 1) * chrSize) div chrSize s = toCopy + charsRead @@ -81,7 +88,10 @@ proc fillBuffer(L: var BaseLexer) = # double the buffer's size and try again: oldBufLen = L.bufLen L.bufLen = L.bufLen * 2 - L.buf = cast[cstring](realloc(L.buf, L.bufLen * chrSize)) + when defined(js): + L.buf.setLen(L.bufLen) + else: + L.buf = cast[cstring](realloc(L.buf, L.bufLen * chrSize)) assert(L.bufLen - oldBufLen == oldBufLen) charsRead = readData(L.input, addr(L.buf[oldBufLen]), oldBufLen * chrSize) div chrSize @@ -139,7 +149,10 @@ proc open*(L: var BaseLexer, input: Stream, bufLen: int = 8192; L.bufpos = 0 L.bufLen = bufLen L.refillChars = refillChars - L.buf = cast[cstring](alloc(bufLen * chrSize)) + when defined(js): + L.buf = newString(bufLen) + else: + L.buf = cast[cstring](alloc(bufLen * chrSize)) L.sentinel = bufLen - 1 L.lineStart = 0 L.lineNumber = 1 # lines start at 1 diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index c9e067a3e..b5a8d5777 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -203,9 +203,12 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, hints.ai_family = toInt(domain) hints.ai_socktype = toInt(sockType) hints.ai_protocol = toInt(protocol) + # OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED. + # FreeBSD doesn't support AI_V4MAPPED but defines the macro. # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092 - when not defined(freebsd): - hints.ai_flags = AI_V4MAPPED + when not defined(freebsd) and not defined(openbsd) and not defined(netbsd): + if domain == AF_INET6: + hints.ai_flags = AI_V4MAPPED var gaiResult = getaddrinfo(address, $port, addr(hints), result) if gaiResult != 0'i32: when useWinVersion: diff --git a/lib/pure/nimprof.nim b/lib/pure/nimprof.nim index cfe6bc40d..e2397b91c 100644 --- a/lib/pure/nimprof.nim +++ b/lib/pure/nimprof.nim @@ -1,7 +1,7 @@ # # # Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -117,24 +117,38 @@ when defined(memProfiler): var gTicker {.threadvar.}: int - proc hook(st: StackTrace, size: int) {.nimcall.} = + proc requestedHook(): bool {.nimcall.} = if gTicker == 0: - gTicker = -1 - when defined(ignoreAllocationSize): - hookAux(st, 1) - else: - hookAux(st, size) gTicker = SamplingInterval + result = true dec gTicker + proc hook(st: StackTrace, size: int) {.nimcall.} = + when defined(ignoreAllocationSize): + hookAux(st, 1) + else: + hookAux(st, size) + else: var t0 {.threadvar.}: Ticks + gTicker: int # we use an additional counter to + # avoid calling 'getTicks' too frequently + + proc requestedHook(): bool {.nimcall.} = + if interval == 0: result = true + elif gTicker == 0: + gTicker = 500 + if getTicks() - t0 > interval: + result = true + else: + dec gTicker proc hook(st: StackTrace) {.nimcall.} = + #echo "profiling! ", interval if interval == 0: hookAux(st, 1) - elif int64(t0) == 0 or getTicks() - t0 > interval: + else: hookAux(st, 1) t0 = getTicks() @@ -145,9 +159,10 @@ proc cmpEntries(a, b: ptr ProfileEntry): int = result = b.getTotal - a.getTotal proc `//`(a, b: int): string = - result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDefault, 2)) + result = format("$1/$2 = $3%", a, b, formatFloat(a / b * 100.0, ffDecimal, 2)) proc writeProfile() {.noconv.} = + system.profilingRequestedHook = nil when declared(system.StackTrace): system.profilerHook = nil const filename = "profile_results.txt" @@ -193,14 +208,15 @@ var proc disableProfiling*() = when declared(system.StackTrace): atomicDec disabled - system.profilerHook = nil + system.profilingRequestedHook = nil proc enableProfiling*() = when declared(system.StackTrace): if atomicInc(disabled) >= 0: - system.profilerHook = hook + system.profilingRequestedHook = requestedHook when declared(system.StackTrace): + system.profilingRequestedHook = requestedHook system.profilerHook = hook addQuitProc(writeProfile) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 3122d58b1..2abb80016 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -28,7 +28,7 @@ ## ## .. code-block:: nim ## -## import optionals +## import options ## ## proc find(haystack: string, needle: char): Option[int] = ## for i, c in haystack: @@ -156,7 +156,7 @@ proc `$`*[T]( self: Option[T] ): string = when isMainModule: import unittest, sequtils - suite "optionals": + suite "options": # work around a bug in unittest let intNone = none(int) let stringNone = none(string) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index c01228563..1e00f92b1 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -810,6 +810,10 @@ type {.deprecated: [TPathComponent: PathComponent].} +proc staticWalkDir(dir: string; relative: bool): seq[ + tuple[kind: PathComponent, path: string]] = + discard + iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {. tags: [ReadDirEffect].} = ## walks over the directory `dir` and yields for each directory or file in @@ -833,49 +837,53 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: ## dirA/dirC ## dirA/fileA1.txt ## dirA/fileA2.txt - when defined(windows): - var f: WIN32_FIND_DATA - var h = findFirstFile(dir / "*", f) - if h != -1: - while true: - var k = pcFile - if not skipFindData(f): - if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32: - k = pcDir - if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: - k = succ(k) - let xx = if relative: extractFilename(getFilename(f)) - else: dir / extractFilename(getFilename(f)) - yield (k, xx) - if findNextFile(h, f) == 0'i32: break - findClose(h) + when nimvm: + for k, v in items(staticWalkDir(dir, relative)): + yield (k, v) else: - var d = opendir(dir) - if d != nil: - while true: - var x = readdir(d) - if x == nil: break - var y = $x.d_name - if y != "." and y != "..": - var s: Stat - if not relative: - y = dir / y + when defined(windows): + var f: WIN32_FIND_DATA + var h = findFirstFile(dir / "*", f) + if h != -1: + while true: var k = pcFile - - when defined(linux) or defined(macosx) or defined(bsd): - if x.d_type != DT_UNKNOWN: - if x.d_type == DT_DIR: k = pcDir - if x.d_type == DT_LNK: - if dirExists(y): k = pcLinkToDir - else: k = succ(k) - yield (k, y) - continue - - if lstat(y, s) < 0'i32: break - if S_ISDIR(s.st_mode): k = pcDir - if S_ISLNK(s.st_mode): k = succ(k) - yield (k, y) - discard closedir(d) + if not skipFindData(f): + if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32: + k = pcDir + if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: + k = succ(k) + let xx = if relative: extractFilename(getFilename(f)) + else: dir / extractFilename(getFilename(f)) + yield (k, xx) + if findNextFile(h, f) == 0'i32: break + findClose(h) + else: + var d = opendir(dir) + if d != nil: + while true: + var x = readdir(d) + if x == nil: break + var y = $x.d_name + if y != "." and y != "..": + var s: Stat + if not relative: + y = dir / y + var k = pcFile + + when defined(linux) or defined(macosx) or defined(bsd): + if x.d_type != DT_UNKNOWN: + if x.d_type == DT_DIR: k = pcDir + if x.d_type == DT_LNK: + if dirExists(y): k = pcLinkToDir + else: k = succ(k) + yield (k, y) + continue + + if lstat(y, s) < 0'i32: break + if S_ISDIR(s.st_mode): k = pcDir + if S_ISLNK(s.st_mode): k = succ(k) + yield (k, y) + discard closedir(d) iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string {. tags: [ReadDirEffect].} = @@ -1353,7 +1361,7 @@ proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # /proc/<pid>/file when defined(windows): when useWinUnicode: - var buf = cast[WideCString](alloc(256*2)) + var buf = newWideCString("", 256) var len = getModuleFileNameW(0, buf, 256) result = buf$len else: diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index de9e63909..8560c3ee4 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -886,7 +886,7 @@ elif not defined(useNimRtl): discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) exitnow(1) - when defined(macosx) or defined(freebsd): + when defined(macosx) or defined(freebsd) or defined(netbsd) or defined(android): var environ {.importc.}: cstringArray proc startProcessAfterFork(data: ptr StartProcessData) = @@ -916,7 +916,7 @@ elif not defined(useNimRtl): discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) if data.optionPoUsePath: - when defined(macosx) or defined(freebsd): + when defined(macosx) or defined(freebsd) or defined(netbsd) or defined(android): # MacOSX doesn't have execvpe, so we need workaround. # On MacOSX we can arrive here only from fork, so this is safe: environ = data.sysEnv @@ -937,9 +937,10 @@ elif not defined(useNimRtl): if p.inStream != nil: close(p.inStream) if p.outStream != nil: close(p.outStream) if p.errStream != nil: close(p.errStream) - discard close(p.inHandle) - discard close(p.outHandle) - discard close(p.errHandle) + if poParentStreams notin p.options: + discard close(p.inHandle) + discard close(p.outHandle) + discard close(p.errHandle) proc suspend(p: Process) = if kill(p.id, SIGSTOP) != 0'i32: raiseOsError(osLastError()) diff --git a/lib/pure/oswalkdir.nim b/lib/pure/oswalkdir.nim new file mode 100644 index 000000000..000fe25a3 --- /dev/null +++ b/lib/pure/oswalkdir.nim @@ -0,0 +1,27 @@ + +## Compile-time only version for walkDir if you need it at compile-time +## for JavaScript. + +type + PathComponent* = enum ## Enumeration specifying a path component. + pcFile, ## path refers to a file + pcLinkToFile, ## path refers to a symbolic link to a file + pcDir, ## path refers to a directory + pcLinkToDir ## path refers to a symbolic link to a directory + +proc staticWalkDir(dir: string; relative: bool): seq[ + tuple[kind: PathComponent, path: string]] = + discard + +iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] = + for k, v in items(staticWalkDir(dir, relative)): + yield (k, v) + +iterator walkDirRec*(dir: string, filter={pcFile, pcDir}): string = + var stack = @[dir] + while stack.len > 0: + for k,p in walkDir(stack.pop()): + if k in filter: + case k + of pcFile, pcLinkToFile: yield p + of pcDir, pcLinkToDir: stack.add(p) diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index 73b498fe0..7fd9c60fe 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -70,7 +70,7 @@ when not defined(createNimRtl): ## Initializes option parser from current command line arguments. return initOptParser(commandLineParams()) -proc next*(p: var OptParser) {.rtl, extern: "npo$1".} +proc next*(p: var OptParser) {.rtl, extern: "npo2$1".} proc nextOption(p: var OptParser, token: string, allowEmpty: bool) = for splitchar in [':', '=']: @@ -113,7 +113,7 @@ proc next(p: var OptParser) = p.key = token p.val = "" -proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1", deprecated.} = +proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo2$1", deprecated.} = ## Returns part of command line string that has not been parsed yet. ## Do not use - does not correctly handle whitespace. return p.cmd[p.pos..p.cmd.len-1].join(" ") diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index b3708838a..698bde42a 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -25,7 +25,7 @@ const proc toLower(c: char): char {.inline.} = result = if c in {'A'..'Z'}: chr(ord(c)-ord('A')+ord('a')) else: c -proc parseHex*(s: string, number: var int, start = 0): int {. +proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. rtl, extern: "npuParseHex", noSideEffect.} = ## Parses a hexadecimal number and stores its value in ``number``. ## @@ -45,11 +45,14 @@ proc parseHex*(s: string, number: var int, start = 0): int {. ## discard parseHex("0x38", value) ## assert value == -200 ## + ## If 'maxLen==0' the length of the hexadecimal number has no + ## upper bound. Not more than ```maxLen`` characters are parsed. var i = start var foundDigit = false if s[i] == '0' and (s[i+1] == 'x' or s[i+1] == 'X'): inc(i, 2) elif s[i] == '#': inc(i) - while true: + let last = if maxLen == 0: s.len else: i+maxLen + while i < last: case s[i] of '_': discard of '0'..'9': diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim deleted file mode 100644 index e3f18a496..000000000 --- a/lib/pure/redis.nim +++ /dev/null @@ -1,1096 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a redis client. It allows you to connect to a -## redis-server instance, send commands and receive replies. -## -## **Beware**: Most (if not all) functions that return a ``RedisString`` may -## return ``redisNil``, and functions which return a ``RedisList`` -## may return ``nil``. - -import sockets, os, strutils, parseutils - -const - redisNil* = "\0\0" - -type - Pipeline = ref object - enabled: bool - buffer: string - expected: int ## number of replies expected if pipelined - -type - SendMode = enum - normal, pipelined, multiple - -type - Redis* = object - socket: Socket - connected: bool - pipeline: Pipeline - - RedisStatus* = string - RedisInteger* = BiggestInt - RedisString* = string ## Bulk reply - RedisList* = seq[RedisString] ## Multi-bulk reply - - ReplyError* = object of IOError ## Invalid reply from redis - RedisError* = object of IOError ## Error in redis - -{.deprecated: [TSendMode: SendMode, TRedis: Redis, TRedisStatus: RedisStatus, - TRedisInteger: RedisInteger, TRedisString: RedisString, - TRedisList: RedisList, EInvalidReply: ReplyError, ERedis: RedisError].} - -proc newPipeline(): Pipeline = - new(result) - result.buffer = "" - result.enabled = false - result.expected = 0 - -proc open*(host = "localhost", port = 6379.Port): Redis = - ## Opens a connection to the redis server. - result.socket = socket(buffered = false) - if result.socket == invalidSocket: - raiseOSError(osLastError()) - result.socket.connect(host, port) - result.pipeline = newPipeline() - -proc raiseInvalidReply(expected, got: char) = - raise newException(ReplyError, - "Expected '$1' at the beginning of a status reply got '$2'" % - [$expected, $got]) - -proc raiseNoOK(status: string, pipelineEnabled: bool) = - if pipelineEnabled and not (status == "QUEUED" or status == "PIPELINED"): - raise newException(ReplyError, "Expected \"QUEUED\" or \"PIPELINED\" got \"$1\"" % status) - elif not pipelineEnabled and status != "OK": - raise newException(ReplyError, "Expected \"OK\" got \"$1\"" % status) - -template readSocket(r: Redis, dummyVal:expr): stmt = - var line {.inject.}: TaintedString = "" - if r.pipeline.enabled: - return dummyVal - else: - readLine(r.socket, line) - -proc parseStatus(r: Redis, line: string = ""): RedisStatus = - if r.pipeline.enabled: - return "PIPELINED" - - if line == "": - raise newException(RedisError, "Server closed connection prematurely") - - if line[0] == '-': - raise newException(RedisError, strip(line)) - if line[0] != '+': - raiseInvalidReply('+', line[0]) - - return line.substr(1) # Strip '+' - -proc readStatus(r:Redis): RedisStatus = - r.readSocket("PIPELINED") - return r.parseStatus(line) - -proc parseInteger(r: Redis, line: string = ""): RedisInteger = - if r.pipeline.enabled: return -1 - - #if line == "+QUEUED": # inside of multi - # return -1 - - if line == "": - raise newException(RedisError, "Server closed connection prematurely") - - if line[0] == '-': - raise newException(RedisError, strip(line)) - if line[0] != ':': - raiseInvalidReply(':', line[0]) - - # Strip ':' - if parseBiggestInt(line, result, 1) == 0: - raise newException(ReplyError, "Unable to parse integer.") - -proc readInteger(r: Redis): RedisInteger = - r.readSocket(-1) - return r.parseInteger(line) - -proc recv(sock: Socket, size: int): TaintedString = - result = newString(size).TaintedString - if sock.recv(cstring(result), size) != size: - raise newException(ReplyError, "recv failed") - -proc parseSingleString(r: Redis, line:string, allowMBNil = false): RedisString = - if r.pipeline.enabled: return "" - - # Error. - if line[0] == '-': - raise newException(RedisError, strip(line)) - - # Some commands return a /bulk/ value or a /multi-bulk/ nil. Odd. - if allowMBNil: - if line == "*-1": - return redisNil - - if line[0] != '$': - raiseInvalidReply('$', line[0]) - - var numBytes = parseInt(line.substr(1)) - if numBytes == -1: - return redisNil - - var s = r.socket.recv(numBytes+2) - result = strip(s.string) - -proc readSingleString(r: Redis): RedisString = - r.readSocket("") - return r.parseSingleString(line) - -proc readNext(r: Redis): RedisList - -proc parseArrayLines(r: Redis, countLine:string): RedisList = - if countLine.string[0] != '*': - raiseInvalidReply('*', countLine.string[0]) - - var numElems = parseInt(countLine.string.substr(1)) - if numElems == -1: return nil - result = @[] - - for i in 1..numElems: - var parsed = r.readNext() - if not isNil(parsed): - for item in parsed: - result.add(item) - -proc readArrayLines(r: Redis): RedisList = - r.readSocket(nil) - return r.parseArrayLines(line) - -proc parseBulkString(r: Redis, allowMBNil = false, line:string = ""): RedisString = - if r.pipeline.enabled: return "" - - return r.parseSingleString(line, allowMBNil) - -proc readBulkString(r: Redis, allowMBNil = false): RedisString = - r.readSocket("") - return r.parseBulkString(allowMBNil, line) - -proc readArray(r: Redis): RedisList = - r.readSocket(@[]) - return r.parseArrayLines(line) - -proc readNext(r: Redis): RedisList = - r.readSocket(@[]) - - var res = case line[0] - of '+', '-': @[r.parseStatus(line)] - of ':': @[$(r.parseInteger(line))] - of '$': @[r.parseBulkString(true,line)] - of '*': r.parseArrayLines(line) - else: - raise newException(ReplyError, "readNext failed on line: " & line) - nil - r.pipeline.expected -= 1 - return res - -proc flushPipeline*(r: Redis, wasMulti = false): RedisList = - ## Send buffered commands, clear buffer, return results - if r.pipeline.buffer.len > 0: - r.socket.send(r.pipeline.buffer) - r.pipeline.buffer = "" - - r.pipeline.enabled = false - result = @[] - - var tot = r.pipeline.expected - - for i in 0..tot-1: - var ret = r.readNext() - for item in ret: - if not (item.contains("OK") or item.contains("QUEUED")): - result.add(item) - - r.pipeline.expected = 0 - -proc startPipelining*(r: Redis) = - ## Enable command pipelining (reduces network roundtrips). - ## Note that when enabled, you must call flushPipeline to actually send commands, except - ## for multi/exec() which enable and flush the pipeline automatically. - ## Commands return immediately with dummy values; actual results returned from - ## flushPipeline() or exec() - r.pipeline.expected = 0 - r.pipeline.enabled = true - -proc sendCommand(r: Redis, cmd: string, args: varargs[string]) = - var request = "*" & $(1 + args.len()) & "\c\L" - request.add("$" & $cmd.len() & "\c\L") - request.add(cmd & "\c\L") - for i in items(args): - request.add("$" & $i.len() & "\c\L") - request.add(i & "\c\L") - - if r.pipeline.enabled: - r.pipeline.buffer.add(request) - r.pipeline.expected += 1 - else: - r.socket.send(request) - -proc sendCommand(r: Redis, cmd: string, arg1: string, - args: varargs[string]) = - var request = "*" & $(2 + args.len()) & "\c\L" - request.add("$" & $cmd.len() & "\c\L") - request.add(cmd & "\c\L") - request.add("$" & $arg1.len() & "\c\L") - request.add(arg1 & "\c\L") - for i in items(args): - request.add("$" & $i.len() & "\c\L") - request.add(i & "\c\L") - - if r.pipeline.enabled: - r.pipeline.expected += 1 - r.pipeline.buffer.add(request) - else: - r.socket.send(request) - -# Keys - -proc del*(r: Redis, keys: varargs[string]): RedisInteger = - ## Delete a key or multiple keys - r.sendCommand("DEL", keys) - return r.readInteger() - -proc exists*(r: Redis, key: string): bool = - ## Determine if a key exists - r.sendCommand("EXISTS", key) - return r.readInteger() == 1 - -proc expire*(r: Redis, key: string, seconds: int): bool = - ## Set a key's time to live in seconds. Returns `false` if the key could - ## not be found or the timeout could not be set. - r.sendCommand("EXPIRE", key, $seconds) - return r.readInteger() == 1 - -proc expireAt*(r: Redis, key: string, timestamp: int): bool = - ## Set the expiration for a key as a UNIX timestamp. Returns `false` - ## if the key could not be found or the timeout could not be set. - r.sendCommand("EXPIREAT", key, $timestamp) - return r.readInteger() == 1 - -proc keys*(r: Redis, pattern: string): RedisList = - ## Find all keys matching the given pattern - r.sendCommand("KEYS", pattern) - return r.readArray() - -proc scan*(r: Redis, cursor: var BiggestInt): RedisList = - ## Find all keys matching the given pattern and yield it to client in portions - ## using default Redis values for MATCH and COUNT parameters - r.sendCommand("SCAN", $cursor) - let reply = r.readArray() - cursor = strutils.parseBiggestInt(reply[0]) - return reply[1..high(reply)] - -proc scan*(r: Redis, cursor: var BiggestInt, pattern: string): RedisList = - ## Find all keys matching the given pattern and yield it to client in portions - ## using cursor as a client query identifier. Using default Redis value for COUNT argument - r.sendCommand("SCAN", $cursor, ["MATCH", pattern]) - let reply = r.readArray() - cursor = strutils.parseBiggestInt(reply[0]) - return reply[1..high(reply)] - -proc scan*(r: Redis, cursor: var BiggestInt, pattern: string, count: int): RedisList = - ## Find all keys matching the given pattern and yield it to client in portions - ## using cursor as a client query identifier. - r.sendCommand("SCAN", $cursor, ["MATCH", pattern, "COUNT", $count]) - let reply = r.readArray() - cursor = strutils.parseBiggestInt(reply[0]) - return reply[1..high(reply)] - -proc move*(r: Redis, key: string, db: int): bool = - ## Move a key to another database. Returns `true` on a successful move. - r.sendCommand("MOVE", key, $db) - return r.readInteger() == 1 - -proc persist*(r: Redis, key: string): bool = - ## Remove the expiration from a key. - ## Returns `true` when the timeout was removed. - r.sendCommand("PERSIST", key) - return r.readInteger() == 1 - -proc randomKey*(r: Redis): RedisString = - ## Return a random key from the keyspace - r.sendCommand("RANDOMKEY") - return r.readBulkString() - -proc rename*(r: Redis, key, newkey: string): RedisStatus = - ## Rename a key. - ## - ## **WARNING:** Overwrites `newkey` if it exists! - r.sendCommand("RENAME", key, newkey) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc renameNX*(r: Redis, key, newkey: string): bool = - ## Same as ``rename`` but doesn't continue if `newkey` exists. - ## Returns `true` if key was renamed. - r.sendCommand("RENAMENX", key, newkey) - return r.readInteger() == 1 - -proc ttl*(r: Redis, key: string): RedisInteger = - ## Get the time to live for a key - r.sendCommand("TTL", key) - return r.readInteger() - -proc keyType*(r: Redis, key: string): RedisStatus = - ## Determine the type stored at key - r.sendCommand("TYPE", key) - return r.readStatus() - - -# Strings - -proc append*(r: Redis, key, value: string): RedisInteger = - ## Append a value to a key - r.sendCommand("APPEND", key, value) - return r.readInteger() - -proc decr*(r: Redis, key: string): RedisInteger = - ## Decrement the integer value of a key by one - r.sendCommand("DECR", key) - return r.readInteger() - -proc decrBy*(r: Redis, key: string, decrement: int): RedisInteger = - ## Decrement the integer value of a key by the given number - r.sendCommand("DECRBY", key, $decrement) - return r.readInteger() - -proc get*(r: Redis, key: string): RedisString = - ## Get the value of a key. Returns `redisNil` when `key` doesn't exist. - r.sendCommand("GET", key) - return r.readBulkString() - -proc getBit*(r: Redis, key: string, offset: int): RedisInteger = - ## Returns the bit value at offset in the string value stored at key - r.sendCommand("GETBIT", key, $offset) - return r.readInteger() - -proc getRange*(r: Redis, key: string, start, stop: int): RedisString = - ## Get a substring of the string stored at a key - r.sendCommand("GETRANGE", key, $start, $stop) - return r.readBulkString() - -proc getSet*(r: Redis, key: string, value: string): RedisString = - ## Set the string value of a key and return its old value. Returns `redisNil` - ## when key doesn't exist. - r.sendCommand("GETSET", key, value) - return r.readBulkString() - -proc incr*(r: Redis, key: string): RedisInteger = - ## Increment the integer value of a key by one. - r.sendCommand("INCR", key) - return r.readInteger() - -proc incrBy*(r: Redis, key: string, increment: int): RedisInteger = - ## Increment the integer value of a key by the given number - r.sendCommand("INCRBY", key, $increment) - return r.readInteger() - -proc setk*(r: Redis, key, value: string) = - ## Set the string value of a key. - ## - ## NOTE: This function had to be renamed due to a clash with the `set` type. - r.sendCommand("SET", key, value) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc setNX*(r: Redis, key, value: string): bool = - ## Set the value of a key, only if the key does not exist. Returns `true` - ## if the key was set. - r.sendCommand("SETNX", key, value) - return r.readInteger() == 1 - -proc setBit*(r: Redis, key: string, offset: int, - value: string): RedisInteger = - ## Sets or clears the bit at offset in the string value stored at key - r.sendCommand("SETBIT", key, $offset, value) - return r.readInteger() - -proc setEx*(r: Redis, key: string, seconds: int, value: string): RedisStatus = - ## Set the value and expiration of a key - r.sendCommand("SETEX", key, $seconds, value) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc setRange*(r: Redis, key: string, offset: int, - value: string): RedisInteger = - ## Overwrite part of a string at key starting at the specified offset - r.sendCommand("SETRANGE", key, $offset, value) - return r.readInteger() - -proc strlen*(r: Redis, key: string): RedisInteger = - ## Get the length of the value stored in a key. Returns 0 when key doesn't - ## exist. - r.sendCommand("STRLEN", key) - return r.readInteger() - -# Hashes -proc hDel*(r: Redis, key, field: string): bool = - ## Delete a hash field at `key`. Returns `true` if the field was removed. - r.sendCommand("HDEL", key, field) - return r.readInteger() == 1 - -proc hExists*(r: Redis, key, field: string): bool = - ## Determine if a hash field exists. - r.sendCommand("HEXISTS", key, field) - return r.readInteger() == 1 - -proc hGet*(r: Redis, key, field: string): RedisString = - ## Get the value of a hash field - r.sendCommand("HGET", key, field) - return r.readBulkString() - -proc hGetAll*(r: Redis, key: string): RedisList = - ## Get all the fields and values in a hash - r.sendCommand("HGETALL", key) - return r.readArray() - -proc hIncrBy*(r: Redis, key, field: string, incr: int): RedisInteger = - ## Increment the integer value of a hash field by the given number - r.sendCommand("HINCRBY", key, field, $incr) - return r.readInteger() - -proc hKeys*(r: Redis, key: string): RedisList = - ## Get all the fields in a hash - r.sendCommand("HKEYS", key) - return r.readArray() - -proc hLen*(r: Redis, key: string): RedisInteger = - ## Get the number of fields in a hash - r.sendCommand("HLEN", key) - return r.readInteger() - -proc hMGet*(r: Redis, key: string, fields: varargs[string]): RedisList = - ## Get the values of all the given hash fields - r.sendCommand("HMGET", key, fields) - return r.readArray() - -proc hMSet*(r: Redis, key: string, - fieldValues: openArray[tuple[field, value: string]]) = - ## Set multiple hash fields to multiple values - var args = @[key] - for field, value in items(fieldValues): - args.add(field) - args.add(value) - r.sendCommand("HMSET", args) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc hSet*(r: Redis, key, field, value: string): RedisInteger = - ## Set the string value of a hash field - r.sendCommand("HSET", key, field, value) - return r.readInteger() - -proc hSetNX*(r: Redis, key, field, value: string): RedisInteger = - ## Set the value of a hash field, only if the field does **not** exist - r.sendCommand("HSETNX", key, field, value) - return r.readInteger() - -proc hVals*(r: Redis, key: string): RedisList = - ## Get all the values in a hash - r.sendCommand("HVALS", key) - return r.readArray() - -# Lists - -proc bLPop*(r: Redis, keys: varargs[string], timeout: int): RedisList = - ## Remove and get the *first* element in a list, or block until - ## one is available - var args: seq[string] = @[] - for i in items(keys): args.add(i) - args.add($timeout) - r.sendCommand("BLPOP", args) - return r.readArray() - -proc bRPop*(r: Redis, keys: varargs[string], timeout: int): RedisList = - ## Remove and get the *last* element in a list, or block until one - ## is available. - var args: seq[string] = @[] - for i in items(keys): args.add(i) - args.add($timeout) - r.sendCommand("BRPOP", args) - return r.readArray() - -proc bRPopLPush*(r: Redis, source, destination: string, - timeout: int): RedisString = - ## Pop a value from a list, push it to another list and return it; or - ## block until one is available. - ## - ## http://redis.io/commands/brpoplpush - r.sendCommand("BRPOPLPUSH", source, destination, $timeout) - return r.readBulkString(true) # Multi-Bulk nil allowed. - -proc lIndex*(r: Redis, key: string, index: int): RedisString = - ## Get an element from a list by its index - r.sendCommand("LINDEX", key, $index) - return r.readBulkString() - -proc lInsert*(r: Redis, key: string, before: bool, pivot, value: string): - RedisInteger = - ## Insert an element before or after another element in a list - var pos = if before: "BEFORE" else: "AFTER" - r.sendCommand("LINSERT", key, pos, pivot, value) - return r.readInteger() - -proc lLen*(r: Redis, key: string): RedisInteger = - ## Get the length of a list - r.sendCommand("LLEN", key) - return r.readInteger() - -proc lPop*(r: Redis, key: string): RedisString = - ## Remove and get the first element in a list - r.sendCommand("LPOP", key) - return r.readBulkString() - -proc lPush*(r: Redis, key, value: string, create: bool = true): RedisInteger = - ## Prepend a value to a list. Returns the length of the list after the push. - ## The ``create`` param specifies whether a list should be created if it - ## doesn't exist at ``key``. More specifically if ``create`` is true, `LPUSH` - ## will be used, otherwise `LPUSHX`. - if create: - r.sendCommand("LPUSH", key, value) - else: - r.sendCommand("LPUSHX", key, value) - return r.readInteger() - -proc lRange*(r: Redis, key: string, start, stop: int): RedisList = - ## Get a range of elements from a list. Returns `nil` when `key` - ## doesn't exist. - r.sendCommand("LRANGE", key, $start, $stop) - return r.readArray() - -proc lRem*(r: Redis, key: string, value: string, count: int = 0): RedisInteger = - ## Remove elements from a list. Returns the number of elements that have been - ## removed. - r.sendCommand("LREM", key, $count, value) - return r.readInteger() - -proc lSet*(r: Redis, key: string, index: int, value: string) = - ## Set the value of an element in a list by its index - r.sendCommand("LSET", key, $index, value) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc lTrim*(r: Redis, key: string, start, stop: int) = - ## Trim a list to the specified range - r.sendCommand("LTRIM", key, $start, $stop) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc rPop*(r: Redis, key: string): RedisString = - ## Remove and get the last element in a list - r.sendCommand("RPOP", key) - return r.readBulkString() - -proc rPopLPush*(r: Redis, source, destination: string): RedisString = - ## Remove the last element in a list, append it to another list and return it - r.sendCommand("RPOPLPUSH", source, destination) - return r.readBulkString() - -proc rPush*(r: Redis, key, value: string, create: bool = true): RedisInteger = - ## Append a value to a list. Returns the length of the list after the push. - ## The ``create`` param specifies whether a list should be created if it - ## doesn't exist at ``key``. More specifically if ``create`` is true, `RPUSH` - ## will be used, otherwise `RPUSHX`. - if create: - r.sendCommand("RPUSH", key, value) - else: - r.sendCommand("RPUSHX", key, value) - return r.readInteger() - -# Sets - -proc sadd*(r: Redis, key: string, member: string): RedisInteger = - ## Add a member to a set - r.sendCommand("SADD", key, member) - return r.readInteger() - -proc scard*(r: Redis, key: string): RedisInteger = - ## Get the number of members in a set - r.sendCommand("SCARD", key) - return r.readInteger() - -proc sdiff*(r: Redis, keys: varargs[string]): RedisList = - ## Subtract multiple sets - r.sendCommand("SDIFF", keys) - return r.readArray() - -proc sdiffstore*(r: Redis, destination: string, - keys: varargs[string]): RedisInteger = - ## Subtract multiple sets and store the resulting set in a key - r.sendCommand("SDIFFSTORE", destination, keys) - return r.readInteger() - -proc sinter*(r: Redis, keys: varargs[string]): RedisList = - ## Intersect multiple sets - r.sendCommand("SINTER", keys) - return r.readArray() - -proc sinterstore*(r: Redis, destination: string, - keys: varargs[string]): RedisInteger = - ## Intersect multiple sets and store the resulting set in a key - r.sendCommand("SINTERSTORE", destination, keys) - return r.readInteger() - -proc sismember*(r: Redis, key: string, member: string): RedisInteger = - ## Determine if a given value is a member of a set - r.sendCommand("SISMEMBER", key, member) - return r.readInteger() - -proc smembers*(r: Redis, key: string): RedisList = - ## Get all the members in a set - r.sendCommand("SMEMBERS", key) - return r.readArray() - -proc smove*(r: Redis, source: string, destination: string, - member: string): RedisInteger = - ## Move a member from one set to another - r.sendCommand("SMOVE", source, destination, member) - return r.readInteger() - -proc spop*(r: Redis, key: string): RedisString = - ## Remove and return a random member from a set - r.sendCommand("SPOP", key) - return r.readBulkString() - -proc srandmember*(r: Redis, key: string): RedisString = - ## Get a random member from a set - r.sendCommand("SRANDMEMBER", key) - return r.readBulkString() - -proc srem*(r: Redis, key: string, member: string): RedisInteger = - ## Remove a member from a set - r.sendCommand("SREM", key, member) - return r.readInteger() - -proc sunion*(r: Redis, keys: varargs[string]): RedisList = - ## Add multiple sets - r.sendCommand("SUNION", keys) - return r.readArray() - -proc sunionstore*(r: Redis, destination: string, - key: varargs[string]): RedisInteger = - ## Add multiple sets and store the resulting set in a key - r.sendCommand("SUNIONSTORE", destination, key) - return r.readInteger() - -# Sorted sets - -proc zadd*(r: Redis, key: string, score: int, member: string): RedisInteger = - ## Add a member to a sorted set, or update its score if it already exists - r.sendCommand("ZADD", key, $score, member) - return r.readInteger() - -proc zcard*(r: Redis, key: string): RedisInteger = - ## Get the number of members in a sorted set - r.sendCommand("ZCARD", key) - return r.readInteger() - -proc zcount*(r: Redis, key: string, min: string, max: string): RedisInteger = - ## Count the members in a sorted set with scores within the given values - r.sendCommand("ZCOUNT", key, min, max) - return r.readInteger() - -proc zincrby*(r: Redis, key: string, increment: string, - member: string): RedisString = - ## Increment the score of a member in a sorted set - r.sendCommand("ZINCRBY", key, increment, member) - return r.readBulkString() - -proc zinterstore*(r: Redis, destination: string, numkeys: string, - keys: openArray[string], weights: openArray[string] = [], - aggregate: string = ""): RedisInteger = - ## Intersect multiple sorted sets and store the resulting sorted set in - ## a new key - var args = @[destination, numkeys] - for i in items(keys): args.add(i) - - if weights.len != 0: - args.add("WITHSCORE") - for i in items(weights): args.add(i) - if aggregate.len != 0: - args.add("AGGREGATE") - args.add(aggregate) - - r.sendCommand("ZINTERSTORE", args) - - return r.readInteger() - -proc zrange*(r: Redis, key: string, start: string, stop: string, - withScores: bool): RedisList = - ## Return a range of members in a sorted set, by index - if not withScores: - r.sendCommand("ZRANGE", key, start, stop) - else: - r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop) - return r.readArray() - -proc zrangebyscore*(r: Redis, key: string, min: string, max: string, - withScore: bool = false, limit: bool = false, - limitOffset: int = 0, limitCount: int = 0): RedisList = - ## Return a range of members in a sorted set, by score - var args = @[key, min, max] - - if withScore: args.add("WITHSCORE") - if limit: - args.add("LIMIT") - args.add($limitOffset) - args.add($limitCount) - - r.sendCommand("ZRANGEBYSCORE", args) - return r.readArray() - -proc zrank*(r: Redis, key: string, member: string): RedisString = - ## Determine the index of a member in a sorted set - r.sendCommand("ZRANK", key, member) - return r.readBulkString() - -proc zrem*(r: Redis, key: string, member: string): RedisInteger = - ## Remove a member from a sorted set - r.sendCommand("ZREM", key, member) - return r.readInteger() - -proc zremrangebyrank*(r: Redis, key: string, start: string, - stop: string): RedisInteger = - ## Remove all members in a sorted set within the given indexes - r.sendCommand("ZREMRANGEBYRANK", key, start, stop) - return r.readInteger() - -proc zremrangebyscore*(r: Redis, key: string, min: string, - max: string): RedisInteger = - ## Remove all members in a sorted set within the given scores - r.sendCommand("ZREMRANGEBYSCORE", key, min, max) - return r.readInteger() - -proc zrevrange*(r: Redis, key: string, start: string, stop: string, - withScore: bool): RedisList = - ## Return a range of members in a sorted set, by index, - ## with scores ordered from high to low - if withScore: - r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop) - else: r.sendCommand("ZREVRANGE", key, start, stop) - return r.readArray() - -proc zrevrangebyscore*(r: Redis, key: string, min: string, max: string, - withScore: bool = false, limit: bool = false, - limitOffset: int = 0, limitCount: int = 0): RedisList = - ## Return a range of members in a sorted set, by score, with - ## scores ordered from high to low - var args = @[key, min, max] - - if withScore: args.add("WITHSCORE") - if limit: - args.add("LIMIT") - args.add($limitOffset) - args.add($limitCount) - - r.sendCommand("ZREVRANGEBYSCORE", args) - return r.readArray() - -proc zrevrank*(r: Redis, key: string, member: string): RedisString = - ## Determine the index of a member in a sorted set, with - ## scores ordered from high to low - r.sendCommand("ZREVRANK", key, member) - return r.readBulkString() - -proc zscore*(r: Redis, key: string, member: string): RedisString = - ## Get the score associated with the given member in a sorted set - r.sendCommand("ZSCORE", key, member) - return r.readBulkString() - -proc zunionstore*(r: Redis, destination: string, numkeys: string, - keys: openArray[string], weights: openArray[string] = [], - aggregate: string = ""): RedisInteger = - ## Add multiple sorted sets and store the resulting sorted set in a new key - var args = @[destination, numkeys] - for i in items(keys): args.add(i) - - if weights.len != 0: - args.add("WEIGHTS") - for i in items(weights): args.add(i) - if aggregate.len != 0: - args.add("AGGREGATE") - args.add(aggregate) - - r.sendCommand("ZUNIONSTORE", args) - - return r.readInteger() - -# HyperLogLog - -proc pfadd*(r: Redis, key: string, elements: varargs[string]): RedisInteger = - ## Add variable number of elements into special 'HyperLogLog' set type - r.sendCommand("PFADD", key, elements) - return r.readInteger() - -proc pfcount*(r: Redis, key: string): RedisInteger = - ## Count approximate number of elements in 'HyperLogLog' - r.sendCommand("PFCOUNT", key) - return r.readInteger() - -proc pfmerge*(r: Redis, destination: string, sources: varargs[string]) = - ## Merge several source HyperLogLog's into one specified by destKey - r.sendCommand("PFMERGE", destination, sources) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -# Pub/Sub - -# TODO: pub/sub -- I don't think this will work synchronously. -discard """ -proc psubscribe*(r: Redis, pattern: openarray[string]): ???? = - ## Listen for messages published to channels matching the given patterns - r.socket.send("PSUBSCRIBE $#\c\L" % pattern) - return ??? - -proc publish*(r: Redis, channel: string, message: string): RedisInteger = - ## Post a message to a channel - r.socket.send("PUBLISH $# $#\c\L" % [channel, message]) - return r.readInteger() - -proc punsubscribe*(r: Redis, [pattern: openarray[string], : string): ???? = - ## Stop listening for messages posted to channels matching the given patterns - r.socket.send("PUNSUBSCRIBE $# $#\c\L" % [[pattern.join(), ]) - return ??? - -proc subscribe*(r: Redis, channel: openarray[string]): ???? = - ## Listen for messages published to the given channels - r.socket.send("SUBSCRIBE $#\c\L" % channel.join) - return ??? - -proc unsubscribe*(r: Redis, [channel: openarray[string], : string): ???? = - ## Stop listening for messages posted to the given channels - r.socket.send("UNSUBSCRIBE $# $#\c\L" % [[channel.join(), ]) - return ??? - -""" - -# Transactions - -proc discardMulti*(r: Redis) = - ## Discard all commands issued after MULTI - r.sendCommand("DISCARD") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc exec*(r: Redis): RedisList = - ## Execute all commands issued after MULTI - r.sendCommand("EXEC") - r.pipeline.enabled = false - # Will reply with +OK for MULTI/EXEC and +QUEUED for every command - # between, then with the results - return r.flushPipeline(true) - - -proc multi*(r: Redis) = - ## Mark the start of a transaction block - r.startPipelining() - r.sendCommand("MULTI") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc unwatch*(r: Redis) = - ## Forget about all watched keys - r.sendCommand("UNWATCH") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc watch*(r: Redis, key: varargs[string]) = - ## Watch the given keys to determine execution of the MULTI/EXEC block - r.sendCommand("WATCH", key) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -# Connection - -proc auth*(r: Redis, password: string) = - ## Authenticate to the server - r.sendCommand("AUTH", password) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc echoServ*(r: Redis, message: string): RedisString = - ## Echo the given string - r.sendCommand("ECHO", message) - return r.readBulkString() - -proc ping*(r: Redis): RedisStatus = - ## Ping the server - r.sendCommand("PING") - return r.readStatus() - -proc quit*(r: Redis) = - ## Close the connection - r.sendCommand("QUIT") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc select*(r: Redis, index: int): RedisStatus = - ## Change the selected database for the current connection - r.sendCommand("SELECT", $index) - return r.readStatus() - -# Server - -proc bgrewriteaof*(r: Redis) = - ## Asynchronously rewrite the append-only file - r.sendCommand("BGREWRITEAOF") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc bgsave*(r: Redis) = - ## Asynchronously save the dataset to disk - r.sendCommand("BGSAVE") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc configGet*(r: Redis, parameter: string): RedisList = - ## Get the value of a configuration parameter - r.sendCommand("CONFIG", "GET", parameter) - return r.readArray() - -proc configSet*(r: Redis, parameter: string, value: string) = - ## Set a configuration parameter to the given value - r.sendCommand("CONFIG", "SET", parameter, value) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc configResetStat*(r: Redis) = - ## Reset the stats returned by INFO - r.sendCommand("CONFIG", "RESETSTAT") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc dbsize*(r: Redis): RedisInteger = - ## Return the number of keys in the selected database - r.sendCommand("DBSIZE") - return r.readInteger() - -proc debugObject*(r: Redis, key: string): RedisStatus = - ## Get debugging information about a key - r.sendCommand("DEBUG", "OBJECT", key) - return r.readStatus() - -proc debugSegfault*(r: Redis) = - ## Make the server crash - r.sendCommand("DEBUG", "SEGFAULT") - -proc flushall*(r: Redis): RedisStatus = - ## Remove all keys from all databases - r.sendCommand("FLUSHALL") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc flushdb*(r: Redis): RedisStatus = - ## Remove all keys from the current database - r.sendCommand("FLUSHDB") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc info*(r: Redis): RedisString = - ## Get information and statistics about the server - r.sendCommand("INFO") - return r.readBulkString() - -proc lastsave*(r: Redis): RedisInteger = - ## Get the UNIX time stamp of the last successful save to disk - r.sendCommand("LASTSAVE") - return r.readInteger() - -discard """ -proc monitor*(r: Redis) = - ## Listen for all requests received by the server in real time - r.socket.send("MONITOR\c\L") - raiseNoOK(r.readStatus(), r.pipeline.enabled) -""" - -proc save*(r: Redis) = - ## Synchronously save the dataset to disk - r.sendCommand("SAVE") - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -proc shutdown*(r: Redis) = - ## Synchronously save the dataset to disk and then shut down the server - r.sendCommand("SHUTDOWN") - var s = "".TaintedString - r.socket.readLine(s) - if s.string.len != 0: raise newException(RedisError, s.string) - -proc slaveof*(r: Redis, host: string, port: string) = - ## Make the server a slave of another instance, or promote it as master - r.sendCommand("SLAVEOF", host, port) - raiseNoOK(r.readStatus(), r.pipeline.enabled) - -iterator hPairs*(r: Redis, key: string): tuple[key, value: string] = - ## Iterator for keys and values in a hash. - var - contents = r.hGetAll(key) - k = "" - for i in items(contents): - if k == "": - k = i - else: - yield (k, i) - k = "" - -proc someTests(r: Redis, how: SendMode):seq[string] = - var list:seq[string] = @[] - - if how == pipelined: - r.startPipelining() - elif how == multiple: - r.multi() - - r.setk("nim:test", "Testing something.") - r.setk("nim:utf8", "こんにちは") - r.setk("nim:esc", "\\ths ągt\\") - r.setk("nim:int", "1") - list.add(r.get("nim:esc")) - list.add($(r.incr("nim:int"))) - list.add(r.get("nim:int")) - list.add(r.get("nim:utf8")) - list.add($(r.hSet("test1", "name", "A Test"))) - var res = r.hGetAll("test1") - for r in res: - list.add(r) - list.add(r.get("invalid_key")) - list.add($(r.lPush("mylist","itema"))) - list.add($(r.lPush("mylist","itemb"))) - r.lTrim("mylist",0,1) - var p = r.lRange("mylist", 0, -1) - - for i in items(p): - if not isNil(i): - list.add(i) - - list.add(r.debugObject("mylist")) - - r.configSet("timeout", "299") - var g = r.configGet("timeout") - for i in items(g): - list.add(i) - - list.add(r.echoServ("BLAH")) - - case how - of normal: - return list - of pipelined: - return r.flushPipeline() - of multiple: - return r.exec() - -proc assertListsIdentical(listA, listB: seq[string]) = - assert(listA.len == listB.len) - var i = 0 - for item in listA: - assert(item == listB[i]) - i = i + 1 - -when not defined(testing) and isMainModule: - when false: - var r = open() - - # Test with no pipelining - var listNormal = r.someTests(normal) - - # Test with pipelining enabled - var listPipelined = r.someTests(pipelined) - assertListsIdentical(listNormal, listPipelined) - - # Test with multi/exec() (automatic pipelining) - var listMulti = r.someTests(multiple) - assertListsIdentical(listNormal, listMulti) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index b61df6086..a446f85b4 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1210,22 +1210,21 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, ## If `s` does not begin with ``prefix`` and end with ``suffix`` a ## ValueError exception will be raised. result = newStringOfCap(s.len) - var i = 0 + var i = prefix.len if not s.startsWith(prefix): raise newException(ValueError, "String does not start with a prefix of: " & prefix) - inc(i) while true: if i == s.len-suffix.len: break case s[i] of '\\': case s[i+1]: of 'x': - inc i + inc i, 2 var c: int - i += parseutils.parseHex(s, c, i) + i += parseutils.parseHex(s, c, i, maxLen=2) result.add(chr(c)) - inc(i, 2) + dec i, 2 of '\\': result.add('\\') of '\'': @@ -1281,7 +1280,7 @@ proc editDistance*(a, b: string): int {.noSideEffect, # another special case: if len1 == 1: - for j in s..len2-1: + for j in s..s+len2-1: if a[s] == b[j]: return len2 - 1 return len2 @@ -1344,8 +1343,8 @@ proc editDistance*(a, b: string): int {.noSideEffect, # floating point formating: - -proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>", +when not defined(js): + proc c_sprintf(buf, frmt: cstring): cint {.header: "<stdio.h>", importc: "sprintf", varargs, noSideEffect.} type @@ -1370,29 +1369,44 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``biggestFloat`` type. ## ## If ``precision == 0``, it tries to format it nicely. - const floatFormatToChar: array[FloatFormatMode, char] = ['g', 'f', 'e'] - var - frmtstr {.noinit.}: array[0..5, char] - buf {.noinit.}: array[0..2500, char] - L: cint - frmtstr[0] = '%' - if precision > 0: - frmtstr[1] = '#' - frmtstr[2] = '.' - frmtstr[3] = '*' - frmtstr[4] = floatFormatToChar[format] - frmtstr[5] = '\0' - L = c_sprintf(buf, frmtstr, precision, f) + when defined(js): + var res: cstring + case format + of ffDefault: + {.emit: "`res` = `f`.toString();".} + of ffDecimal: + {.emit: "`res` = `f`.toFixed(`precision`);".} + of ffScientific: + {.emit: "`res` = `f`.toExponential(`precision`);".} + result = $res + for i in 0 ..< result.len: + # Depending on the locale either dot or comma is produced, + # but nothing else is possible: + if result[i] in {'.', ','}: result[i] = decimalsep else: - frmtstr[1] = floatFormatToChar[format] - frmtstr[2] = '\0' - L = c_sprintf(buf, frmtstr, f) - result = newString(L) - for i in 0 ..< L: - # Depending on the locale either dot or comma is produced, - # but nothing else is possible: - if buf[i] in {'.', ','}: result[i] = decimalsep - else: result[i] = buf[i] + const floatFormatToChar: array[FloatFormatMode, char] = ['g', 'f', 'e'] + var + frmtstr {.noinit.}: array[0..5, char] + buf {.noinit.}: array[0..2500, char] + L: cint + frmtstr[0] = '%' + if precision > 0: + frmtstr[1] = '#' + frmtstr[2] = '.' + frmtstr[3] = '*' + frmtstr[4] = floatFormatToChar[format] + frmtstr[5] = '\0' + L = c_sprintf(buf, frmtstr, precision, f) + else: + frmtstr[1] = floatFormatToChar[format] + frmtstr[2] = '\0' + L = c_sprintf(buf, frmtstr, f) + result = newString(L) + for i in 0 ..< L: + # Depending on the locale either dot or comma is produced, + # but nothing else is possible: + if buf[i] in {'.', ','}: result[i] = decimalsep + else: result[i] = buf[i] proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, precision: range[0..32] = 16; decimalSep = '.'): string {. @@ -1706,3 +1720,4 @@ when isMainModule: doAssert isUpper("ABC") doAssert(not isUpper("AAcc")) doAssert(not isUpper("A#$")) + doAssert(unescape(r"\x013", "", "") == "\x013") diff --git a/lib/pure/times.nim b/lib/pure/times.nim index a478b9d65..03745d54e 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -29,7 +29,7 @@ ## echo "epochTime() float value: ", epochTime() ## echo "getTime() float value: ", toSeconds(getTime()) ## echo "cpuTime() float value: ", cpuTime() -## echo "An hour from now : ", getLocalTime(getTime()) + initInterval(0,0,0,1) +## echo "An hour from now : ", getLocalTime(getTime()) + 1.hours ## echo "An hour from (UTC) now: ", getGmTime(getTime()) + initInterval(0,0,0,1) {.push debugger:off.} # the user does not want to trace a part @@ -171,11 +171,6 @@ type {.deprecated: [TMonth: Month, TWeekDay: WeekDay, TTime: Time, TTimeInterval: TimeInterval, TTimeInfo: TimeInfo].} -proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds - -proc `miliseconds=`*(t:var TimeInterval, milliseconds: int) {.deprecated.} = - t.milliseconds = milliseconds - proc getTime*(): Time {.tags: [TimeEffect], benign.} ## gets the current calendar time as a UNIX epoch value (number of seconds ## elapsed since 1970) with integer precission. Use epochTime for higher @@ -245,13 +240,59 @@ proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} proc initInterval*(milliseconds, seconds, minutes, hours, days, months, years: int = 0): TimeInterval = ## creates a new ``TimeInterval``. - result.milliseconds = milliseconds - result.seconds = seconds - result.minutes = minutes - result.hours = hours - result.days = days - result.months = months - result.years = years + ## + ## You can also use the convenience procedures called ``milliseconds``, + ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. + ## + ## Example: + ## + ## .. code-block:: nim + ## + ## let day = initInterval(hours=24) + ## let tomorrow = getTime() + day + ## echo(tomorrow) + var carryO = 0 + result.milliseconds = `mod`(milliseconds, 1000) + carryO = `div`(milliseconds, 1000) + result.seconds = `mod`(carryO + seconds, 60) + carryO = `div`(seconds, 60) + result.minutes = `mod`(carryO + minutes, 60) + carryO = `div`(minutes, 60) + result.hours = `mod`(carryO + hours, 24) + carryO = `div`(hours, 24) + result.days = carryO + days + carryO = 0 + result.months = `mod`(months, 12) + carryO = `div`(months, 12) + result.years = carryO + years + +proc `+`*(ti1, ti2: TimeInterval): TimeInterval = + ## Adds two ``TimeInterval`` objects together. + var carryO = 0 + result.milliseconds = `mod`(ti1.milliseconds + ti2.milliseconds, 1000) + carryO = `div`(ti1.milliseconds + ti2.milliseconds, 1000) + result.seconds = `mod`(carryO + ti1.seconds + ti2.seconds, 60) + carryO = `div`(ti1.seconds + ti2.seconds, 60) + result.minutes = `mod`(carryO + ti1.minutes + ti2.minutes, 60) + carryO = `div`(ti1.minutes + ti2.minutes, 60) + result.hours = `mod`(carryO + ti1.hours + ti2.hours, 24) + carryO = `div`(ti1.hours + ti2.hours, 24) + result.days = carryO + ti1.days + ti2.days + carryO = 0 + result.months = `mod`(ti1.months + ti2.months, 12) + carryO = `div`(ti1.months + ti2.months, 12) + result.years = carryO + ti1.years + ti2.years + +proc `-`*(ti1, ti2: TimeInterval): TimeInterval = + ## Subtracts TimeInterval ``ti1`` from ``ti2``. + result = ti1 + result.milliseconds -= ti2.milliseconds + result.seconds -= ti2.seconds + result.minutes -= ti2.minutes + result.hours -= ti2.hours + result.days -= ti2.days + result.months -= ti2.months + result.years -= ti2.years proc isLeapYear*(year: int): bool = ## returns true if ``year`` is a leap year @@ -288,13 +329,22 @@ proc toSeconds(a: TimeInfo, interval: TimeInterval): float = newinterv.months += interval.years * 12 var curMonth = anew.month - for mth in 1 .. newinterv.months: - result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) - if curMonth == mDec: - curMonth = mJan - anew.year.inc() - else: - curMonth.inc() + if newinterv.months < 0: # subtracting + for mth in countDown(-1 * newinterv.months, 1): + result -= float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) + if curMonth == mJan: + curMonth = mDec + anew.year.dec() + else: + curMonth.dec() + else: # adding + for mth in 1 .. newinterv.months: + result += float(getDaysInMonth(curMonth, anew.year) * 24 * 60 * 60) + if curMonth == mDec: + curMonth = mJan + anew.year.inc() + else: + curMonth.inc() result += float(newinterv.days * 24 * 60 * 60) result += float(newinterv.hours * 60 * 60) result += float(newinterv.minutes * 60) @@ -302,28 +352,39 @@ proc toSeconds(a: TimeInfo, interval: TimeInterval): float = result += newinterv.milliseconds / 1000 proc `+`*(a: TimeInfo, interval: TimeInterval): TimeInfo = - ## adds ``interval`` time. + ## adds ``interval`` time from TimeInfo ``a``. ## ## **Note:** This has been only briefly tested and it may not be ## very accurate. let t = toSeconds(timeInfoToTime(a)) let secs = toSeconds(a, interval) - #if a.tzname == "UTC": - # result = getGMTime(fromSeconds(t + secs)) - #else: result = getLocalTime(fromSeconds(t + secs)) proc `-`*(a: TimeInfo, interval: TimeInterval): TimeInfo = - ## subtracts ``interval`` time. + ## subtracts ``interval`` time from TimeInfo ``a``. ## ## **Note:** This has been only briefly tested, it is inaccurate especially ## when you subtract so much that you reach the Julian calendar. let t = toSeconds(timeInfoToTime(a)) - let secs = toSeconds(a, interval) - #if a.tzname == "UTC": - # result = getGMTime(fromSeconds(t - secs)) - #else: - result = getLocalTime(fromSeconds(t - secs)) + var intval: TimeInterval + intval.milliseconds = - interval.milliseconds + intval.seconds = - interval.seconds + intval.minutes = - interval.minutes + intval.hours = - interval.hours + intval.days = - interval.days + intval.months = - interval.months + intval.years = - interval.years + let secs = toSeconds(a, intval) + result = getLocalTime(fromSeconds(t + secs)) + +proc miliseconds*(t: TimeInterval): int {.deprecated.} = t.milliseconds + +proc `miliseconds=`*(t: var TimeInterval, milliseconds: int) {.deprecated.} = + ## An alias for a misspelled field in ``TimeInterval``. + ## + ## **Warning:** This should not be used! It will be removed in the next + ## version. + t.milliseconds = milliseconds when not defined(JS): proc epochTime*(): float {.rtl, extern: "nt$1", tags: [TimeEffect].} @@ -603,6 +664,69 @@ proc `$`*(m: Month): string = "November", "December"] return lookup[m] +proc milliseconds*(ms: int): TimeInterval {.inline.} = + ## TimeInterval of `ms` milliseconds + ## + ## Note: not all time functions have millisecond resolution + initInterval(`mod`(ms,1000), `div`(ms,1000)) + +proc seconds*(s: int): TimeInterval {.inline.} = + ## TimeInterval of `s` seconds + ## + ## ``echo getTime() + 5.second`` + initInterval(0,`mod`(s,60), `div`(s,60)) + +proc minutes*(m: int): TimeInterval {.inline.} = + ## TimeInterval of `m` minutes + ## + ## ``echo getTime() + 5.minutes`` + initInterval(0,0,`mod`(m,60), `div`(m,60)) + +proc hours*(h: int): TimeInterval {.inline.} = + ## TimeInterval of `h` hours + ## + ## ``echo getTime() + 2.hours`` + initInterval(0,0,0,`mod`(h,24),`div`(h,24)) + +proc days*(d: int): TimeInterval {.inline.} = + ## TimeInterval of `d` days + ## + ## ``echo getTime() + 2.days`` + initInterval(0,0,0,0,d) + +proc months*(m: int): TimeInterval {.inline.} = + ## TimeInterval of `m` months + ## + ## ``echo getTime() + 2.months`` + initInterval(0,0,0,0,0,`mod`(m,12),`div`(m,12)) + +proc years*(y: int): TimeInterval {.inline.} = + ## TimeInterval of `y` years + ## + ## ``echo getTime() + 2.years`` + initInterval(0,0,0,0,0,0,y) + +proc `+=`*(t: var Time, ti: TimeInterval) = + ## modifies `t` by adding the interval `ti` + t = timeInfoToTime(getLocalTime(t) + ti) + +proc `+`*(t: Time, ti: TimeInterval): Time = + ## adds the interval `ti` to Time `t` + ## by converting to localTime, adding the interval, and converting back + ## + ## ``echo getTime() + 1.day`` + result = timeInfoToTime(getLocalTime(t) + ti) + +proc `-=`*(t: var Time, ti: TimeInterval) = + ## modifies `t` by subtracting the interval `ti` + t = timeInfoToTime(getLocalTime(t) - ti) + +proc `-`*(t: Time, ti: TimeInterval): Time = + ## adds the interval `ti` to Time `t` + ## + ## ``echo getTime() - 1.day`` + result = timeInfoToTime(getLocalTime(t) - ti) + proc formatToken(info: TimeInfo, token: string, buf: var string) = ## Helper of the format proc to parse individual tokens. ## @@ -1192,112 +1316,10 @@ proc timeToTimeInterval*(t: Time): TimeInterval = # Milliseconds not available from Time when isMainModule: - # $ date --date='@2147483647' - # Tue 19 Jan 03:14:07 GMT 2038 - - var t = getGMTime(fromSeconds(2147483647)) - assert t.format("ddd dd MMM hh:mm:ss ZZZ yyyy") == "Tue 19 Jan 03:14:07 UTC 2038" - assert t.format("ddd ddMMMhh:mm:ssZZZyyyy") == "Tue 19Jan03:14:07UTC2038" - - assert t.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "19 19 Tue Tuesday 3 03 3 03 14 14 1 01 Jan January 7 07 A AM 8 38 038 2038 02038 0 00 00:00 UTC" - - assert t.format("yyyyMMddhhmmss") == "20380119031407" - - var t2 = getGMTime(fromSeconds(160070789)) # Mon 27 Jan 16:06:29 GMT 1975 - assert t2.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "27 27 Mon Monday 4 04 16 16 6 06 1 01 Jan January 29 29 P PM 5 75 975 1975 01975 0 00 00:00 UTC" - - when not defined(JS): - when sizeof(Time) == 8: - var t3 = getGMTime(fromSeconds(889067643645)) # Fri 7 Jun 19:20:45 BST 30143 - assert t3.format("d dd ddd dddd h hh H HH m mm M MM MMM MMMM s" & - " ss t tt y yy yyy yyyy yyyyy z zz zzz ZZZ") == - "7 07 Fri Friday 6 06 18 18 20 20 6 06 Jun June 45 45 P PM 3 43 143 0143 30143 0 00 00:00 UTC" - assert t3.format(":,[]()-/") == ":,[]()-/" - - var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 - assert t4.format("M MM MMM MMMM") == "10 10 Oct October" - - # Interval tests - assert((t4 - initInterval(years = 2)).format("yyyy") == "1995") - assert((t4 - initInterval(years = 7, minutes = 34, seconds = 24)).format("yyyy mm ss") == "1990 24 10") - - var s = "Tuesday at 09:04am on Dec 15, 2015" - var f = "dddd at hh:mmtt on MMM d, yyyy" - assert($s.parse(f) == "Tue Dec 15 09:04:00 2015") - # ANSIC = "Mon Jan _2 15:04:05 2006" - s = "Thu Jan 12 15:04:05 2006" - f = "ddd MMM dd HH:mm:ss yyyy" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # UnixDate = "Mon Jan _2 15:04:05 MST 2006" - s = "Thu Jan 12 15:04:05 MST 2006" - f = "ddd MMM dd HH:mm:ss ZZZ yyyy" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RubyDate = "Mon Jan 02 15:04:05 -0700 2006" - s = "Thu Jan 12 15:04:05 -07:00 2006" - f = "ddd MMM dd HH:mm:ss zzz yyyy" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC822 = "02 Jan 06 15:04 MST" - s = "12 Jan 16 15:04 MST" - f = "dd MMM yy HH:mm ZZZ" - assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") - # RFC822Z = "02 Jan 06 15:04 -0700" # RFC822 with numeric zone - s = "12 Jan 16 15:04 -07:00" - f = "dd MMM yy HH:mm zzz" - assert($s.parse(f) == "Tue Jan 12 15:04:00 2016") - # RFC850 = "Monday, 02-Jan-06 15:04:05 MST" - s = "Monday, 12-Jan-06 15:04:05 MST" - f = "dddd, dd-MMM-yy HH:mm:ss ZZZ" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" - s = "Thu, 12 Jan 2006 15:04:05 MST" - f = "ddd, dd MMM yyyy HH:mm:ss ZZZ" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" # RFC1123 with numeric zone - s = "Thu, 12 Jan 2006 15:04:05 -07:00" - f = "ddd, dd MMM yyyy HH:mm:ss zzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC3339 = "2006-01-02T15:04:05Z07:00" - s = "2006-01-12T15:04:05Z-07:00" - f = "yyyy-MM-ddTHH:mm:ssZzzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - f = "yyyy-MM-dd'T'HH:mm:ss'Z'zzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" - s = "2006-01-12T15:04:05.999999999Z-07:00" - f = "yyyy-MM-ddTHH:mm:ss.999999999Zzzz" - assert($s.parse(f) == "Thu Jan 12 15:04:05 2006") - # Kitchen = "3:04PM" - s = "3:04PM" - f = "h:mmtt" - assert "15:04:00" in $s.parse(f) - when not defined(testing): - echo "Kitchen: " & $s.parse(f) - var ti = timeToTimeInfo(getTime()) - echo "Todays date after decoding: ", ti - var tint = timeToTimeInterval(getTime()) - echo "Todays date after decoding to interval: ", tint - # checking dayOfWeek matches known days - assert getDayOfWeek(21, 9, 1900) == dFri - assert getDayOfWeek(1, 1, 1970) == dThu - assert getDayOfWeek(21, 9, 1970) == dMon - assert getDayOfWeek(1, 1, 2000) == dSat - assert getDayOfWeek(1, 1, 2021) == dFri - # Julian tests - assert getDayOfWeekJulian(21, 9, 1900) == dFri - assert getDayOfWeekJulian(21, 9, 1970) == dMon - assert getDayOfWeekJulian(1, 1, 2000) == dSat - assert getDayOfWeekJulian(1, 1, 2021) == dFri - - # toSeconds tests with GM and Local timezones - #var t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 - var t4L = getLocalTime(fromSeconds(876124714)) - assert toSeconds(timeInfoToTime(t4L)) == 876124714 # fromSeconds is effectively "localTime" - assert toSeconds(timeInfoToTime(t4L)) + t4L.timezone.float == toSeconds(timeInfoToTime(t4)) - + # this is testing non-exported function + var + t4 = getGMTime(fromSeconds(876124714)) # Mon 6 Oct 08:58:34 BST 1997 + t4L = getLocalTime(fromSeconds(876124714)) assert toSeconds(t4, initInterval(seconds=0)) == 0.0 assert toSeconds(t4L, initInterval(milliseconds=1)) == toSeconds(t4, initInterval(milliseconds=1)) assert toSeconds(t4L, initInterval(seconds=1)) == toSeconds(t4, initInterval(seconds=1)) @@ -1307,12 +1329,5 @@ when isMainModule: assert toSeconds(t4L, initInterval(months=1)) == toSeconds(t4, initInterval(months=1)) assert toSeconds(t4L, initInterval(years=1)) == toSeconds(t4, initInterval(years=1)) - # adding intervals - var - a1L = toSeconds(timeInfoToTime(t4L + initInterval(hours = 1))) + t4L.timezone.float - a1G = toSeconds(timeInfoToTime(t4)) + 60.0 * 60.0 - assert a1L == a1G - # subtracting intervals - a1L = toSeconds(timeInfoToTime(t4L - initInterval(hours = 1))) + t4L.timezone.float - a1G = toSeconds(timeInfoToTime(t4)) - (60.0 * 60.0) - assert a1L == a1G + # Further tests are in tests/stdlib/ttime.nim + # koch test c stdlib diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index b059a7315..45f52eb7f 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -114,6 +114,7 @@ proc validateUtf8*(s: string): int = if ord(s[i]) <=% 127: inc(i) elif ord(s[i]) shr 5 == 0b110: + if ord(s[i]) < 0xc2: return i # Catch overlong ascii representations. if i+1 < L and ord(s[i+1]) shr 6 == 0b10: inc(i, 2) else: return i elif ord(s[i]) shr 4 == 0b1110: diff --git a/lib/pure/xmlparser.nim b/lib/pure/xmlparser.nim index 56b122000..2a2c3e1dd 100644 --- a/lib/pure/xmlparser.nim +++ b/lib/pure/xmlparser.nim @@ -96,7 +96,7 @@ proc parse(x: var XmlParser, errors: var seq[string]): XmlNode = next(x) of xmlEntity: ## &entity; - errors.add(errorMsg(x, "unknown entity: " & x.entityName)) + result = newEntity(x.entityName) next(x) of xmlEof: discard @@ -143,17 +143,24 @@ proc loadXml*(path: string): XmlNode = result = loadXml(path, errors) if errors.len > 0: raiseInvalidXml(errors) -when not defined(testing) and isMainModule: - import os +when isMainModule: + when not defined(testing): + import os - var errors: seq[string] = @[] - var x = loadXml(paramStr(1), errors) - for e in items(errors): echo e + var errors: seq[string] = @[] + var x = loadXml(paramStr(1), errors) + for e in items(errors): echo e - var f: File - if open(f, "xmltest.txt", fmWrite): - f.write($x) - f.close() + var f: File + if open(f, "xmltest.txt", fmWrite): + f.write($x) + f.close() + else: + quit("cannot write test.txt") else: - quit("cannot write test.txt") + block: # correctly parse ../../tests/testdata/doc1.xml + let filePath = "tests/testdata/doc1.xml" + var errors: seq[string] = @[] + var xml = loadXml(filePath, errors) + assert(errors.len == 0, "The file tests/testdata/doc1.xml should be parsed without errors.") |