Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

^D now works with readLineFromStdin (in particular in nim secret) #18442

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,9 @@
the official Nim style guide. To be enabled, this has to be combined either
with `--styleCheck:error` or `--styleCheck:hint`.

- `nim secret` and other tools relying on `rdstdin.readLineFromStdin` now supports
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
exiting via `^D` for platforms that support `linenoise`.



## Tool changes
Expand Down
42 changes: 18 additions & 24 deletions compiler/llstream.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,8 @@
#

## Low-level streams for high performance.

import
pathutils

# support `useGnuReadline`, `useLinenoise` for backwards compatibility
const hasRstdin = (defined(nimUseLinenoise) or defined(useLinenoise) or defined(useGnuReadline)) and
not defined(windows)

when hasRstdin: import rdstdin
import std/rdstdin
import pathutils

type
TLLRepl* = proc (s: PLLStream, buf: pointer, bufLen: int): int
Expand All @@ -28,6 +21,7 @@ type
llsStdIn # stream encapsulates stdin
TLLStream* = object of RootObj
kind*: TLLStreamKind # accessible for low-level access (lexbase uses this)
closed*: bool # only used for llsStdIn at the moment; we could use a case object module refactorings
f*: File
s*: string
rd*, wr*: int # for string streams
Expand Down Expand Up @@ -72,15 +66,6 @@ proc llStreamClose*(s: PLLStream) =
of llsFile:
close(s.f)

when not declared(readLineFromStdin):
# fallback implementation:
proc readLineFromStdin(prompt: string, line: var string): bool =
stdout.write(prompt)
result = readLine(stdin, line)
if not result:
stdout.write("\n")
quit(0)

proc endsWith*(x: string, s: set[char]): bool =
var i = x.len-1
while i >= 0 and x[i] == ' ': dec(i)
Expand Down Expand Up @@ -111,13 +96,22 @@ proc countTriples(s: string): int =
proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int =
s.s = ""
s.rd = 0
var line = newStringOfCap(120)
var triples = 0
while readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line):
s.s.add(line)
s.s.add("\n")
inc triples, countTriples(line)
if not continueLine(line, (triples and 1) == 1): break
var data: ReadLine
while true:
data.prompt = if s.s.len == 0: ">>> " else: "... "
readLineFromStdin(data)
if isEndOfFile(data.status) or isError(data.status):
s.closed = true
# better than `quit()` which prevents using in a library or epilogue to complete
break
elif isInterrupt(data.status): continue
elif isNormal(data.status):
s.s.add(data.line)
s.s.add("\n")
inc triples, countTriples(data.line)
if not continueLine(data.line, (triples and 1) == 1): break
else: doAssert false, $data.status
inc(s.lineOffset)
result = min(bufLen, s.s.len - s.rd)
if result > 0:
Expand Down
2 changes: 1 addition & 1 deletion compiler/passes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ proc processModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator;
#echo "----- single\n", n
if not processTopLevelStmt(graph, n, a): break
closeParser(p)
if s.kind != llsStdIn: break
if s.kind != llsStdIn or s.closed: break
closePasses(graph, a)
if graph.config.backend notin {backendC, backendCpp, backendObjc}:
# We only write rod files here if no C-like backend is active.
Expand Down
87 changes: 49 additions & 38 deletions lib/impure/rdstdin.nim
Original file line number Diff line number Diff line change
Expand Up @@ -22,48 +22,59 @@ runnableExamples("-r:off"):
if line.len > 0: echo line
echo "exiting"

when defined(windows):
proc readLineFromStdin*(prompt: string): string {.
tags: [ReadIOEffect, WriteIOEffect].} =
## Reads a line from stdin.
stdout.write(prompt)
result = readLine(stdin)
import std/private/rdstdin_impl

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)
type ReadLine* = object
prompt*: string
line*: string
status*: ReadlineStatus

elif defined(genode):
proc readLineFromStdin*(prompt: string): string {.
tags: [ReadIOEffect, WriteIOEffect].} =
stdin.readLine()
proc initReadLine*(prompt: string): ReadLine = ReadLine(prompt: prompt)

proc readLineFromStdin*(prompt: string, line: var string): bool {.
tags: [ReadIOEffect, WriteIOEffect].} =
stdin.readLine(line)
# These APIs are recommended to allow growing `ReadlineStatus`.
proc isError*(a: ReadlineStatus): bool {.inline.} =
a == lnCtrlUnkown

else:
proc isEndOfFile*(a: ReadlineStatus): bool {.inline.} =
a == lnCtrlD

proc isInterrupt*(a: ReadlineStatus): bool {.inline.} =
a == lnCtrlC

proc isNormal*(a: ReadlineStatus): bool {.inline.} =
a == lnNormal

