rdstdin.nim 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module contains code for reading from `stdin`:idx:. On UNIX the
  10. ## linenoise library is wrapped and set up to provide default key bindings
  11. ## (e.g. you can navigate with the arrow keys). On Windows ``system.readLine``
  12. ## is used. This suffices because Windows' console already provides the
  13. ## wanted functionality.
  14. {.deadCodeElim: on.}
  15. when defined(Windows):
  16. proc readLineFromStdin*(prompt: string): TaintedString {.
  17. tags: [ReadIOEffect, WriteIOEffect].} =
  18. ## Reads a line from stdin.
  19. stdout.write(prompt)
  20. result = readLine(stdin)
  21. proc readLineFromStdin*(prompt: string, line: var TaintedString): bool {.
  22. tags: [ReadIOEffect, WriteIOEffect].} =
  23. ## Reads a `line` from stdin. `line` must not be
  24. ## ``nil``! May throw an IO exception.
  25. ## A line of text may be delimited by ``CR``, ``LF`` or
  26. ## ``CRLF``. The newline character(s) are not part of the returned string.
  27. ## Returns ``false`` if the end of the file has been reached, ``true``
  28. ## otherwise. If ``false`` is returned `line` contains no new data.
  29. stdout.write(prompt)
  30. result = readLine(stdin, line)
  31. import winlean
  32. const
  33. VK_SHIFT* = 16
  34. VK_CONTROL* = 17
  35. VK_MENU* = 18
  36. KEY_EVENT* = 1
  37. type
  38. KEY_EVENT_RECORD = object
  39. bKeyDown: WinBool
  40. wRepeatCount: uint16
  41. wVirtualKeyCode: uint16
  42. wVirtualScanCode: uint16
  43. unicodeChar: uint16
  44. dwControlKeyState: uint32
  45. INPUT_RECORD = object
  46. eventType*: int16
  47. reserved*: int16
  48. event*: KEY_EVENT_RECORD
  49. safetyBuffer: array[0..5, DWORD]
  50. proc readConsoleInputW*(hConsoleInput: HANDLE, lpBuffer: var INPUTRECORD,
  51. nLength: uint32,
  52. lpNumberOfEventsRead: var uint32): WINBOOL{.
  53. stdcall, dynlib: "kernel32", importc: "ReadConsoleInputW".}
  54. proc getch(): uint16 =
  55. let hStdin = getStdHandle(STD_INPUT_HANDLE)
  56. var
  57. irInputRecord: INPUT_RECORD
  58. dwEventsRead: uint32
  59. while readConsoleInputW(hStdin, irInputRecord, 1, dwEventsRead) != 0:
  60. if irInputRecord.eventType == KEY_EVENT and
  61. irInputRecord.event.wVirtualKeyCode notin {VK_SHIFT, VK_MENU, VK_CONTROL}:
  62. result = irInputRecord.event.unicodeChar
  63. discard readConsoleInputW(hStdin, irInputRecord, 1, dwEventsRead)
  64. return result
  65. from unicode import toUTF8, Rune, runeLenAt
  66. proc readPasswordFromStdin*(prompt: string, password: var TaintedString):
  67. bool {.tags: [ReadIOEffect, WriteIOEffect].} =
  68. ## Reads a `password` from stdin without printing it. `password` must not
  69. ## be ``nil``! Returns ``false`` if the end of the file has been reached,
  70. ## ``true`` otherwise.
  71. password.setLen(0)
  72. stdout.write(prompt)
  73. while true:
  74. let c = getch()
  75. case c.char
  76. of '\r', chr(0xA):
  77. break
  78. of '\b':
  79. # ensure we delete the whole UTF-8 character:
  80. var i = 0
  81. var x = 1
  82. while i < password.len:
  83. x = runeLenAt(password, i)
  84. inc i, x
  85. password.setLen(max(password.len - x, 0))
  86. else:
  87. password.add(toUTF8(c.Rune))
  88. stdout.write "\n"
  89. else:
  90. import linenoise, termios
  91. proc readLineFromStdin*(prompt: string): TaintedString {.
  92. tags: [ReadIOEffect, WriteIOEffect].} =
  93. var buffer = linenoise.readLine(prompt)
  94. if isNil(buffer):
  95. raise newException(IOError, "Linenoise returned nil")
  96. result = TaintedString($buffer)
  97. if result.string.len > 0:
  98. historyAdd(buffer)
  99. linenoise.free(buffer)
  100. proc readLineFromStdin*(prompt: string, line: var TaintedString): bool {.
  101. tags: [ReadIOEffect, WriteIOEffect].} =
  102. var buffer = linenoise.readLine(prompt)
  103. if isNil(buffer):
  104. line.string.setLen(0)
  105. return false
  106. line = TaintedString($buffer)
  107. if line.string.len > 0:
  108. historyAdd(buffer)
  109. linenoise.free(buffer)
  110. result = true
  111. proc readPasswordFromStdin*(prompt: string, password: var TaintedString):
  112. bool {.tags: [ReadIOEffect, WriteIOEffect].} =
  113. password.setLen(0)
  114. let fd = stdin.getFileHandle()
  115. var cur, old: Termios
  116. discard fd.tcgetattr(cur.addr)
  117. old = cur
  118. cur.c_lflag = cur.c_lflag and not Cflag(ECHO)
  119. discard fd.tcsetattr(TCSADRAIN, cur.addr)
  120. stdout.write prompt
  121. result = stdin.readLine(password)
  122. stdout.write "\n"
  123. discard fd.tcsetattr(TCSADRAIN, old.addr)
  124. proc readPasswordFromStdin*(prompt: string): TaintedString =
  125. ## Reads a password from stdin without printing it.
  126. result = TaintedString("")
  127. discard readPasswordFromStdin(prompt, result)