summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorThijs <thijs@thijsschreijer.nl>2023-11-16 09:09:54 +0100
committerThijs Schreijer <thijs@thijsschreijer.nl>2024-04-30 09:28:01 +0200
commitbd994461ef7c2553da9a6945c685152bad50eb8f (patch)
tree28adc32712f00a200a34357e731a570bf1a359dc /examples
parent47c24eed0191f8f72646be63dee94ac2b35eb062 (diff)
downloadluasystem-bd994461ef7c2553da9a6945c685152bad50eb8f.zip
feat(term): getting/setting terminal config flags
Diffstat (limited to 'examples')
-rw-r--r--examples/compat.lua37
-rw-r--r--examples/flag_debugging.lua7
-rw-r--r--examples/password_input.lua59
-rw-r--r--examples/read.lua119
-rw-r--r--examples/spinner.lua64
-rw-r--r--examples/spiral_snake.lua72
6 files changed, 358 insertions, 0 deletions
diff --git a/examples/compat.lua b/examples/compat.lua
new file mode 100644
index 0000000..c00d44a
--- /dev/null
+++ b/examples/compat.lua
@@ -0,0 +1,37 @@
+-- This example shows how to remove platform differences to create a
+-- cross-platform level playing field.
+
+local sys = require "system"
+
+
+
+if sys.is_windows then
+ -- Windows holds multiple copies of environment variables, to ensure `getenv`
+ -- returns what `setenv` sets we need to use the `system.getenv` instead of
+ -- `os.getenv`.
+ os.getenv = sys.getenv -- luacheck: ignore
+
+ -- Set up the terminal to handle ANSI escape sequences on Windows.
+ if sys.isatty(io.stdout) then
+ sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+ end
+ if sys.isatty(io.stderr) then
+ sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+ end
+ if sys.isatty(io.stdin) then
+ sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdout) + sys.ENABLE_VIRTUAL_TERMINAL_INPUT)
+ end
+
+
+else
+ -- On Posix, one can set a variable to an empty string, but on Windows, this
+ -- will remove the variable from the environment. To make this consistent
+ -- across platforms, we will remove the variable from the environment if the
+ -- value is an empty string.
+ local old_setenv = sys.setenv
+ function sys.setenv(name, value)
+ if value == "" then value = nil end
+ return old_setenv(name, value)
+ end
+end
+
diff --git a/examples/flag_debugging.lua b/examples/flag_debugging.lua
new file mode 100644
index 0000000..5f1d496
--- /dev/null
+++ b/examples/flag_debugging.lua
@@ -0,0 +1,7 @@
+local sys = require "system"
+
+-- Print the Windows Console flags for stdin
+sys.listconsoleflags(io.stdin)
+
+-- Print the Posix termios flags for stdin
+sys.listtermflags(io.stdin)
diff --git a/examples/password_input.lua b/examples/password_input.lua
new file mode 100644
index 0000000..2994062
--- /dev/null
+++ b/examples/password_input.lua
@@ -0,0 +1,59 @@
+local sys = require "system"
+
+print [[
+
+This example shows how to disable the "echo" of characters read to the console,
+useful for reading secrets from the user.
+
+]]
+
+--- Function to read from stdin without echoing the input (for secrets etc).
+-- It will (in a platform agnostic way) disable echo on the terminal, read the
+-- input, and then re-enable echo.
+-- @param ... Arguments to pass to `io.stdin:read()`
+-- @return the results of `io.stdin:read(...)`
+local function read_secret(...)
+ local w_oldflags, p_oldflags
+
+ if sys.isatty(io.stdin) then
+ -- backup settings, configure echo flags
+ w_oldflags = sys.getconsoleflags(io.stdin)
+ p_oldflags = sys.tcgetattr(io.stdin)
+ -- set echo off to not show password on screen
+ assert(sys.setconsoleflags(io.stdin, w_oldflags - sys.CIF_ECHO_INPUT))
+ assert(sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = p_oldflags.lflag - sys.L_ECHO }))
+ end
+
+ local secret, err = io.stdin:read(...)
+
+ -- restore settings
+ if sys.isatty(io.stdin) then
+ io.stdout:write("\n") -- Add newline after reading the password
+ sys.setconsoleflags(io.stdin, w_oldflags)
+ sys.tcsetattr(io.stdin, sys.TCSANOW, p_oldflags)
+ end
+
+ return secret, err
+end
+
+
+
+-- Get username
+io.write("Username: ")
+local username = io.stdin:read("*l")
+
+-- Get the secret
+io.write("Password: ")
+local password = read_secret("*l")
+
+-- Get domainname
+io.write("Domain : ")
+local domain = io.stdin:read("*l")
+
+
+-- Print the results
+print("")
+print("Here's what we got:")
+print(" username: " .. username)
+print(" password: " .. password)
+print(" domain : " .. domain)
diff --git a/examples/read.lua b/examples/read.lua
new file mode 100644
index 0000000..7a1c747
--- /dev/null
+++ b/examples/read.lua
@@ -0,0 +1,119 @@
+local sys = require "system"
+
+print [[
+
+This example shows how to do a non-blocking read from the cli.
+
+]]
+
+-- setup Windows console to handle ANSI processing
+local of_in = sys.getconsoleflags(io.stdin)
+local of_out = sys.getconsoleflags(io.stdout)
+sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
+
+-- setup Posix terminal to use non-blocking mode, and disable line-mode
+local of_attr = sys.tcgetattr(io.stdin)
+local of_block = sys.getnonblock(io.stdin)
+sys.setnonblock(io.stdin, true)
+sys.tcsetattr(io.stdin, sys.TCSANOW, {
+ lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO, -- disable canonical mode and echo
+})
+
+-- cursor sequences
+local get_cursor_pos = "\27[6n"
+
+
+
+local read_input do
+ local left_over_key
+
+ -- Reads a single key, if it is a 27 (start of ansi escape sequence) then it reads
+ -- the rest of the sequence.
+ -- This function is non-blocking, and will return nil if no key is available.
+ -- In case of an ANSI sequence, it will return the full sequence as a string.
+ -- @return nil|string the key read, or nil if no key is available
+ function read_input()
+ if left_over_key then
+ -- we still have a cached key, return it
+ local key = left_over_key
+ left_over_key = nil
+ return string.char(key)
+ end
+
+ local key = sys.readkey()
+ if key == nil then
+ return nil
+ end
+
+ if key ~= 27 then
+ return string.char(key)
+ end
+
+ -- looks like an ansi escape sequence, immediately read next char
+ -- as an heuristic against manually typing escape sequences
+ local brack = sys.readkey()
+ if brack ~= 91 then
+ -- not the expected [ character, so we return the key as is
+ -- and store the extra key read for the next call
+ left_over_key = brack
+ return string.char(key)
+ end
+
+ -- escape sequence detected, read the rest of the sequence
+ local seq = { key, brack }
+ while true do
+ key = sys.readkey()
+ table.insert(seq, key)
+ if (key >= 65 and key <= 90) or (key >= 97 and key <= 126) then
+ -- end of sequence, return the full sequence
+ return string.char((unpack or table.unpack)(seq))
+ end
+ end
+ -- unreachable
+ end
+end
+
+
+
+print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
+while true do
+ local key
+
+ -- wait for a key, and sleep a bit to not do a busy-wait
+ while not key do
+ key = read_input()
+ if not key then sys.sleep(0.1) end
+ end
+
+ if key == "A" then io.write(get_cursor_pos); io.flush() end
+
+ -- check if we got a key or ANSI sequence
+ if #key == 1 then
+ -- just a key
+ local b = key:byte()
+ if b < 32 then
+ key = "." -- replace control characters with a simple "." to not mess up the screen
+ end
+
+ print("you pressed: " .. key .. " (" .. b .. ")")
+ if b == 27 then
+ print("Escape pressed, exiting")
+ break
+ end
+
+ else
+ -- we got an ANSI sequence
+ local seq = { key:byte(1, #key) }
+ print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
+ end
+end
+
+
+
+-- Clean up afterwards
+sys.setnonblock(io.stdin, false)
+sys.setconsoleflags(io.stdout, of_out)
+sys.setconsoleflags(io.stdin, of_in)
+sys.tcsetattr(io.stdin, sys.TCSANOW, of_attr)
+sys.setnonblock(io.stdin, of_block)
diff --git a/examples/spinner.lua b/examples/spinner.lua
new file mode 100644
index 0000000..5526adc
--- /dev/null
+++ b/examples/spinner.lua
@@ -0,0 +1,64 @@
+local sys = require("system")
+
+print [[
+
+An example to display a spinner, whilst a long running task executes.
+
+]]
+
+
+-- start make backup, to auto-restore on exit
+sys.autotermrestore()
+-- configure console
+sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
+local of = sys.tcgetattr(io.stdin)
+sys.tcsetattr(io.stdin, sys.TCSANOW, { lflag = of.lflag - sys.L_ICANON - sys.L_ECHO })
+sys.setnonblock(io.stdin, true)
+
+
+
+local function hideCursor()
+ io.write("\27[?25l")
+ io.flush()
+end
+
+local function showCursor()
+ io.write("\27[?25h")
+ io.flush()
+end
+
+local function left(n)
+ io.write("\27[",n or 1,"D")
+ io.flush()
+end
+
+
+
+local spinner do
+ local spin = [[|/-\]]
+ local i = 1
+ spinner = function()
+ hideCursor()
+ io.write(spin:sub(i, i))
+ left()
+ i = i + 1
+ if i > #spin then i = 1 end
+
+ if sys.keypressed() then
+ sys.readkey() -- consume key pressed
+ io.write(" ");
+ left()
+ showCursor()
+ return true
+ else
+ return false
+ end
+ end
+end
+
+io.stdout:write("press any key to stop the spinner... ")
+while not spinner() do
+ sys.sleep(0.1)
+end
+
+print("Done!")
diff --git a/examples/spiral_snake.lua b/examples/spiral_snake.lua
new file mode 100644
index 0000000..84a2040
--- /dev/null
+++ b/examples/spiral_snake.lua
@@ -0,0 +1,72 @@
+local sys = require "system"
+
+print [[
+
+This example will draw a snake like spiral on the screen. Showing ANSI escape
+codes for moving the cursor around.
+
+]]
+
+-- backup term settings with auto-restore on exit
+sys.autotermrestore()
+
+-- setup Windows console to handle ANSI processing
+sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+
+-- start drawing the spiral.
+-- start from current pos, then right, then up, then left, then down, and again.
+local x, y = 1, 1 -- current position
+local dx, dy = 1, 0 -- direction after each step
+local wx, wy = 30, 30 -- width and height of the room
+local mx, my = 1, 1 -- margin
+
+-- commands to move the cursor
+local move_left = "\27[1D"
+local move_right = "\27[1C"
+local move_up = "\27[1A"
+local move_down = "\27[1B"
+
+-- create room: 30 empty lines
+print(("\n"):rep(wy))
+local move = move_right
+
+while wx > 0 and wy > 0 do
+ sys.sleep(0.01) -- slow down the drawing a little
+ io.write("*" .. move_left .. move )
+ io.flush()
+ x = x + dx
+ y = y + dy
+
+ if x > wx and move == move_right then
+ -- end of move right
+ dx = 0
+ dy = 1
+ move = move_up
+ wy = wy - 1
+ my = my + 1
+ elseif y > wy and move == move_up then
+ -- end of move up
+ dx = -1
+ dy = 0
+ move = move_left
+ wx = wx - 1
+ mx = mx + 1
+ elseif x < mx and move == move_left then
+ -- end of move left
+ dx = 0
+ dy = -1
+ move = move_down
+ wy = wy - 1
+ my = my + 1
+ elseif y < my and move == move_down then
+ -- end of move down
+ dx = 1
+ dy = 0
+ move = move_right
+ wx = wx - 1
+ mx = mx + 1
+ end
+end
+
+io.write(move_down:rep(15))
+print("\nDone!")