import std/os

const hasReadline = not (defined(windows) or defined(genode)) and fileExists(currentSourcePath.parentDir.parentDir / "wrappers/linenoise/linenoise.c")

when hasReadline:
import 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*(data: var ReadLine) {.tags: [ReadIOEffect, WriteIOEffect].} =
when hasReadline:
var data2 = ReadLineResult(line: data.line)
readLineStatus(data.prompt, data2)
data.line = data2.line
data.status = data2.status
if data.line.len > 0:
historyAdd data.line.cstring
else:
stdout.write(data.prompt)
let ok = stdin.readLine(data.line)
data.status = if ok: lnNormal else: lnCtrlUnkown

proc readLineFromStdin*(prompt: string, line: var string): bool =
## Reads a `line` from stdin. 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.
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
## Returns `false` if the end of the file has been reached, `true`
## otherwise. If `false` is returned `line` contains no new data.
var data = ReadLine(prompt: prompt)
line = data.line
result = not data.status.isError()

proc readLineFromStdin*(prompt: string): string {.inline.} =
if not readLineFromStdin(prompt, result):
raise newException(IOError, "Linenoise returned nil")
proc readLineFromStdin*(prompt: string): string {.inline.} =
## Reads a line from stdin.
if not readLineFromStdin(prompt, result):
raise newException(IOError, "Linenoise returned nil")
5 changes: 5 additions & 0 deletions lib/std/private/rdstdin_impl.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type ReadlineStatus* = enum
lnCtrlUnkown
lnCtrlC
lnCtrlD
lnNormal
22 changes: 9 additions & 13 deletions lib/wrappers/linenoise/linenoise.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# distribution, for details about the copyright.
#

import std/private/rdstdin_impl

type
Completions* = object
len*: csize_t
Expand All @@ -32,7 +34,11 @@ proc printKeyCodes*() {.importc: "linenoisePrintKeyCodes".}

proc free*(s: cstring) {.importc: "free", header: "<stdlib.h>".}

when defined(nimExperimentalLinenoiseExtra) and not defined(windows):
type ReadLineResult* = object
line*: string
status*: ReadlineStatus

when not defined(windows):
# C interface
type LinenoiseStatus = enum
linenoiseStatus_ctrl_unknown
Expand All @@ -44,21 +50,11 @@ when defined(nimExperimentalLinenoiseExtra) and not defined(windows):

proc linenoiseExtra(prompt: cstring, data: ptr LinenoiseData): cstring {.importc.}

# stable nim interface
type Status* = enum
lnCtrlUnkown
lnCtrlC
lnCtrlD

type ReadLineResult* = object
line*: string
status*: Status

proc readLineStatus*(prompt: string, result: var ReadLineResult) =
## line editing API that allows returning the line entered and an indicator
## of which control key was entered, allowing user to distinguish between
## for example ctrl-C vs ctrl-D.
runnableExamples("-d:nimExperimentalLinenoiseExtra -r:off"):
runnableExamples("-r:off"):
var ret: ReadLineResult
while true:
readLineStatus("name: ", ret) # ctrl-D will exit, ctrl-C will go to next prompt
Expand All @@ -69,4 +65,4 @@ when defined(nimExperimentalLinenoiseExtra) and not defined(windows):
let buf = linenoiseExtra(prompt, data.addr)
result.line = $buf
free(buf)
result.status = data.status.ord.Status
result.status = if buf != nil: lnNormal else: data.status.ord.ReadlineStatus
1 change: 0 additions & 1 deletion tests/config.nims
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,3 @@ hint("Processing", off)
# sync with `kochdocs.docDefines` or refactor.
switch("define", "nimExperimentalAsyncjsThen")
switch("define", "nimExperimentalJsfetch")
switch("define", "nimExperimentalLinenoiseExtra")
2 changes: 1 addition & 1 deletion tools/kochdocs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const
gaCode* = " --doc.googleAnalytics:UA-48159761-1"
# errormax: subsequent errors are probably consequences of 1st one; a simple
# bug could cause unlimited number of errors otherwise, hard to debug in CI.
docDefines = "-d:nimExperimentalAsyncjsThen -d:nimExperimentalJsfetch -d:nimExperimentalLinenoiseExtra"
docDefines = "-d:nimExperimentalAsyncjsThen -d:nimExperimentalJsfetch"
nimArgs = "--errormax:3 --hint:Conf:off --hint:Path:off --hint:Processing:off --hint:XDeclaredButNotUsed:off --warning:UnusedImport:off -d:boot --putenv:nimversion=$# $#" % [system.NimVersion, docDefines]
gitUrl = "https://github.com/nim-lang/Nim"
docHtmlOutput = "doc/html"
Expand Down