diff --git a/CHANGELOG.md b/CHANGELOG.md index eb85c94..a3544ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,20 @@ The scope of what is covered by the version number excludes: ## Version history +### Version 0.4.0, released 20-Jun-2024 + +- Feat: `getconsoleflags` and `setconsoleflags` for getting/setting the current console configuration flags on Windows +- Feat: `getconsolecp` and `setconsolecp` for getting/setting the console codepage on Windows +- Feat: `getconsoleoutputcp` and `setconsoleoutputcp` for getting/setting the console output codepage on Windows +- Feat: `tcgetattr` and `tcsetattr` for getting/setting the current console configuration flags on Posix +- Feat: `getnonblock` and `setnonblock` for getting/setting the non-blocking flag on Posix +- Feat: `bitflags`: a support feature for the above flag type controls to facilitate bit manipulation without resorting to binary operations (to also support PuC Lua 5.1) +- Feat: `readkey` reads a keyboard input from `stdin` in a non-blocking way (utf8, also on Windows) +- Feat: `readansi` reads a keyboard input from `stdin` in a non-blocking way, parses ansi and utf8 sequences +- Feat: `termsize` gets the current terminal size in rows and columns +- Feat: `utf8cwidth` and `utf8swidth` for getting the display width (in columns) of respectively a single utf8 character, or a utf8 string +- Feat: helpers; `termbackup`, `termrestore`, `autotermrestore`, and `termwrap` for managing the many terminal settings on all platforms. + ### Version 0.3.0, released 15-Dec-2023 - Feat: on Windows `sleep` now has a precision parameter diff --git a/LICENSE.md b/LICENSE.md index df2befb..16c86d0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ # MIT License -### Copyright (c) 2016-2023 Oscar Lim +### Copyright (c) 2016-2024 Oscar Lim Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docs/classes/bitflags.html b/docs/classes/bitflags.html new file mode 100644 index 0000000..f063149 --- /dev/null +++ b/docs/classes/bitflags.html @@ -0,0 +1,305 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

Class bitflags

+

Bitflags module.

+

The bitflag object makes it easy to manipulate flags in a bitmask.

+ +

It has metamethods that do the hard work, adding flags sets them, substracting + unsets them. Comparing flags checks if all flags in the second set are also set + in the first set. The has method checks if all flags in the second set are + also set in the first set, but behaves slightly different.

+ +

Indexing allows checking values or setting them by bit index (eg. 0-7 for flags + in the first byte).

+ +

NOTE: unavailable flags (eg. Windows flags on a Posix system) should not be + omitted, but be assigned a value of 0. This is because the has method will + return false if the flags are checked and the value is 0.

+ +

See system.bitflag (the constructor) for extensive examples on usage.

+ + +

Bit flags

+ + + + + + + + + + + + + + + + + +
bitflag:has_all_of (subset)Checks if all the flags in the given subset are set.
bitflag:has_any_of (subset)Checks if any of the flags in the given subset are set.
bitflag:value ()Retrieves the numeric value of the bitflag object.
system.bitflag ([value=0])Creates a new bitflag object from the given value.
+ +
+
+ + +

Bit flags

+ +
+ Bitflag objects can be used to easily manipulate and compare bit flags. + These are primarily for use with the terminal functions, but can be used + in other places as well. +
+
+
+ + bitflag:has_all_of (subset) +
+
+ Checks if all the flags in the given subset are set. +If the flags to check has a value 0, it will always return false. So if there are flags that are +unsupported on a platform, they can be set to 0 and the has_all_of function will +return false if the flags are checked. + + +

Parameters:

+
    +
  • subset + bitflag + the flags to check for. +
  • +
+ +

Returns:

+
    + + boolean + true if all the flags are set, false otherwise. +
+ + + +

Usage:

+
    +
    local sys = require 'system'
    +local flags = sys.bitflag(12)     -- b1100
    +local myflags = sys.bitflag(15)   -- b1111
    +print(flags:has_all_of(myflags))  -- false, not all bits in myflags are set in flags
    +print(myflags:has_all_of(flags))  -- true, all bits in flags are set in myflags
    +
+ +
+
+ + bitflag:has_any_of (subset) +
+
+ Checks if any of the flags in the given subset are set. +If the flags to check has a value 0, it will always return false. So if there are flags that are +unsupported on a platform, they can be set to 0 and the has_any_of function will +return false if the flags are checked. + + +

Parameters:

+
    +
  • subset + bitflag + the flags to check for. +
  • +
+ +

Returns:

+
    + + boolean + true if any of the flags are set, false otherwise. +
+ + + +

Usage:

+
    +
    local sys = require 'system'
    +local flags = sys.bitflag(12)     -- b1100
    +local myflags = sys.bitflag(7)    -- b0111
    +print(flags:has_any_of(myflags))  -- true, some bits in myflags are set in flags
    +print(myflags:has_any_of(flags))  -- true, some bits in flags are set in myflags
    +
+ +
+
+ + bitflag:value () +
+
+ Retrieves the numeric value of the bitflag object. + + + +

Returns:

+
    + + number + the numeric value of the bitflags. +
+ + + +

Usage:

+
    +
    local sys = require 'system'
    +local flags = sys.bitflag()     -- b0000
    +flags[0] = true                 -- b0001
    +flags[2] = true                 -- b0101
    +print(flags:value())            -- 5
    +
+ +
+
+ + system.bitflag ([value=0]) +
+
+ Creates a new bitflag object from the given value. + + +

Parameters:

+
    +
  • value + number + the value to create the bitflag object from. + (default 0) +
  • +
+ +

Returns:

+
    + + bitflag + bitflag object with the given values set. +
+ + + +

Usage:

+
    +
    local sys = require 'system'
    +local flags = sys.bitflag(2)    -- b0010
    +
    +-- get state of individual bits
    +print(flags[0])                 -- false
    +print(flags[1])                 -- true
    +
    +-- set individual bits
    +flags[0] = true                 -- b0011
    +print(flags:value())            -- 3
    +print(flags)                    -- "bitflags: 3"
    +
    +-- adding flags (bitwise OR)
    +local flags1 = sys.bitflag(1)   -- b0001
    +local flags2 = sys.bitflag(2)   -- b0010
    +local flags3 = flags1 + flags2  -- b0011
    +
    +-- substracting flags (bitwise AND NOT)
    +print(flags3:value())           -- 3
    +flag3 = flag3 - flag3           -- b0000
    +print(flags3:value())           -- 0
    +
    +-- comparing flags
    +local flags4 = sys.bitflag(7)   -- b0111
    +local flags5 = sys.bitflag(255) -- b11111111
    +print(flags5 ~= flags4)         -- true, not the same flags
    +local flags6 = sys.bitflag(7)   -- b0111
    +print(flags6 == flags4)         -- true, same flags
    +
    +-- comparison of subsets
    +local flags7 = sys.bitflag(0)    -- b0000
    +local flags8 = sys.bitflag(3)    -- b0011
    +local flags9 = sys.bitflag(7)    -- b0111
    +print(flags9:has_all_of(flags8)) -- true, flags8 bits are all set in flags9
    +print(flags8:has_any_of(flags9)) -- true, some of flags9 bits are set in flags8
    +print(flags8:has_all_of(flags7)) -- false, flags7 (== 0) is not set in flags8
    +
