diff options
Diffstat (limited to 'lib/impure')
-rwxr-xr-x | lib/impure/dialogs.nim | 234 | ||||
-rw-r--r-- | lib/impure/nre.nim | 751 | ||||
-rw-r--r-- | lib/impure/nre/.gitignore | 9 | ||||
-rw-r--r-- | lib/impure/nre/private/util.nim | 51 | ||||
-rw-r--r-- | lib/impure/rdstdin.nim | 74 | ||||
-rw-r--r-- | lib/impure/re.nim | 611 | ||||
-rwxr-xr-x | lib/impure/web.nim | 57 | ||||
-rwxr-xr-x | lib/impure/zipfiles.nim | 144 |
8 files changed, 1496 insertions, 435 deletions
diff --git a/lib/impure/dialogs.nim b/lib/impure/dialogs.nim deleted file mode 100755 index cf81a3d29..000000000 --- a/lib/impure/dialogs.nim +++ /dev/null @@ -1,234 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2009 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - - -## This module implements portable dialogs for Nimrod; the implementation -## builds on the GTK interface. On Windows, native dialogs are shown if -## appropriate. - -import - glib2, gtk2 - -when defined(Windows): - import windows, ShellAPI, os - -type - PWindow* = PGtkWindow ## A shortcut for a GTK window. - -proc info*(window: PWindow, msg: string) = - ## Shows an information message to the user. The process waits until the - ## user presses the OK button. - when defined(Windows): - discard MessageBoxA(0, msg, "Information", MB_OK or MB_ICONINFORMATION) - else: - var dialog = GTK_DIALOG(gtk_message_dialog_new(window, - GTK_DIALOG_MODAL or GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", cstring(msg))) - gtk_window_set_title(dialog, "Information") - discard gtk_dialog_run(dialog) - gtk_widget_destroy(dialog) - -proc warning*(window: PWindow, msg: string) = - ## Shows a warning message to the user. The process waits until the user - ## presses the OK button. - when defined(Windows): - discard MessageBoxA(0, msg, "Warning", MB_OK or MB_ICONWARNING) - else: - var dialog = GTK_DIALOG(gtk_message_dialog_new(window, - GTK_DIALOG_MODAL or GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "%s", cstring(msg))) - gtk_window_set_title(dialog, "Warning") - discard gtk_dialog_run(dialog) - gtk_widget_destroy(dialog) - -proc error*(window: PWindow, msg: string) = - ## Shows an error message to the user. The process waits until the user - ## presses the OK button. - when defined(Windows): - discard MessageBoxA(0, msg, "Error", MB_OK or MB_ICONERROR) - else: - var dialog = GTK_DIALOG(gtk_message_dialog_new(window, - GTK_DIALOG_MODAL or GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", cstring(msg))) - gtk_window_set_title(dialog, "Error") - discard gtk_dialog_run(dialog) - gtk_widget_destroy(dialog) - - -proc ChooseFileToOpen*(window: PWindow, root: string = ""): string = - ## Opens a dialog that requests a filename from the user. Returns "" - ## if the user closed the dialog without selecting a file. On Windows, - ## the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - opf: TOPENFILENAME - buf: array [0..2047, char] - opf.lStructSize = sizeof(opf) - if root.len > 0: - opf.lpstrInitialDir = root - opf.lpstrFilter = "All Files\0*.*\0\0" - opf.flags = OFN_FILEMUSTEXIST - opf.lpstrFile = buf - opf.nMaxFile = sizeof(buf) - var res = GetOpenFileName(addr(opf)) - if res != 0: - result = $buf - else: - result = "" - else: - var - chooser: PGtkDialog - chooser = GTK_DIALOG(gtk_file_chooser_dialog_new("Open File", window, - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_OK, nil)) - if root.len > 0: - discard gtk_file_chooser_set_current_folder(chooser, root) - if gtk_dialog_run(chooser) == cint(GTK_RESPONSE_OK): - var x = gtk_file_chooser_get_filename(chooser) - result = $x - g_free(x) - else: - result = "" - gtk_widget_destroy(chooser) - -proc ChooseFilesToOpen*(window: PWindow, root: string = ""): seq[string] = - ## Opens a dialog that requests filenames from the user. Returns ``@[]`` - ## if the user closed the dialog without selecting a file. On Windows, - ## the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - opf: TOPENFILENAME - buf: array [0..2047*4, char] - opf.lStructSize = sizeof(opf) - if root.len > 0: - opf.lpstrInitialDir = root - opf.lpstrFilter = "All Files\0*.*\0\0" - opf.flags = OFN_FILEMUSTEXIST or OFN_ALLOWMULTISELECT or OFN_EXPLORER - opf.lpstrFile = buf - opf.nMaxFile = sizeof(buf) - var res = GetOpenFileName(addr(opf)) - result = @[] - if res != 0: - # parsing the result is horrible: - var - i = 0 - s: string - path = "" - while buf[i] != '\0': - add(path, buf[i]) - inc(i) - inc(i) - if buf[i] != '\0': - while true: - s = "" - while buf[i] != '\0': - add(s, buf[i]) - inc(i) - add(result, s) - inc(i) - if buf[i] == '\0': break - for i in 0..result.len-1: result[i] = os.joinPath(path, result[i]) - else: - var - chooser: PGtkDialog - chooser = GTK_DIALOG(gtk_file_chooser_dialog_new("Open Files", window, - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_OK, nil)) - if root.len > 0: - discard gtk_file_chooser_set_current_folder(chooser, root) - gtk_file_chooser_set_select_multiple(chooser, true) - result = @[] - if gtk_dialog_run(chooser) == cint(GTK_RESPONSE_OK): - var L = gtk_file_chooser_get_filenames(chooser) - var it = L - while it != nil: - add(result, $cast[cstring](it.data)) - g_free(it.data) - it = it.next - g_slist_free(L) - gtk_widget_destroy(chooser) - - -proc ChooseFileToSave*(window: PWindow, root: string = ""): string = - ## Opens a dialog that requests a filename to save to from the user. - ## Returns "" if the user closed the dialog without selecting a file. - ## On Windows, the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - opf: TOPENFILENAME - buf: array [0..2047, char] - opf.lStructSize = sizeof(opf) - if root.len > 0: - opf.lpstrInitialDir = root - opf.lpstrFilter = "All Files\0*.*\0\0" - opf.flags = OFN_OVERWRITEPROMPT - opf.lpstrFile = buf - opf.nMaxFile = sizeof(buf) - var res = GetSaveFileName(addr(opf)) - if res != 0: - result = $buf - else: - result = "" - else: - var - chooser: PGtkDialog - chooser = GTK_DIALOG(gtk_file_chooser_dialog_new("Save File", window, - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_OK, nil)) - if root.len > 0: - discard gtk_file_chooser_set_current_folder(chooser, root) - gtk_file_chooser_set_do_overwrite_confirmation(chooser, true) - if gtk_dialog_run(chooser) == cint(GTK_RESPONSE_OK): - var x = gtk_file_chooser_get_filename(chooser) - result = $x - g_free(x) - else: - result = "" - gtk_widget_destroy(chooser) - - -proc ChooseDir*(window: PWindow, root: string = ""): string = - ## Opens a dialog that requests a directory from the user. - ## Returns "" if the user closed the dialog without selecting a directory. - ## On Windows, the native dialog is used, else the GTK dialog is used. - when defined(Windows): - var - lpItemID: PItemIDList - BrowseInfo: TBrowseInfo - DisplayName: array [0..MAX_PATH, char] - TempPath: array [0..MAX_PATH, char] - Result = "" - #BrowseInfo.hwndOwner = Application.Handle - BrowseInfo.pszDisplayName = DisplayName - BrowseInfo.ulFlags = 1 #BIF_RETURNONLYFSDIRS - lpItemID = SHBrowseForFolder(cast[LPBrowseInfo](addr(BrowseInfo))) - if lpItemId != nil: - discard SHGetPathFromIDList(lpItemID, TempPath) - Result = $TempPath - discard GlobalFreePtr(lpItemID) - else: - var - chooser: PGtkDialog - chooser = GTK_DIALOG(gtk_file_chooser_dialog_new("Select Directory", window, - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_OK, nil)) - if root.len > 0: - discard gtk_file_chooser_set_current_folder(chooser, root) - if gtk_dialog_run(chooser) == cint(GTK_RESPONSE_OK): - var x = gtk_file_chooser_get_filename(chooser) - result = $x - g_free(x) - else: - result = "" - gtk_widget_destroy(chooser) - diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim new file mode 100644 index 000000000..39d238055 --- /dev/null +++ b/lib/impure/nre.nim @@ -0,0 +1,751 @@ +# +# Nim's Runtime Library +# (c) Copyright 2015 Nim Contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +when defined(js): + {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".} + +## What is NRE? +## ============ +## +## A regular expression library for Nim using PCRE to do the hard work. +## +## For documentation on how to write patterns, there exists `the official PCRE +## pattern documentation +## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_. You can also +## search the internet for a wide variety of third-party documentation and +## tools. +## +## .. warning:: If you love `sequtils.toSeq` we have bad news for you. This +## library doesn't work with it due to documented compiler limitations. As +## a workaround, use this: +runnableExamples: + # either `import std/nre except toSeq` or fully qualify `sequtils.toSeq`: + import std/sequtils + iterator iota(n: int): int = + for i in 0..<n: yield i + assert sequtils.toSeq(iota(3)) == @[0, 1, 2] +## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre) +## and [regex](https://github.com/nitely/nim-regex). +## Licencing +## --------- +## +## PCRE has `some additional terms`_ that you must agree to in order to use +## this module. +## +## .. _`some additional terms`: http://pcre.sourceforge.net/license.txt +runnableExamples: + import std/sugar + let vowels = re"[aeoui]" + let bounds = collect: + for match in "moiga".findIter(vowels): match.matchBounds + assert bounds == @[1 .. 1, 2 .. 2, 4 .. 4] + from std/sequtils import toSeq + let s = sequtils.toSeq("moiga".findIter(vowels)) + # fully qualified to avoid confusion with nre.toSeq + assert s.len == 3 + + let firstVowel = "foo".find(vowels) + let hasVowel = firstVowel.isSome() + assert hasVowel + let matchBounds = firstVowel.get().captureBounds[-1] + assert matchBounds.a == 1 + + # as with module `re`, unless specified otherwise, `start` parameter in each + # proc indicates where the scan starts, but outputs are relative to the start + # of the input string, not to `start`: + assert find("uxabc", re"(?<=x|y)ab", start = 1).get.captures[-1] == "ab" + assert find("uxabc", re"ab", start = 3).isNone + +from std/pcre import nil +import nre/private/util +import std/tables +from std/strutils import `%` +import std/options +from std/unicode import runeLenAt + +when defined(nimPreviewSlimSystem): + import std/assertions + +export options + +type + Regex* = ref object + ## Represents the pattern that things are matched against, constructed with + ## `re(string)`. Examples: `re"foo"`, `re(r"(*ANYCRLF)(?x)foo # + ## comment".` + ## + ## `pattern: string` + ## : the string that was used to create the pattern. For details on how + ## to write a pattern, please see `the official PCRE pattern + ## documentation. + ## <https://www.pcre.org/original/doc/html/pcrepattern.html>`_ + ## + ## `captureCount: int` + ## : the number of captures that the pattern has. + ## + ## `captureNameId: Table[string, int]` + ## : a table from the capture names to their numeric id. + ## + ## + ## Options + ## ....... + ## + ## The following options may appear anywhere in the pattern, and they affect + ## the rest of it. + ## + ## - `(?i)` - case insensitive + ## - `(?m)` - multi-line: `^` and `$` match the beginning and end of + ## lines, not of the subject string + ## - `(?s)` - `.` also matches newline (*dotall*) + ## - `(?U)` - expressions are not greedy by default. `?` can be added + ## to a qualifier to make it greedy + ## - `(?x)` - whitespace and comments (`#`) are ignored (*extended*) + ## - `(?X)` - character escapes without special meaning (`\w` vs. + ## `\a`) are errors (*extra*) + ## + ## One or a combination of these options may appear only at the beginning + ## of the pattern: + ## + ## - `(*UTF8)` - treat both the pattern and subject as UTF-8 + ## - `(*UCP)` - Unicode character properties; `\w` matches `я` + ## - `(*U)` - a combination of the two options above + ## - `(*FIRSTLINE*)` - fails if there is not a match on the first line + ## - `(*NO_AUTO_CAPTURE)` - turn off auto-capture for groups; + ## `(?<name>...)` can be used to capture + ## - `(*CR)` - newlines are separated by `\r` + ## - `(*LF)` - newlines are separated by `\n` (UNIX default) + ## - `(*CRLF)` - newlines are separated by `\r\n` (Windows default) + ## - `(*ANYCRLF)` - newlines are separated by any of the above + ## - `(*ANY)` - newlines are separated by any of the above and Unicode + ## newlines: + ## + ## single characters VT (vertical tab, U+000B), FF (form feed, U+000C), + ## NEL (next line, U+0085), LS (line separator, U+2028), and PS + ## (paragraph separator, U+2029). For the 8-bit library, the last two + ## are recognized only in UTF-8 mode. + ## — man pcre + ## + ## - `(*JAVASCRIPT_COMPAT)` - JavaScript compatibility + ## - `(*NO_STUDY)` - turn off studying; study is enabled by default + ## + ## For more details on the leading option groups, see the `Option + ## Setting <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#OPTION_SETTING>`_ + ## and the `Newline + ## Convention <http://man7.org/linux/man-pages/man3/pcresyntax.3.html#NEWLINE_CONVENTION>`_ + ## sections of the `PCRE syntax + ## manual <http://man7.org/linux/man-pages/man3/pcresyntax.3.html>`_. + ## + ## Some of these options are not part of PCRE and are converted by nre + ## into PCRE flags. These include `NEVER_UTF`, `ANCHORED`, + ## `DOLLAR_ENDONLY`, `FIRSTLINE`, `NO_AUTO_CAPTURE`, + ## `JAVASCRIPT_COMPAT`, `U`, `NO_STUDY`. In other PCRE wrappers, you + ## will need to pass these as separate flags to PCRE. + pattern*: string + pcreObj: ptr pcre.Pcre ## not nil + pcreExtra: ptr pcre.ExtraData ## nil + + captureNameToId: Table[string, int] + + RegexMatch* = object + ## Usually seen as Option[RegexMatch], it represents the result of an + ## execution. On failure, it is none, on success, it is some. + ## + ## `pattern: Regex` + ## : the pattern that is being matched + ## + ## `str: string` + ## : the string that was matched against + ## + ## `captures[]: string` + ## : the string value of whatever was captured at that id. If the value + ## is invalid, then behavior is undefined. If the id is `-1`, then + ## the whole match is returned. If the given capture was not matched, + ## `nil` is returned. See examples for `match`. + ## + ## `captureBounds[]: HSlice[int, int]` + ## : gets the bounds of the given capture according to the same rules as + ## the above. If the capture is not filled, then `None` is returned. + ## The bounds are both inclusive. See examples for `match`. + ## + ## `match: string` + ## : the full text of the match. + ## + ## `matchBounds: HSlice[int, int]` + ## : the bounds of the match, as in `captureBounds[]` + ## + ## `(captureBounds|captures).toTable` + ## : returns a table with each named capture as a key. + ## + ## `(captureBounds|captures).toSeq` + ## : returns all the captures by their number. + ## + ## `$: string` + ## : same as `match` + pattern*: Regex ## The regex doing the matching. + ## Not nil. + str*: string ## The string that was matched against. + pcreMatchBounds: seq[HSlice[cint, cint]] ## First item is the bounds of the match + ## Other items are the captures + ## `a` is inclusive start, `b` is exclusive end + + Captures* = distinct RegexMatch + CaptureBounds* = distinct RegexMatch + + RegexError* = ref object of CatchableError + + RegexInternalError* = ref object of RegexError + ## Internal error in the module, this probably means that there is a bug + + InvalidUnicodeError* = ref object of RegexError + ## Thrown when matching fails due to invalid unicode in strings + pos*: int ## the location of the invalid unicode in bytes + + SyntaxError* = ref object of RegexError + ## Thrown when there is a syntax error in the + ## regular expression string passed in + pos*: int ## the location of the syntax error in bytes + pattern*: string ## the pattern that caused the problem + + StudyError* = ref object of RegexError + ## Thrown when studying the regular expression fails + ## for whatever reason. The message contains the error + ## code. + +proc destroyRegex(pattern: Regex) = + `=destroy`(pattern.pattern) + pcre.free_substring(cast[cstring](pattern.pcreObj)) + if pattern.pcreExtra != nil: + pcre.free_study(pattern.pcreExtra) + `=destroy`(pattern.captureNameToId) + +proc getinfo[T](pattern: Regex, opt: cint): T = + let retcode = pcre.fullinfo(pattern.pcreObj, pattern.pcreExtra, opt, addr result) + + if retcode < 0: + # XXX Error message that doesn't expose implementation details + raise newException(FieldDefect, "Invalid getinfo for $1, errno $2" % [$opt, $retcode]) + +proc getNameToNumberTable(pattern: Regex): Table[string, int] = + let entryCount = getinfo[cint](pattern, pcre.INFO_NAMECOUNT) + let entrySize = getinfo[cint](pattern, pcre.INFO_NAMEENTRYSIZE) + let table = cast[ptr UncheckedArray[uint8]]( + getinfo[int](pattern, pcre.INFO_NAMETABLE)) + + result = initTable[string, int]() + + for i in 0 ..< entryCount: + let pos = i * entrySize + let num = (int(table[pos]) shl 8) or int(table[pos + 1]) - 1 + var name = "" + + var idx = 2 + while table[pos + idx] != 0: + name.add(char(table[pos + idx])) + idx += 1 + + result[name] = num + +proc initRegex(pattern: string, flags: int, study = true): Regex = + new(result, destroyRegex) + result.pattern = pattern + + var errorMsg: cstring + var errOffset: cint + + result.pcreObj = pcre.compile(cstring(pattern), + # better hope int is at least 4 bytes.. + cint(flags), addr errorMsg, + addr errOffset, nil) + if result.pcreObj == nil: + # failed to compile + raise SyntaxError(msg: $errorMsg, pos: errOffset, pattern: pattern) + + if study: + var options: cint = 0 + var hasJit: cint + if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0: + if hasJit == 1'i32: + options = pcre.STUDY_JIT_COMPILE + result.pcreExtra = pcre.study(result.pcreObj, options, addr errorMsg) + if errorMsg != nil: + raise StudyError(msg: $errorMsg) + + result.captureNameToId = result.getNameToNumberTable() + +proc captureCount*(pattern: Regex): int = + return getinfo[cint](pattern, pcre.INFO_CAPTURECOUNT) + +proc captureNameId*(pattern: Regex): Table[string, int] = + return pattern.captureNameToId + +proc matchesCrLf(pattern: Regex): bool = + let flags = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS)) + let newlineFlags = flags and (pcre.NEWLINE_CRLF or + pcre.NEWLINE_ANY or + pcre.NEWLINE_ANYCRLF) + if newlineFlags > 0u32: + return true + + # get flags from build config + var confFlags: cint + if pcre.config(pcre.CONFIG_NEWLINE, addr confFlags) != 0: + assert(false, "CONFIG_NEWLINE apparently got screwed up") + + case confFlags + of 13: return false + of 10: return false + of (13 shl 8) or 10: return true + of -2: return true + of -1: return true + else: return false + + +func captureBounds*(pattern: RegexMatch): CaptureBounds = return CaptureBounds(pattern) + +func captures*(pattern: RegexMatch): Captures = return Captures(pattern) + +func contains*(pattern: CaptureBounds, i: int): bool = + let pattern = RegexMatch(pattern) + pattern.pcreMatchBounds[i + 1].a != -1 + +func contains*(pattern: Captures, i: int): bool = + i in CaptureBounds(pattern) + +func `[]`*(pattern: CaptureBounds, i: int): HSlice[int, int] = + let pattern = RegexMatch(pattern) + if not (i in pattern.captureBounds): + raise newException(IndexDefect, "Group '" & $i & "' was not captured") + + let bounds = pattern.pcreMatchBounds[i + 1] + int(bounds.a)..int(bounds.b-1) + +func `[]`*(pattern: Captures, i: int): string = + let pattern = RegexMatch(pattern) + let bounds = pattern.captureBounds[i] + + pattern.str.substr(bounds.a, bounds.b) + +func match*(pattern: RegexMatch): string = + return pattern.captures[-1] + +func matchBounds*(pattern: RegexMatch): HSlice[int, int] = + return pattern.captureBounds[-1] + +func contains*(pattern: CaptureBounds, name: string): bool = + let pattern = RegexMatch(pattern) + let nameToId = pattern.pattern.captureNameToId + if not (name in nameToId): + return false + nameToId[name] in pattern.captureBounds + +func contains*(pattern: Captures, name: string): bool = + name in CaptureBounds(pattern) + +func checkNamedCaptured(pattern: RegexMatch, name: string) = + if not (name in pattern.captureBounds): + raise newException(KeyError, "Group '" & name & "' was not captured") + +func `[]`*(pattern: CaptureBounds, name: string): HSlice[int, int] = + let pattern = RegexMatch(pattern) + checkNamedCaptured(pattern, name) + {.noSideEffect.}: + result = pattern.captureBounds[pattern.pattern.captureNameToId[name]] + +func `[]`*(pattern: Captures, name: string): string = + let pattern = RegexMatch(pattern) + checkNamedCaptured(pattern, name) + {.noSideEffect.}: + result = pattern.captures[pattern.pattern.captureNameToId[name]] + +template toTableImpl() {.dirty.} = + for key in RegexMatch(pattern).pattern.captureNameId.keys: + if key in pattern: + result[key] = pattern[key] + +func toTable*(pattern: Captures): Table[string, string] = + result = initTable[string, string]() + toTableImpl() + +func toTable*(pattern: CaptureBounds): Table[string, HSlice[int, int]] = + result = initTable[string, HSlice[int, int]]() + toTableImpl() + +template itemsImpl() {.dirty.} = + for i in 0 ..< RegexMatch(pattern).pattern.captureCount: + # done in this roundabout way to avoid multiple yields (potential code + # bloat) + let nextYieldVal = if i in pattern: + some(pattern[i]) + else: + default + + yield nextYieldVal + +iterator items*(pattern: CaptureBounds, + default = none(HSlice[int, int])): Option[HSlice[int, int]] = + itemsImpl() + +iterator items*(pattern: Captures, + default: Option[string] = none(string)): Option[string] = + itemsImpl() + +proc toSeq*(pattern: CaptureBounds, + default = none(HSlice[int, int])): seq[Option[HSlice[int, int]]] = + result = @[] + for it in pattern.items(default): result.add it + +proc toSeq*(pattern: Captures, + default: Option[string] = none(string)): seq[Option[string]] = + result = @[] + for it in pattern.items(default): result.add it + +proc `$`*(pattern: RegexMatch): string = + return pattern.captures[-1] + +proc `==`*(a, b: Regex): bool = + if not a.isNil and not b.isNil: + return a.pattern == b.pattern and + a.pcreObj == b.pcreObj and + a.pcreExtra == b.pcreExtra + else: + return system.`==`(a, b) + +proc `==`*(a, b: RegexMatch): bool = + return a.pattern == b.pattern and + a.str == b.str + +const PcreOptions = { + "NEVER_UTF": pcre.NEVER_UTF, + "ANCHORED": pcre.ANCHORED, + "DOLLAR_ENDONLY": pcre.DOLLAR_ENDONLY, + "FIRSTLINE": pcre.FIRSTLINE, + "NO_AUTO_CAPTURE": pcre.NO_AUTO_CAPTURE, + "JAVASCRIPT_COMPAT": pcre.JAVASCRIPT_COMPAT, + "U": pcre.UTF8 or pcre.UCP +}.toTable + +# Options that are supported inside regular expressions themselves +const SkipOptions = [ + "LIMIT_MATCH=", "LIMIT_RECURSION=", "NO_AUTO_POSSESS", "NO_START_OPT", + "UTF8", "UTF16", "UTF32", "UTF", "UCP", + "CR", "LF", "CRLF", "ANYCRLF", "ANY", "BSR_ANYCRLF", "BSR_UNICODE" +] + +proc extractOptions(pattern: string): tuple[pattern: string, flags: int, study: bool] = + result = ("", 0, true) + + var optionStart = 0 + var equals = false + for i, c in pattern: + if optionStart == i: + if c != '(': + break + optionStart = i + + elif optionStart == i-1: + if c != '*': + break + + elif c == ')': + let name = pattern[optionStart+2 .. i-1] + if equals or name in SkipOptions: + result.pattern.add pattern[optionStart .. i] + elif PcreOptions.hasKey name: + result.flags = result.flags or PcreOptions[name] + elif name == "NO_STUDY": + result.study = false + else: + break + optionStart = i+1 + equals = false + + elif not equals: + if c == '=': + equals = true + if pattern[optionStart+2 .. i] notin SkipOptions: + break + elif c notin {'A'..'Z', '0'..'9', '_'}: + break + + result.pattern.add pattern[optionStart .. pattern.high] + +proc re*(pattern: string): Regex = + let (pattern, flags, study) = extractOptions(pattern) + initRegex(pattern, flags, study) + +proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Option[RegexMatch] = + var myResult = RegexMatch(pattern: pattern, str: str) + # See PCRE man pages. + # 2x capture count to make room for start-end pairs + # 1x capture count as slack space for PCRE + let vecsize = (pattern.captureCount() + 1) * 3 + # div 2 because each element is 2 cints long + # plus 1 because we need the ceiling, not the floor + myResult.pcreMatchBounds = newSeq[HSlice[cint, cint]]((vecsize + 1) div 2) + myResult.pcreMatchBounds.setLen(vecsize div 3) + + let strlen = if endpos == int.high: str.len else: endpos+1 + doAssert(strlen <= str.len) # don't want buffer overflows + + let execRet = pcre.exec(pattern.pcreObj, + pattern.pcreExtra, + cstring(str), + cint(strlen), + cint(start), + cint(flags), + cast[ptr cint](addr myResult.pcreMatchBounds[0]), + cint(vecsize)) + if execRet >= 0: + return some(myResult) + + case execRet: + of pcre.ERROR_NOMATCH: + return none(RegexMatch) + of pcre.ERROR_NULL: + raise newException(AccessViolationDefect, "Expected non-null parameters") + of pcre.ERROR_BADOPTION: + raise RegexInternalError(msg: "Unknown pattern flag. Either a bug or " & + "outdated PCRE.") + of pcre.ERROR_BADUTF8, pcre.ERROR_SHORTUTF8, pcre.ERROR_BADUTF8_OFFSET: + raise InvalidUnicodeError(msg: "Invalid unicode byte sequence", + pos: myResult.pcreMatchBounds[0].a) + else: + raise RegexInternalError(msg: "Unknown internal error: " & $execRet) + +proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = + ## Like `find(...)<#find,string,Regex,int>`_, but anchored to the start of the + ## string. + runnableExamples: + assert "foo".match(re"f").isSome + assert "foo".match(re"o").isNone + + assert "abc".match(re"(\w)").get.captures[0] == "a" + assert "abc".match(re"(?<letter>\w)").get.captures["letter"] == "a" + assert "abc".match(re"(\w)\w").get.captures[-1] == "ab" + + assert "abc".match(re"(\w)").get.captureBounds[0] == 0 .. 0 + assert 0 in "abc".match(re"(\w)").get.captureBounds + assert "abc".match(re"").get.captureBounds[-1] == 0 .. -1 + assert "abc".match(re"abc").get.captureBounds[-1] == 0 .. 2 + return str.matchImpl(pattern, start, endpos, pcre.ANCHORED) + +iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch = + ## Works the same as `find(...)<#find,string,Regex,int>`_, but finds every + ## non-overlapping match: + runnableExamples: + import std/sugar + assert collect(for a in "2222".findIter(re"22"): a.match) == @["22", "22"] + # not @["22", "22", "22"] + ## Arguments are the same as `find(...)<#find,string,Regex,int>`_ + ## + ## Variants: + ## + ## - `proc findAll(...)` returns a `seq[string]` + # see pcredemo for explanation => https://www.pcre.org/original/doc/html/pcredemo.html + let matchesCrLf = pattern.matchesCrLf() + let unicode = uint32(getinfo[culong](pattern, pcre.INFO_OPTIONS) and + pcre.UTF8) > 0u32 + let strlen = if endpos == int.high: str.len else: endpos+1 + var offset = start + var match: Option[RegexMatch] + var neverMatched = true + + while true: + var flags = 0 + if match.isSome and + match.get.matchBounds.a > match.get.matchBounds.b: + # 0-len match + flags = pcre.NOTEMPTY_ATSTART + match = str.matchImpl(pattern, offset, endpos, flags) + + if match.isNone: + # either the end of the input or the string + # cannot be split here - we also need to bail + # if we've never matched and we've already tried to... + if flags == 0 or offset >= strlen or neverMatched: # All matches found + break + + if matchesCrLf and offset < (str.len - 1) and + str[offset] == '\r' and str[offset + 1] == '\L': + # if PCRE treats CrLf as newline, skip both at the same time + offset += 2 + elif unicode: + # XXX what about invalid unicode? + offset += str.runeLenAt(offset) + assert(offset <= strlen) + else: + offset += 1 + else: + neverMatched = false + offset = match.get.matchBounds.b + 1 + yield match.get + +proc find*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = + ## Finds the given pattern in the string between the end and start + ## positions. + ## + ## `start` + ## : The start point at which to start matching. `|abc` is `0`; + ## `a|bc` is `1` + ## + ## `endpos` + ## : The maximum index for a match; `int.high` means the end of the + ## string, otherwise it’s an inclusive upper bound. + return str.matchImpl(pattern, start, endpos, 0) + +proc findAll*(str: string, pattern: Regex, start = 0, endpos = int.high): seq[string] = + result = @[] + for match in str.findIter(pattern, start, endpos): + result.add(match.match) + +proc contains*(str: string, pattern: Regex, start = 0, endpos = int.high): bool = + ## Determine if the string contains the given pattern between the end and + ## start positions: + ## This function is equivalent to `isSome(str.find(pattern, start, endpos))`. + runnableExamples: + assert "abc".contains(re"bc") + assert not "abc".contains(re"cd") + assert not "abc".contains(re"a", start = 1) + + return isSome(str.find(pattern, start, endpos)) + +proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] = + ## Splits the string with the given regex. This works according to the + ## rules that Perl and Javascript use. + ## + ## `start` behaves the same as in `find(...)<#find,string,Regex,int>`_. + ## + runnableExamples: + # - If the match is zero-width, then the string is still split: + assert "123".split(re"") == @["1", "2", "3"] + + # - If the pattern has a capture in it, it is added after the string + # split: + assert "12".split(re"(\d)") == @["", "1", "", "2", ""] + + # - If `maxsplit != -1`, then the string will only be split + # `maxsplit - 1` times. This means that there will be `maxsplit` + # strings in the output seq. + assert "1.2.3".split(re"\.", maxsplit = 2) == @["1", "2.3"] + + result = @[] + var lastIdx = start + var splits = 0 + var bounds = 0 .. -1 + var never_ran = true + + for match in str.findIter(pattern, start = start): + never_ran = false + + # bounds are inclusive: + # + # 0123456 + # ^^^ + # (1, 3) + bounds = match.matchBounds + + # "12".split("") would be @["", "1", "2"], but + # if we skip an empty first match, it's the correct + # @["1", "2"] + if bounds.a <= bounds.b or bounds.a > start: + result.add(str.substr(lastIdx, bounds.a - 1)) + splits += 1 + + lastIdx = bounds.b + 1 + + for cap in match.captures: + # if there are captures, include them in the result + if cap.isSome: + result.add(cap.get) + + if splits == maxSplit - 1: + break + + # "12".split("\b") would be @["1", "2", ""], but + # if we skip an empty last match, it's the correct + # @["1", "2"] + # If matches were never found, then the input string is the result + if bounds.a <= bounds.b or bounds.b < str.high or never_ran: + # last match: Each match takes the previous substring, + # but "1 2".split(/ /) needs to return @["1", "2"]. + # This handles "2" + result.add(str.substr(bounds.b + 1, str.high)) + +template replaceImpl(str: string, pattern: Regex, + replacement: untyped) {.dirty.} = + # XXX seems very similar to split, maybe I can reduce code duplication + # somehow? + result = "" + var lastIdx = 0 + for match {.inject.} in str.findIter(pattern): + let bounds = match.matchBounds + result.add(str.substr(lastIdx, bounds.a - 1)) + let nextVal = replacement + result.add(nextVal) + + lastIdx = bounds.b + 1 + + result.add(str.substr(lastIdx, str.len - 1)) + return result + +proc replace*(str: string, pattern: Regex, + subproc: proc (match: RegexMatch): string): string = + ## Replaces each match of Regex in the string with `subproc`, which should + ## never be or return `nil`. + ## + ## If `subproc` is a `proc (RegexMatch): string`, then it is executed with + ## each match and the return value is the replacement value. + ## + ## If `subproc` is a `proc (string): string`, then it is executed with the + ## full text of the match and the return value is the replacement value. + ## + ## If `subproc` is a string, the syntax is as follows: + ## + ## - `$$` - literal `$` + ## - `$123` - capture number `123` + ## - `$foo` - named capture `foo` + ## - `${foo}` - same as above + ## - `$1$#` - first and second captures + ## - `$#` - first capture + ## - `$0` - full match + ## + ## If a given capture is missing, `IndexDefect` thrown for un-named captures + ## and `KeyError` for named captures. + replaceImpl(str, pattern, subproc(match)) + +proc replace*(str: string, pattern: Regex, + subproc: proc (match: string): string): string = + replaceImpl(str, pattern, subproc(match.match)) + +proc replace*(str: string, pattern: Regex, sub: string): string = + # - 1 because the string numbers are 0-indexed + replaceImpl(str, pattern, + formatStr(sub, match.captures[name], match.captures[id - 1])) + +proc escapeRe*(str: string): string {.gcsafe.} = + ## Escapes the string so it doesn't match any special characters. + ## Incompatible with the Extra flag (`X`). + ## + ## Escaped char: `\ + * ? [ ^ ] $ ( ) { } = ! < > | : -` + runnableExamples: + assert escapeRe("fly+wind") == "fly\\+wind" + assert escapeRe("!") == "\\!" + assert escapeRe("nim*") == "nim\\*" + + #([\\+*?[^\]$(){}=!<>|:-]) + const SpecialCharMatcher = {'\\', '+', '*', '?', '[', '^', ']', '$', '(', + ')', '{', '}', '=', '!', '<', '>', '|', ':', + '-'} + + for c in items(str): + case c + of SpecialCharMatcher: + result.add("\\") + result.add(c) + else: + result.add(c) diff --git a/lib/impure/nre/.gitignore b/lib/impure/nre/.gitignore new file mode 100644 index 000000000..3d647a25e --- /dev/null +++ b/lib/impure/nre/.gitignore @@ -0,0 +1,9 @@ +# all executables +* +!*/ +!*.* +*.exe + +# Wildcard patterns. +*.swp +nimcache diff --git a/lib/impure/nre/private/util.nim b/lib/impure/nre/private/util.nim new file mode 100644 index 000000000..ed8420776 --- /dev/null +++ b/lib/impure/nre/private/util.nim @@ -0,0 +1,51 @@ +## INTERNAL FILE FOR USE ONLY BY nre.nim. +import std/tables + +const Ident = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} +const StartIdent = Ident - {'0'..'9'} + +template formatStr*(howExpr, namegetter, idgetter): untyped = + let how = howExpr + var val = newStringOfCap(how.len) + var i = 0 + var lastNum = 1 + + while i < how.len: + if how[i] != '$': + val.add(how[i]) + i += 1 + else: + if how[i + 1] == '$': + val.add('$') + i += 2 + elif how[i + 1] == '#': + var id {.inject.} = lastNum + val.add(idgetter) + lastNum += 1 + i += 2 + elif how[i + 1] in {'0'..'9'}: + i += 1 + var id {.inject.} = 0 + while i < how.len and how[i] in {'0'..'9'}: + id += (id * 10) + (ord(how[i]) - ord('0')) + i += 1 + val.add(idgetter) + lastNum = id + 1 + elif how[i + 1] in StartIdent: + i += 1 + var name {.inject.} = "" + while i < how.len and how[i] in Ident: + name.add(how[i]) + i += 1 + val.add(namegetter) + elif how[i + 1] == '{': + i += 2 + var name {.inject.} = "" + while i < how.len and how[i] != '}': + name.add(how[i]) + i += 1 + i += 1 + val.add(namegetter) + else: + raise newException(ValueError, "Syntax error in format string at " & $i) + val diff --git a/lib/impure/rdstdin.nim b/lib/impure/rdstdin.nim new file mode 100644 index 000000000..f4fc26380 --- /dev/null +++ b/lib/impure/rdstdin.nim @@ -0,0 +1,74 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains code for reading from `stdin`:idx:. On UNIX the +## linenoise library is wrapped and set up to provide default key bindings +## (e.g. you can navigate with the arrow keys). On Windows `system.readLine` +## is used. This suffices because Windows' console already provides the +## wanted functionality. + +runnableExamples("-r:off"): + echo readLineFromStdin("Is Nim awesome? (Y/n): ") + var line: string + while true: + let ok = readLineFromStdin("How are you? ", line) + if not ok: break # ctrl-C or ctrl-D will cause a break + if line.len > 0: echo line + echo "exiting" + + +when defined(windows): + when defined(nimPreviewSlimSystem): + import std/syncio + + proc readLineFromStdin*(prompt: string): string {. + tags: [ReadIOEffect, WriteIOEffect].} = + ## Reads a line from stdin. + stdout.write(prompt) + stdout.flushFile() + result = readLine(stdin) + + proc readLineFromStdin*(prompt: string, line: var string): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = + ## Reads a `line` from stdin. `line` must not be + ## `nil`! May throw an IO exception. + ## A line of text may be delimited by `CR`, `LF` or + ## `CRLF`. The newline character(s) are not part of the returned string. + ## Returns `false` if the end of the file has been reached, `true` + ## otherwise. If `false` is returned `line` contains no new data. + stdout.write(prompt) + result = readLine(stdin, line) + +elif defined(genode): + proc readLineFromStdin*(prompt: string): string {. + tags: [ReadIOEffect, WriteIOEffect].} = + stdin.readLine() + + proc readLineFromStdin*(prompt: string, line: var string): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = + stdin.readLine(line) + +else: + import std/linenoise + + proc readLineFromStdin*(prompt: string, line: var string): bool {. + tags: [ReadIOEffect, WriteIOEffect].} = + var buffer = linenoise.readLine(prompt) + if isNil(buffer): + line.setLen(0) + return false + line = $buffer + if line.len > 0: + historyAdd(buffer) + linenoise.free(buffer) + result = true + + proc readLineFromStdin*(prompt: string): string {.inline.} = + if not readLineFromStdin(prompt, result): + raise newException(IOError, "Linenoise returned nil") diff --git a/lib/impure/re.nim b/lib/impure/re.nim new file mode 100644 index 000000000..053c6ab55 --- /dev/null +++ b/lib/impure/re.nim @@ -0,0 +1,611 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +when defined(js): + {.error: "This library needs to be compiled with a c-like backend, and depends on PCRE; See jsre for JS backend.".} + +## Regular expression support for Nim. +## +## This module is implemented by providing a wrapper around the +## `PCRE (Perl-Compatible Regular Expressions) <http://www.pcre.org>`_ +## C library. This means that your application will depend on the PCRE +## library's licence when using this module, which should not be a problem +## though. +## +## .. note:: There are also alternative nimble packages such as [tinyre](https://github.com/khchen/tinyre) +## and [regex](https://github.com/nitely/nim-regex). +## +## PCRE's licence follows: +## +## .. include:: ../../doc/regexprs.txt +## + +runnableExamples: + ## Unless specified otherwise, `start` parameter in each proc indicates + ## where the scan starts, but outputs are relative to the start of the input + ## string, not to `start`: + doAssert find("uxabc", re"(?<=x|y)ab", start = 1) == 2 # lookbehind assertion + doAssert find("uxabc", re"ab", start = 3) == -1 # we're past `start` => not found + doAssert not match("xabc", re"^abc$", start = 1) + # can't match start of string since we're starting at 1 + +import + std/[pcre, strutils, rtarrays] + +when defined(nimPreviewSlimSystem): + import std/syncio + +const + MaxSubpatterns* = 20 + ## defines the maximum number of subpatterns that can be captured. + ## This limit still exists for `replacef` and `parallelReplace`. + +type + RegexFlag* = enum ## options for regular expressions + reIgnoreCase = 0, ## do caseless matching + reMultiLine = 1, ## `^` and `$` match newlines within data + reDotAll = 2, ## `.` matches anything including NL + reExtended = 3, ## ignore whitespace and `#` comments + reStudy = 4 ## study the expression (may be omitted if the + ## expression will be used only once) + + RegexDesc = object + h: ptr Pcre + e: ptr ExtraData + + Regex* = ref RegexDesc ## a compiled regular expression + + RegexError* = object of ValueError + ## is raised if the pattern is no valid regular expression. + +when defined(gcDestructors): + when defined(nimAllowNonVarDestructor): + proc `=destroy`(x: RegexDesc) = + pcre.free_substring(cast[cstring](x.h)) + if not isNil(x.e): + pcre.free_study(x.e) + else: + proc `=destroy`(x: var RegexDesc) = + pcre.free_substring(cast[cstring](x.h)) + if not isNil(x.e): + pcre.free_study(x.e) + +proc raiseInvalidRegex(msg: string) {.noinline, noreturn.} = + var e: ref RegexError + new(e) + e.msg = msg + raise e + +proc rawCompile(pattern: string, flags: cint): ptr Pcre = + var + msg: cstring = "" + offset: cint = 0 + result = pcre.compile(pattern, flags, addr(msg), addr(offset), nil) + if result == nil: + raiseInvalidRegex($msg & "\n" & pattern & "\n" & spaces(offset) & "^\n") + +proc finalizeRegEx(x: Regex) = + # XXX This is a hack, but PCRE does not export its "free" function properly. + # Sigh. The hack relies on PCRE's implementation (see `pcre_get.c`). + # Fortunately the implementation is unlikely to change. + pcre.free_substring(cast[cstring](x.h)) + if not isNil(x.e): + pcre.free_study(x.e) + +proc re*(s: string, flags = {reStudy}): Regex = + ## Constructor of regular expressions. + ## + ## Note that Nim's + ## extended raw string literals support the syntax `re"[abc]"` as + ## a short form for `re(r"[abc]")`. Also note that since this + ## compiles the regular expression, which is expensive, you should + ## avoid putting it directly in the arguments of the functions like + ## the examples show below if you plan to use it a lot of times, as + ## this will hurt performance immensely. (e.g. outside a loop, ...) + when defined(gcDestructors): + result = Regex() + else: + new(result, finalizeRegEx) + result.h = rawCompile(s, cast[cint](flags - {reStudy})) + if reStudy in flags: + var msg: cstring = "" + var options: cint = 0 + var hasJit: cint = 0 + if pcre.config(pcre.CONFIG_JIT, addr hasJit) == 0: + if hasJit == 1'i32: + options = pcre.STUDY_JIT_COMPILE + result.e = pcre.study(result.h, options, addr msg) + if not isNil(msg): raiseInvalidRegex($msg) + +proc rex*(s: string, flags = {reStudy, reExtended}): Regex = + ## Constructor for extended regular expressions. + ## + ## The extended means that comments starting with `#` and + ## whitespace are ignored. + result = re(s, flags) + +proc bufSubstr(b: cstring, sPos, ePos: int): string {.inline.} = + ## Return a Nim string built from a slice of a cstring buffer. + ## Don't assume cstring is '\0' terminated + let sz = ePos - sPos + result = newString(sz+1) + copyMem(addr(result[0]), unsafeAddr(b[sPos]), sz) + result.setLen(sz) + +proc matchOrFind(buf: cstring, pattern: Regex, matches: var openArray[string], + start, bufSize, flags: cint): cint = + var + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize, start, flags, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) + if res < 0'i32: return res + for i in 1..int(res)-1: + var a = rawMatches[i * 2] + var b = rawMatches[i * 2 + 1] + if a >= 0'i32: + matches[i-1] = bufSubstr(buf, int(a), int(b)) + else: matches[i-1] = "" + return rawMatches[1] - rawMatches[0] + +const MaxReBufSize* = high(cint) + ## Maximum PCRE (API 1) buffer start/size equal to `high(cint)`, which even + ## for 64-bit systems can be either 2`31`:sup:-1 or 2`63`:sup:-1. + +proc findBounds*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): tuple[first, last: int] = + ## returns the starting position and end position of `pattern` in `buf` + ## (where `buf` has length `bufSize` and is not necessarily `'\0'` terminated), + ## and the captured + ## substrings in the array `matches`. If it does not match, nothing + ## is written into `matches` and `(-1,0)` is returned. + ## + ## Note: The memory for `matches` needs to be allocated before this function is + ## called, otherwise it will just remain empty. + var + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) + if res < 0'i32: return (-1, 0) + for i in 1..int(res)-1: + var a = rawMatches[i * 2] + var b = rawMatches[i * 2 + 1] + if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) + else: matches[i-1] = "" + return (rawMatches[0].int, rawMatches[1].int - 1) + +proc findBounds*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): tuple[first, last: int] {.inline.} = + ## returns the starting position and end position of `pattern` in `s` + ## and the captured substrings in the array `matches`. + ## If it does not match, nothing + ## is written into `matches` and `(-1,0)` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + runnableExamples: + var matches = newSeq[string](1) + let (first, last) = findBounds("Hello World", re"(W\w+)", matches) + doAssert first == 6 + doAssert last == 10 + doAssert matches[0] == "World" + result = findBounds(cstring(s), pattern, matches, + min(start, MaxReBufSize), min(s.len, MaxReBufSize)) + +proc findBounds*(buf: cstring, pattern: Regex, + matches: var openArray[tuple[first, last: int]], + start = 0, bufSize: int): tuple[first, last: int] = + ## returns the starting position and end position of `pattern` in `buf` + ## (where `buf` has length `bufSize` and is not necessarily `'\0'` terminated), + ## and the captured substrings in the array `matches`. + ## If it does not match, nothing is written into `matches` and + ## `(-1,0)` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + var + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) + if res < 0'i32: return (-1, 0) + for i in 1..int(res)-1: + var a = rawMatches[i * 2] + var b = rawMatches[i * 2 + 1] + if a >= 0'i32: matches[i-1] = (int(a), int(b)-1) + else: matches[i-1] = (-1,0) + return (rawMatches[0].int, rawMatches[1].int - 1) + +proc findBounds*(s: string, pattern: Regex, + matches: var openArray[tuple[first, last: int]], + start = 0): tuple[first, last: int] {.inline.} = + ## returns the starting position and end position of `pattern` in `s` + ## and the captured substrings in the array `matches`. + ## If it does not match, nothing is written into `matches` and + ## `(-1,0)` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + runnableExamples: + var matches = newSeq[tuple[first, last: int]](1) + let (first, last) = findBounds("Hello World", re"(\w+)", matches) + doAssert first == 0 + doAssert last == 4 + doAssert matches[0] == (0, 4) + result = findBounds(cstring(s), pattern, matches, + min(start, MaxReBufSize), min(s.len, MaxReBufSize)) + +proc findBoundsImpl(buf: cstring, pattern: Regex, + start = 0, bufSize = 0, flags = 0): tuple[first, last: int] = + var rtarray = initRtArray[cint](3) + let rawMatches = rtarray.getRawData + let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags.int32, + cast[ptr cint](rawMatches), 3) + + if res < 0'i32: + result = (-1, 0) + else: + result = (int(rawMatches[0]), int(rawMatches[1]-1)) + +proc findBounds*(buf: cstring, pattern: Regex, + start = 0, bufSize: int): tuple[first, last: int] = + ## returns the `first` and `last` position of `pattern` in `buf`, + ## where `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## If it does not match, `(-1,0)` is returned. + var + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), 3) + if res < 0'i32: return (int(res), 0) + return (int(rawMatches[0]), int(rawMatches[1]-1)) + +proc findBounds*(s: string, pattern: Regex, + start = 0): tuple[first, last: int] {.inline.} = + ## returns the `first` and `last` position of `pattern` in `s`. + ## If it does not match, `(-1,0)` is returned. + ## + ## Note: there is a speed improvement if the matches do not need to be captured. + runnableExamples: + assert findBounds("01234abc89", re"abc") == (5,7) + result = findBounds(cstring(s), pattern, + min(start, MaxReBufSize), min(s.len, MaxReBufSize)) + +proc matchOrFind(buf: cstring, pattern: Regex, start, bufSize: int, flags: cint): cint = + var + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + result = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, flags, + cast[ptr cint](rawMatches), 3) + if result >= 0'i32: + result = rawMatches[1] - rawMatches[0] + +proc matchLen*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + result = matchOrFind(cstring(s), pattern, matches, start.cint, s.len.cint, pcre.ANCHORED) + +proc matchLen*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + return matchOrFind(buf, pattern, matches, start.cint, bufSize.cint, pcre.ANCHORED) + +proc matchLen*(s: string, pattern: Regex, start = 0): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + ## + runnableExamples: + doAssert matchLen("abcdefg", re"cde", 2) == 3 + doAssert matchLen("abcdefg", re"abcde") == 5 + doAssert matchLen("abcdefg", re"cde") == -1 + result = matchOrFind(cstring(s), pattern, start.cint, s.len.cint, pcre.ANCHORED) + +proc matchLen*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int {.inline.} = + ## the same as `match`, but it returns the length of the match, + ## if there is no match, `-1` is returned. Note that a match length + ## of zero can happen. + result = matchOrFind(buf, pattern, start.cint, bufSize, pcre.ANCHORED) + +proc match*(s: string, pattern: Regex, start = 0): bool {.inline.} = + ## returns `true` if `s[start..]` matches the `pattern`. + result = matchLen(cstring(s), pattern, start, s.len) != -1 + +proc match*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): bool {.inline.} = + ## returns `true` if `s[start..]` matches the `pattern` and + ## the captured substrings in the array `matches`. If it does not + ## match, nothing is written into `matches` and `false` is + ## returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + runnableExamples: + import std/sequtils + var matches: array[2, string] + if match("abcdefg", re"c(d)ef(g)", matches, 2): + doAssert toSeq(matches) == @["d", "g"] + result = matchLen(cstring(s), pattern, matches, start, s.len) != -1 + +proc match*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): bool {.inline.} = + ## returns `true` if `buf[start..<bufSize]` matches the `pattern` and + ## the captured substrings in the array `matches`. If it does not + ## match, nothing is written into `matches` and `false` is + ## returned. + ## `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + result = matchLen(buf, pattern, matches, start, bufSize) != -1 + +proc find*(buf: cstring, pattern: Regex, matches: var openArray[string], + start = 0, bufSize: int): int = + ## returns the starting position of `pattern` in `buf` and the captured + ## substrings in the array `matches`. If it does not match, nothing + ## is written into `matches` and `-1` is returned. + ## `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + var + rtarray = initRtArray[cint]((matches.len+1)*3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), (matches.len+1).cint*3) + if res < 0'i32: return res + for i in 1..int(res)-1: + var a = rawMatches[i * 2] + var b = rawMatches[i * 2 + 1] + if a >= 0'i32: matches[i-1] = bufSubstr(buf, int(a), int(b)) + else: matches[i-1] = "" + return rawMatches[0] + +proc find*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): int {.inline.} = + ## returns the starting position of `pattern` in `s` and the captured + ## substrings in the array `matches`. If it does not match, nothing + ## is written into `matches` and `-1` is returned. + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + result = find(cstring(s), pattern, matches, start, s.len) + +proc find*(buf: cstring, pattern: Regex, start = 0, bufSize: int): int = + ## returns the starting position of `pattern` in `buf`, + ## where `buf` has length `bufSize` (not necessarily `'\0'` terminated). + ## If it does not match, `-1` is returned. + var + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, start.cint, 0'i32, + cast[ptr cint](rawMatches), 3) + if res < 0'i32: return res + return rawMatches[0] + +proc find*(s: string, pattern: Regex, start = 0): int {.inline.} = + ## returns the starting position of `pattern` in `s`. If it does not + ## match, `-1` is returned. We start the scan at `start`. + runnableExamples: + doAssert find("abcdefg", re"cde") == 2 + doAssert find("abcdefg", re"abc") == 0 + doAssert find("abcdefg", re"zz") == -1 # not found + doAssert find("abcdefg", re"cde", start = 2) == 2 # still 2 + doAssert find("abcdefg", re"cde", start = 3) == -1 # we're past the start position + doAssert find("xabc", re"(?<=x|y)abc", start = 1) == 1 + # lookbehind assertion `(?<=x|y)` can look behind `start` + result = find(cstring(s), pattern, start, s.len) + +iterator findAll*(s: string, pattern: Regex, start = 0): string = + ## Yields all matching *substrings* of `s` that match `pattern`. + ## + ## Note that since this is an iterator you should not modify the string you + ## are iterating over: bad things could happen. + var + i = int32(start) + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + while true: + let res = pcre.exec(pattern.h, pattern.e, s, len(s).cint, i, 0'i32, + cast[ptr cint](rawMatches), 3) + if res < 0'i32: break + let a = rawMatches[0] + let b = rawMatches[1] + if a == b and a == i: break + yield substr(s, int(a), int(b)-1) + i = b + +iterator findAll*(buf: cstring, pattern: Regex, start = 0, bufSize: int): string = + ## Yields all matching `substrings` of `s` that match `pattern`. + ## + ## Note that since this is an iterator you should not modify the string you + ## are iterating over: bad things could happen. + var + i = int32(start) + rtarray = initRtArray[cint](3) + rawMatches = rtarray.getRawData + while true: + let res = pcre.exec(pattern.h, pattern.e, buf, bufSize.cint, i, 0'i32, + cast[ptr cint](rawMatches), 3) + if res < 0'i32: break + let a = rawMatches[0] + let b = rawMatches[1] + if a == b and a == i: break + var str = newString(b-a) + copyMem(str[0].addr, unsafeAddr(buf[a]), b-a) + yield str + i = b + +proc findAll*(s: string, pattern: Regex, start = 0): seq[string] {.inline.} = + ## returns all matching `substrings` of `s` that match `pattern`. + ## If it does not match, `@[]` is returned. + result = @[] + for x in findAll(s, pattern, start): result.add x + +template `=~` *(s: string, pattern: Regex): untyped = + ## This calls `match` with an implicit declared `matches` array that + ## can be used in the scope of the `=~` call: + runnableExamples: + proc parse(line: string): string = + if line =~ re"\s*(\w+)\s*\=\s*(\w+)": # matches a key=value pair: + result = $(matches[0], matches[1]) + elif line =~ re"\s*(\#.*)": # matches a comment + # note that the implicit `matches` array is different from 1st branch + result = $(matches[0],) + else: raiseAssert "unreachable" + doAssert not declared(matches) + doAssert parse("NAME = LENA") == """("NAME", "LENA")""" + doAssert parse(" # comment ... ") == """("# comment ... ",)""" + bind MaxSubpatterns + when not declaredInScope(matches): + var matches {.inject.}: array[MaxSubpatterns, string] + match(s, pattern, matches) + +# ------------------------- more string handling ------------------------------ + +proc contains*(s: string, pattern: Regex, start = 0): bool {.inline.} = + ## same as `find(s, pattern, start) >= 0` + return find(s, pattern, start) >= 0 + +proc contains*(s: string, pattern: Regex, matches: var openArray[string], + start = 0): bool {.inline.} = + ## same as `find(s, pattern, matches, start) >= 0` + ## + ## .. note:: The memory for `matches` needs to be allocated before this function is called, otherwise it will just remain empty. + return find(s, pattern, matches, start) >= 0 + +proc startsWith*(s: string, prefix: Regex): bool {.inline.} = + ## returns true if `s` starts with the pattern `prefix` + result = matchLen(s, prefix) >= 0 + +proc endsWith*(s: string, suffix: Regex): bool {.inline.} = + ## returns true if `s` ends with the pattern `suffix` + for i in 0 .. s.len-1: + if matchLen(s, suffix, i) == s.len - i: return true + +proc replace*(s: string, sub: Regex, by = ""): string = + ## Replaces `sub` in `s` by the string `by`. Captures cannot be + ## accessed in `by`. + runnableExamples: + doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)") == "; " + doAssert "var1=key; var2=key2".replace(re"(\w+)=(\w+)", "?") == "?; ?" + result = "" + var prev = 0 + var flags = int32(0) + while prev < s.len: + var match = findBoundsImpl(s.cstring, sub, prev, s.len, flags) + flags = 0 + if match.first < 0: break + add(result, substr(s, prev, match.first-1)) + add(result, by) + if match.first > match.last: + # 0-len match + flags = pcre.NOTEMPTY_ATSTART + prev = match.last + 1 + add(result, substr(s, prev)) + +proc replacef*(s: string, sub: Regex, by: string): string = + ## Replaces `sub` in `s` by the string `by`. Captures can be accessed in `by` + ## with the notation `$i` and `$#` (see strutils.\`%\`). + runnableExamples: + doAssert "var1=key; var2=key2".replacef(re"(\w+)=(\w+)", "$1<-$2$2") == + "var1<-keykey; var2<-key2key2" + result = "" + var caps: array[MaxSubpatterns, string] + var prev = 0 + while prev < s.len: + var match = findBounds(s, sub, caps, prev) + if match.first < 0: break + add(result, substr(s, prev, match.first-1)) + addf(result, by, caps) + if match.last + 1 == prev: break + prev = match.last + 1 + add(result, substr(s, prev)) + +proc multiReplace*(s: string, subs: openArray[ + tuple[pattern: Regex, repl: string]]): string = + ## Returns a modified copy of `s` with the substitutions in `subs` + ## applied in parallel. + result = "" + var i = 0 + var caps: array[MaxSubpatterns, string] + while i < s.len: + block searchSubs: + for j in 0..high(subs): + var x = matchLen(s, subs[j][0], caps, i) + if x > 0: + addf(result, subs[j][1], caps) + inc(i, x) + break searchSubs + add(result, s[i]) + inc(i) + # copy the rest: + add(result, substr(s, i)) + +proc transformFile*(infile, outfile: string, + subs: openArray[tuple[pattern: Regex, repl: string]]) = + ## reads in the file `infile`, performs a parallel replacement (calls + ## `parallelReplace`) and writes back to `outfile`. Raises `IOError` if an + ## error occurs. This is supposed to be used for quick scripting. + var x = readFile(infile) + writeFile(outfile, x.multiReplace(subs)) + +iterator split*(s: string, sep: Regex; maxsplit = -1): string = + ## Splits the string `s` into substrings. + ## + ## Substrings are separated by the regular expression `sep` + ## (and the portion matched by `sep` is not returned). + runnableExamples: + import std/sequtils + doAssert toSeq(split("00232this02939is39an22example111", re"\d+")) == + @["", "this", "is", "an", "example", ""] + var last = 0 + var splits = maxsplit + var x = -1 + if len(s) == 0: + last = 1 + if matchLen(s, sep, 0) == 0: + x = 0 + while last <= len(s): + var first = last + var sepLen = 1 + if x == 0: + inc(last) + while last < len(s): + x = matchLen(s, sep, last) + if x >= 0: + sepLen = x + break + inc(last) + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) + +proc split*(s: string, sep: Regex, maxsplit = -1): seq[string] {.inline.} = + ## Splits the string `s` into a seq of substrings. + ## + ## The portion matched by `sep` is not returned. + result = @[] + for x in split(s, sep, maxsplit): result.add x + +proc escapeRe*(s: string): string = + ## escapes `s` so that it is matched verbatim when used as a regular + ## expression. + result = "" + for c in items(s): + case c + of 'a'..'z', 'A'..'Z', '0'..'9', '_': + result.add(c) + else: + result.add("\\x") + result.add(toHex(ord(c), 2)) diff --git a/lib/impure/web.nim b/lib/impure/web.nim deleted file mode 100755 index 83d1406af..000000000 --- a/lib/impure/web.nim +++ /dev/null @@ -1,57 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2009 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module contains simple high-level procedures for dealing with the -## web. Use cases: -## -## * requesting URLs -## * sending and retrieving emails -## * sending and retrieving files from an FTP server -## -## Currently only requesting URLs is implemented. The implementation depends -## on the libcurl library! - -import libcurl, streams - -proc curlwrapperWrite(p: pointer, size, nmemb: int, - data: pointer): int {.cdecl.} = - var stream = cast[PStream](data) - stream.writeData(stream, p, size*nmemb) - return size*nmemb - -proc URLretrieveStream*(url: string): PStream = - ## retrieves the given `url` and returns a stream which one can read from to - ## obtain the contents. Returns nil if an error occurs. - result = newStringStream() - var hCurl = curl_easy_init() - if hCurl == nil: return nil - if curl_easy_setopt(hCurl, CURLOPT_URL, url) != CURLE_OK: return nil - if curl_easy_setopt(hCurl, CURLOPT_WRITEFUNCTION, - curlwrapperWrite) != CURLE_OK: return nil - if curl_easy_setopt(hCurl, CURLOPT_WRITEDATA, result) != CURLE_OK: return nil - if curl_easy_perform(hCurl) != CURLE_OK: return nil - curl_easy_cleanup(hCurl) - -proc URLretrieveString*(url: string): string = - ## retrieves the given `url` and returns the contents. Returns nil if an - ## error occurs. - var stream = newStringStream() - var hCurl = curl_easy_init() - if hCurl == nil: return nil - if curl_easy_setopt(hCurl, CURLOPT_URL, url) != CURLE_OK: return nil - if curl_easy_setopt(hCurl, CURLOPT_WRITEFUNCTION, - curlwrapperWrite) != CURLE_OK: return nil - if curl_easy_setopt(hCurl, CURLOPT_WRITEDATA, stream) != CURLE_OK: return nil - if curl_easy_perform(hCurl) != CURLE_OK: return nil - curl_easy_cleanup(hCurl) - result = stream.data - -when isMainModule: - echo URLretrieveString("http://nimrod.ethexor.com/") - diff --git a/lib/impure/zipfiles.nim b/lib/impure/zipfiles.nim deleted file mode 100755 index e48b0f08e..000000000 --- a/lib/impure/zipfiles.nim +++ /dev/null @@ -1,144 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2008 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a zip archive creator/reader/modifier. - -import - streams, libzip, times, os - -type - TZipArchive* = object of TObject ## represents a zip archive - mode: TFileMode - w: PZip - - -proc zipError(z: var TZipArchive) = - var e: ref EIO - new(e) - e.msg = zip_strerror(z.w) - raise e - -proc open*(z: var TZipArchive, filename: string, mode: TFileMode = fmRead): bool = - ## Opens a zip file for reading, writing or appending. All file modes are - ## supported. Returns true iff successful, false otherwise. - var err, flags: int32 - case mode - of fmRead, fmReadWriteExisting, fmAppend: flags = 0 - of fmWrite: - if existsFile(filename): removeFile(filename) - flags = ZIP_CREATE or ZIP_EXCL - of fmReadWrite: flags = ZIP_CREATE - z.w = zip_open(filename, flags, addr(err)) - z.mode = mode - result = z.w != nil - -proc close*(z: var TZipArchive) = - ## Closes a zip file. - zip_close(z.w) - -proc createDir*(z: var TZipArchive, dir: string) = - ## Creates a directory within the `z` archive. This does not fails if the - ## directory already exists. Note that for adding a file like - ## ``"path1/path2/filename"`` it is not necessary - ## to create the ``"path/path2"`` subdirectories - it will be done - ## automatically by ``addFile``. - assert(z.mode != fmRead) - discard zip_add_dir(z.w, dir) - zip_error_clear(z.w) - -proc addFile*(z: var TZipArchive, dest, src: string) = - ## Adds the file `src` to the archive `z` with the name `dest`. `dest` - ## may contain a path that will be created. - assert(z.mode != fmRead) - var zipsrc = zip_source_file(z.w, src, 0, -1) - if zipsrc == nil: - echo("Dest: " & dest) - echo("Src: " & src) - zipError(z) - if zip_add(z.w, dest, zipsrc) < 0'i32: - zip_source_free(zipsrc) - zipError(z) - -proc addFile*(z: var TZipArchive, file: string) = - ## A shortcut for ``addFile(z, file, file)``, i.e. the name of the source is - ## the name of the destination. - addFile(z, file, file) - -proc mySourceCallback(state, data: pointer, len: int, - cmd: Tzip_source_cmd): int {.cdecl.} = - var src = cast[PStream](state) - case cmd - of ZIP_SOURCE_OPEN: - if src.setPosition != nil: src.setPosition(src, 0) # reset - of ZIP_SOURCE_READ: - result = src.readData(src, data, len) - of ZIP_SOURCE_CLOSE: src.close(src) - of ZIP_SOURCE_STAT: - var stat = cast[PZipStat](data) - zip_stat_init(stat) - stat.size = high(int32)-1 # we don't know the size - stat.mtime = getTime() - result = sizeof(TZipStat) - of ZIP_SOURCE_ERROR: - var err = cast[ptr array[0..1, cint]](data) - err[0] = ZIP_ER_INTERNAL - err[1] = 0 - result = 2*sizeof(cint) - of constZIP_SOURCE_FREE: GC_unref(src) - else: assert(false) - -proc addFile*(z: var TZipArchive, dest: string, src: PStream) = - ## Adds a file named with `dest` to the archive `z`. `dest` - ## may contain a path. The file's content is read from the `src` stream. - assert(z.mode != fmRead) - GC_ref(src) - var zipsrc = zip_source_function(z.w, mySourceCallback, cast[pointer](src)) - if zipsrc == nil: zipError(z) - if zip_add(z.w, dest, zipsrc) < 0'i32: - zip_source_free(zipsrc) - zipError(z) - -# -------------- zip file stream --------------------------------------------- - -type - TZipFileStream = object of TStream - f: Pzip_file - - PZipFileStream* = - ref TZipFileStream ## a reader stream of a file within a zip archive - -proc fsClose(s: PZipFileStream) = zip_fclose(s.f) -proc fsReadData(s: PZipFileStream, buffer: pointer, bufLen: int): int = - result = zip_fread(s.f, buffer, bufLen) - -proc newZipFileStream(f: PZipFile): PZipFileStream = - new(result) - result.f = f - result.close = fsClose - result.readData = fsReadData - # other methods are nil! - -# ---------------------------------------------------------------------------- - -proc getStream*(z: var TZipArchive, filename: string): PZipFileStream = - ## returns a stream that can be used to read the file named `filename` - ## from the archive `z`. Returns nil in case of an error. - ## The returned stream does not support the `setPosition`, `getPosition`, - ## `writeData` or `atEnd` methods. - var x = zip_fopen(z.w, filename, 0'i32) - if x != nil: result = newZipFileStream(x) - -iterator walkFiles*(z: var TZipArchive): string = - ## walks over all files in the archive `z` and returns the filename - ## (including the path). - var i = 0 - var num = int(zip_get_num_files(z.w)) - while i < num: - yield $zip_get_name(z.w, i, 0'i32) - inc(i) |