diff options
author | bptato <nincsnevem662@gmail.com> | 2022-09-12 00:30:21 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2022-09-12 00:30:21 +0200 |
commit | 51ea622d58bfca19212fac1800cfb033bb85ec39 (patch) | |
tree | b75891690f67b190c60584751f2a30c96f342fdc /src/io | |
parent | e38402dfa1bbc33db6b9d9736517eb45533d595c (diff) | |
download | chawan-51ea622d58bfca19212fac1800cfb033bb85ec39.tar.gz |
Add JS binding generation
Diffstat (limited to 'src/io')
-rw-r--r-- | src/io/buffer.nim | 47 | ||||
-rw-r--r-- | src/io/lineedit.nim | 6 | ||||
-rw-r--r-- | src/io/loader.nim | 83 | ||||
-rw-r--r-- | src/io/process.nim | 22 | ||||
-rw-r--r-- | src/io/socketstream.nim | 35 | ||||
-rw-r--r-- | src/io/term.nim | 30 |
6 files changed, 131 insertions, 92 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim index 52a55819..8b1268ef 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -931,12 +931,12 @@ proc displayStatusMessage*(buffer: Buffer) = print(SGR()) # https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set -proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): Table[string, string] = +proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): seq[tuple[name, value: string]] = if form.constructingentrylist: return form.constructingentrylist = true - var entrylist: Table[string, string] + var entrylist: seq[tuple[name, value: string]] for field in form.controls: if field.findAncestor({TAG_DATALIST}) != nil or field.attrb("disabled") or @@ -950,8 +950,8 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin field.attr("name") & '.' else: "" - entrylist[name & 'x'] = $field.xcoord - entrylist[name & 'y'] = $field.ycoord + entrylist.add((name & 'x', $field.xcoord)) + entrylist.add((name & 'y', $field.ycoord)) continue #TODO custom elements @@ -965,13 +965,13 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin let field = HTMLSelectElement(field) for option in field.options: if option.selected or option.disabled: - entrylist[name] = option.value + entrylist.add((name, option.value)) elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_CHECKBOX, INPUT_RADIO}: let value = if field.attr("value") != "": field.attr("value") else: "on" - entrylist[name] = value + entrylist.add((name, value)) elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType == INPUT_FILE: #TODO file discard @@ -980,10 +980,10 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin encoding else: "UTF-8" - entrylist[name] = charset + entrylist.add((name, charset)) else: if field.tagType == TAG_INPUT: - entrylist[name] = HTMLInputElement(field).value + entrylist.add((name, HTMLInputElement(field).value)) else: assert false if field.tagType == TAG_TEXTAREA or @@ -991,20 +991,11 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin if field.attr("dirname") != "": let dirname = field.attr("dirname") let dir = "ltr" #TODO bidi - entrylist[dirname] = dir + entrylist.add((dirname, dir)) form.constructingentrylist = false return entrylist -#https://url.spec.whatwg.org/#concept-urlencoded-serializer -proc serializeApplicationXWWFormUrlEncoded(kvs: Table[string, string]): string = - for name, value in kvs: - if result != "": - result &= '&' - result.percentEncode(name, ApplicationXWWWFormUrlEncodedSet, true) - result &= '=' - result.percentEncode(value, ApplicationXWWWFormUrlEncodedSet, true) - #https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm proc makeCRLF(s: string): string = result = newStringOfCap(s.len) @@ -1022,19 +1013,19 @@ proc makeCRLF(s: string): string = result &= s[i] inc i -proc serializeMultipartFormData(kvs: Table[string, string]): MimeData = - for name, value in kvs: - let name = makeCRLF(name) - let value = makeCRLF(value) +proc serializeMultipartFormData(kvs: seq[(string, string)]): MimeData = + for it in kvs: + let name = makeCRLF(it[0]) + let value = makeCRLF(it[1]) result[name] = value -proc serializePlainTextFormData(kvs: Table[string, string]): string = - for name, value in kvs: +proc serializePlainTextFormData(kvs: seq[(string, string)]): string = + for it in kvs: + let (name, value) = it result &= name result &= '=' result &= value - result &= '\r' - result &= '\n' + result &= "\r\n" proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] = let entrylist = form.constructEntryList(submitter) @@ -1068,7 +1059,7 @@ proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] = #let noopener = true #TODO template mutateActionUrl() = - let query = serializeApplicationXWWFormUrlEncoded(entrylist) + let query = serializeApplicationXWWWFormUrlEncoded(entrylist) parsedaction.query = query.some return newRequest(parsedaction, httpmethod).some @@ -1078,7 +1069,7 @@ proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] = var multipart = none(MimeData) case enctype of FORM_ENCODING_TYPE_URLENCODED: - body = serializeApplicationXWWFormUrlEncoded(entrylist).some + body = serializeApplicationXWWWFormUrlEncoded(entrylist).some mimeType = $enctype of FORM_ENCODING_TYPE_MULTIPART: multipart = serializeMultipartFormData(entrylist).some diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim index 5c756625..def2feb2 100644 --- a/src/io/lineedit.nim +++ b/src/io/lineedit.nim @@ -4,8 +4,9 @@ import strutils import sequtils import sugar -import utils/twtstr import config/config +import io/term +import utils/twtstr type LineState* = object news*: seq[Rune] @@ -143,7 +144,8 @@ proc readLine(state: var LineState): bool = else: state.feedNext = false - let c = getch() + restoreStdin() + let c = stdin.readChar() state.s &= c var action = getLinedAction(state.s) diff --git a/src/io/loader.nim b/src/io/loader.nim index 31b51930..7c68258e 100644 --- a/src/io/loader.nim +++ b/src/io/loader.nim @@ -15,15 +15,16 @@ import options import streams import tables import net -import os when defined(posix): import posix import bindings/curl import io/http +import io/process import io/request import io/serialize import io/socketstream +import types/mime import types/url import utils/twtstr @@ -75,88 +76,80 @@ proc loadResource(loader: FileLoader, request: Request, ostream: Stream) = ostream.swrite(-1) # error ostream.flush() -const SocketDirectory = "/tmp/cha/" -const SocketPathPrefix = SocketDirectory & "cha_sock_" -func getSocketPath(pid: Pid): string = - SocketPathPrefix & $pid - -proc runFileLoader(loader: FileLoader) = +proc runFileLoader(loader: FileLoader, loadcb: proc()) = if curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK: raise newException(Defect, "Failed to initialize libcurl.") - let path = getSocketPath(getpid()) - discard unlink(cstring(path)) - createDir(SocketDirectory) - let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP) - bindUnix(sock, path) - listen(sock) - stdout.write(char(0u8)) - stdout.flushFile() + let ssock = initServerSocket(getpid()) + # The server has been initialized, so the main process can resume execution. + loadcb() while true: - var sock2: Socket - sock.accept(sock2) - let istream = newSocketStream(sock2, nil) - let ostream = newSocketStream(nil, sock2) + let stream = ssock.acceptSocketStream() try: - let request = istream.readRequest() + let request = stream.readRequest() for k, v in loader.defaultHeaders.table: if k notin request.headers.table: request.headers.table[k] = v - loader.loadResource(request, ostream) + loader.loadResource(request, stream) except IOError: # End-of-file, quit. # TODO this should be EOFError break - close(sock2) + stream.close() curl_global_cleanup() - close(sock) - discard unlink(cstring(path)) + ssock.close() quit(0) proc doRequest*(loader: FileLoader, request: Request): Response = - let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP) - let path = getSocketPath(loader.process) - connectUnix(sock, path) - let istream = newSocketStream(nil, sock) - let ostream = newSocketStream(sock, nil) - istream.swrite(request) - istream.flush() - ostream.sread(result.res) + let stream = connectSocketStream(loader.process) + stream.swrite(request) + stream.flush() + stream.sread(result.res) if result.res == 0: - ostream.sread(result.status) - ostream.sread(result.headers) + stream.sread(result.status) + stream.sread(result.headers) if "Content-Type" in result.headers.table: result.contenttype = result.headers.table["Content-Type"][0].until(';') + else: + result.contenttype = guessContentType($request.url) if "Location" in result.headers.table: let location = result.headers.table["Location"][0] result.redirect = parseUrl(location, some(request.url)) # Only a stream of the response body may arrive after this point. - result.body = ostream + result.body = stream proc newFileLoader*(defaultHeaders: HeaderList): FileLoader = new(result) result.defaultHeaders = defaultHeaders when defined(posix): - var pipefd_b: array[0..1, cint] - if pipe(pipefd_b) == -1: + var pipefd: array[0..1, cint] + if pipe(pipefd) == -1: raise newException(Defect, "Failed to open pipe.") let pid = fork() if pid == -1: raise newException(Defect, "Failed to fork network process") elif pid == 0: # child process - let writefd = pipefd_b[1] # get write b - discard close(pipefd_b[0]) # close read b - discard dup2(writefd, stdout.getFileHandle()) - result.runFileLoader() + discard close(pipefd[0]) # close read + var writef: File + if not open(writef, FileHandle(pipefd[1]), fmWrite): + raise newException(Defect, "Failed to open input handle.") + result.runFileLoader((proc() = + writef.write(char(0u8)) + writef.flushFile() + close(writef) + discard close(pipefd[1]) + )) else: result.process = pid - let readfd = pipefd_b[0] # get read b - discard close(pipefd_b[1]) # close write b + let readfd = pipefd[0] # get read + discard close(pipefd[1]) # close write var readf: File if not open(readf, FileHandle(readfd), fmRead): raise newException(Defect, "Failed to open output handle.") - var n: uint8 - assert newFileStream(readf).readUint8() == 0u8 + assert readf.readChar() == char(0u8) + close(readf) + discard close(pipefd[0]) + proc newFileLoader*(): FileLoader = newFileLoader(DefaultHeaders) diff --git a/src/io/process.nim b/src/io/process.nim index e081ecc6..ffdf5d91 100644 --- a/src/io/process.nim +++ b/src/io/process.nim @@ -1,6 +1,12 @@ +import net +import os when defined(posix): import posix +type ServerSocket* = object + sock*: Socket + path*: string + proc doFork*(): Pid = result = fork() if result == -1: @@ -14,3 +20,19 @@ proc doFork*(): Pid = quit(0) return 0 +const SocketDirectory = "/tmp/cha/" +const SocketPathPrefix = SocketDirectory & "cha_sock_" +func getSocketPath*(pid: Pid): string = + SocketPathPrefix & $pid + +proc initServerSocket*(pid: Pid): ServerSocket = + createDir(SocketDirectory) + result.sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP) + result.path = getSocketPath(getpid()) + discard unlink(cstring(result.path)) + bindUnix(result.sock, result.path) + listen(result.sock) + +proc close*(ssock: ServerSocket) = + close(ssock.sock) + discard unlink(cstring(ssock.path)) diff --git a/src/io/socketstream.nim b/src/io/socketstream.nim index 73a00aa2..d7f43625 100644 --- a/src/io/socketstream.nim +++ b/src/io/socketstream.nim @@ -1,37 +1,50 @@ import net import streams +when defined(posix): + import posix + +import io/process type SocketStream* = ref object of Stream - isource: Socket - osource: Socket + source: Socket isend: bool proc sockReadData(s: Stream, buffer: pointer, len: int): int = let s = SocketStream(s) - result = s.isource.recv(buffer, len) + result = s.source.recv(buffer, len) if result < 0: raise newException(Defect, "Failed to read data") elif result < len: s.isend = true proc sockWriteData(s: Stream, buffer: pointer, len: int) = - discard SocketStream(s).osource.send(buffer, len) + discard SocketStream(s).source.send(buffer, len) proc sockAtEnd(s: Stream): bool = SocketStream(s).isend proc sockClose(s: Stream) = {.cast(tags: []).}: #...sigh let s = SocketStream(s) - if s.isource != nil: - s.isource.close() - if s.osource != nil and s.isource != s.osource: - s.osource.close() + s.source.close() -func newSocketStream*(isource, osource: Socket): SocketStream = +func newSocketStream*(): SocketStream = new(result) - result.isource = isource - result.osource = osource result.readDataImpl = sockReadData result.writeDataImpl = sockWriteData result.atEndImpl = sockAtEnd result.closeImpl = sockClose + +proc connectSocketStream*(path: string): SocketStream = + result = newSocketStream() + let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP) + connectUnix(sock, path) + result.source = sock + +proc connectSocketStream*(pid: Pid): SocketStream = + connectSocketStream(getSocketPath(pid)) + +proc acceptSocketStream*(ssock: ServerSocket): SocketStream = + result = newSocketStream() + var sock: Socket + ssock.sock.accept(sock) + result.source = sock diff --git a/src/io/term.nim b/src/io/term.nim index 914b5398..d9609715 100644 --- a/src/io/term.nim +++ b/src/io/term.nim @@ -1,7 +1,6 @@ import terminal + import std/exitprocs -when defined(posix): - import termios type TermAttributes* = object @@ -14,6 +13,9 @@ type height_px*: int when defined(posix): + import posix + import termios + # see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html let stdin_fileno = stdin.getFileHandle() var orig_termios: Termios @@ -27,12 +29,28 @@ when defined(posix): raw.c_iflag = raw.c_iflag and not (BRKINT or ICRNL or INPCK or ISTRIP or IXON) raw.c_oflag = raw.c_oflag and not (OPOST) raw.c_cflag = raw.c_cflag or CS8 - # we do not currently set ISIG, so that ctrl+c can be used to - # immediately return to the input loop. - #TODO set it once we have separated i/o from layout - raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or IEXTEN) + raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or ISIG or IEXTEN) discard tcSetAttr(stdin_fileno, TCSAFLUSH, addr raw) + var orig_flags: cint + var stdin_unblocked = false + proc unblockStdin*() = + orig_flags = fcntl(getFileHandle(stdin), F_GETFL, 0) + let flags = orig_flags or O_NONBLOCK + discard fcntl(getFileHandle(stdin), F_SETFL, flags) + stdin_unblocked = true + + proc restoreStdin*() = + if stdin_unblocked: + discard fcntl(getFileHandle(stdin), F_SETFL, orig_flags) + stdin_unblocked = false +else: + proc unblockStdin*(): cint = + discard + + proc restoreStdin*(flags: cint) = + discard + proc getTermAttributes*(): TermAttributes = if stdin.isatty(): when defined(posix): |