+ +
+
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/compat.lua.html b/docs/examples/compat.lua.html new file mode 100644 index 0000000..a0abafe --- /dev/null +++ b/docs/examples/compat.lua.html @@ -0,0 +1,119 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

compat.lua

+
+-- This example shows how to remove platform differences to create a
+-- cross-platform level playing field.
+
+local sys = require "system"
+
+
+
+if sys.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 console output to UTF-8 encoding.
+  sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
+
+  -- 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
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/flag_debugging.lua.html b/docs/examples/flag_debugging.lua.html new file mode 100644 index 0000000..38f506a --- /dev/null +++ b/docs/examples/flag_debugging.lua.html @@ -0,0 +1,87 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

flag_debugging.lua

+
+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)
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/password_input.lua.html b/docs/examples/password_input.lua.html new file mode 100644 index 0000000..4234fbb --- /dev/null +++ b/docs/examples/password_input.lua.html @@ -0,0 +1,139 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

password_input.lua

+
+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)
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/read.lua.html b/docs/examples/read.lua.html new file mode 100644 index 0000000..c7697d3 --- /dev/null +++ b/docs/examples/read.lua.html @@ -0,0 +1,150 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

read.lua

+
+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"
+
+
+
+print("Press a key, or 'A' to get cursor position, 'ESC' to exit")
+while true do
+  local key, keytype
+
+  -- wait for a key
+  while not key do
+    key, keytype = sys.readansi(math.huge)
+  end
+
+  if key == "A" then io.write(get_cursor_pos); io.flush() end
+
+  -- check if we got a key or ANSI sequence
+  if keytype == "char" 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
+
+  elseif keytype == "ansi" then
+    -- we got an ANSI sequence
+    local seq = { key:byte(1, #key) }
+    print("ANSI sequence received: " .. key:sub(2,-1), "(bytes: " .. table.concat(seq, ", ")..")")
+
+  else
+    print("unknown key type received: " .. tostring(keytype))
+  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)
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/readline.lua.html b/docs/examples/readline.lua.html new file mode 100644 index 0000000..7895a81 --- /dev/null +++ b/docs/examples/readline.lua.html @@ -0,0 +1,552 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

readline.lua

+
+--- An example class for reading a line of input from the user in a non-blocking way.
+-- It uses ANSI escape sequences to move the cursor and handle input.
+-- It can be used to read a line of input from the user, with a prompt.
+-- It can handle double-width UTF-8 characters.
+-- It can be used asynchroneously if system.sleep is patched to yield to a coroutine scheduler.
+
+local sys = require("system")
+
+
+-- Mapping of key-sequences to key-names
+local key_names = {
+  ["\27[C"] = "right",
+  ["\27[D"] = "left",
+  ["\127"] = "backspace",
+  ["\27[3~"] = "delete",
+  ["\27[H"] = "home",
+  ["\27[F"] = "end",
+  ["\27"] = "escape",
+  ["\9"] = "tab",
+  ["\27[Z"] = "shift-tab",
+}
+
+if sys.windows then
+  key_names["\13"] = "enter"
+else
+  key_names["\10"] = "enter"
+end
+
+
+-- Mapping of key-names to key-sequences
+local key_sequences = {}
+for k, v in pairs(key_names) do
+  key_sequences[v] = k
+end
+
+
+-- bell character
+local function bell()
+  io.write("\7")
+  io.flush()
+end
+
+
+-- generate string to move cursor horizontally
+-- positive goes right, negative goes left
+local function cursor_move_horiz(n)
+  if n == 0 then
+    return ""
+  end
+  return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "C" or "D")
+end
+
+
+-- -- generate string to move cursor vertically
+-- -- positive goes down, negative goes up
+-- local function cursor_move_vert(n)
+--   if n == 0 then
+--     return ""
+--   end
+--   return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "B" or "A")
+-- end
+
+
+-- -- log to the line above the current line
+-- local function log(...)
+--   local arg = { n = select("#", ...), ...}
+--   for i = 1, arg.n do
+--     arg[i] = tostring(arg[i])
+--   end
+--   arg = " " .. table.concat(arg, " ") .. " "
+
+--   io.write(cursor_move_vert(-1), arg, cursor_move_vert(1), cursor_move_horiz(-#arg))
+-- end
+
+
+-- UTF8 character size in bytes
+-- @tparam number b the byte value of the first byte of a UTF8 character
+local function utf8size(b)
+  return b < 128 and 1 or b < 224 and 2 or b < 240 and 3 or b < 248 and 4
+end
+
+
+
+local utf8parse do
+  local utf8_value_mt = {
+    __tostring = function(self)
+      return table.concat(self, "")
+    end,
+  }
+
+  -- Parses a UTF8 string into list of individual characters.
+  -- key 'chars' gets the length in UTF8 characters, whilst # returns the length
+  -- for display (to handle double-width UTF8 chars).
+  -- in the list the double-width characters are followed by an empty string.
+  -- @tparam string s the UTF8 string to parse
+  -- @treturn table the list of characters
+  function utf8parse(s)
+    local t = setmetatable({ chars = 0 }, utf8_value_mt)
+    local i = 1
+    while i <= #s do
+      local b = s:byte(i)
+      local w = utf8size(b)
+      local char = s:sub(i, i + w - 1)
+      t[#t + 1] = char
+      t.chars = t.chars + 1
+      if sys.utf8cwidth(char) == 2 then
+        -- double width character, add empty string to keep the length of the
+        -- list the same as the character width on screen
+        t[#t + 1] = ""
+      end
+      i = i + w
+    end
+    return t
+  end
+end
+
+
+
+-- inline tests for utf8parse
+-- do
+--   local t = utf8parse("a你b好c")
+--   assert(t[1] == "a")
+--   assert(t[2] == "你")  -- double width
+--   assert(t[3] == "")
+--   assert(t[4] == "b")
+--   assert(t[5] == "好")  -- double width
+--   assert(t[6] == "")
+--   assert(t[7] == "c")
+--   assert(#t == 7)       -- size as displayed
+-- end
+
+
+
+-- readline class
+
+local readline = {}
+readline.__index = readline
+
+
+--- Create a new readline object.
+-- @tparam table opts the options for the readline object
+-- @tparam[opt=""] string opts.prompt the prompt to display
+-- @tparam[opt=80] number opts.max_length the maximum length of the input (in characters, not bytes)
+-- @tparam[opt=""] string opts.value the default value
+-- @tparam[opt=#value] number opts.position of the cursor in the input
+-- @tparam[opt={"\10"/"\13"}] table opts.exit_keys an array of keys that will cause the readline to exit
+-- @treturn readline the new readline object
+function readline.new(opts)
+  local value = utf8parse(opts.value or "")
+  local prompt = utf8parse(opts.prompt or "")
+  local pos = math.floor(opts.position or (#value + 1))
+  pos = math.max(math.min(pos, (#value + 1)), 1)
+  local len = math.floor(opts.max_length or 80)
+  if len < 1 then
+    error("max_length must be at least 1", 2)
+  end
+
+  if value.chars > len then
+    error("value is longer than max_length", 2)
+  end
+
+  local exit_keys = {}
+  for _, key in ipairs(opts.exit_keys or {}) do
+    exit_keys[key] = true
+  end
+  if exit_keys[1] == nil then
+    -- nothing provided, default to Enter-key
+    exit_keys[1] = key_sequences.enter
+  end
+
+  local self = {
+    value = value,          -- the default value
+    max_length = len,       -- the maximum length of the input
+    prompt = prompt,        -- the prompt to display
+    position = pos,         -- the current position in the input
+    drawn_before = false,   -- if the prompt has been drawn
+    exit_keys = exit_keys,  -- the keys that will cause the readline to exit
+  }
+
+  setmetatable(self, readline)
+  return self
+end
+
+
+
+-- draw the prompt and the input value, and position the cursor.
+local function draw(self, redraw)
+  if redraw or not self.drawn_before then
+    -- we are at start of prompt
+    self.drawn_before = true
+  else
+    -- we are at current cursor position, move to start of prompt
+    io.write(cursor_move_horiz(-(#self.prompt + self.position)))
+  end
+  -- write prompt & value
+  io.write(tostring(self.prompt) .. tostring(self.value))
+  -- clear remainder of input size
+  io.write(string.rep(" ", self.max_length - self.value.chars))
+  io.write(cursor_move_horiz(-(self.max_length - self.value.chars)))
+  -- move to cursor position
+  io.write(cursor_move_horiz(-(#self.value + 1 - self.position)))
+  io.flush()
+end
+
+
+local handle_key do -- keyboard input handler
+
+  local key_handlers
+  key_handlers = {
+    left = function(self)
+      if self.position == 1 then
+        bell()
+        return
+      end
+
+      local new_pos = self.position - 1
+      while self.value[new_pos] == "" do -- skip empty strings; double width chars
+        new_pos = new_pos - 1
+      end
+
+      io.write(cursor_move_horiz(-(self.position - new_pos)))
+      io.flush()
+      self.position = new_pos
+    end,
+
+    right = function(self)
+      if self.position == #self.value + 1 then
+        bell()
+        return
+      end
+
+      local new_pos = self.position + 1
+      while self.value[new_pos] == "" do -- skip empty strings; double width chars
+        new_pos = new_pos + 1
+      end
+
+      io.write(cursor_move_horiz(new_pos - self.position))
+      io.flush()
+      self.position = new_pos
+    end,
+
+    backspace = function(self)
+      if self.position == 1 then
+        bell()
+        return
+      end
+
+      while self.value[self.position - 1] == "" do -- remove empty strings; double width chars
+        io.write(cursor_move_horiz(-1))
+        self.position = self.position - 1
+        table.remove(self.value, self.position)
+      end
+      -- remove char itself
+      io.write(cursor_move_horiz(-1))
+      self.position = self.position - 1
+      table.remove(self.value, self.position)
+      self.value.chars = self.value.chars - 1
+      draw(self)
+    end,
+
+    home = function(self)
+      local new_pos = 1
+      io.write(cursor_move_horiz(new_pos - self.position))
+      self.position = new_pos
+    end,
+
+    ["end"] = function(self)
+      local new_pos = #self.value + 1
+      io.write(cursor_move_horiz(new_pos - self.position))
+      self.position = new_pos
+    end,
+
+    delete = function(self)
+      if self.position > #self.value then
+        bell()
+        return
+      end
+
+      key_handlers.right(self)
+      key_handlers.backspace(self)
+    end,
+  }
+
+
+  -- handles a single input key/ansi-sequence.
+  -- @tparam string key the key or ansi-sequence (from system.readansi)
+  -- @tparam string keytype the type of the key, either "char" or "ansi" (from system.readansi)
+  -- @treturn string status the status of the key handling, either "ok", "exit_key" or an error message
+  function handle_key(self, key, keytype)
+    if self.exit_keys[key] then
+      -- registered exit key
+      return "exit_key"
+    end
+
+    local handler = key_handlers[key_names[key] or true ]
+    if handler then
+      handler(self)
+      return "ok"
+    end
+
+    if keytype == "ansi" then
+      -- we got an ansi sequence, but dunno how to handle it, ignore
+      -- print("unhandled ansi: ", key:sub(2,-1), string.byte(key, 1, -1))
+      bell()
+      return "ok"
+    end
+
+    -- just a single key
+    if key < " " then
+      -- control character
+      bell()
+      return "ok"
+    end
+
+    if self.value.chars >= self.max_length then
+      bell()
+      return "ok"
+    end
+
+    -- insert the key into the value
+    if sys.utf8cwidth(key) == 2 then
+      -- double width character, insert empty string after it
+      table.insert(self.value, self.position, "")
+      table.insert(self.value, self.position, key)
+      self.position = self.position + 2
+      io.write(cursor_move_horiz(2))
+    else
+      table.insert(self.value, self.position, key)
+      self.position = self.position + 1
+      io.write(cursor_move_horiz(1))
+    end
+    self.value.chars = self.value.chars + 1
+    draw(self)
+    return "ok"
+  end
+end
+
+
+
+--- Get_size returns the maximum size of the input box (prompt + input).
+-- The size is in rows and columns. Columns is determined by
+-- the prompt and the max_length * 2 (characters can be double-width).
+-- @treturn number the number of rows (always 1)
+-- @treturn number the number of columns
+function readline:get_size()
+  return 1, #self.prompt + self.max_length * 2
+end
+
+
+
+--- Get coordinates of the cursor in the input box (prompt + input).
+-- The coordinates are 1-based. They are returned as row and column, within the
+-- size as reported by get_size.
+-- @treturn number the row of the cursor (always 1)
+-- @treturn number the column of the cursor
+function readline:get_cursor()
+  return 1, #self.prompt + self.position
+end
+
+
+
+--- Set the coordinates of the cursor in the input box (prompt + input).
+-- The coordinates are 1-based. They are expected to be within the
+-- size as reported by get_size, and beyond the prompt.
+-- If the position is invalid, it will be corrected.
+-- Use the results to check if the position was adjusted.
+-- @tparam number row the row of the cursor (always 1)
+-- @tparam number col the column of the cursor
+-- @return results of get_cursor
+function readline:set_cursor(row, col)
+  local l_prompt = #self.prompt
+  local l_value = #self.value
+
+  if col < l_prompt + 1 then
+    col = l_prompt + 1
+  elseif col > l_prompt + l_value + 1 then
+    col = l_prompt + l_value + 1
+  end
+
+  while self.value[col - l_prompt] == "" do
+    col = col - 1 -- on an empty string, so move back to start of double-width char
+  end
+
+  local new_pos = col - l_prompt
+
+  cursor_move_horiz(self.position - new_pos)
+  io.flush()
+
+  self.position = new_pos
+  return self:get_cursor()
+end
+
+
+
+--- Read a line of input from the user.
+-- It will first print the prompt and then wait for input. Ensure the cursor
+-- is at the correct position before calling this function. This function will
+-- do all cursor movements in a relative way.
+-- Can be called again after an exit-key or timeout has occurred. Just make sure
+-- the cursor is at the same position where is was when it returned the last time.
+-- Alternatively the cursor can be set to the position of the prompt (the position
+-- the cursor was in before the first call), and the parameter redraw can be set
+-- to true.
+-- @tparam[opt=math.huge] number timeout the maximum time to wait for input in seconds
+-- @tparam[opt=false] boolean redraw if true the prompt will be redrawn (cursor must be at prompt position!)
+-- @treturn[1] string the input string as entered the user
+-- @treturn[1] string the exit-key used to exit the readline (see new)
+-- @treturn[2] nil when input is incomplete
+-- @treturn[2] string error message, the reason why the input is incomplete, "timeout", or an error reading a key
+function readline:__call(timeout, redraw)
+  draw(self, redraw)
+  timeout = timeout or math.huge
+  local timeout_end = sys.gettime() + timeout
+
+  while true do
+    local key, keytype = sys.readansi(timeout_end - sys.gettime())
+    if not key then
+      -- error or timeout
+      return nil, keytype
+    end
+
+    local status = handle_key(self, key, keytype)
+    if status == "exit_key" then
+      return tostring(self.value), key
+
+    elseif status ~= "ok" then
+      error("unknown status received: " .. tostring(status))
+    end
+  end
+end
+
+
+
+-- return readline  -- normally we'd return here, but for the example we continue
+
+
+
+
+local backup = sys.termbackup()
+
+-- setup Windows console to handle ANSI processing
+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)
+-- set output to UTF-8
+sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
+
+-- setup Posix terminal to disable canonical mode and echo
+sys.tcsetattr(io.stdin, sys.TCSANOW, {
+  lflag = sys.tcgetattr(io.stdin).lflag - sys.L_ICANON - sys.L_ECHO,
+})
+-- setup stdin to non-blocking mode
+sys.setnonblock(io.stdin, true)
+
+
+local rl = readline.new{
+  prompt = "Enter something: ",
+  max_length = 60,
+  value = "Hello, 你-好 World 🚀!",
+  -- position = 2,
+  exit_keys = {key_sequences.enter, "\27", "\t", "\27[Z"}, -- enter, escape, tab, shift-tab
+}
+
+
+local result, key = rl()
+print("")  -- newline after input, to move cursor down from the input line
+print("Result (string): '" .. result .. "'")
+print("Result (bytes):", result:byte(1,-1))
+print("Exit-Key (bytes):", key:byte(1,-1))
+
+
+-- Clean up afterwards
+sys.termrestore(backup)
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/spinner.lua.html b/docs/examples/spinner.lua.html new file mode 100644 index 0000000..181cbe5 --- /dev/null +++ b/docs/examples/spinner.lua.html @@ -0,0 +1,144 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

spinner.lua

+
+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.readkey(0) ~= nil then
+      while sys.readkey(0) ~= nil do end -- consume keys 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!")
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/spiral_snake.lua.html b/docs/examples/spiral_snake.lua.html new file mode 100644 index 0000000..7ebb838 --- /dev/null +++ b/docs/examples/spiral_snake.lua.html @@ -0,0 +1,152 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

spiral_snake.lua

+
+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!")
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/examples/terminalsize.lua.html b/docs/examples/terminalsize.lua.html new file mode 100644 index 0000000..d7f902d --- /dev/null +++ b/docs/examples/terminalsize.lua.html @@ -0,0 +1,117 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

terminalsize.lua

+
+local sys = require("system")
+
+sys.autotermrestore()  -- set up auto restore of terminal settings on exit
+
+-- setup Windows console to handle ANSI processing
+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 to disable canonical mode and echo
+local of_attr = sys.tcgetattr(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
+})
+
+
+-- generate string to move cursor horizontally
+-- positive goes right, negative goes left
+local function cursor_move_horiz(n)
+  if n == 0 then
+    return ""
+  end
+  return "\27[" .. (n > 0 and n or -n) .. (n > 0 and "C" or "D")
+end
+
+
+local rows, cols
+print("Change the terminal window size, press any key to exit")
+while not sys.readansi(0.2) do  -- use readansi to not leave stray bytes in the input buffer
+  local nrows, ncols = sys.termsize()
+  if rows ~= nrows or cols ~= ncols then
+    rows, cols = nrows, ncols
+    local text = "Terminal size: " .. rows .. "x" .. cols .. "     "
+    io.write(text .. cursor_move_horiz(-#text))
+    io.flush()
+  end
+end
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/index.html b/docs/index.html index cd95fc2..52c93fe 100644 --- a/docs/index.html +++ b/docs/index.html @@ -27,339 +27,122 @@

Lua-System

-

Contents

- +

Modules

+

Classes

+

Topics

+

Examples

+
-

Module system

-

Platform independent system calls for Lua.

-

- -

+

Platform independent system calls for Lua

-

environment Functions

- +

Modules

+
- - + + +
getenv (name)Gets the value of an environment variable.systemPlatform independent system calls for Lua.
+

Classes

+ - - + + +
getenvs ()Returns a table with all environment variables.bitflagsBitflags module.
+

Topics

+ - - + + -
setenv (name[, value])Sets an environment variable.01-introduction.md
-

random Functions

- - - + + -
random ([length=1])Generate random bytes.02-development.md
-

term Functions

- - - + + + + + + + + + +
isatty (file)Checks if a file-handle is a TTY.03-terminal.md
CHANGELOG.md
LICENSE.md
-

time Functions

- +

Examples

+
+ + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + +
compat.lua
flag_debugging.lua
password_input.lua
read.lua
readline.lua
gettime ()Get system time.spinner.lua
monotime ()Get monotonic time.spiral_snake.lua
sleep (seconds[, precision=16])Sleep without a busy loop.terminalsize.lua
-
-
- - -

environment Functions

- -
-
- - getenv (name) -
-
- Gets the value of an environment variable.

- -

NOTE: Windows has multiple copies of environment variables. For this reason, -the setenv function will not work with Lua's os.getenv on Windows. If you want -to use setenv then consider patching os.getenv with this implementation of getenv. - - -

Parameters:

-
    -
  • name - string - name of the environment variable -
  • -
- -

Returns:

-
    - - string or nil - value of the environment variable, or nil if the variable is not set -
- - - - -
-
- - getenvs () -
-
- Returns a table with all environment variables. - - - -

Returns:

-
    - - table - table with all environment variables and their values -
- - - - -
-
- - setenv (name[, value]) -
-
- Sets an environment variable.

- -

NOTE: Windows has multiple copies of environment variables. For this reason, the -setenv function will not work with Lua's os.getenv on Windows. If you want to use -it then consider patching os.getenv with the implementation of system.getenv. - - -

Parameters:

-
    -
  • name - string - name of the environment variable -
  • -
  • value - string - value of the environment variable, if nil the variable will be deleted (on -Windows, setting an empty string, will also delete the variable) - (optional) -
  • -
- -

Returns:

-
    - - boolean - success -
- - - - -
-
-

random Functions

- -
-
- - random ([length=1]) -
-
- Generate random bytes. -This uses CryptGenRandom() on Windows, and /dev/urandom on other platforms. It will return the -requested number of bytes, or an error, never a partial result. - - -

Parameters:

-
    -
  • length - int - number of bytes to get - (default 1) -
  • -
- -

Returns:

-
    - - string - string of random bytes -
-

Or

-
    -
  1. - nil - - -
  2. -
  3. - string - error message
  4. -
- - - - -
-
-

term Functions

- -
-
- - isatty (file) -
-
- Checks if a file-handle is a TTY. - - -

Parameters:

-
    -
  • file - file - the file-handle to check -
  • -
- -

Returns:

-
    - - boolean - true if the file is a tty -
- - - - -
-
-

time Functions

- -
-
- - gettime () -
-
- Get system time. -The time is returned as the seconds since the epoch (1 January 1970 00:00:00). - - - -

Returns:

-
    - - number - seconds (fractional) -
- - - - -
-
- - monotime () -
-
- Get monotonic time. -The time is returned as the seconds since system start. - - - -

Returns:

-
    - - number - seconds (fractional) -
- - - - -
-
- - sleep (seconds[, precision=16]) -
-
- Sleep without a busy loop. -This function will sleep, without doing a busy-loop and wasting CPU cycles. - - -

Parameters:

-
    -
  • seconds - number - seconds to sleep (fractional). -
  • -
  • precision - integer - minimum stepsize in milliseconds (Windows only, ignored elsewhere) - (default 16) -
  • -
- -

Returns:

-
    - - true on success, or nil+err on failure -
- - - - -
-
- -
-generated by LDoc 1.4.6 -Last updated 2023-12-15 13:15:09 +generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37
diff --git a/docs/modules/system.html b/docs/modules/system.html new file mode 100644 index 0000000..0423e37 --- /dev/null +++ b/docs/modules/system.html @@ -0,0 +1,1418 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ +

Module system

+

Platform independent system calls for Lua.

+

+ +

+ + +

Fields

+ + + + + +
windowsFlag to identify Windows.
+

Environment

+ + + + + + + + + + + + + +
getenv (name)Gets the value of an environment variable.
getenvs ()Returns a table with all environment variables.
setenv (name[, value])Sets an environment variable.
+

Random

+ + + + + +
random ([length=1])Generate random bytes.
+

Terminal

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CODEPAGE_UTF8UTF8 codepage.
_readkey ()Reads a key from the console non-blocking.
autotermrestore ()Backs up terminal settings and restores them on application exit.
getconsolecp ()Gets the current console code page (Windows).
getconsoleflags (file)Gets console flags (Windows).
getconsoleoutputcp ()Gets the current console output code page (Windows).
getnonblock (fd)Gets non-blocking mode status for a file (Posix).
isatty (file)Checks if a file-handle is a TTY.
listconsoleflags (fh)Debug function for console flags (Windows).
listtermflags (fh)Debug function for terminal flags (Posix).
readansi (timeout)Reads a single key, if it is the start of ansi escape sequence then it reads + the full sequence.
readkey (timeout)Reads a single byte from the console, with a timeout.
setconsolecp (cp)Sets the current console code page (Windows).
setconsoleflags (file, bitflags)Sets the console flags (Windows).
setconsoleoutputcp (cp)Sets the current console output code page (Windows).
setnonblock (fd, make_non_block)Enables or disables non-blocking mode for a file (Posix).
tcgetattr (fd)Get termios state (Posix).
tcsetattr (fd, actions, termios)Set termios state (Posix).
termbackup ()Returns a backup of terminal settings for stdin/out/err.
termrestore (backup)Restores terminal settings from a backup
termsize ()Get the size of the terminal in rows and columns.
termwrap (f)Wraps a function to automatically restore terminal settings upon returning.
utf8cwidth (utf8_char)Get the width of a utf8 character for terminal display.
utf8swidth (utf8_string)Get the width of a utf8 string for terminal display.
+

Time

+ + + + + + + + + + + + + +
gettime ()Get system time.
monotime ()Get monotonic time.
sleep (seconds[, precision=16])Sleep without a busy loop.
+ +
+
+ + +

Fields

+ +
+
+ + windows +
+
+ Flag to identify Windows. + + +
    +
  • windows + true if on Windows, false otherwise. +
  • +
+ + + + + +
+
+

Environment

+ +
+
+ + getenv (name) +
+
+ Gets the value of an environment variable.

+ +

NOTE: Windows has multiple copies of environment variables. For this reason, +the setenv function will not work with Lua's os.getenv on Windows. If you want +to use setenv then consider patching os.getenv with this implementation of getenv. + + +

Parameters:

+
    +
  • name + string + name of the environment variable +
  • +
+ +

Returns:

+
    + + string or nil + value of the environment variable, or nil if the variable is not set +
+ + + + +
+
+ + getenvs () +
+
+ Returns a table with all environment variables. + + + +

Returns:

+
    + + table + table with all environment variables and their values +
+ + + + +
+
+ + setenv (name[, value]) +
+
+ Sets an environment variable.

+ +

NOTE: Windows has multiple copies of environment variables. For this reason, the +setenv function will not work with Lua's os.getenv on Windows. If you want to use +it then consider patching os.getenv with the implementation of system.getenv. + + +

Parameters:

+
    +
  • name + string + name of the environment variable +
  • +
  • value + string + value of the environment variable, if nil the variable will be deleted (on +Windows, setting an empty string, will also delete the variable) + (optional) +
  • +
+ +

Returns:

+
    + + boolean + success +
+ + + + +
+
+

Random

+ +
+
+ + random ([length=1]) +
+
+ Generate random bytes. +This uses CryptGenRandom() on Windows, and /dev/urandom on other platforms. It will return the +requested number of bytes, or an error, never a partial result. + + +

Parameters:

+
    +
  • length + int + number of bytes to get + (default 1) +
  • +
+ +

Returns:

+
    + + string + string of random bytes +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
+ + + + +
+
+

Terminal

+ +
+ Unix: see https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/

+ +

Windows: see https://learn.microsoft.com/en-us/windows/console/console-reference +

+
+
+ + CODEPAGE_UTF8 +
+
+ UTF8 codepage. + To be used with system.setconsoleoutputcp and system.setconsolecp. + + +
    +
  • CODEPAGE_UTF8 + The Windows CodePage for UTF8. +
  • +
+ + + + + +
+
+ + _readkey () +
+
+ Reads a key from the console non-blocking. This function should not be called +directly, but through the system.readkey or system.readansi functions. It +will return the next byte from the input stream, or nil if no key was pressed.

+ +

On Posix, io.stdin must be set to non-blocking mode using setnonblock +and canonical mode must be turned off using tcsetattr, +before calling this function. Otherwise it will block. No conversions are +done on Posix, so the byte read is returned as-is.

+ +

On Windows this reads a wide character and converts it to UTF-8. Multi-byte +sequences will be buffered internally and returned one byte at a time. + + + +

Returns:

+
    + + integer + the byte read from the input stream +
+

Or

+
    + + nil + if no key was pressed +
+

Or

+
    +
  1. + nil + on error
  2. +
  3. + string + error message
  4. +
  5. + int + errnum (on posix)
  6. +
+ + + + +
+
+ + autotermrestore () +
+
+ Backs up terminal settings and restores them on application exit. + Calls termbackup to back up terminal settings and sets up a GC method to + automatically restore them on application exit (also works on Lua 5.1). + + + +

Returns:

+
    + + boolean + true +
+

Or

+
    +
  1. + nil + if the backup was already created
  2. +
  3. + string + error message
  4. +
+ + + + +
+
+ + getconsolecp () +
+
+ Gets the current console code page (Windows). + + + +

Returns:

+
    + + int + the current code page (always 65001 on Posix systems) +
+ + + + +
+
+ + getconsoleflags (file) +
+
+ Gets console flags (Windows). +The CIF_ and COF_ constants are available on the module table. Where CIF are the +input flags (for use with io.stdin) and COF are the output flags (for use with +io.stdout/io.stderr).

+ +

Note: See setconsolemode documentation +for more information on the flags. + + +

Parameters:

+ + +

Returns:

+
    + + bitflags + the current console flags. +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
+ + + +

Usage:

+
    +
    local system = require('system')
    +
    +local flags = system.getconsoleflags(io.stdout)
    +print("Current stdout flags:", tostring(flags))
    +
    +if flags:has_all_of(system.COF_VIRTUAL_TERMINAL_PROCESSING + system.COF_PROCESSED_OUTPUT) then
    +    print("Both flags are set")
    +else
    +    print("At least one flag is not set")
    +end
    +
+ +
+
+ + getconsoleoutputcp () +
+
+ Gets the current console output code page (Windows). + + + +

Returns:

+
    + + int + the current code page (always 65001 on Posix systems) +
+ + + + +
+
+ + getnonblock (fd) +
+
+ Gets non-blocking mode status for a file (Posix). + + +

Parameters:

+ + +

Returns:

+
    + + bool + true if set to non-blocking, false if not. Always returns false on Windows. +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
  5. + int + errnum
  6. +
+ + + + +
+
+ + isatty (file) +
+
+ Checks if a file-handle is a TTY. + + +

Parameters:

+ + +

Returns:

+
    + + boolean + true if the file is a tty +
+ + + +

Usage:

+
    +
    local system = require('system')
    +if system.isatty(io.stdin) then
    +    -- enable ANSI coloring etc on Windows, does nothing in Posix.
    +    local flags = system.getconsoleflags(io.stdout)
    +    system.setconsoleflags(io.stdout, flags + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
    +end
    +
+ +
+
+ + listconsoleflags (fh) +
+
+ Debug function for console flags (Windows). + Pretty prints the current flags set for the handle. + + +

Parameters:

+ + + + + +

Usage:

+
    +
    -- Print the flags for stdin/out/err
    +system.listconsoleflags(io.stdin)
    +system.listconsoleflags(io.stdout)
    +system.listconsoleflags(io.stderr)
    +
+ +
+
+ + listtermflags (fh) +
+
+ Debug function for terminal flags (Posix). + Pretty prints the current flags set for the handle. + + +

Parameters:

+ + + + + +

Usage:

+
    +
    -- Print the flags for stdin/out/err
    +system.listconsoleflags(io.stdin)
    +system.listconsoleflags(io.stdout)
    +system.listconsoleflags(io.stderr)
    +
+ +
+
+ + readansi (timeout) +
+
+ Reads a single key, if it is the start of ansi escape sequence then it reads + the full sequence. The key can be a multi-byte string in case of multibyte UTF-8 character. + This function uses system.readkey, and hence system.sleep to wait until either a key is + available or the timeout is reached. + It returns immediately if a key is available or if timeout is less than or equal to 0. + In case of an ANSI sequence, it will return the full sequence as a string. + + +

Parameters:

+
    +
  • timeout + number + the timeout in seconds. +
  • +
+ +

Returns:

+
    +
  1. + string + the character that was received (can be multi-byte), or a complete ANSI sequence
  2. +
  3. + string + the type of input: "char" for a single key, "ansi" for an ANSI sequence
  4. +
+

Or

+
    +
  1. + nil + in case of an error
  2. +
  3. + string + error message; "timeout" if the timeout was reached.
  4. +
  5. + string + partial result in case of an error while reading a sequence, the sequence so far.
  6. +
+ + + + +
+
+ + readkey (timeout) +
+
+ Reads a single byte from the console, with a timeout. + This function uses system.sleep to wait until either a byte is available or the timeout is reached. + The sleep period is exponentially backing off, starting at 0.0125 seconds, with a maximum of 0.2 seconds. + It returns immediately if a byte is available or if timeout is less than or equal to 0.

+ +

Using system.readansi is preferred over this function. Since this function can leave stray/invalid + byte-sequences in the input buffer, while system.readansi reads full ANSI and UTF8 sequences. + + +

Parameters:

+
    +
  • timeout + number + the timeout in seconds. +
  • +
+ +

Returns:

+
    + + byte + the byte value that was read. +
+

Or

+
    +
  1. + nil + if no key was read
  2. +
  3. + string + error message; "timeout" if the timeout was reached.
  4. +
+ + + + +
+
+ + setconsolecp (cp) +
+
+ Sets the current console code page (Windows). + + +

Parameters:

+ + +

Returns:

+
    + + bool + true on success (always true on Posix systems) +
+ + + + +
+
+ + setconsoleflags (file, bitflags) +
+
+ Sets the console flags (Windows). +The CIF_ and COF_ constants are available on the module table. Where CIF are the +input flags (for use with io.stdin) and COF are the output flags (for use with +io.stdout/io.stderr).

+ +

To see flag status and constant names check listconsoleflags.

+ +

Note: not all combinations of flags are allowed, as some are mutually exclusive or mutually required. +See setconsolemode documentation + + +

Parameters:

+ + +

Returns:

+
    + + boolean + true on success +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
+ + + +

Usage:

+
    +
    local system = require('system')
    +system.listconsoleflags(io.stdout) -- List all the available flags and their current status
    +
    +local flags = system.getconsoleflags(io.stdout)
    +assert(system.setconsoleflags(io.stdout,
    +        flags + system.COF_VIRTUAL_TERMINAL_PROCESSING)
    +
    +system.listconsoleflags(io.stdout) -- List again to check the differences
    +
+ +
+
+ + setconsoleoutputcp (cp) +
+
+ Sets the current console output code page (Windows). + + +

Parameters:

+ + +

Returns:

+
    + + bool + true on success (always true on Posix systems) +
+ + + + +
+
+ + setnonblock (fd, make_non_block) +
+
+ Enables or disables non-blocking mode for a file (Posix). + + +

Parameters:

+
    +
  • fd + file + file handle to operate on, one of io.stdin, io.stdout, io.stderr +
  • +
  • make_non_block + boolean + a truthy value will enable non-blocking mode, a falsy value will disable it. +
  • +
+ +

Returns:

+
    + + bool + true, if successful +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
  5. + int + errnum
  6. +
+ + +

See also:

+ + +

Usage:

+
    +
    local sys = require('system')
    +
    +-- set io.stdin to non-blocking mode
    +local old_setting = sys.getnonblock(io.stdin)
    +sys.setnonblock(io.stdin, true)
    +
    +-- do stuff
    +
    +-- restore old setting
    +sys.setnonblock(io.stdin, old_setting)
    +
+ +
+
+ + tcgetattr (fd) +
+
+ +

Get termios state (Posix). +The terminal attributes is a table with the following fields:

+ +
    +
  • iflag input flags
  • +
  • oflag output flags
  • +
  • lflag local flags
  • +
  • cflag control flags
  • +
  • ispeed input speed
  • +
  • ospeed output speed
  • +
  • cc control characters
  • +
+ + + + +

Parameters:

+ + +

Returns:

+
    + + error message if failed +
+

Or

+
    + + termios + terminal attributes, if successful. On Windows the bitflags are all 0, and the cc table is empty. +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
  5. + int + errnum
  6. +
+ + + +

Usage:

+
    +
    local system = require('system')
    +
    +local status = assert(tcgetattr(io.stdin))
    +if status.iflag:has_all_of(system.I_IGNBRK) then
    +    print("Ignoring break condition")
    +end
    +
+ +
+
+ + tcsetattr (fd, actions, termios) +
+
+ Set termios state (Posix). +This function will set the flags as given.

+ +

The I_, O_, and L_ constants are available on the module table. They are the respective +flags for the iflags, oflags, and lflags bitmasks.

+ +

To see flag status and constant names check listtermflags. For their meaning check +the manpage.

+ +

Note: only iflag, oflag, and lflag are supported at the moment. The other fields are ignored. + + +

Parameters:

+
    +
  • fd + file + file handle to operate on, one of io.stdin, io.stdout, io.stderr +
  • +
  • actions + integer + one of TCSANOW, TCSADRAIN, TCSAFLUSH +
  • +
  • termios a table with bitflag fields: +
      +
    • iflag + bitflags + if given will set the input flags + (optional) +
    • +
    • oflag + bitflags + if given will set the output flags + (optional) +
    • +
    • lflag + bitflags + if given will set the local flags + (optional) +
    • +
    +
+ +

Returns:

+
    + + bool + true, if successful. Always returns true on Windows. +
+

Or

+
    +
  1. + nil
  2. +
  3. + string + error message
  4. +
  5. + int + errnum
  6. +
+ + + +

Usage:

+
    +
    local system = require('system')
    +
    +local status = assert(tcgetattr(io.stdin))
    +if not status.lflag:has_all_of(system.L_ECHO) then
    +    -- if echo is off, turn echoing newlines on
    +    tcsetattr(io.stdin, system.TCSANOW, { lflag = status.lflag + system.L_ECHONL }))
    +end
    +
+ +
+
+ + termbackup () +
+
+ Returns a backup of terminal settings for stdin/out/err. + Handles terminal/console flags, Windows codepage, and non-block flags on the streams. + Backs up terminal/console flags only if a stream is a tty. + + + +

Returns:

+
    + + table with backup of terminal settings +
+ + + + +
+
+ + termrestore (backup) +
+
+ Restores terminal settings from a backup + + +

Parameters:

+ + +

Returns:

+
    + + boolean + true +
+ + + + +
+
+ + termsize () +
+
+ Get the size of the terminal in rows and columns. + + + +

Returns:

+
    +
  1. + int + the number of rows
  2. +
  3. + int + the number of columns
  4. +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
+ + + + +
+
+ + termwrap (f) +
+
+ Wraps a function to automatically restore terminal settings upon returning. + Calls termbackup before calling the function and termrestore after. + + +

Parameters:

+
    +
  • f + function + function to wrap +
  • +
+ +

Returns:

+
    + + function + wrapped function +
+ + + + +
+
+ + utf8cwidth (utf8_char) +
+
+ Get the width of a utf8 character for terminal display. + + +

Parameters:

+
    +
  • utf8_char + string + the utf8 character to check, only the width of the first character will be returned +
  • +
+ +

Returns:

+
    + + int + the display width in columns of the first character in the string (0 for an empty string) +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
+ + + + +
+
+ + utf8swidth (utf8_string) +
+
+ Get the width of a utf8 string for terminal display. + + +

Parameters:

+
    +
  • utf8_string + string + the utf8 string to check +
  • +
+ +

Returns:

+
    + + int + the display width of the string in columns (0 for an empty string) +
+

Or

+
    +
  1. + nil + + +
  2. +
  3. + string + error message
  4. +
+ + + + +
+
+

Time

+ +
+
+ + gettime () +
+
+ Get system time. +The time is returned as the seconds since the epoch (1 January 1970 00:00:00). + + + +

Returns:

+
    + + number + seconds (fractional) +
+ + + + +
+
+ + monotime () +
+
+ Get monotonic time. +The time is returned as the seconds since system start. + + + +

Returns:

+
    + + number + seconds (fractional) +
+ + + + +
+
+ + sleep (seconds[, precision=16]) +
+
+ Sleep without a busy loop. +This function will sleep, without doing a busy-loop and wasting CPU cycles. + + +

Parameters:

+
    +
  • seconds + number + seconds to sleep (fractional). +
  • +
  • precision + integer + minimum stepsize in milliseconds (Windows only, ignored elsewhere) + (default 16) +
  • +
+ +

Returns:

+
    + + true on success, or nil+err on failure +
+ + + + +
+
+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/topics/01-introduction.md.html b/docs/topics/01-introduction.md.html index 319b9d5..019f7e4 100644 --- a/docs/topics/01-introduction.md.html +++ b/docs/topics/01-introduction.md.html @@ -27,17 +27,38 @@

Lua-System

+ +

Topics

Modules

+

Classes

+ +

Examples

+ @@ -62,8 +83,8 @@

1. Introduction

-generated by LDoc 1.4.6 -Last updated 2023-12-15 13:15:09 +generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37
diff --git a/docs/topics/02-development.md.html b/docs/topics/02-development.md.html new file mode 100644 index 0000000..e911eed --- /dev/null +++ b/docs/topics/02-development.md.html @@ -0,0 +1,91 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ + +

2. Development

+ +

Some tests cannot be run in CI becasue they test system specifics that +simply do not exist in a CI environment. An example is the isatty function +Which cannot be set to return a truthy value in CI.

+ +

The tests concerned are all labelled with #manual. And in CI they will +be skipped because --exclude-tags=manual is being passed to the +busted command line.

+ +

Hence if tests like this are being added, then please ensure the tests +pass locally, and do not rely on CI only.

+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/topics/03-terminal.md.html b/docs/topics/03-terminal.md.html new file mode 100644 index 0000000..3e7e548 --- /dev/null +++ b/docs/topics/03-terminal.md.html @@ -0,0 +1,225 @@ + + + + + Lua-System docs + + + + +
+ +
+ +
+
+
+ + +
+ + + + + + +
+ + +

3. Terminal functionality

+ +

Terminals are fundamentally different on Windows and Posix. So even though +luasystem provides primitives to manipulate both the Windows and Posix terminals, +the user will still have to write platform specific code.

+ +

To mitigate this a little, all functions are available on all platforms. They just +will be a no-op if invoked on another platform. This means that no platform specific +branching is required (but still possible) in user code. The user must simply set +up both platforms to make it work.

+ +

+

3.1 Backup and Restore terminal settings

+ +

Since there are a myriad of settings available;

+ + + +

Some helper functions are available to backup and restore them all at once. +See termbackup, termrestore, autotermrestore and termwrap.

+ + +

+

3.1 Terminal ANSI sequences

+ +

Windows is catching up with this. In Windows 10 (since 2019), the Windows Terminal application (not to be +mistaken for the cmd console application) supports ANSI sequences. However this +might not be enabled by default.

+ +

ANSI processing can be set up both on the input (key sequences, reading cursor position) +as well as on the output (setting colors and cursor shapes).

+ +

To enable it use system.setconsoleflags like this:

+ + +
+-- setup Windows console to handle ANSI processing on output
+sys.setconsoleflags(io.stdout, sys.getconsoleflags(io.stdout) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+sys.setconsoleflags(io.stderr, sys.getconsoleflags(io.stderr) + sys.COF_VIRTUAL_TERMINAL_PROCESSING)
+
+-- setup Windows console to handle ANSI processing on input
+sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) + sys.CIF_VIRTUAL_TERMINAL_INPUT)
+
+ +

+

3.2 UTF-8 in/output and display width

+ +

3.2.1 UTF-8 in/output

+ +

Where (most) Posix systems use UTF-8 by default, Windows internally uses UTF-16. More +recent versions of Lua also have UTF-8 support. So luasystem also focusses on UTF-8.

+ +

On Windows UTF-8 output can be enabled by setting the output codepage like this:

+ + +
+-- setup Windows output codepage to UTF-8
+sys.setconsoleoutputcp(sys.CODEPAGE_UTF8)
+
+ +

Terminal input is handled by the _getwchar() function on Windows which returns +UTF-16 surrogate pairs. luasystem will automatically convert those to UTF-8. +So when using readkey or readansi to read keyboard input no additional changes +are required.

+ +

3.2.2 UTF-8 display width

+ +

Typical western characters and symbols are single width characters and will use only +a single column when displayed on a terminal. However many characters from other +languages/cultures or emojis require 2 columns for display.

+ +

Typically the wcwidth function is used on Posix to check the number of columns +required for display. However since Windows doesn't provide this functionality a +custom implementation is included based on the work by Markus Kuhn.

+ +

2 functions are provided, system.utf8cwidth for a single character, and system.utf8swidth for +a string. When writing terminal applications the display width is relevant to +positioning the cursor properly. For an example see the examples/readline.lua file.

+ + +

+

3.3 reading keyboard input

+ +

3.3.1 Non-blocking

+ +

There are 2 functions for keyboard input (actually 3, if taking system._readkey into +account): readkey and readansi.

+ +

readkey is a low level function and should preferably not be used, it returns +a byte at a time, and hence can leave stray/invalid byte sequences in the buffer if +only the start of a UTF-8 or ANSI sequence is consumed.

+ +

The preferred way is to use readansi which will parse and return entire characters in +single or multiple bytes, or a full ANSI sequence.

+ +

On Windows the input is read using _getwchar() which bypasses the terminal and reads +the input directly from the keyboard buffer. This means however that the character is +also not being echoed to the terminal (independent of the echo settings used with +system.setconsoleflags).

+ +

On Posix the traditional file approach is used, which:

+ +
    +
  • is blocking by default
  • +
  • echoes input to the terminal
  • +
  • requires enter to be pressed to pass the input (canonical mode)
  • +
+ +

To use non-blocking input here's how to set it up:

+ + +
+-- setup Windows console to disable echo and line input (not required since _getwchar is used, just for consistency)
+sys.setconsoleflags(io.stdin, sys.getconsoleflags(io.stdin) - sys.CIF_ECHO_INPUT - sys.CIF_LINE_INPUT)
+
+-- setup Posix by disabling echo, canonical mode, and making non-blocking
+local of_attr = sys.tcgetattr(io.stdin)
+sys.tcsetattr(io.stdin, sys.TCSANOW, {
+  lflag = of_attr.lflag - sys.L_ICANON - sys.L_ECHO,
+})
+sys.setnonblock(io.stdin, true)
+
+ +

Both functions require a timeout to be provided which allows for proper asynchronous +code to be written. Since the underlying sleep method used is system.sleep, just patching +that function with a coroutine based yielding one should be all that is needed to make +the result work with asynchroneous coroutine schedulers.

+ +

3.3.2 Blocking input

+ +

When using traditional input method like io.stdin:read() (which is blocking) the echo +and newline properties should be set on Windows similar to Posix. +For an example see examples/password_input.lua.

+ + +
+
+
+generated by LDoc 1.5.0 +Last updated 2024-06-20 23:11:37 +
+
+ + diff --git a/docs/topics/CHANGELOG.md.html b/docs/topics/CHANGELOG.md.html index 4e656c7..da8ddde 100644 --- a/docs/topics/CHANGELOG.md.html +++ b/docs/topics/CHANGELOG.md.html @@ -27,6 +27,10 @@

Lua-System

+ +

Contents