diff options
Diffstat (limited to 'lib/pure')
-rwxr-xr-x | lib/pure/browsers.nim | 43 | ||||
-rwxr-xr-x | lib/pure/cgi.nim | 86 | ||||
-rwxr-xr-x | lib/pure/os.nim | 2 | ||||
-rwxr-xr-x | lib/pure/sockets.nim | 381 | ||||
-rwxr-xr-x | lib/pure/strutils.nim | 6 | ||||
-rwxr-xr-x | lib/pure/times.nim | 2 |
6 files changed, 374 insertions, 146 deletions
diff --git a/lib/pure/browsers.nim b/lib/pure/browsers.nim new file mode 100755 index 000000000..243c07dad --- /dev/null +++ b/lib/pure/browsers.nim @@ -0,0 +1,43 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2010 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements a simple proc for opening URLs with the user's +## default browser. + +import strutils + +when defined(windows): + import winlean +else: + import os, osproc + +proc openDefaultBrowser*(url: string) = + ## opens `url` with the user's default browser. This does not block. + ## + ## Under Windows, ``ShellExecute`` is used. Under Mac OS X the ``open`` + ## command is used. Under Unix, it is checked if ``gnome-open`` exists and + ## used if it does. Next attempt is ``kde-open``, then ``xdg-open``. + ## Otherwise the environment variable ``BROWSER`` is used to determine the + ## default browser to use. + when defined(windows): + discard ShellExecute(0'i32, "open", url, nil, nil, SW_SHOWNORMAL) + elif defined(macosx): + discard execShellCmd("open " & quoteIfContainsWhite(url)) + else: + const attempts = ["gnome-open ", "kde-open ", "xdg-open "] + var u = quoteIfContainsWhite(url) + for a in items(attempts): + if execShellCmd(a & u) == 0: return + for b in getEnv("BROWSER").split(PathSep): + try: + # we use ``startProcess`` here because we don't want to block! + discard startProcess(command=b, args=[url], options={poUseShell}) + return + except EOS: + nil diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 93d29189b..210af00fc 100755 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -120,51 +120,57 @@ proc getEncodedData(allowedMethods: set[TRequestMethod]): string = if methodNone notin allowedMethods: cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'") +iterator decodeData*(data: string): tuple[key, value: string] = + ## Reads and decodes CGI data and yields the (name, value) pairs the + ## data consists of. + var i = 0 + var name = "" + var value = "" + # decode everything in one pass: + while data[i] != '\0': + setLen(name, 0) # reuse memory + while true: + case data[i] + of '\0': break + of '%': + var x = 0 + handleHexChar(data[i+1], x) + handleHexChar(data[i+2], x) + inc(i, 2) + add(name, chr(x)) + of '+': add(name, ' ') + of '=', '&': break + else: add(name, data[i]) + inc(i) + if data[i] != '=': cgiError("'=' expected") + inc(i) # skip '=' + setLen(value, 0) # reuse memory + while true: + case data[i] + of '%': + var x = 0 + handleHexChar(data[i+1], x) + handleHexChar(data[i+2], x) + inc(i, 2) + add(value, chr(x)) + of '+': add(value, ' ') + of '&', '\0': break + else: add(value, data[i]) + inc(i) + yield (name, value) + if data[i] == '&': inc(i) + elif data[i] == '\0': break + else: cgiError("'&' expected") + iterator decodeData*(allowedMethods: set[TRequestMethod] = {methodNone, methodPost, methodGet}): tuple[key, value: string] = ## Reads and decodes CGI data and yields the (name, value) pairs the ## data consists of. If the client does not use a method listed in the ## `allowedMethods` set, an `ECgi` exception is raised. - var enc = getEncodedData(allowedMethods) - if not isNil(enc): - # decode everything in one pass: - var i = 0 - var name = "" - var value = "" - while enc[i] != '\0': - setLen(name, 0) # reuse memory - while true: - case enc[i] - of '\0': break - of '%': - var x = 0 - handleHexChar(enc[i+1], x) - handleHexChar(enc[i+2], x) - inc(i, 2) - add(name, chr(x)) - of '+': add(name, ' ') - of '=', '&': break - else: add(name, enc[i]) - inc(i) - if enc[i] != '=': cgiError("'=' expected") - inc(i) # skip '=' - setLen(value, 0) # reuse memory - while true: - case enc[i] - of '%': - var x = 0 - handleHexChar(enc[i+1], x) - handleHexChar(enc[i+2], x) - inc(i, 2) - add(value, chr(x)) - of '+': add(value, ' ') - of '&', '\0': break - else: add(value, enc[i]) - inc(i) - yield (name, value) - if enc[i] == '&': inc(i) - elif enc[i] == '\0': break - else: cgiError("'&' expected") + var data = getEncodedData(allowedMethods) + if not isNil(data): + for key, value in decodeData(data): + yield key, value proc readData*(allowedMethods: set[TRequestMethod] = {methodNone, methodPost, methodGet}): PStringTable = diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 85ac9c83c..1bbe55bb9 100755 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -577,7 +577,7 @@ proc cmpPaths*(pathA, pathB: string): int {.noSideEffect.} = proc sameFile*(path1, path2: string): bool = ## Returns True if both pathname arguments refer to the same file or ## directory (as indicated by device number and i-node number). - ## Raises an exception if an os.stat() call on either pathname fails. + ## Raises an exception if an stat() call on either pathname fails. when defined(Windows): var a, b: TWin32FindData diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index be2e4434a..9419b82b9 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -7,26 +7,7 @@ # distribution, for details about the copyright. # -## This module implements a simple portable type-safe sockets layer. **Note**: -## This module is incomplete and probably buggy. It does not work on Windows -## yet. Help if you are interested. - -# TODO: -# getservbyname(name, proto) -# getservbyport(port, proto) -# gethostbyname(name) -# gethostbyaddr(addr) -# shutdown(sock, how) -# connect(sock, address, port) -# select({ socket, ... }, timeout) - -# sendto -# recvfrom - -# bind(socket, address, port) - -# getsockopt(socket, level, optname) -# setsockopt(socket, level, optname, value) +## This module implements a simple portable type-safe sockets layer. import os @@ -35,6 +16,8 @@ when defined(Windows): else: import posix +# Note: The enumerations are mapped to Window's constants. + type TSocket* = distinct cint ## socket type TPort* = distinct int16 ## port type @@ -42,87 +25,149 @@ type TDomain* = enum ## domain, which specifies the protocol family of the ## created socket. Other domains than those that are listed ## here are unsupported. - AF_UNIX, ## for local socket (using a file). - AF_INET, ## for network protocol IPv4 or - AF_INET6 ## for network protocol IPv6. - - TType* = enum ## second argument to `socket` proc - SOCK_STREAM, ## reliable stream-oriented service or Stream Sockets - SOCK_DGRAM, ## datagram service or Datagram Sockets - SOCK_SEQPACKET, ## reliable sequenced packet service, or - SOCK_RAW ## raw protocols atop the network layer. - - TProtocol* = enum ## third argument to `socket` proc - IPPROTO_TCP, ## Transmission control protocol. - IPPROTO_UDP, ## User datagram protocol. - IPPROTO_IP, ## Internet protocol. - IPPROTO_IPV6, ## Internet Protocol Version 6. - IPPROTO_RAW, ## Raw IP Packets Protocol. - IPPROTO_ICMP ## Control message protocol. + AF_UNIX, ## for local socket (using a file). Unsupported on Windows. + AF_INET = 2, ## for network protocol IPv4 or + AF_INET6 = 23 ## for network protocol IPv6. + + TType* = enum ## second argument to `socket` proc + SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets + SOCK_DGRAM = 2, ## datagram service or Datagram Sockets + SOCK_RAW = 3, ## raw protocols atop the network layer. + SOCK_SEQPACKET = 5 ## reliable sequenced packet service, or + + TProtocol* = enum ## third argument to `socket` proc + IPPROTO_TCP = 6, ## Transmission control protocol. + IPPROTO_UDP = 17, ## User datagram protocol. + IPPROTO_IP, ## Internet protocol. Unsupported on Windows. + IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows. + IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows. + IPPROTO_ICMP ## Control message protocol. Unsupported on Windows. + + TServent* {.pure, final.} = object ## information about a service + name*: string + aliases*: seq[string] + port*: TPort + proto*: string + + Thostent* {.pure, final.} = object ## information about a given host + name*: string + aliases*: seq[string] + addrtype*: TDomain + length*: int + addrList*: seq[string] const InvalidSocket* = TSocket(-1'i32) ## invalid socket number proc `==`*(a, b: TSocket): bool {.borrow.} + ## ``==`` for sockets. + proc `==`*(a, b: TPort): bool {.borrow.} + ## ``==`` for ports. -proc `$`* (p: TPort): string = result = $ze(int16(p)) - -proc ToInt(domain: TDomain): cint = - case domain - of AF_UNIX: result = posix.AF_UNIX - of AF_INET: result = posix.AF_INET - of AF_INET6: result = posix.AF_INET6 - -proc ToInt(typ: TType): cint = - case typ - of SOCK_STREAM: result = posix.SOCK_STREAM - of SOCK_DGRAM: result = posix.SOCK_DGRAM - of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET - of SOCK_RAW: result = posix.SOCK_RAW - -proc ToInt(p: TProtocol): cint = - case p - of IPPROTO_TCP: result = posix.IPPROTO_TCP - of IPPROTO_UDP: result = posix.IPPROTO_UDP - of IPPROTO_IP: result = posix.IPPROTO_IP - of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 - of IPPROTO_RAW: result = posix.IPPROTO_RAW - of IPPROTO_ICMP: result = posix.IPPROTO_ICMP - -proc socket*(domain: TDomain = AF_INET6, typ: TType = SOCK_STREAM, +proc `$`*(p: TPort): string = + ## returns the port number as a string + result = $ze(int16(p)) + +proc ntohl*(x: int32): int32 = + ## Converts 32-bit integers from network to host byte order. + ## On machines where the host byte order is the same as network byte order, + ## this is a no-op; otherwise, it performs a 4-byte swap operation. + when cpuEndian == bigEndian: result = x + else: result = (x shr 24'i32) or + (x shr 8'i32 and 0xff00'i32) or + (x shl 8'i32 and 0xff0000'i32) or + (x shl 24'i32) + +proc ntohs*(x: int16): int16 = + ## Converts 16-bit integers from network to host byte order. On machines + ## where the host byte order is the same as network byte order, this is + ## a no-op; otherwise, it performs a 2-byte swap operation. + when cpuEndian == bigEndian: result = x + else: result = (x shr 8'i16) or (x shl 8'i16) + +proc htonl*(x: int32): int32 = + ## Converts 32-bit integers from host to network byte order. On machines + ## where the host byte order is the same as network byte order, this is + ## a no-op; otherwise, it performs a 4-byte swap operation. + result = sockets.ntohl(x) + +proc htons*(x: int16): int16 = + ## Converts 16-bit positive integers from host to network byte order. + ## On machines where the host byte order is the same as network byte + ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. + result = sockets.ntohs(x) + +when defined(Posix): + proc ToInt(domain: TDomain): cint = + case domain + of AF_UNIX: result = posix.AF_UNIX + of AF_INET: result = posix.AF_INET + of AF_INET6: result = posix.AF_INET6 + else: nil + + proc ToInt(typ: TType): cint = + case typ + of SOCK_STREAM: result = posix.SOCK_STREAM + of SOCK_DGRAM: result = posix.SOCK_DGRAM + of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET + of SOCK_RAW: result = posix.SOCK_RAW + else: nil + + proc ToInt(p: TProtocol): cint = + case p + of IPPROTO_TCP: result = posix.IPPROTO_TCP + of IPPROTO_UDP: result = posix.IPPROTO_UDP + of IPPROTO_IP: result = posix.IPPROTO_IP + of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 + of IPPROTO_RAW: result = posix.IPPROTO_RAW + of IPPROTO_ICMP: result = posix.IPPROTO_ICMP + else: nil + +proc socket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, protocol: TProtocol = IPPROTO_TCP): TSocket = ## creates a new socket; returns `InvalidSocket` if an error occurs. - result = TSocket(posix.socket(ToInt(domain), ToInt(typ), ToInt(protocol))) + when defined(Windows): + result = TSocket(winlean.socket(ord(domain), ord(typ), ord(protocol))) + else: + result = TSocket(posix.socket(ToInt(domain), ToInt(typ), ToInt(protocol))) proc listen*(socket: TSocket, attempts = 5) = ## listens to socket. - if posix.listen(cint(socket), cint(attempts)) < 0'i32: OSError() + if listen(cint(socket), cint(attempts)) < 0'i32: OSError() proc bindAddr*(socket: TSocket, port = TPort(0)) = + ## binds a port number to a socket. var name: Tsockaddr_in - name.sin_family = posix.AF_INET - name.sin_port = htons(int16(port)) - name.sin_addr.s_addr = htonl(INADDR_ANY) + when defined(Windows): + name.sin_family = int16(ord(AF_INET)) + else: + name.sin_family = posix.AF_INET + name.sin_port = sockets.htons(int16(port)) + name.sin_addr.s_addr = sockets.htonl(INADDR_ANY) if bindSocket(cint(socket), cast[ptr TSockAddr](addr(name)), sizeof(name)) < 0'i32: OSError() proc getSockName*(socket: TSocket): TPort = + ## returns the socket's associated port number. var name: Tsockaddr_in - name.sin_family = posix.AF_INET + when defined(Windows): + name.sin_family = int16(ord(AF_INET)) + else: + name.sin_family = posix.AF_INET #name.sin_port = htons(cint16(port)) #name.sin_addr.s_addr = htonl(INADDR_ANY) var namelen: cint = sizeof(name) if getsockname(cint(socket), cast[ptr TSockAddr](addr(name)), addr(namelen)) == -1'i32: OSError() - result = TPort(ntohs(name.sin_port)) + result = TPort(sockets.ntohs(name.sin_port)) proc accept*(server: TSocket): TSocket = ## waits for a client and returns its socket var client: Tsockaddr_in - var clientLen: TsockLen = sizeof(client) + var clientLen: cint = sizeof(client) result = TSocket(accept(cint(server), cast[ptr TSockAddr](addr(client)), addr(clientLen))) @@ -133,8 +178,163 @@ proc close*(socket: TSocket) = else: discard posix.close(cint(socket)) +proc getServByName*(name, proto: string): TServent = + ## well-known getservbyname proc. + when defined(Windows): + var s = winlean.getservbyname(name, proto) + else: + var s = posix.getservbyname(name, proto) + if s == nil: OSError() + result.name = $s.s_name + result.aliases = cstringArrayToSeq(s.s_aliases) + result.port = TPort(s.s_port) + result.proto = $s.s_proto + +proc getServByPort*(port: TPort, proto: string): TServent = + ## well-known getservbyport proc. + when defined(Windows): + var s = winlean.getservbyport(ze(int16(port)), proto) + else: + var s = posix.getservbyport(ze(int16(port)), proto) + if s == nil: OSError() + result.name = $s.s_name + result.aliases = cstringArrayToSeq(s.s_aliases) + result.port = TPort(s.s_port) + result.proto = $s.s_proto + +proc getHostByName*(name: string): THostEnt = + ## well-known gethostbyname proc. + when defined(Windows): + var s = winlean.gethostbyname(name) + else: + var s = posix.gethostbyname(name) + if s == nil: OSError() + result.name = $s.h_name + result.aliases = cstringArrayToSeq(s.h_aliases) + when defined(windows): + result.addrType = TDomain(s.h_addrtype) + else: + if s.h_addrtype == posix.AF_INET: + result.addrType = AF_INET + elif s.h_addrtype == posix.AF_INET6: + result.addrType = AF_INET6 + else: + OSError("unknown h_addrtype") + result.addrList = cstringArrayToSeq(s.h_addr_list) + result.length = int(s.h_length) + +proc getSockOptInt*(socket: TSocket, level, optname: int): int = + ## getsockopt for integer options. + var res: cint + var size: cint = sizeof(res) + if getsockopt(cint(socket), cint(level), cint(optname), + addr(res), addr(size)) < 0'i32: + OSError() + result = int(res) + +proc setSockOptInt*(socket: TSocket, level, optname, optval: int) = + ## setsockopt for integer options. + var value = cint(optval) + if setsockopt(cint(socket), cint(level), cint(optname), addr(value), + sizeof(value)) < 0'i32: + OSError() + +proc connect*(socket: TSocket, name: string, port = TPort(0), + af: TDomain = AF_INET) = + ## well-known connect operation. Already does ``htons`` on the port number, + ## so you shouldn't do it. + var s: TSockAddrIn + s.sin_addr.s_addr = inet_addr(name) + s.sin_port = sockets.htons(int16(port)) + when defined(windows): + s.sin_family = toU16(ord(af)) + else: + case af + of AF_UNIX: s.sin_family = posix.AF_UNIX + of AF_INET: s.sin_family = posix.AF_INET + of AF_INET6: s.sin_family = posix.AF_INET6 + else: nil + if connect(cint(socket), cast[ptr TSockAddr](addr(s)), sizeof(s)) < 0'i32: + OSError() + +#proc recvfrom*(s: TWinSocket, buf: cstring, len, flags: cint, +# fromm: ptr TSockAddr, fromlen: ptr cint): cint + +#proc sendto*(s: TWinSocket, buf: cstring, len, flags: cint, +# to: ptr TSockAddr, tolen: cint): cint + +proc createFdSet(fd: var TFdSet, s: seq[TSocket], m: var int) = + FD_ZERO(fd) + for i in items(s): + m = max(m, int(i)) + FD_SET(cint(i), fd) + +proc pruneSocketSet(s: var seq[TSocket], fd: var TFdSet) = + var i = 0 + var L = s.len + while i < L: + if FD_ISSET(cint(s[i]), fd) != 0'i32: + s[i] = s[L-1] + dec(L) + else: + inc(i) + setLen(s, L) + +proc select*(readfds, writefds, exceptfds: var seq[TSocket], + timeout = 500): int = + ## select with a sensible Nimrod interface. `timeout` is in miliseconds. + var tv: TTimeVal + tv.tv_sec = 0 + tv.tv_usec = timeout * 1000 + + var rd, wr, ex: TFdSet + var m = 0 + createFdSet((rd), readfds, m) + createFdSet((wr), writefds, m) + createFdSet((ex), exceptfds, m) + + result = int(select(cint(m), addr(rd), addr(wr), addr(ex), addr(tv))) + + pruneSocketSet(readfds, (rd)) + pruneSocketSet(writefds, (wr)) + pruneSocketSet(exceptfds, (ex)) + +proc select*(readfds, writefds: var seq[TSocket], + timeout = 500): int = + ## select with a sensible Nimrod interface. `timeout` is in miliseconds. + var tv: TTimeVal + tv.tv_sec = 0 + tv.tv_usec = timeout * 1000 + + var rd, wr: TFdSet + var m = 0 + createFdSet((rd), readfds, m) + createFdSet((wr), writefds, m) + + result = int(select(cint(m), addr(rd), addr(wr), nil, addr(tv))) + + pruneSocketSet(readfds, (rd)) + pruneSocketSet(writefds, (wr)) + + +proc select*(readfds: var seq[TSocket], timeout = 500): int = + ## select with a sensible Nimrod interface. `timeout` is in miliseconds. + var tv: TTimeVal + tv.tv_sec = 0 + tv.tv_usec = timeout * 1000 + + var rd: TFdSet + var m = 0 + createFdSet((rd), readfds, m) + + result = int(select(cint(m), addr(rd), nil, nil, addr(tv))) + + pruneSocketSet(readfds, (rd)) + + proc recvLine*(socket: TSocket, line: var string): bool = - ## returns false if no further data is available. + ## returns false if no further data is available. `line` must be initalized + ## and not nil! setLen(line, 0) while true: var c: char @@ -150,11 +350,11 @@ proc recvLine*(socket: TSocket, line: var string): bool = add(line, c) proc recv*(socket: TSocket, data: pointer, size: int): int = - ## receive data from a socket - result = posix.recv(cint(socket), data, size, 0'i32) + ## receives data from a socket + result = recv(cint(socket), data, size, 0'i32) proc recv*(socket: TSocket): string = - ## receive all the data from the socket + ## receives all the data from the socket const bufSize = 200 var buf = newString(bufSize) result = "" @@ -173,36 +373,15 @@ proc skip*(socket: TSocket) = dealloc(buf) proc send*(socket: TSocket, data: pointer, size: int): int = - result = posix.send(cint(socket), data, size, 0'i32) + ## sends data to a socket. + result = send(cint(socket), data, size, 0'i32) proc send*(socket: TSocket, data: string) = + ## sends data to a socket. if send(socket, cstring(data), data.len) != data.len: OSError() -proc ntohl*(x: int32): int32 = - ## Convert 32-bit integers from network to host byte order. - ## On machines where the host byte order is the same as network byte order, - ## this is a no-op; otherwise, it performs a 4-byte swap operation. - when cpuEndian == bigEndian: result = x - else: result = (x shr 24'i32) or - (x shr 8'i32 and 0xff00'i32) or - (x shl 8'i32 and 0xff0000'i32) or - (x shl 24'i32) - -proc ntohs*(x: int16): int16 = - ## Convert 16-bit integers from network to host byte order. On machines - ## where the host byte order is the same as network byte order, this is - ## a no-op; otherwise, it performs a 2-byte swap operation. - when cpuEndian == bigEndian: result = x - else: result = (x shr 8'i16) or (x shl 8'i16) +when defined(Windows): + var wsa: TWSADATA + if WSAStartup(0x0101'i16, wsa) != 0: OSError() -proc htonl*(x: int32): int32 = - ## Convert 32-bit integers from host to network byte order. On machines - ## where the host byte order is the same as network byte order, this is - ## a no-op; otherwise, it performs a 4-byte swap operation. - result = sockets.ntohl(x) -proc htons*(x: int16): int16 = - ## Convert 16-bit positive integers from host to network byte order. - ## On machines where the host byte order is the same as network byte - ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. - result = sockets.ntohs(x) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index d7fd69f61..1dfb070bc 100755 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2009 Andreas Rumpf +# (c) Copyright 2010 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -655,8 +655,7 @@ proc replace*(s: string, sub, by: char): string = proc delete*(s: var string, first, last: int) = ## Deletes in `s` the characters at position `first`..`last`. This modifies ## `s` itself, it does not return a copy. - var - i = first + var i = first # example: "abc___uvwxyz\0" (___ is to be deleted) # --> first == 3, last == 5 # s[first..] = s[last+1..] @@ -851,6 +850,7 @@ proc escape*(s: string, prefix = "\"", suffix = "\""): string = proc validEmailAddress*(s: string): bool = ## returns true if `s` seems to be a valid e-mail address. ## The checking also uses a domain list. + ## Note: This will be moved into another module soon. const chars = Letters + Digits + {'!','#','$','%','&', '\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'} diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 8c21b6027..a54af3254 100755 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2009 Andreas Rumpf +# (c) Copyright 2010 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. |